YouTube to MP3 — in-browser (Pyodide + ffmpeg.wasm)

Same output as the server-side version — MP3 with embedded thumbnail and ID3 metadata — but the entire pipeline runs in your browser: Python (Pyodide) + yt-dlp resolves the stream, ffmpeg.wasm encodes the MP3. The server is used only as a thin CORS relay to forward bytes (browsers can't fetch YouTube directly).

First load: ~12 MB (Pyodide runtime + yt-dlp). Subsequent loads are cached and instant. Audio encoding uses lamejs (pure JS, no SharedArrayBuffer needed) — ~2-3× slower than native ffmpeg, plenty fast for typical music videos.

Cookies are forwarded through the CORS proxy in a header (X-Cookie). Server doesn't store or log them — same trust model as a normal HTTP proxy. For maximum privacy use a throwaway YouTube account.

How is this different from the server-side version?

What does "in-browser" actually mean here?

Pyodide loads CPython compiled to WebAssembly into your browser. We then pip install yt-dlp via micropip — the same yt-dlp that runs on the command line, just in WASM. yt-dlp resolves the YouTube stream URL and metadata client-side. ffmpeg.wasm then decodes the audio, re-encodes to MP3 with embedded ID3v2 tags + APIC thumbnail, all in your browser. The server never sees the audio bytes — it only relays opaque encrypted HTTPS chunks between your browser and Google's CDN, exactly like a regular HTTP proxy.

Why is a CORS proxy needed at all?

Browsers enforce the Same-Origin Policy. YouTube (youtube.com, googlevideo.com) doesn't send Access-Control-Allow-Origin headers, so a direct fetch() from any other domain is blocked. Our proxy adds the missing CORS headers so the browser will accept the response. The proxy only allows *.youtube.com, *.googlevideo.com, and *.ytimg.com — it can't be abused to fetch arbitrary URLs.

Why is the first load so big?

Pyodide (~10 MB) + yt-dlp Python package (~3 MB) + ffmpeg.wasm core (~30 MB) all load on first visit. After that they're cached by the browser and subsequent uses are near-instant. The server-side version is faster for one-off downloads, the WASM version is comparable on the second+ download in the same session.

Does this fix the YouTube bot-check?

Partially. The actual HTTP requests still go through our server's IP (via the proxy), so YouTube can still flag it. But because the requests come from yt-dlp in a fresh Pyodide context with different fingerprinting from native yt-dlp, the bot-check trips less often. For really hard videos you still need cookies — paste them above and they're forwarded through the proxy.

Can you really run yt-dlp in WebAssembly?

Yes — yt-dlp is pure Python, and Pyodide supports installing pure-Python wheels from PyPI via micropip. A few optional C extensions (pycryptodomex, brotli) don't work in Pyodide, but yt-dlp's core extraction works without them for most videos. Pyodide ships its own Python brotli and crypto support, so yt-dlp can still decipher and decrypt streams.