Before converting anything, ask whether you need to convert at all. A great many format changes are only a container change. The video and audio inside are already what you need, and only the wrapper is wrong. That takes seconds instead of hours and loses no quality.
ffprobe -hide_banner input.mkv
If that reports H.264 video and AAC audio and you want MP4, you do not need to re-encode. Copy the streams into the new container:
ffmpeg -i input.mkv -c copy output.mp4
No decoding, no quality loss, limited only by disk speed. Try this first every time; when it works it is the whole job.
When you do have to re-encode
Re-encoding is necessary when the codec itself is wrong for the destination, when you need a smaller file, or when the container cannot hold what is inside. The basic form:
ffmpeg -i input.avi -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output.mp4
Every part of that matters. -c:v libx264 chooses the video encoder, -crf 23 sets quality, -preset medium sets how hard the encoder works, and the audio options convert sound to AAC at a reasonable bitrate.
Leaving out the audio options is a common mistake: FFmpeg picks a default that may not suit the container, and MP4 in particular is fussy about what it accepts.
CRF, and why not to specify a bitrate
CRF asks for a consistent visual quality and lets the file size follow. For anything watched on demand this is what you want, because simple scenes use fewer bits and complex ones use more, which is exactly right.
- 18: visually lossless, large files
- 23; the default, a good balance
- 28: noticeably smaller, acceptable for most web video
The scale is inverted and logarithmic: lower means better and bigger, and a change of about 6 halves or doubles the size.
Specify a fixed bitrate only when something external requires it: a delivery specification, or a hard bandwidth ceiling. Otherwise CRF produces a better result at a smaller average size.
Format combinations that actually work
MP4 with H.264 and AAC. The safe default. Plays in every browser, on every phone, in every player. Choose this unless you have a specific reason not to.
ffmpeg -i input.avi -c:v libx264 -crf 23 -c:a aac -b:a 128k -movflags +faststart output.mp4
-movflags +faststart moves the file's index to the beginning so playback can start before the whole file has downloaded. Without it, a browser may wait for the entire file. For anything served over the web, include it every time.
WebM with VP9 and Opus. Open formats, meaningfully smaller than H.264 at the same quality, and slower to encode.
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus output.webm
The -b:v 0 is required for VP9 to operate in constant-quality mode; without it the CRF value is ignored and you get a default bitrate instead. This trips people up regularly because the command appears to work.
MKV accepts almost any combination, which makes it useful for archiving and poor for the web.
Common conversions
Extract or change only the audio while leaving the video untouched:
ffmpeg -i input.mkv -c:v copy -c:a aac -b:a 192k output.mp4
Resize while converting, faster than doing both separately, since it encodes fewer pixels:
ffmpeg -i input.mp4 -vf "scale=1280:-2" -c:v libx264 -crf 23 -c:a copy output.mp4
Use -2 rather than -1 so the result is an even number, which H.264 requires.
Convert a whole folder:
for f in *.avi; do
ffmpeg -i "$f" -c:v libx264 -crf 23 -c:a aac "${f%.avi}.mp4"
done
Quote the variable, or filenames with spaces break the loop.
Changing frame rate and dropping streams
To change frame rate, set it explicitly: converting 60fps to 30fps roughly halves the encoding work:
ffmpeg -i input.mp4 -r 30 -c:v libx264 -crf 23 -c:a copy output.mp4
To remove audio entirely, which is right for silent background video:
ffmpeg -i input.mp4 -an -c:v libx264 -crf 23 output.mp4
When a file has several audio tracks and you want a specific one, list the streams with ffprobe and map by index:
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:1 -c copy output.mp4
Without -map, FFmpeg picks one stream of each type by its own rules, which is frequently not the one you wanted.
What goes wrong
Could not write header. The container cannot hold that codec. Re-encode, or use MKV.
Height not divisible by 2. Use -2 in the scale filter.
The output is enormous. No CRF was set and a default bitrate was used. Add -crf.
The WebM ignored the CRF. -b:v 0 is missing.
Video plays but there is no sound. The audio codec was not converted and the container silently dropped it. Set -c:a aac explicitly.
Browser waits before playing. -movflags +faststart is missing.
Troubleshooting common FFmpeg errors deals with the wider set, and FFmpeg performance optimization explains making these run faster.
For a short looping clip in place of a video file, How to Create Animated GIFs with FFmpeg explains why the obvious command produces a poor result.
Before running any conversion, it is worth asking whether one is needed: if the codecs inside the file already suit the target, the container can be swapped in seconds without touching the video. See How to Remux and Stream Copy Without Re-encoding.
The setting that decides how the result looks and how large it is deserves more than a copied number. There is more on the three approaches and which suits a website in CRF, Two-Pass and Bitrate: What to Use When.
If the colours shift during conversion, the pixels are usually fine and their description is not. Why Converted Video Looks Washed Out or Too Dark explains diagnosing it.