fix(download): handle empty format selections (#64)

This commit is contained in:
Nexmoe
2026-01-02 16:16:04 +08:00
committed by GitHub
parent 71ae4a4425
commit a5b94411fe
4 changed files with 98 additions and 59 deletions

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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:

View File

@@ -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 (
<div className="space-y-5">
<div className="space-y-3">
<Label className="text-sm font-semibold">{t('download.selectVideoFormat')}</Label>
<Select
value={selectedVideo}
onValueChange={(value) => {
setSelectedVideo(value)
onVideoFormatChange?.(value)
}}
>
<SelectTrigger className="h-11">
<SelectValue />
</SelectTrigger>
<SelectContent className="max-h-[300px] p-1.5">
{videoFormats.map((format) => (
<SelectItem
key={format.format_id}
value={format.format_id}
className="cursor-pointer py-2.5"
>
<span className="text-sm">{formatVideoLabel(format)}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{videoFormats.length > 0 && (
<div className="space-y-3">
<Label className="text-sm font-semibold">{t('download.selectVideoFormat')}</Label>
<Select
value={selectedVideo}
onValueChange={(value) => {
setSelectedVideo(value)
onVideoFormatChange?.(value)
}}
>
<SelectTrigger className="h-11">
<SelectValue />
</SelectTrigger>
<SelectContent className="max-h-[300px] p-1.5">
{videoFormats.map((format) => (
<SelectItem
key={format.format_id}
value={format.format_id}
className="cursor-pointer py-2.5"
>
<span className="text-sm">{formatVideoLabel(format)}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<div className="space-y-3">
<Label className="text-sm font-semibold">{t('download.selectAudioFormat')}</Label>
<Select
value={selectedAudio}
onValueChange={(value) => {
setSelectedAudio(value)
onAudioFormatChange?.(value)
}}
>
<SelectTrigger className="h-11">
<SelectValue />
</SelectTrigger>
<SelectContent className="max-h-[300px] p-1.5">
<SelectItem value="none" className="cursor-pointer py-2.5">
<span className="text-sm">{t('download.noAudio')}</span>
</SelectItem>
{audioFormats.map((format) => (
<SelectItem
key={format.format_id}
value={format.format_id}
className="cursor-pointer py-2.5"
>
<span className="text-sm">{formatAudioLabel(format)}</span>
{audioFormats.length > 0 && (
<div className="space-y-3">
<Label className="text-sm font-semibold">{t('download.selectAudioFormat')}</Label>
<Select
value={selectedAudio}
onValueChange={(value) => {
setSelectedAudio(value)
onAudioFormatChange?.(value)
}}
>
<SelectTrigger className="h-11">
<SelectValue />
</SelectTrigger>
<SelectContent className="max-h-[300px] p-1.5">
<SelectItem value="none" className="cursor-pointer py-2.5">
<span className="text-sm">{t('download.noAudio')}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{audioFormats.map((format) => (
<SelectItem
key={format.format_id}
value={format.format_id}
className="cursor-pointer py-2.5"
>
<span className="text-sm">{formatAudioLabel(format)}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div>
)
}
// Audio only
if (audioFormats.length === 0) {
return null
}
return (
<div className="space-y-3">
<Label className="text-sm font-semibold">{t('download.selectFormat')}</Label>