This commit is contained in:
2026-02-09 17:34:40 -08:00
parent c86aae1ff6
commit 567472bca0
2 changed files with 81 additions and 11 deletions

View File

@@ -1954,7 +1954,7 @@ class Download_File(Cmdlet):
height_selector = None height_selector = None
if query_wants_audio: if query_wants_audio:
# Explicit audio request should map to best-audio-only selector # Explicit audio request should map to best-audio-only selector
ytdl_format = "ba" ytdl_format = "bestaudio"
elif height_selector: elif height_selector:
ytdl_format = height_selector ytdl_format = height_selector
elif query_format: elif query_format:

View File

@@ -907,8 +907,18 @@ class YtDlpTool:
"fragment_retries": 10, "fragment_retries": 10,
"http_chunk_size": 10_485_760, "http_chunk_size": 10_485_760,
"restrictfilenames": True, "restrictfilenames": True,
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
"referer": "https://www.youtube.com/",
} }
base_options.setdefault(
"http_headers",
{
"User-Agent": base_options.get("user_agent"),
"Referer": base_options.get("referer"),
},
)
try: try:
repo_root = Path(__file__).resolve().parents[1] repo_root = Path(__file__).resolve().parents[1]
bundled_ffmpeg_dir = repo_root / "MPV" / "ffmpeg" / "bin" bundled_ffmpeg_dir = repo_root / "MPV" / "ffmpeg" / "bin"
@@ -1095,7 +1105,7 @@ class YtDlpTool:
base_options["playlist_items"] = opts.playlist_items base_options["playlist_items"] = opts.playlist_items
if not opts.quiet: if not opts.quiet:
debug(f"yt-dlp: mode={opts.mode}, format={base_options.get('format')}") debug(f"yt-dlp: mode={opts.mode}, format={base_options.get('format')}, cookiefile={base_options.get('cookiefile')}")
return base_options return base_options
@@ -1729,6 +1739,31 @@ except ImportError:
extract_ytdlp_tags = None # type: ignore extract_ytdlp_tags = None # type: ignore
def _is_http_403(exc: Exception) -> bool:
msg_parts: list[str] = []
try:
msg_parts.append(str(exc))
except Exception:
pass
try:
cause = getattr(exc, "__cause__", None)
if cause is not None:
msg_parts.append(str(cause))
except Exception:
pass
try:
context = getattr(exc, "__context__", None)
if context is not None:
msg_parts.append(str(context))
except Exception:
pass
for msg in msg_parts:
if "HTTP Error 403" in msg or "403: Forbidden" in msg or "403 Forbidden" in msg:
return True
return False
def download_media(opts: DownloadOptions, *, config: Optional[Dict[str, Any]] = None, debug_logger: Optional[DebugLogger] = None) -> Any: def download_media(opts: DownloadOptions, *, config: Optional[Dict[str, Any]] = None, debug_logger: Optional[DebugLogger] = None) -> Any:
"""Download streaming media exclusively via yt-dlp. """Download streaming media exclusively via yt-dlp.
@@ -1798,14 +1833,15 @@ def download_media(opts: DownloadOptions, *, config: Optional[Dict[str, Any]] =
debug_logger.write_record("ytdlp-start", {"url": opts.url}) debug_logger.write_record("ytdlp-start", {"url": opts.url})
assert yt_dlp is not None assert yt_dlp is not None
info: Optional[Dict[str, Any]] = None
session_id = None
first_section_info: Dict[str, Any] = {}
try: try:
if not opts.quiet: if not opts.quiet:
if ytdl_options.get("download_sections"): if ytdl_options.get("download_sections"):
debug(f"[yt-dlp] download_sections: {ytdl_options['download_sections']}") debug(f"[yt-dlp] download_sections: {ytdl_options['download_sections']}")
debug(f"[yt-dlp] force_keyframes_at_cuts: {ytdl_options.get('force_keyframes_at_cuts', False)}") debug(f"[yt-dlp] force_keyframes_at_cuts: {ytdl_options.get('force_keyframes_at_cuts', False)}")
session_id = None
first_section_info: Dict[str, Any] = {}
if ytdl_options.get("download_sections"): if ytdl_options.get("download_sections"):
live_ui, _ = PipelineProgress(pipeline_context).ui_and_pipe_index() live_ui, _ = PipelineProgress(pipeline_context).ui_and_pipe_index()
quiet_sections = bool(opts.quiet) or (live_ui is not None) quiet_sections = bool(opts.quiet) or (live_ui is not None)
@@ -1820,13 +1856,47 @@ def download_media(opts: DownloadOptions, *, config: Optional[Dict[str, Any]] =
with yt_dlp.YoutubeDL(ytdl_options) as ydl: # type: ignore[arg-type] with yt_dlp.YoutubeDL(ytdl_options) as ydl: # type: ignore[arg-type]
info = ydl.extract_info(opts.url, download=True) info = ydl.extract_info(opts.url, download=True)
except Exception as exc: except Exception as exc:
log(f"yt-dlp failed: {exc}", file=sys.stderr) retry_attempted = False
if debug_logger is not None: if _is_http_403(exc) and not ytdl_options.get("download_sections"):
debug_logger.write_record( retry_attempted = True
"exception", try:
{"phase": "yt-dlp", "error": str(exc), "traceback": traceback.format_exc()}, if not opts.quiet:
) debug("yt-dlp hit HTTP 403; retrying with browser cookies + android/web player client")
raise DownloadError("yt-dlp download failed") from exc
fallback_options = dict(ytdl_options)
fallback_options.pop("cookiefile", None)
_add_browser_cookies_if_available(fallback_options)
extractor_args = fallback_options.get("extractor_args")
if not isinstance(extractor_args, dict):
extractor_args = {}
youtube_args = extractor_args.get("youtube")
if not isinstance(youtube_args, dict):
youtube_args = {}
if "player_client" not in youtube_args:
youtube_args["player_client"] = ["android", "web"]
extractor_args["youtube"] = youtube_args
fallback_options["extractor_args"] = extractor_args
with yt_dlp.YoutubeDL(fallback_options) as ydl: # type: ignore[arg-type]
info = ydl.extract_info(opts.url, download=True)
except Exception as exc2:
log(f"yt-dlp failed: {exc2}", file=sys.stderr)
if debug_logger is not None:
debug_logger.write_record(
"exception",
{"phase": "yt-dlp", "error": str(exc2), "traceback": traceback.format_exc()},
)
raise DownloadError("yt-dlp download failed") from exc2
if not retry_attempted:
log(f"yt-dlp failed: {exc}", file=sys.stderr)
if debug_logger is not None:
debug_logger.write_record(
"exception",
{"phase": "yt-dlp", "error": str(exc), "traceback": traceback.format_exc()},
)
raise DownloadError("yt-dlp download failed") from exc
if info is None: if info is None:
try: try: