ffmpeg's documentation reads like a phone book. Here's the operator's cut — twelve battle-tested recipes for converting, compressing, trimming, and extracting media, with the flags that matter and the gotchas that eat your afternoon.

Every Linux box worth its salt has ffmpeg one apt install away, and almost nobody has read its manual. The manual is 100,000 words of flags; real life is a dozen jobs you do over and over. The trap most people fall into is copying a random invocation from a forum, watching it work once, and never learning why — so the next file breaks in a new and exciting way.

This is the 12-recipe field guide. Learn these and you stop cargo-culting flags.

Ground rules before the recipes

Three things make every ffmpeg command readable:

  • -i marks an input. Options placed before -i configure the input; options after it configure the output. Order matters.
  • -c copy means "don't re-encode." It copies the stream as-is: instant and lossless. Anything else re-encodes, which costs CPU and (slightly) quality.
  • Test on a copy. -y overwrites output without asking; -n refuses to overwrite. For batch jobs, -n is the safe default.

1. Convert anything to a plays-everywhere MP4

ffmpeg -i input.mkv -c:v libx264 -crf 23 -preset medium -c:a aac -movflags +faststart output.mp4

This is the one you'll use weekly. -crf 23 sets quality (lower is better and bigger; 18–28 is the sane range). -preset medium trades encoding speed for file size — veryfast for rush jobs, slow when size matters. -movflags +faststart moves the metadata to the front so web players start before the whole file downloads.

Gotcha: some old players and picky hardware choke on anything but 8-bit 4:2:0. Add -pix_fmt yuv420p and the "black screen on grandma's TV" class of bugs disappears.

2. Shrink a video for messaging apps

ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset veryfast -vf "scale=1280:-2" -c:a aac -b:a 96k output.mp4

CRF 28 plus downscaling to 1280px wide turns a 200 MB clip into something you can actually send. Audio at 96k is plenty for phone speakers.

Gotcha: scale=1280:-2 — the -2 keeps the aspect ratio and forces an even height. x264 requires even dimensions; scale=1280:-1 on an odd-height source fails with a cryptic error.

3. Extract audio as MP3

ffmpeg -i video.mp4 -vn -c:a libmp3lame -q:a 2 audio.mp3

-vn drops the video stream; -q:a 2 is VBR quality (~190 kbps, transparent for most ears).

Gotcha: forget -vn and ffmpeg errors out — MP3 can't hold a video stream, and the error message won't say that plainly.

4. Trim a clip without re-encoding

ffmpeg -ss 00:01:30 -to 00:02:15 -i input.mp4 -c copy cut.mp4

Stream copy makes this instant: no quality loss, no waiting.

Gotcha: -ss before -i seeks fast but cuts at the nearest keyframe, so your cut point can drift a second or two. Put -ss after -i for frame-accurate cuts — slower, because it decodes everything up to that point. For sample-exact trims, drop -c copy and re-encode.

5. Make a GIF that doesn't look like a 1998 relic

ffmpeg -i input.mp4 -vf "fps=12,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -i input.mp4 -i palette.png -lavfi "fps=12,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" out.gif

GIF only holds 256 colors; the two-pass palette trick picks the right 256 for your clip. Twelve fps and 480px wide keeps the file small enough to actually share.

Gotcha: the one-pass ffmpeg -i in.mp4 out.gif everyone tries first produces muddy, banded garbage. The palette pass is the entire trick.

6. Grab a thumbnail frame

ffmpeg -ss 00:00:42 -i video.mp4 -frames:v 1 -q:v 2 thumb.jpg

Seek to the moment, dump one frame. -q:v 2 sets JPEG quality (2–5 is the sweet spot).

Gotcha: -ss before -i is fast but lands on a keyframe, which might be a blurry scene change. For a precise frame, seek after -i — or just try a few timestamps; it's cheap.

7. Normalize podcast or interview audio

ffmpeg -i in.wav -af loudnorm=I=-16:TP=-1.5:LRA=11 out.wav

The loudnorm filter targets broadcast loudness standards (-16 LUFS is the podcast norm). One command, consistent volume.

Gotcha: single-pass loudnorm adjusts dynamically and may miss the target on tricky material. The correct workflow is two-pass: first pass with print_format=json to measure, second pass feeding the measured values in. For quick jobs one pass is fine; for published episodes, do both.

8. Join clips with no re-encode

printf "file 'a.mp4'\nfile 'b.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4

The concat demuxer stitches files together at stream level — instant, lossless.

Gotcha: every file must share the same codec, resolution, and container parameters. Mixing a phone clip with a screen recording? Re-encode instead (-c:v libx264 -crf 20) or the join will glitch.

9. Record your screen

ffmpeg -f x11grab -video_size 1920x1080 -framerate 30 -i :0.0+0,0 -c:v libx264 -preset ultrafast out.mp4

On X11 this just works: capture the display, encode fast, sort it out later.

Gotcha: on Wayland, x11grab can't see native Wayland windows — that's a security feature, not a bug. Use your compositor's built-in recorder (GNOME and KDE both have one), wf-recorder on wlroots compositors, or OBS. Don't fight the display server; route around it.

10. Extract subtitles

ffmpeg -i movie.mkv -map 0:s:0 subs.srt

-map 0:s:0 means "first input, subtitle streams, the first one." Without -map, ffmpeg picks a "best" video and audio stream and silently drops everything else.

Gotcha: streams are zero-indexed per type. 0:s:1 is the second subtitle track (often the forced/SDH one you actually want). Check with ffprobe first — that's recipe 12.

11. Batch-convert a whole folder

for f in *.mkv; do
  ffmpeg -n -i "$f" -c:v libx264 -crf 23 -c:a aac "${f%.mkv}.mp4"
done

Loop, convert, done. -n means "never overwrite" — the batch-safe choice when you're processing 50 files at 2 AM. "${f%.mkv}" strips the old extension.

Gotcha: quote "$f". Filenames with spaces will ruin your evening otherwise — the oldest shell trap in the book, still catching people.

12. Inspect a file before you touch it

ffprobe -v error -show_entries stream=codec_name,width,height,duration \
  -of default=noprint_wrappers=1 input.mp4

ffprobe ships with ffmpeg and answers the questions you should ask before guessing flags: what codec is this, what resolution, how long, which audio track is English.

Gotcha: "why is this file 4 GB" and "why does the audio drift out of sync" are both answered here in two seconds. Make this a reflex.

What this doesn't cover

Filter graphs, hardware encoding (-hwaccel, NVENC/VAAPI — same recipes, different -c:v), and streaming are the other 5%. And when you want preset-driven encoding with a progress bar instead of flags, HandBrake exists for a reason — no shame in it.

But for the daily work — convert, shrink, trim, extract, join, inspect — these twelve are the whole job. The manual stays on the shelf for the day something truly weird shows up.

Sources: FFmpeg Documentation · FFmpeg Wiki · ffprobe Documentation