TechEarl

How to Convert WebM to MP4 (and Back) with ffmpeg

Convert WebM to MP4 with ffmpeg: why it has to re-encode (VP9/Opus into H.264/AAC, not a stream copy), the yuv420p flag that fixes the QuickTime black screen, and the reverse MP4 to WebM with VP9 and Opus.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
Convert WebM to MP4 with ffmpeg, re-encode VP9 and Opus into H.264 and AAC, fix the QuickTime black screen with yuv420p, and convert back to WebM with VP9 and Opus.

To convert a WebM file to MP4 with ffmpeg, re-encode it: WebM holds VP8 or VP9 video and Vorbis or Opus audio, MP4 wants H.264 and AAC, so the streams have to be transcoded, not copied.

bash
ffmpeg -i in.webm -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 192k -movflags +faststart out.mp4

That is the whole job for most files. -crf 23 is the default quality, -preset medium balances speed against size, and -movflags +faststart moves the MP4 index to the front so the file starts playing before it has fully downloaded (worth it for anything you serve on the web). The rest of this page is the why behind that command, the one flag that fixes the "plays in ffmpeg but black in QuickTime" bug, and the reverse trip back to WebM.

You cannot stream-copy WebM into MP4

The first thing people try is the fast path, copying the streams without re-encoding:

bash
# This will NOT work for WebM -> MP4
ffmpeg -i in.webm -c copy out.mp4

-c copy (stream copy) is instant and lossless because it just lifts the existing video and audio streams into a new container. It works when the codecs already fit the target container, for example remuxing H.264-in-MKV to H.264-in-MP4. It does not work here. WebM's VP8/VP9 video and Vorbis/Opus audio are not valid in an MP4 container, so ffmpeg either errors with a muxer complaint or writes a file that no normal player will open. There is no shortcut: WebM to MP4 is a transcode, and a transcode takes time and loses a little quality. Budget for it.

The full WebM to MP4 command, flag by flag

bash
ffmpeg -i in.webm -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 192k -movflags +faststart out.mp4
  • -c:v libx264 encodes the video as H.264, the codec every browser, phone, and TV understands. Use the modern -c:v, not the legacy -vcodec alias.
  • -crf 23 is the quality knob for libx264. Lower means better and bigger; the sane range is 18 to 28 and 23 is the default. Drop to 18 for near-visually-lossless, push to 26 to 28 when size matters more than fidelity.
  • -preset medium trades encode speed for compression efficiency. slow and veryslow give a smaller file at the same quality for more CPU time; fast and veryfast are the reverse.
  • -pix_fmt yuv420p is the compatibility flag (see the next section).
  • -c:a aac -b:a 192k re-encodes the audio to AAC at 192 kbps. Passing -b:a to ffmpeg's native aac encoder selects constant-bitrate mode, and the built-in encoder is fine for general use. Stereo AAC is effectively transparent from about 128k (roughly 64k per channel), so 192k leaves comfortable headroom; drop to 128k if you want a smaller file.
  • -movflags +faststart relocates the moov atom (the index) to the start of the file so it streams progressively instead of needing the whole download first.

Fix the QuickTime black screen: pix_fmt yuv420p

If your MP4 plays in VLC and ffmpeg's own player but shows a black screen (or refuses to open) in QuickTime, Safari, or an iPhone, the cause is almost always the pixel format. libx264 will happily encode to yuv444p or yuv422p when the source is in one of those formats, and Apple's decoders only reliably handle yuv420p 8-bit H.264. The fix is to force it:

bash
ffmpeg -i in.webm -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 192k out.mp4

I add -pix_fmt yuv420p to every MP4 I intend to share, even when the source is already 4:2:0, because it costs nothing and removes a whole category of "works on my machine" support tickets.

Convert MP4 to WebM (VP9 and Opus)

Going the other way also re-encodes, for the same codec-incompatibility reason. The modern WebM is VP9 video with Opus audio, not the old VP8 and Vorbis pairing:

bash
ffmpeg -i in.mp4 -c:v libvpx-vp9 -crf 31 -b:v 0 -c:a libopus -b:a 128k out.webm

The detail that trips people up is -b:v 0. VP9's constant-quality mode only engages when you set the target bitrate to zero alongside -crf; leave -b:v out and ffmpeg runs in a constrained-quality mode that behaves differently. VP9's CRF scale is 0 to 63 (not libx264's 0 to 51), so the numbers do not transfer between encoders. Around 31 is a good general-purpose starting point; 15 to 35 covers most needs, lower being higher quality.

VP9 is slow to encode. If you can spare the wall-clock time, a two-pass encode produces a noticeably better file at a given size:

bash
ffmpeg -i in.mp4 -c:v libvpx-vp9 -b:v 1M -pass 1 -an -f null /dev/null
ffmpeg -i in.mp4 -c:v libvpx-vp9 -b:v 1M -pass 2 -c:a libopus -b:a 128k out.webm

You will still see the old VP8 form in tutorials from a decade ago:

bash
# Legacy VP8 + Vorbis (use VP9 + Opus instead unless you need ancient compatibility)
ffmpeg -i in.mp4 -c:v libvpx -crf 10 -b:v 1M -c:a libvorbis out.webm

VP8 has a much smaller CRF scale (4 to 63, lower is better) and worse compression than VP9. There is rarely a reason to choose it in 2026; every browser that plays WebM at all plays VP9. AV1 (via libaom-av1 or the faster libsvtav1) is the next step up in compression if you have the encode time to spend, but it sits outside this WebM-versus-MP4 conversion question.

Which CRF for which encoder

The quality numbers are not interchangeable. A quick reference for the two encoders in play here:

EncoderCodecCRF rangeSensible defaultBitrate flag
libx264H.264 (MP4)0 to 51 (use 18 to 28)23omit, CRF drives it
libvpx-vp9VP9 (WebM)0 to 63 (use 15 to 35)31-b:v 0 (required)

Lower CRF is always higher quality and a larger file. The takeaway: do not copy a -crf 23 you used for H.264 onto a VP9 command and expect the same result, and never forget VP9's -b:v 0.

FAQ

No. WebM carries VP8/VP9 video and Vorbis/Opus audio, none of which are valid streams inside an MP4 container, so -c copy fails or produces a file no normal player opens. MP4 needs H.264 and AAC, which means a real transcode: -c:v libx264 -c:a aac. Stream copy only works when the existing codecs already fit the target container.

The pixel format. libx264 may have encoded to a 4:2:2 or 4:4:4 format that Apple's decoders reject, leaving a black frame. Add -pix_fmt yuv420p to the command and re-encode; that is the broadly compatible 8-bit format QuickTime, Safari, and iOS expect.

For H.264 (libx264) the range is 18 to 28 and 23 is the default. For VP9 (libvpx-vp9) the scale is 0 to 63 and around 31 is a good start. Lower is higher quality and a bigger file. The two scales are different, so do not reuse an H.264 number on a VP9 command.

VP9 with Opus audio. It compresses better than the old VP8/Vorbis pair and every browser that plays WebM supports it. Only reach for libvpx (VP8) if you are targeting genuinely ancient software. Remember -b:v 0 to enable VP9's constant-quality mode.

VP9's encoder is more computationally expensive than libx264, especially at low CRF. A two-pass encode is slower still but gives a better result at a target bitrate. If turnaround matters more than file size, H.264 in MP4 is the faster choice; if you want the smallest WebM, spend the time on two-pass VP9.

See also

Sources

Authoritative references this article was fact-checked against.

Tagsffmpegwebm to mp4mp4 to webmVP9H.264video conversionCLI

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

Crop a video with ffmpeg using the crop filter: width:height:x:y from the top-left origin, centered and square crops with in_w/in_h, and cropdetect for black bars.

How to Crop a Video with ffmpeg

Crop a video with ffmpeg's crop filter: crop=w:h:x:y from the top-left origin, centered crops with in_w/in_h expressions, square crops for social, and cropdetect to strip black bars automatically.

Reverse a video from the command line with ffmpeg: the reverse video filter, the areverse audio filter, and the new-output-file rule that keeps the source intact.

How to Reverse a Video with ffmpeg

Reverse a video with ffmpeg using the reverse filter for picture and areverse for sound. Why you must write to a new output file, and why you trim before you reverse.