Because yt-dlp was picking a YouTube client whose media URLs started returning HTTP 403, and ffmpeg reports that as exit code 8. Upgrading yt-dlp fixes the 403 — but then a second problem appears: YouTube no longer offers progressive (muxed) formats at all, so a format selector that asks for them matches nothing.
Exit code 8 is what ffmpeg returns when it cannot open its input. Run the same download with verbose output and the real cause shows up one line earlier: HTTP 403 Forbidden on the media URL.
Look at the URL itself and you can see which client the extractor used. In our case it read c=ANDROID_VR — a client whose URLs YouTube had started rejecting.
The fix for the 403 lived only in nightly builds; the newest stable release on PyPI still had the old client. After upgrading, the 403 disappeared and a different error took its place: "Requested format is not available".
That second error is the interesting one. Checking the format list showed 31 video formats and every single one marked video only — the progressive muxed formats (itag 18 and 22) were simply gone. A selector written to prefer progressive mp4 first therefore matched nothing at all.
| Measured | Source | |
|---|---|---|
| Video formats offered | 31, all "video only" | yt-dlp --list-formats, 20.08.2026 |
| Progressive muxed formats (itag 18/22) | none | same format listing |
We first concluded that a JavaScript runtime was missing, because the tool prints a warning saying exactly that and pointing at deno. It was wrong. Testing in a container without any JS runtime produced the full 31-format list and a successful download — the earlier "format is not available" had come from the old selector, not from a missing runtime.
The other trap was our own cache. One run appeared to succeed while everything was broken, because it reused an already-downloaded source: the download stage took 1.5 seconds and moved no bytes. If you are verifying a fix like this, use a source you have never fetched before.
Once DASH is the only option, downloads of long sections get throttled hard. Our own guard rejected downloads below 100 KB/s — and the working download ran at 58.8 KB/s. So the fix looked broken until we measured the actual speed and lowered the threshold.
Worth checking on your side too: a throttle guard tuned for progressive downloads will reject perfectly healthy DASH ones.
We did not. A container with no JavaScript runtime at all returned the complete format list and downloaded successfully once yt-dlp was current and the format selector no longer asked for progressive streams. Your setup may differ, but the warning alone is not proof that a runtime is the blocker.
Use a source that has never been downloaded before, and check that the download stage actually took time. A cached hit finishes in a couple of seconds and proves nothing about the network path.
It reintroduces throttling on long section downloads — we measured 58.8 KB/s on a 20-minute window from a 3h49m source. It completed, but the guard timings around it need to allow for it. There is no progressive alternative left to fall back to.
5 free videos every month, no card required.