· 7 min read
Using FFmpeg with YT-DLP
Integrate yt-dlp with FFmpeg for high-quality video and audio downloads, format merging, and audio extraction on Windows and Linux.
If yt-dlp is printing one of these:
WARNING: You have requested merging of multiple formats but ffmpeg is not installed. The formats won't be merged
ERROR: Postprocessing: ffprobe and ffmpeg not found. Please install or provide the path using --ffmpeg-location
yt-dlp wants to shell out to a real ffmpeg binary (and sometimes ffprobe) and can’t find one. The symptom is usually a video file with no audio, two separate .f<id> files that need manual syncing, or a download stuck in its original container. yt-dlp doesn’t bundle ffmpeg — you install it yourself, then point yt-dlp at it.
How yt-dlp uses ffmpeg
yt-dlp handles the network side — locating streams, picking formats, fetching bytes. Anything that touches the media itself goes through ffmpeg: merging separate video and audio tracks, swapping containers, re-encoding codecs, extracting audio, and embedding subtitles, thumbnails, and metadata. ffprobe (ffmpeg’s analyzer) is called by some of those postprocessors to inspect codec details of the file on disk. Neither binary is bundled with yt-dlp; it just shells out to whatever it finds on your machine.
Installing ffmpeg for yt-dlp
First trap: pip install ffmpeg does NOT install ffmpeg. It installs an unrelated Python package. yt-dlp needs the real ffmpeg binary, and that mistake is the most common reason it prints “ffmpeg not found” after a successful pip install.
What you want is a build that ships ffmpeg, ffprobe, and libmp3lame in the same archive — that covers every yt-dlp operation. yt-dlp publishes nightly Windows and Linux binaries with those included on its latest release page.
On Windows, grab ffmpeg-master-latest-win64-gpl.zip, unzip it, and put both ffmpeg.exe and ffprobe.exe somewhere yt-dlp can find them — see the next section.
On Linux, grab the static ffmpeg-master-latest-linux64-gpl.tar.xz and extract ffmpeg and ffprobe to somewhere on your PATH (e.g. ~/.local/bin). apt install ffmpeg or dnf install ffmpeg will also work for merge, remux, and MP3 extraction, but distro versions can lag a release or two behind.
On macOS, yt-dlp/FFmpeg-Builds doesn’t ship a binary, so grab a current Apple Silicon or Intel build from the ffmpeg.download picker. brew install ffmpeg also works but can lag a few weeks.
Whichever path you chose, open a fresh terminal and run ffmpeg -version. PATH changes don’t apply to terminal windows that were already open, so always test in a new one.
Pointing yt-dlp at ffmpeg
yt-dlp looks for the binary in three places, in order:
- The path passed to
--ffmpeg-location. - The directory containing the yt-dlp binary.
- Your
PATH.
If you’ve added ffmpeg’s bin directory to PATH, the flag is unnecessary. Otherwise:
yt-dlp --ffmpeg-location "C:\ffmpeg\bin" -f "bv*+ba" <url>
--ffmpeg-location accepts either the directory containing ffmpeg.exe (and ffprobe.exe) or the path to the binary itself. The directory form is the common one because it covers both at once:
# Directory — covers both ffmpeg and ffprobe
yt-dlp --ffmpeg-location "$HOME/.local/bin" -f "bv*+ba" <url>
# Single binary
yt-dlp --ffmpeg-location "$HOME/.local/bin/ffmpeg" -f "bv*+ba" <url>
To stop typing the flag, put it in ~/.config/yt-dlp/config (or %APPDATA%\yt-dlp\config.txt on Windows):
--ffmpeg-location C:\ffmpeg\bin
Does yt-dlp actually need ffmpeg?
Only for operations that touch the media. Single-format downloads don’t:
| Operation | Flag | Needs ffmpeg? | Needs ffprobe? |
|---|---|---|---|
| Download a single existing format | -f 22 | no | no |
| Merge best video + best audio | -f "bv*+ba" (any +) | yes | yes |
| Remux container (lossless) | --remux-video mp4 | yes | no |
| Re-encode (lossy) | --recode-video mp4 | yes | no |
| Audio extraction in source codec | -x | yes | yes |
| Audio extraction to MP3 | -x --audio-format mp3 | yes (needs libmp3lame) | yes |
| Embed subs / thumbnail / metadata | --embed-subs, etc. | yes | sometimes |
| HLS / DASH via ffmpeg | --downloader m3u8:ffmpeg | yes | no |
Any single existing format (-f 22, -f bv, -f ba, …) works on a fresh install with no ffmpeg. Anything that combines streams or post-processes them doesn’t.
Merging, remuxing, recoding
When you pass any format selector with a +, yt-dlp downloads the two streams separately and runs ffmpeg to mux them:
yt-dlp -f "bv*+ba" --merge-output-format mp4 <url>
--merge-output-format controls the container. The merge runs through the FFmpegMergerPP postprocessor (verbose logs show it as [Merger]).
Remux vs. recode is the distinction worth getting right:
--remux-video mp4swaps the container only. Codecs are preserved. Fast and lossless. Accepted targets:mp4,mkv,webm,mov,avi,flv,mka,m4a,mp3,ogg,opus.--recode-video mp4re-encodes the video. Slow and lossy. Needed only when the source codec isn’t supported by the target container.
# Lossless container swap
yt-dlp --remux-video mp4 <url>
# Re-encode (slow, quality loss)
yt-dlp --recode-video mp4 <url>
If your goal is “I want an MP4,” start with --remux-video mp4. Fall back to --recode-video only if remux fails because the codec doesn’t fit the container.
Audio extraction (MP3 and others)
For MP3:
yt-dlp -x --audio-format mp3 --audio-quality 0 <url>
-x (or --extract-audio) triggers the FFmpegExtractAudioPP postprocessor. --audio-format mp3 tells it to convert via ffmpeg’s libmp3lame encoder — included in any reasonable ffmpeg build, yt-dlp/FFmpeg-Builds included. --audio-quality 0 is VBR best.
Supported audio codecs: best, aac, alac, flac, m4a, mp3, opus, vorbis, wav.
To skip re-encoding entirely and keep the source codec (Opus or M4A on YouTube), drop --audio-format:
yt-dlp -x <url>
What the verbose log calls them
Run yt-dlp with -v and you’ll see postprocessor tags like [Merger] or [ExtractAudio]. They map one-to-one to ffmpeg postprocessor classes:
| Log tag | Class | Triggered by |
|---|---|---|
[Merger] | FFmpegMergerPP | any + in -f |
[VideoRemuxer] | FFmpegVideoRemuxerPP | --remux-video |
[VideoConvertor] | FFmpegVideoConvertorPP | --recode-video |
[ExtractAudio] | FFmpegExtractAudioPP | -x |
One quirk: the convertor’s constructor parameter is spelled preferedformat — one r, long-standing typo. If you searched it after seeing it in a Python-API traceback, you weren’t wrong.
ffmpeg as the downloader (HLS, DASH)
By default yt-dlp downloads HLS/DASH segments with its native fragment downloader. For live streams or fragile sources, swapping in ffmpeg per protocol often helps:
yt-dlp --downloader m3u8:ffmpeg --downloader-args "ffmpeg_i:-loglevel error" <url>
--downloader [PROTO:]NAME scopes the choice by protocol so plain HTTP stays on the native downloader. --downloader-args passes flags through; the ffmpeg_i: prefix targets the -i (input) side.
Passing arguments to the postprocessor
--postprocessor-args NAME:ARGS pushes flags into the ffmpeg command the postprocessor builds. For example, to be explicit about stream copy on the merge step:
yt-dlp -f "bv*+ba" --postprocessor-args "Merger:-c copy" <url>
The name on the left is the postprocessor (Merger, VideoRemuxer, VideoConvertor, ExtractAudio, …). Stream copy is already the merge default, but the same syntax lets you pass, say, -movflags +faststart to a recode step.
Picking a build
For yt-dlp specifically you don’t have to overthink it. A default GPL build covers merge, remux, MP3, and recode to H.264/H.265. If you’re shipping yt-dlp inside a closed-source product, the LGPL variant is enough for merge, remux, and MP3 — libx264/libx265 re-encode is the only thing that needs GPL. See the GPL vs LGPL explainer on ffmpeg.download for the full breakdown.
One case where ffmpeg can’t help at all: YouTube’s SABR streams. yt-dlp + ffmpeg don’t speak the protocol, so you have to bypass it upstream — see bypassing YouTube’s SABR streaming.
