FFmpeg Explained: The Flags That Actually Matter
FFmpeg does more or less everything, which is why its documentation is impenetrable and why most people use a command they copied years ago without knowing what it does.
This covers the flags that change the outcome, and the ones that only look like they do.
The shape of a command
FFmpeg reads arguments positionally. Options before an input apply to that input. Options before the output apply to the output.
ffmpeg -i input.mkv -c:v libx264 -crf 20 output.mp4
# ^input ^^^^^^^^^^^^^^^^^^^ output options
Getting this backwards is the single most common source of “my flag did nothing.” A -ss before -i seeks the input. A -ss after it decodes and discards, which is far slower and behaves differently.
Quality: use CRF, not bitrate
Two ways to tell an encoder how much to spend:
# constant quality, variable size <- almost always what you want
ffmpeg -i in.mkv -c:v libx264 -crf 20 out.mp4
# constant bitrate, variable quality <- only for hard bandwidth limits
ffmpeg -i in.mkv -c:v libx264 -b:v 4M out.mp4
CRF asks for a level of visual quality and lets the file be whatever size that takes. A simple scene compresses small, a complex one gets the bits it needs. That is the right trade for anything you are storing.
Bitrate forces a data rate and lets quality suffer when the content is demanding. It exists for streaming, where the pipe has a fixed width, and it is the wrong default for a local file.
Useful CRF values, keeping in mind that the scales differ per encoder:
| Encoder | Visually lossless | Good | Acceptable |
|---|---|---|---|
| libx264 | 17 to 18 | 20 to 23 | 24 to 28 |
| libx265 | 20 to 22 | 24 to 26 | 27 to 30 |
| libsvtav1 | 20 to 24 | 28 to 32 | 35 to 40 |
Lower is better quality and larger. Each increase of about 6 roughly halves the file size.
Preset buys size, not quality
ffmpeg -i in.mkv -c:v libx264 -crf 20 -preset slow out.mp4
Presets run from ultrafast to veryslow. The important thing to understand: at a fixed CRF, a slower preset gives you a smaller file at the same quality, not a better-looking one.
The encoder is being given more time to search for efficient ways to describe the same picture. It is a time-for-size trade.
slow is a sensible default for anything you are keeping. veryslow costs a great deal of time for a few percent. ultrafast is for when you are capturing in real time and cannot afford to fall behind.
Copy is free, re-encoding never is
# change container only, no quality loss, nearly instant
ffmpeg -i input.mkv -c copy output.mp4
# drop a stream
ffmpeg -i input.mkv -map 0:v -map 0:a:0 -c copy output.mkv
-c copy moves compressed data without decoding it. No quality loss, no meaningful CPU use, limited by disk speed.
Use it whenever you are not actually changing the pictures: remuxing containers, dropping audio tracks or subtitles, or trimming on keyframe boundaries.
Every re-encode loses something, even at CRF 18. Encoding an already-encoded file means the second encoder is faithfully reproducing the first one’s artefacts as well as the picture.
Trimming, and why your cut is off
# fast, keyframe-accurate, lossless
ffmpeg -ss 00:01:30 -i input.mp4 -t 60 -c copy clip.mp4
# slow, frame-accurate, re-encodes
ffmpeg -ss 00:01:30 -i input.mp4 -t 60 -c:v libx264 -crf 20 clip.mp4
With -c copy, FFmpeg can only cut at keyframes. Ask for 00:01:30 when the nearest keyframe is at 00:01:28 and you get two extra seconds, or a frozen opening frame, depending on how the player handles it.
That is not a bug. A non-keyframe is defined relative to frames you have just discarded, so there is nothing to decode it against.
If you need the exact frame, you must re-encode. If you need speed and lossless output, accept the keyframe boundary.
Hardware acceleration, honestly
# NVIDIA
ffmpeg -i in.mkv -c:v h264_nvenc -cq 23 out.mp4
# Intel / AMD via VAAPI
ffmpeg -vaapi_device /dev/dri/renderD128 -i in.mkv \
-vf 'format=nv12,hwupload' -c:v h264_vaapi -qp 23 out.mp4
# check what your hardware offers
ffmpeg -hwaccels
vainfo
Hardware encoders are much faster and much less power-hungry. They are also less efficient per bit: at the same file size, software x264 on a slow preset looks better.
The right rule is about volume and purpose. Transcoding a media library for a device, or handling several streams at once, is what hardware is for, and our Jellyfin hardware transcoding guide covers that case in detail. Producing an archival copy you will keep for a decade is worth the CPU time.
The flags that prevent “works on my machine”
ffmpeg -i in.mkv \
-c:v libx264 -crf 20 -preset slow \
-pix_fmt yuv420p \
-c:a aac -b:a 192k \
-movflags +faststart \
out.mp4
-pix_fmt yuv420p forces the widely supported chroma format. Without it, FFmpeg may pick yuv444p or 10-bit output that plays fine locally and fails on phones, browsers and televisions.
-movflags +faststart moves the MP4 index to the front of the file so it can begin playing before it has fully downloaded. Essential for anything served over HTTP, irrelevant for local files.
-c:a aac -b:a 192k gives audio a sane codec and rate. Audio is a small fraction of the file, so being generous costs little.
Scaling and filters
# scale to 1080p, preserving aspect ratio
ffmpeg -i in.mkv -vf 'scale=-2:1080' -c:v libx264 -crf 20 out.mp4
# burn in subtitles
ffmpeg -i in.mkv -vf "subtitles=subs.srt" -c:v libx264 -crf 20 out.mp4
-2 rather than -1 in a scale filter means “work it out, and make it divisible by two.” Most encoders reject odd dimensions, and -1 will hand them one sooner or later.
Inspecting before you encode
# what is actually in the file
ffprobe -hide_banner input.mkv
# machine-readable
ffprobe -v error -show_streams -of json input.mkv
Look before you convert. Knowing the existing codec, resolution and bit depth usually tells you whether you need to re-encode at all, and a surprising number of conversions turn out to be remuxes.
A quick reference
# shrink a large file, keep it looking good
ffmpeg -i big.mkv -c:v libx265 -crf 26 -preset slow -c:a copy small.mkv
# extract audio without touching it
ffmpeg -i video.mkv -vn -c:a copy audio.m4a
# make a GIF that is not enormous
ffmpeg -i clip.mp4 -vf "fps=12,scale=480:-2:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" out.gif
# concatenate files with identical codecs
printf "file '%s'\n" part1.mp4 part2.mp4 > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
Our FFmpeg command builder assembles these interactively if you would rather pick options than memorise them.
One habit worth adopting: never overwrite your source. FFmpeg will happily read and write the same path in some configurations and leave you with neither file. Write to a new name, verify it, then delete the original if you want to.
Frequently Asked Questions
What is the difference between CRF and bitrate in FFmpeg?
CRF targets a constant level of visual quality and lets the file size vary, which is what you want for files you are storing. Bitrate targets a fixed data rate and lets quality vary, which is what you want when something downstream has a hard bandwidth limit. For almost all local encoding, CRF is the correct choice.
Does a slower FFmpeg preset make the video look better?
Not directly. At a fixed CRF, a slower preset produces a smaller file at roughly the same quality, because the encoder spends more time finding efficient ways to represent the same picture. You are trading encoding time for file size, not for visual quality.
When should I use -c copy instead of re-encoding?
Whenever you are only changing the container, trimming on keyframe boundaries, or removing streams. Copy moves the compressed data without decoding it, so it is nearly instant and completely lossless. Re-encoding always loses some quality, even at high settings.
Is hardware encoding worse quality than software?
At the same bitrate, generally yes. Hardware encoders are built for speed and low power rather than maximum compression efficiency, so software x264 or x265 at a slow preset will produce a smaller file at equivalent quality. Hardware wins decisively on speed and on power draw.
Why does my trimmed video start late or begin with a frozen frame?
Because seeking with stream copy can only cut at keyframes. If your requested time is not a keyframe, FFmpeg starts at the nearest one, which produces an offset or a stall. Re-encoding allows a frame-accurate cut, at the cost of quality and time.
What does -pix_fmt yuv420p do and why do I need it?
It sets the chroma subsampling and bit depth to the most widely supported combination. Encoders sometimes choose formats like yuv444p or 10-bit that many players, browsers and phones cannot decode, producing a file that works on your machine and nowhere else. Setting it explicitly avoids that.