This commit is contained in:
2026-02-10 15:57:33 -08:00
parent 2fd13a6b3f
commit c2449d0ba7
3 changed files with 220 additions and 10 deletions

View File

@@ -1177,7 +1177,7 @@ class Download_File(Cmdlet):
return f"https://www.youtube.com/watch?v={entry_id.strip()}"
return None
table = Table()
table = Table(preserve_order=True)
safe_url = str(url or "").strip()
table.title = f'download-file -url "{safe_url}"' if safe_url else "download-file"
if table_type:
@@ -1477,7 +1477,7 @@ class Download_File(Cmdlet):
actual_playlist_items = None
if mode == "audio" and not actual_format:
actual_format = "bestaudio"
actual_format = "bestaudio/best"
if mode == "video" and not actual_format:
configured = (ytdlp_tool.default_format("video") or "").strip()
@@ -1493,6 +1493,20 @@ class Download_File(Cmdlet):
):
actual_format = forced_single_format_id
forced_single_applied = True
# Proactive fallback for single audio formats which might be unstable
if (
actual_format
and isinstance(actual_format, str)
and mode == "audio"
and "/" not in actual_format
and "+" not in actual_format
and not forced_single_applied
and actual_format not in {"best", "bestaudio", "bw", "ba"}
):
debug(f"Appending fallback to specific audio format: {actual_format} -> {actual_format}/bestaudio/best")
actual_format = f"{actual_format}/bestaudio/best"
if (
actual_format
and isinstance(actual_format, str)
@@ -1521,7 +1535,14 @@ class Download_File(Cmdlet):
except Exception as e:
pass
debug(
"[download-file] Resolved format for download: "
f"mode={mode}, format={actual_format or 'default'}, playlist_items={actual_playlist_items}"
)
attempted_single_format_fallback = False
attempted_audio_fallback_specific = False
attempted_audio_fallback_generic = False
while True:
try:
opts = DownloadOptions(
@@ -1551,7 +1572,79 @@ class Download_File(Cmdlet):
except Exception:
detail = ""
if ("requested format is not available" in (detail or "").lower()) and mode != "audio":
detail_lc = (detail or "").lower()
msg_lc = ""
try:
msg_lc = str(e or "").lower()
except Exception:
msg_lc = ""
requested_format_unavailable = (
"requested format is not available" in detail_lc
or "requested format is not available" in msg_lc
)
if requested_format_unavailable and mode == "audio":
# Level 1: Try to find a specific audio-only format from the list that hopefully works
if not attempted_audio_fallback_specific:
attempted_audio_fallback_specific = True
audio_format_id = None
try:
formats = self._list_formats_cached(
url,
playlist_items_value=actual_playlist_items,
formats_cache=formats_cache,
ytdlp_tool=ytdlp_tool,
)
if formats:
audio_candidates = []
for fmt in formats:
if not isinstance(fmt, dict):
continue
vcodec = str(fmt.get("vcodec", "none"))
acodec = str(fmt.get("acodec", "none"))
if acodec != "none" and vcodec == "none":
audio_candidates.append(fmt)
if audio_candidates:
def _score_audio(fmt: Dict[str, Any]) -> float:
score = 0.0
# Penalize DRC formats heavily
fid = str(fmt.get("format_id") or "").lower()
if "drc" in fid:
score -= 1000.0
for key in ("abr", "tbr", "filesize", "filesize_approx"):
try:
val = fmt.get(key)
if isinstance(val, (int, float)):
score += float(val)
break # Use the first valid metric found
if isinstance(val, str) and val.strip().isdigit():
score += float(val)
break
except Exception:
continue
return score
audio_candidates.sort(key=_score_audio, reverse=True)
audio_format_id = str(audio_candidates[0].get("format_id") or "").strip() or None
except Exception:
audio_format_id = None
if audio_format_id:
actual_format = audio_format_id
debug(f"Requested audio format not available; retrying with best specific audio format: {actual_format}")
continue
# Level 2: Fallback to generic 'bestaudio/best' if specific selection failed or wasn't found
if not attempted_audio_fallback_generic:
attempted_audio_fallback_generic = True
if actual_format != "bestaudio/best":
actual_format = "bestaudio/best"
debug("Requested audio format not available; retrying with generic fallback: bestaudio/best")
continue
if requested_format_unavailable and mode != "audio":
if (
forced_single_format_for_batch
and forced_single_format_id
@@ -1953,8 +2046,8 @@ class Download_File(Cmdlet):
except Exception:
height_selector = None
if query_wants_audio:
# Explicit audio request should map to best-audio-only selector
ytdl_format = "bestaudio"
# Explicit audio request should map to the configured audio selector (usually '251/140/bestaudio')
ytdl_format = ytdlp_tool.default_format("audio")
elif height_selector:
ytdl_format = height_selector
elif query_format:
@@ -2045,6 +2138,51 @@ class Download_File(Cmdlet):
if early_ret is not None:
return int(early_ret)
# Auto-detect audio-only sources (e.g., Bandcamp albums) when no explicit format is requested.
if mode == "video" and not ytdl_format and not query_format and not query_wants_audio:
try:
sample_url = str(supported_url[0]) if supported_url else ""
fmts = self._list_formats_cached(
sample_url,
playlist_items_value=playlist_items,
formats_cache=formats_cache,
ytdlp_tool=ytdlp_tool,
)
if fmts:
has_video = any(str(f.get("vcodec", "none")) != "none" for f in fmts if isinstance(f, dict))
has_audio = any(str(f.get("acodec", "none")) != "none" for f in fmts if isinstance(f, dict))
if has_audio and not has_video:
mode = "audio"
ytdl_format = ytdlp_tool.default_format("audio")
debug("[download-file] No video formats detected; switching to audio mode")
else:
if "bandcamp.com/album/" in sample_url:
mode = "audio"
ytdl_format = ytdlp_tool.default_format("audio")
debug("[download-file] Bandcamp album detected; switching to audio mode")
except Exception as e:
debug(f"[download-file] Audio-only detection error: {e}")
# Auto-detect audio mode for explicit format IDs
if len(supported_url) == 1 and ytdl_format and mode != "audio":
try:
candidates = self._list_formats_cached(
supported_url[0],
playlist_items_value=playlist_items,
formats_cache=formats_cache,
ytdlp_tool=ytdlp_tool,
)
if candidates:
match = next((f for f in candidates if str(f.get("format_id", "")) == str(ytdl_format)), None)
if match:
vcodec = str(match.get("vcodec", "none"))
acodec = str(match.get("acodec", "none"))
if vcodec == "none" and acodec != "none":
debug(f"[download-file] Requested format {ytdl_format} is audio-only; switching mode to audio")
mode = "audio"
except Exception as e:
debug(f"[download-file] Error validating format mode: {e}")
timeout_seconds = 300
try:
override = config.get("_pipeobject_timeout_seconds") if isinstance(config, dict) else None