diff --git a/conductor.json b/conductor.json index a5487d9..0b41678 100644 --- a/conductor.json +++ b/conductor.json @@ -1,6 +1,6 @@ { "scripts": { - "setup": "cp -r $CONDUCTOR_ROOT_PATH/resources resources && pnpm install", + "setup": "rm -rf resources && cp -r $CONDUCTOR_ROOT_PATH/resources resources && pnpm install", "run": "pnpm run dev" } } diff --git a/src/main/lib/download-engine.ts b/src/main/lib/download-engine.ts index 7e57370..1e4a0c6 100644 --- a/src/main/lib/download-engine.ts +++ b/src/main/lib/download-engine.ts @@ -36,6 +36,19 @@ interface DownloadProcess { process: YTDlpEventEmitter } +const formatYtDlpCommand = (args: string[]): string => { + const quoted = args.map((arg) => { + if (arg === '') { + return '""' + } + if (/[\s"'\\]/.test(arg)) { + return `"${arg.replace(/(["\\])/g, '\\$1')}"` + } + return arg + }) + return `yt-dlp ${quoted.join(' ')}` +} + const ensureDirectoryExists = (dir?: string): void => { if (!dir) { return @@ -732,6 +745,8 @@ class DownloadEngine extends EventEmitter { args.push('--ffmpeg-location', ffmpegPath) args.push(urlArg) + scopedLoggers.download.info('yt-dlp command:', formatYtDlpCommand(args)) + const controller = new AbortController() const ytdlpProcess = ytdlp.exec(args, { signal: controller.signal diff --git a/src/renderer/src/components/download/DownloadDialog.tsx b/src/renderer/src/components/download/DownloadDialog.tsx index 2466cb6..cc47e2b 100644 --- a/src/renderer/src/components/download/DownloadDialog.tsx +++ b/src/renderer/src/components/download/DownloadDialog.tsx @@ -790,11 +790,12 @@ export function DownloadDialog({ onOpenSupportedSites, onOpenSettings }: Downloa type, format: type === 'video' - ? videoInfoCardState.selectedVideoFormat + ? videoInfoCardState.selectedVideoFormat || undefined : type === 'extract' ? undefined - : videoInfoCardState.selectedAudioFormat, - audioFormat: type === 'video' ? videoInfoCardState.selectedAudioForVideo : undefined, + : videoInfoCardState.selectedAudioFormat || undefined, + audioFormat: + type === 'video' ? videoInfoCardState.selectedAudioForVideo || undefined : undefined, extractFormat: type === 'extract' ? videoInfoCardState.audioExtractor.extractFormat : undefined, extractQuality: diff --git a/src/renderer/src/components/video/FormatSelector.tsx b/src/renderer/src/components/video/FormatSelector.tsx index 762ce38..a24e816 100644 --- a/src/renderer/src/components/video/FormatSelector.tsx +++ b/src/renderer/src/components/video/FormatSelector.tsx @@ -155,9 +155,20 @@ export function FormatSelector({ setVideoFormats(finalVideos) setAudioFormats(finalAudios) - // Auto-select best format based on preferences - if (finalVideos.length > 0 && !selectedVideo) { - const preferred = pickVideoFormatForPreset(finalVideos, settings.oneClickQuality) + // Auto-select best format based on preferences. + // If no separate audio formats exist, prefer muxed video formats (with audio). + const videosWithAudio = finalVideos.filter( + (format) => format.acodec && format.acodec !== 'none' + ) + const autoVideos = + finalAudios.length > 0 + ? finalVideos + : videosWithAudio.length > 0 + ? videosWithAudio + : finalVideos + + if (autoVideos.length > 0 && !selectedVideo) { + const preferred = pickVideoFormatForPreset(autoVideos, settings.oneClickQuality) if (preferred) { setSelectedVideo(preferred.format_id) onVideoFormatChange?.(preferred.format_id) @@ -226,67 +237,79 @@ export function FormatSelector({ } if (type === 'video') { + if (videoFormats.length === 0 && audioFormats.length === 0) { + return null + } + return (
-
- - -
+ {videoFormats.length > 0 && ( +
+ + +
+ )} -
- - { + setSelectedAudio(value) + onAudioFormatChange?.(value) + }} + > + + + + + + {t('download.noAudio')} - ))} - - -
+ {audioFormats.map((format) => ( + + {formatAudioLabel(format)} + + ))} + + +
+ )} ) } // Audio only + if (audioFormats.length === 0) { + return null + } + return (