Updated 2026-08-20

Why did YouTube downloads suddenly start failing with "ffmpeg exited with code 8"?

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.

The error is not really about ffmpeg

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.

Upgrading is half the fix

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.

MeasuredSource
Video formats offered31, all "video only"yt-dlp --list-formats, 20.08.2026
Progressive muxed formats (itag 18/22)nonesame format listing

The part that cost us a second diagnosis

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.

The third thing, which nothing warns you about

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.

FAQ

Do I need to install deno?

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.

How do I tell a real fix from a cached success?

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.

Is switching to DASH risky?

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.