Use two-pass Constant Quality mode:
ffmpeg -i input.mp4 -b:v 0 -crf 30 -pass 1 -an -f webm -y /dev/null
ffmpeg -i input.mp4 -b:v 0 -crf 30 -pass 2 output.webm
If you're using Microsoft Windows™ instead of UNIX, change
We discard the video using
/dev/null because the first pass's only purpose is to generate a log file of statistics.
Setting the video bitrate to zero while also specifying the CRF (Constant Rate Factor) enables Constant Quality mode which targets a certain perceptual quality level. For VP9, the CRF can range from 0 (best quality) to 63 (smallest file size).
It is important to set the video bitrate to zero. If you do not specify a video bitrate, it defaults to something low which gets you Constrained Quality mode, where each frame has a maximum bitrate. While that can be useful for streaming, it reduces quality significantly as frames with a lot of motion will not have the bandwidth needed to look good.
Another part of the reason you’re losing quality is that
webm (VP9) prefers to encode in two passes for best results. The first pass compiles statistics about the video which is then used in the second pass to make a smaller and higher quality video.
Please see the ffmpeg VP9 documentation if you’d like to learn more.
A word about transcoding
By the way, in general, transcoding formats (taking a compressed video and recompressing it with a different codec) is a bad idea as you’ll not only add the normal errors from lossy video encoding, but you’ll waste bits in the new format trying to preserve the artifacts from the old.
If you mean “without quality loss” literally
-lossless 1 option of ffmpeg’s VP9 encoder enables lossless quality mode, but with a potentially enormous increase in filesize.
All you need is this:
ffmpeg -i input.mp4 -lossless 1 output.webm
When I tried
-lossless 1 on a low-quality
mp4, the resulting
webm was 100× larger, which was not accceptable to me. However, if your
mp4 files are already very high quality, I expect the size difference will not be so great.