f
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user