From b16cf7668c32839715a53041d52d558f0c9f9649 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 10 Oct 2026 02:08:43 +0200 Subject: [PATCH] fix(downloads): YouTube downloads reach their profile's height again The primary player client, android_vr, was still offered 1080p and 2160p, but every fetch of those formats died with HTTP 403: it now needs a stream PO token the bgutil sidecar cannot mint. The fallback set then served 360p only, so every download finished at 360p without failing. tv_simply is offered the full ladder and its fetch completes (measured: 1080p, and a localdev download through the pipeline reached the default profile's 720p). yt-dlp 2026.8.19 marks the clients that need such a token, so it stops fetching into the 403. --- backend/app/config.py | 15 +++++++++------ backend/app/downloads/adapters.py | 3 ++- backend/requirements.lock | 5 +++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/backend/app/config.py b/backend/app/config.py index b7680444..8edf93aa 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -181,15 +181,18 @@ class Settings(BaseSettings): # for YouTube Proof-of-Origin tokens (bypasses bot-detection + unlocks high-quality # formats). Empty disables it (yt-dlp still works, but may hit "confirm you're not a bot"). download_pot_base_url: str = "http://bgutil-pot:4416" - # yt-dlp player clients (comma-separated). PRIMARY = android_vr: high-quality DASH, no PO - # token / JS runtime needed, reliable for normal use. On a bot/DRM/format failure (YouTube's - # per-client responses are flaky, and android_vr can get bot-flagged under heavy load) the - # worker retries with the FALLBACK set — the POT-capable web clients (web needs the token + - # Deno) plus android for audio. Both admin-tunable when YouTube shifts what works. + # yt-dlp player clients (comma-separated). PRIMARY = tv_simply: measured 2026-10-10 as the one + # client that is offered 1080p+ AND finishes the fetch, its stream token minted by the bgutil + # sidecar. android_vr (the primary until then) was still offered 2160p, but every fetch died + # with HTTP 403 — it now needs a stream PO token the sidecar cannot mint — and the fallback + # below served 360p only, so every download came out at 360p without failing. On a + # bot/DRM/format failure the worker retries with the FALLBACK set — the POT-capable web clients + # (web needs the token + Deno) plus android, whose 360p is the last resort. Both admin-tunable + # (env) when YouTube shifts what works. # A SIGNED-IN download (the user added YouTube cookies) keeps only the cookie-capable clients # of these two sets — yt-dlp drops the rest, so android_vr / android / ios never run there. # See adapters.YouTubeAdapter._COOKIE_CAPABLE. - download_player_clients: str = "android_vr" + download_player_clients: str = "tv_simply" download_player_clients_fallback: str = "web_safari,web,android" @staticmethod diff --git a/backend/app/downloads/adapters.py b/backend/app/downloads/adapters.py index 5c8e4150..7878bffe 100644 --- a/backend/app/downloads/adapters.py +++ b/backend/app/downloads/adapters.py @@ -106,7 +106,8 @@ class YouTubeAdapter(SourceAdapter): # `player_client=android_vr` yt-dlp logs `Skipping client "android_vr" since it does not support # cookies`, drops it, and the request that is left with no client fails with "Requested format is # not available" — which is already a retriable marker above, so the cost is a wasted attempt - # rather than a hard failure. Our primary set is android_vr ALONE, so without this filter every + # rather than a hard failure. Our primary set is tv_simply ALONE, which cannot carry cookies either + # (measured on yt-dlp 2026.08.19: `SUPPORTS_COOKIES` False), so without this filter every # signed-in download would burn its first attempt that way before reaching the POT-backed web # fallback the cookie exists for. The list is yt-dlp's own INNERTUBE_CLIENTS[...] # ["SUPPORTS_COOKIES"], pinned by test_adapters against the installed yt-dlp, so a version bump diff --git a/backend/requirements.lock b/backend/requirements.lock index de7bbd57..0d6f8dac 100644 --- a/backend/requirements.lock +++ b/backend/requirements.lock @@ -4,7 +4,8 @@ # The prod Dockerfile + Dockerfile.test install THIS file; the gate's pip-audit (scripts/check.sh) # scans it, blocking. A security fix may be ONE pin bumped in place instead of a full # regeneration, which would move every other package too — then `pip install -r requirements.lock && pip check` must -# pass. In place so far: urllib3 2.7.0 -> 2.8.0 (PYSEC-2026-4175/4176/4177). +# pass. In place so far: urllib3 2.7.0 -> 2.8.0 (PYSEC-2026-4175/4176/4177); yt-dlp 2026.7.4 -> 2026.8.19 +# (it marks the clients that need a stream PO token, so the downloader stops fetching into a 403). APScheduler==3.11.3 Authlib==1.7.2 Mako==1.4.1 @@ -62,4 +63,4 @@ uvloop==0.22.1 watchfiles==1.2.0 websockets==17.0.1 yt-dlp-ejs==0.8.0 -yt-dlp==2026.7.4 +yt-dlp==2026.8.19