From 86bd77d9954664c353882d1f08097aac615dabb8 Mon Sep 17 00:00:00 2001
From: Nexmoe <16796652+nexmoe@users.noreply.github.com>
Date: Fri, 26 Dec 2025 13:02:07 +0800
Subject: [PATCH] fix(download): improve format fallbacks (#55)
---
conductor.json | 2 +-
src/main/download-engine/args-builder.ts | 4 ++
.../components/download/DownloadDialog.tsx | 50 +++++++++--------
.../src/components/video/FormatSelector.tsx | 55 +++++++++++--------
4 files changed, 66 insertions(+), 45 deletions(-)
diff --git a/conductor.json b/conductor.json
index 5ce006c..1a1b483 100644
--- a/conductor.json
+++ b/conductor.json
@@ -1,6 +1,6 @@
{
"scripts": {
- "setup": "cp $CONDUCTOR_ROOT_PATH/resources/ resources/ && pnpm install",
+ "setup": "cp -r $CONDUCTOR_ROOT_PATH/resources resources/ && pnpm install",
"run": "pnpm run dev"
}
}
diff --git a/src/main/download-engine/args-builder.ts b/src/main/download-engine/args-builder.ts
index 6dd033d..53aa440 100644
--- a/src/main/download-engine/args-builder.ts
+++ b/src/main/download-engine/args-builder.ts
@@ -21,6 +21,10 @@ export const resolveVideoFormatSelector = (options: DownloadOptions): string =>
const format = options.format
const audioFormat = options.audioFormat
+ if (format && audioFormat === '') {
+ return format
+ }
+
if (format && (format.includes('/') || (audioFormat === undefined && format.includes('+')))) {
return format
}
diff --git a/src/renderer/src/components/download/DownloadDialog.tsx b/src/renderer/src/components/download/DownloadDialog.tsx
index efc96d8..179d1a6 100644
--- a/src/renderer/src/components/download/DownloadDialog.tsx
+++ b/src/renderer/src/components/download/DownloadDialog.tsx
@@ -114,10 +114,12 @@ const buildVideoFormatPreference = (settings: AppSettings): string => {
combinations.push(video)
}
} else {
- // Use bestvideo+bestaudio as fallback instead of 'best' to ensure merging
+ // Prefer merged formats, then allow 'best' as a compatibility fallback.
combinations.push('bestvideo+bestaudio')
}
+ combinations.push('best')
+
return dedupe(combinations).join('/')
}
@@ -935,29 +937,33 @@ export function DownloadDialog({ onOpenSupportedSites, onOpenSettings }: Downloa
{t('sites.viewAll')}
-
- {settings.oneClickDownload && (
-
-
-
{t('download.oneClickDownloadEnabled')}
- {onOpenSettings && (
-
- )}
-
- )}
+ {/* One-click download indicator */}
+ {settings.oneClickDownload && (
+
+
+
+ {t('download.oneClickDownloadEnabled')}
+ {onOpenSettings && (
+
+ )}
+
+
+
+ )}
+
{/* Error Display */}
{error && (
diff --git a/src/renderer/src/components/video/FormatSelector.tsx b/src/renderer/src/components/video/FormatSelector.tsx
index 5cf2769..762ce38 100644
--- a/src/renderer/src/components/video/FormatSelector.tsx
+++ b/src/renderer/src/components/video/FormatSelector.tsx
@@ -73,23 +73,31 @@ export function FormatSelector({
useEffect(() => {
// Filter and sort formats
// Exclude m3u8/HLS formats as they are streaming formats not suitable for direct download
- const videos = formats.filter(
- (f) =>
- f.video_ext !== 'none' &&
- f.vcodec &&
- f.vcodec !== 'none' &&
- f.protocol !== 'm3u8' &&
- f.protocol !== 'm3u8_native'
+ const isVideoFormat = (format: VideoFormat) =>
+ format.video_ext !== 'none' && format.vcodec && format.vcodec !== 'none'
+ const isAudioFormat = (format: VideoFormat) =>
+ format.acodec &&
+ format.acodec !== 'none' &&
+ (format.video_ext === 'none' || !format.video_ext)
+ const isHlsFormat = (format: VideoFormat) =>
+ format.protocol === 'm3u8' || format.protocol === 'm3u8_native'
+
+ const videoCandidates = formats.filter(
+ (format) => isVideoFormat(format) && !isHlsFormat(format)
)
- const audios = formats.filter(
- (f) =>
- f.acodec &&
- f.acodec !== 'none' &&
- (f.video_ext === 'none' || !f.video_ext) &&
- f.protocol !== 'm3u8' &&
- f.protocol !== 'm3u8_native'
+ const audioCandidates = formats.filter(
+ (format) => isAudioFormat(format) && !isHlsFormat(format)
)
+ const videos =
+ videoCandidates.length > 0
+ ? videoCandidates
+ : formats.filter((format) => isVideoFormat(format))
+ const audios =
+ audioCandidates.length > 0
+ ? audioCandidates
+ : formats.filter((format) => isAudioFormat(format))
+
// Apply showMoreFormats filter
const filteredVideos = settings.showMoreFormats
? videos
@@ -99,6 +107,9 @@ export function FormatSelector({
? audios
: audios.filter((f) => f.ext !== 'webm')
+ const finalVideos = filteredVideos.length > 0 ? filteredVideos : videos
+ const finalAudios = filteredAudios.length > 0 ? filteredAudios : audios
+
// Sort formats by quality (best first)
const sortVideoFormatsByQuality = (a: VideoFormat, b: VideoFormat) => {
// Sort by height (higher is better)
@@ -138,23 +149,23 @@ export function FormatSelector({
return 0
}
- filteredVideos.sort(sortVideoFormatsByQuality)
- filteredAudios.sort(sortAudioFormatsByQuality)
+ finalVideos.sort(sortVideoFormatsByQuality)
+ finalAudios.sort(sortAudioFormatsByQuality)
- setVideoFormats(filteredVideos)
- setAudioFormats(filteredAudios)
+ setVideoFormats(finalVideos)
+ setAudioFormats(finalAudios)
// Auto-select best format based on preferences
- if (filteredVideos.length > 0 && !selectedVideo) {
- const preferred = pickVideoFormatForPreset(filteredVideos, settings.oneClickQuality)
+ if (finalVideos.length > 0 && !selectedVideo) {
+ const preferred = pickVideoFormatForPreset(finalVideos, settings.oneClickQuality)
if (preferred) {
setSelectedVideo(preferred.format_id)
onVideoFormatChange?.(preferred.format_id)
}
}
- if (filteredAudios.length > 0 && !selectedAudio) {
- const best = filteredAudios[0]
+ if (finalAudios.length > 0 && !selectedAudio) {
+ const best = finalAudios[0]
setSelectedAudio(best.format_id)
onAudioFormatChange?.(best.format_id)
}