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

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

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

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.

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.