Skip to content

fix(yt-dlp): upgrade a stale copy before import, stop misrouting 403s to streamlink - #4

Open
AdinathChaudhari wants to merge 1 commit into
mainfrom
fix/stale-ytdlp-403
Open

AdinathChaudhari wants to merge 1 commit into
mainfrom
fix/stale-ytdlp-403

Conversation

@AdinathChaudhari

Copy link
Copy Markdown
Owner

The failure

A download would list every quality correctly, start, and then stop partway with a bare
HTTP Error 403: Forbidden. anydl then announced "yt-dlp can't grab this directly", installed
~8 MB of streamlink dependencies, and finished with No plugin can handle URL.

Both halves of that were wrong. The 403 was a stale yt-dlp — the site had rotated its player
and the signatures the old copy computes no longer validate. And streamlink was never going to
help: the media was found, the CDN just refused the transfer.

Root cause

ensure_yt_dlp() only installed yt-dlp when it was missing. A working install was never
touched again, so it quietly rotted — and these breakages land every few weeks.

Changes

1. Upgrade a stale yt-dlp before the import.
Read the installed version with importlib.metadata (which doesn't import the package), and
pip install --upgrade when it's older than YT_DLP_MAX_AGE_DAYS (14). This has to happen
before the first import yt_dlp — a package swapped out underneath a running process isn't
picked up by a re-import, so there's no mid-run rescue. A failed pip prints and continues on
whatever is installed rather than aborting; a genuinely missing yt-dlp now exits with an install
hint instead of a traceback.

2. Don't route a CDN rejection to streamlink.
_looks_stale() matches the 403 / "unable to download video data" / "requested format is not
available" signatures. When one of those hits a URL that a specific (non-generic) extractor
claimed, print the upgrade command instead of the streamlink detour. Every other download
error still falls through to streamlink — the never-a-dead-end path is intact.

Verification

Reproduced against a real URL that was failing:

yt-dlp result
2026.07.04 (52 days old) 403 at ~42%
2026.08.19 completes; output verified with ffprobe — full duration, video + audio both present

Both new branches exercised directly, with the streamlink call stubbed to see which path is taken:

  • stale version → pip install --upgrade yt-dlp called; current version → no pip call
  • 403 on a known extractor → returns without touching streamlink
  • an unrelated error on the same URL → still reaches streamlink

known_extractor() re-checked on real URLs (it correctly rejects malformed IDs, which is what
makes the guard safe to gate on).

… to streamlink

A yt-dlp more than a couple of weeks old still lists formats correctly and then
dies partway through the transfer with a bare HTTP 403 — the site rotated its
player and the signatures the old copy computes no longer validate. ensure_yt_dlp()
only ever installed a *missing* copy, so a working install silently rotted.

- Read the installed version with importlib.metadata (no package import) and
  pip install --upgrade when it is older than YT_DLP_MAX_AGE_DAYS (14). This has
  to run before the first `import yt_dlp`: a package replaced underneath a live
  process is not picked up by a re-import, so a mid-run upgrade cannot help.
- A failed pip no longer raises; the run continues on whatever is installed, and a
  genuinely missing yt-dlp now exits with an install hint instead of a traceback.
- On a download failure, _looks_stale() recognises the CDN-rejection signatures.
  When one hits a URL a specific (non-generic) extractor claimed, the media was
  found and the transfer was refused, so streamlink cannot help — it installs 8 MB
  of dependencies and ends in "No plugin can handle URL". Print the upgrade command
  instead. Every other download error still falls through to streamlink.

Verified against a real URL that reproduced the 403: fails on 2026.07.04, downloads
complete on 2026.08.19. Both new branches exercised — a stale version triggers the
upgrade and a current one does not; a 403 on a known extractor skips streamlink
while an unrelated error still reaches it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant