fix: name the error yt-dlp actually raises for a cookie-emptied client set

A review round read yt-dlp's source and found the comment behind the previous commit's safety-net
marker did not match it. Measured in the worker with a real account cookie and
`player_client=android_vr`: yt-dlp logs `Skipping client "android_vr" since it does not support
cookies`, drops it, and the request left with no client fails with "Requested format is not
available" — not "No player clients have been requested". That raise sits EARLIER in yt-dlp, before
the cookie-capability removal, so the cookie path can never reach it; and an unknown or renamed
client is skipped with a warning and replaced by yt-dlp's defaults, so the other scenario the
comment named cannot reach it either.

So the marker bought nothing: the symptom that does occur was already retriable here before this
branch. It is dropped, the comment now states what was measured, and the test asserts the real
symptom instead of a message the code cannot produce. The cookie-capability filter itself is
vindicated by the same measurement — without it every signed-in download would burn its first
attempt on a client yt-dlp discards.

Also from the round: an enqueue test for a `source_kind="youtube"` job, the one join nothing else
made (a YouTube job stores a bare 11-char ref and the URL the cookie gate keys on is rebuilt by the
adapter — both ends were pinned apart, the join was not, and a break there would stop every YouTube
cookie silently while signed-out downloads kept working). And copy corrections: the group's only
unhedged claim is hedged, "described the video" loses the pipeline vocabulary, and four Hungarian
phrasings that read as translated English are rewritten.
This commit is contained in:
2026-09-16 04:29:13 +02:00
parent c06a456a9a
commit 2b56c5c2eb
5 changed files with 42 additions and 20 deletions
+9 -11
View File
@@ -93,10 +93,6 @@ class YouTubeAdapter(SourceAdapter):
"Requested format is not available", "Requested format is not available",
"Only images are available", "Only images are available",
"Sign in to confirm", "Sign in to confirm",
# A set yt-dlp emptied because none of its clients take cookies. `client_sets` filters those
# out below, so this is the safety net for a yt-dlp that renames a client or an admin who
# configures both knobs with cookie-incapable clients only.
"No player clients have been requested",
# Measured 2026-09-16: a gated video whose EXTRACTION succeeds and whose media fetch is then # Measured 2026-09-16: a gated video whose EXTRACTION succeeds and whose media fetch is then
# refused by the CDN ("unable to download video data: HTTP Error 403"). The wall moves # refused by the CDN ("unable to download video data: HTTP Error 403"). The wall moves
# between the player response and the stream, so this is the same per-client flakiness one # between the player response and the stream, so this is the same per-client flakiness one
@@ -105,13 +101,15 @@ class YouTubeAdapter(SourceAdapter):
# them, so the next client set is a real second chance. # them, so the next client set is a real second chance.
"unable to download video data", "unable to download video data",
) )
# Measured against yt-dlp's own INNERTUBE_CLIENTS table (2026-09-16, on 2026.07.04 and # Measured in the worker against a REAL account cookie (2026-09-16, yt-dlp 2026.07.04): with
# 2026.08.19): with account cookies attached yt-dlp REMOVES every client whose SUPPORTS_COOKIES # `player_client=android_vr` yt-dlp logs `Skipping client "android_vr" since it does not support
# is False, and a set left empty raises "No player clients have been requested". Our primary set # cookies`, drops it, and the request that is left with no client fails with "Requested format is
# is android_vr — one of those — so a signed-in download would fail on the first attempt and # not available" — which is already a retriable marker above, so the cost is a wasted attempt
# never reach the POT-backed web fallback that the cookie exists for. Pinned by test_adapters # rather than a hard failure. Our primary set is android_vr ALONE, so without this filter every
# against the installed yt-dlp, so a version bump that moves the table turns the gate red # signed-in download would burn its first attempt that way before reaching the POT-backed web
# instead of silently un-authing every YouTube download. # 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
# that moves the table turns the gate red instead of silently un-authing every YouTube download.
_COOKIE_CAPABLE = frozenset( _COOKIE_CAPABLE = frozenset(
{ {
"mweb", "mweb",
+11 -3
View File
@@ -112,9 +112,17 @@ def test_client_sets_and_retriable_are_per_source():
assert SourceAdapter().retriable("Sign in to confirm you're not a bot") is False assert SourceAdapter().retriable("Sign in to confirm you're not a bot") is False
assert YouTubeAdapter().retriable("Sign in to confirm you're not a bot") is True assert YouTubeAdapter().retriable("Sign in to confirm you're not a bot") is True
assert YouTubeAdapter().retriable("some unrelated network blip") is False assert YouTubeAdapter().retriable("some unrelated network blip") is False
# The empty-set error of a cookie-incapable client list (see _COOKIE_CAPABLE): retriable, so an # A client list yt-dlp empties because none of its clients take cookies does NOT raise "No
# admin who configures one still gets the next set tried instead of a hard failure. # player clients have been requested" (that raise sits earlier in yt-dlp, before the
assert YouTubeAdapter().retriable("ERROR: No player clients have been requested") is True # cookie-capability removal). Measured with a real cookie: the emptied request fails with
# "Requested format is not available", which is retriable here already — so the wasted attempt
# is recoverable and `_COOKIE_CAPABLE` exists to avoid spending it at all.
assert (
YouTubeAdapter().retriable(
"ERROR: [youtube] abc: Requested format is not available. Use --list-formats"
)
is True
)
# The media fetch refused after a successful extraction: the format URLs belong to the client # The media fetch refused after a successful extraction: the format URLs belong to the client
# that minted them, so the next client set is a real second chance (it used to end the job). # that minted them, so the next client set is a real second chance (it used to end the job).
assert ( assert (
@@ -185,6 +185,22 @@ class TestCachePrivacy:
asset = db.get(MediaAsset, job.asset_id) asset = db.get(MediaAsset, job.asset_id)
assert asset.format_sig == formats.format_sig(formats.normalize({})) assert asset.format_sig == formats.format_sig(formats.normalize({}))
def test_a_youtube_job_reaches_the_gate_through_its_rebuilt_watch_url(self, db):
# The one join nothing else makes. A YouTube job stores a bare 11-char ref, and the URL the
# cookie gate keys on is REBUILT by the adapter (service.source_url -> YouTubeAdapter). Both
# ends are pinned apart — the watch-URL shape in test_adapters, the host_key in
# TestHostKeyForUrl — so this welds them: an enqueue that ever handed the gate the raw ref
# would silently stop salting, every YouTube cookie would quietly stop being attached, and
# signed-out downloads would keep working, so nothing else here would go red.
u = _user(db)
credentials.upsert_credential(db, u.id, "youtube", COOKIES)
db.commit()
job = service.enqueue(db, u.id, "youtube", "VIDID12345_", {})
asset = db.get(MediaAsset, job.asset_id)
spec = formats.normalize({})
assert asset.format_sig == credentials.authed_format_sig(spec, u.id, "youtube")
assert asset.format_sig != formats.format_sig(spec)
def test_two_users_never_share_the_authed_asset(self, db): def test_two_users_never_share_the_authed_asset(self, db):
a = _user(db, "a@example.com") a = _user(db, "a@example.com")
b = _user(db, "b@example.com") b = _user(db, "b@example.com")
+2 -2
View File
@@ -308,7 +308,7 @@
"youtube": { "youtube": {
"bot_wall": { "bot_wall": {
"short": "YouTube sign-in required", "short": "YouTube sign-in required",
"body": "YouTube only serves this video to a signed-in viewer — it asks the downloader to confirm it isn't a bot. Other YouTube videos are unaffected. Add your YouTube cookies under Settings → Account → Download authentication, then retry the download from the list." "body": "YouTube only serves this video to a signed-in viewer — it asks the downloader to confirm it isn't a bot. Other YouTube videos are usually unaffected. Add your YouTube cookies under Settings → Account → Download authentication, then retry the download from the list."
}, },
"cookie_rejected": { "cookie_rejected": {
"short": "YouTube didn't accept the cookies", "short": "YouTube didn't accept the cookies",
@@ -316,7 +316,7 @@
}, },
"media_forbidden": { "media_forbidden": {
"short": "YouTube refused the video stream", "short": "YouTube refused the video stream",
"body": "YouTube described the video but then refused to hand over the file to a signed-out download. It comes and goes, so a retry from the list is worth one try — and adding your YouTube cookies under Settings → Account → Download authentication is what gets a stubborn one through." "body": "YouTube found the video but refused to send the file to a signed-out download. It comes and goes, so a retry from the list is worth one try — and adding your YouTube cookies under Settings → Account → Download authentication is what gets a stubborn one through."
} }
} }
} }
+4 -4
View File
@@ -308,15 +308,15 @@
"youtube": { "youtube": {
"bot_wall": { "bot_wall": {
"short": "YouTube-bejelentkezés szükséges", "short": "YouTube-bejelentkezés szükséges",
"body": "A YouTube ezt a videót csak bejelentkezett nézőnek adja ki — a letöltőtől azt kéri, igazolja, hogy nem robot. A többi YouTube-videót ez nem érinti. Illeszd be a YouTube-cookie-kat a Beállítások → Fiók → Letöltés-hitelesítés alatt, majd a listában indítsd újra a letöltést." "body": "A YouTube ezt a videót csak bejelentkezett nézőnek adja ki — a letöltőprogramtól azt kéri, igazolja, hogy nem robot. A többi YouTube-videót ez általában nem érinti. Illeszd be a YouTube-cookie-kat a Beállítások → Fiók → Letöltés-hitelesítés alatt, majd a listában indítsd újra a letöltést."
}, },
"cookie_rejected": { "cookie_rejected": {
"short": "A YouTube nem fogadta el a cookie-kat", "short": "A YouTube nem fogadta el a cookie-kat",
"body": "A YouTube biztonsági okból cserélgeti az exportált cookie-kat, ezért a tiéid valószínűleg lejártak. Exportáld őket újra egy privát böngészőablakból, majd zárd be azt az ablakot további böngészés nélkül, illeszd be a Beállítások → Fiók → Letöltés-hitelesítés alatt, majd a listában indítsd újra a letöltést." "body": "A YouTube biztonsági okból lecseréli az exportált cookie-kat, ezért a tiéid valószínűleg lejártak. Exportáld őket újra egy privát böngészőablakból, majd zárd be azt az ablakot további böngészés nélkül, illeszd be a Beállítások → Fiók → Letöltés-hitelesítés alatt, majd a listában indítsd újra a letöltést."
}, },
"media_forbidden": { "media_forbidden": {
"short": "A YouTube megtagadta a videó adatfolyamát", "short": "A YouTube nem adta ki a videófájlt",
"body": "A YouTube leírta a videót, de a fájlt már nem adta ki egy kijelentkezett letöltésnek. Ez jön-megy, ezért egy újrapróbálkozás a listából megér egy kísérletet — a makacs eseteken pedig a YouTube-cookie-k segítenek, a Beállítások → Fiók → Letöltés-hitelesítés alatt." "body": "A YouTube visszaadta a videó adatait, de a fájlt már nem adta ki bejelentkezés nélkül. Ez hol van, hol nincs, ezért érdemes újrapróbálni a listából — a makacs eseteken pedig a YouTube-cookie-k segítenek, a Beállítások → Fiók → Letöltés-hitelesítés alatt."
} }
} }
} }