A reference of the FFmpeg commands that come up most often, with enough context to know when each one applies. Every command here is safe to copy and adjust.
Two rules cover most of what goes wrong. Put -ss before -i when seeking, or FFmpeg decodes everything up to that point. And check whether you need to re-encode at all before writing a transcode, -c copy is instant and lossless when the codecs already match.
Inspect a file first
# What is actually inside ffprobe -hide_banner input.mp4 # Just the duration, as a number ffprobe -v error -show_entries format=duration -of csv=p=0 input.mp4 # Just the resolution ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 input.mp4
Run the first one before debugging anything. A surprising share of FFmpeg problems are a file that is not what someone assumed.
Convert and re-encode
# Container change only - instant, no quality loss ffmpeg -i input.mkv -c copy output.mp4 # Standard web MP4 ffmpeg -i input.avi -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k -movflags +faststart output.mp4 # Smaller file, still fine for web ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset slow -c:a aac -b:a 96k output.mp4 # WebM with VP9 ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus output.webm
-movflags +faststart matters for anything served over the web: it moves the index to the front so playback starts before the file has fully downloaded.
-b:v 0 is required for VP9, otherwise the CRF is ignored and you get a default bitrate.
Resize
# Fixed width, height follows ffmpeg -i input.mp4 -vf "scale=1280:-2" -c:v libx264 -crf 23 -c:a copy output.mp4 # Fit inside a box without distortion ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease" output.mp4 # Fill a box exactly, cropping the overflow ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=increase,crop=1280:720" output.mp4
Use -2 rather than -1. It rounds to an even number, which H.264 requires, and -1 produces an odd height that fails.
Cut and join
# Extract a clip - fast, no re-encode ffmpeg -ss 00:01:30 -i input.mp4 -t 60 -c copy clip.mp4 # Cut to a specific end time ffmpeg -ss 00:01:30 -i input.mp4 -to 00:02:30 -c copy clip.mp4 # Trim the first 10 seconds off ffmpeg -ss 10 -i input.mp4 -c copy output.mp4
To join files that share the same codecs, list them and concatenate:
printf "file '%s'\n" *.mp4 > list.txt ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
This only works when every input has identical codecs and parameters. Mixed sources need re-encoding first.
Audio
# Extract without re-encoding ffmpeg -i input.mp4 -vn -acodec copy output.m4a # Extract as MP3 ffmpeg -i input.mp4 -vn -c:a libmp3lame -q:a 2 output.mp3 # Remove audio ffmpeg -i input.mp4 -an -c:v copy output.mp4 # Replace the audio track ffmpeg -i video.mp4 -i audio.mp3 -map 0:v -map 1:a -c:v copy -shortest output.mp4 # Change volume ffmpeg -i input.mp4 -af "volume=1.5" -c:v copy output.mp4
-shortest stops at whichever input ends first, which prevents a long audio file leaving silent video on the end.
Images and thumbnails
# One frame at a timestamp ffmpeg -ss 00:00:05 -i input.mp4 -frames:v 1 -q:v 2 thumb.jpg # Let FFmpeg choose a representative frame ffmpeg -i input.mp4 -vf "thumbnail" -frames:v 1 thumb.jpg # One frame every 10 seconds ffmpeg -i input.mp4 -vf "fps=1/10" -q:v 3 thumb_%04d.jpg # Sprite sheet for seek previews ffmpeg -i input.mp4 -vf "fps=1/10,scale=160:-2,tile=5x5" -q:v 3 sprite_%03d.jpg # Build a video from a sequence of images ffmpeg -framerate 30 -i frame_%04d.png -c:v libx264 -pix_fmt yuv420p output.mp4
-pix_fmt yuv420p on the last one is what makes the result play in browsers and QuickTime. Without it FFmpeg may choose a format many players reject.
Streaming
# HLS for on-demand ffmpeg -i input.mp4 -c:v libx264 -crf 21 -preset veryfast -c:a aac -b:a 128k \ -g 48 -keyint_min 48 -sc_threshold 0 \ -hls_time 6 -hls_playlist_type vod \ -hls_segment_filename "seg_%03d.ts" playlist.m3u8 # DASH for on-demand ffmpeg -i input.mp4 -c:v libx264 -crf 21 -c:a aac -b:a 128k \ -g 48 -keyint_min 48 -sc_threshold 0 \ -f dash -seg_duration 6 output.mpd
The keyframe options are required, not optional. Segments can only start at keyframes, so without forcing them the segment lengths drift and quality switching breaks.
Setting up HLS streaming deals with the multi-bitrate version and how to serve it.
Useful filters
# Rotate 90 degrees clockwise ffmpeg -i input.mp4 -vf "transpose=1" output.mp4 # Crop: width:height:x:y ffmpeg -i input.mp4 -vf "crop=640:480:100:50" output.mp4 # Watermark in the bottom right ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=W-w-10:H-h-10" output.mp4 # Fade in over the first 2 seconds ffmpeg -i input.mp4 -vf "fade=t=in:st=0:d=2" output.mp4 # Change frame rate ffmpeg -i input.mp4 -r 30 -c:v libx264 -crf 23 output.mp4
Batch processing
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. Add -n to skip files that already exist, which matters when a long batch is interrupted and restarted.
Quieter output
# Errors only ffmpeg -hide_banner -loglevel error -i input.mp4 output.mp4 # Overwrite without asking ffmpeg -y -i input.mp4 output.mp4 # Never overwrite ffmpeg -n -i input.mp4 output.mp4
In cron, use -y or the job hangs forever waiting for an answer to a prompt nobody will see. Use the full path to the binary as well: cron does not inherit your shell's PATH.
Values worth remembering
- CRF 18 visually lossless · 23 default · 28 smaller, acceptable for web
- Presets ultrafast → superfast → veryfast → faster → fast → medium → slow → slower → veryslow
- Audio 96k speech · 128k general · 192k music
- -ss before -i seeks · -ss after -i decodes everything first
- scale=W:-2 keeps aspect ratio and stays even
When something fails rather than running slowly, troubleshooting common FFmpeg errors explains what the messages mean.
The commands here that look impenetrable are filter graphs, and the syntax is smaller than it appears, Understanding FFmpeg Filters: -vf and -filter_complex takes it apart.
Speed changes are two operations rather than one, and the video factor runs the opposite way to the audio one. There is more in How to Speed Up, Slow Down and Reverse Video.
Make the commands safe to run unattended
Commands copied from examples work when typed and misbehave inside a script, for two reasons that are easy to fix.
ffmpeg -nostdin -hide_banner -loglevel error -y -i in.mp4 -c:v libx264 -crf 23 out.mp4 echo "cikis kodu: $?"
The first option stops the process consuming standard input, which inside a loop means the first job swallows the rest of the list and nothing else runs.
The overwrite option prevents a job stopping to ask a question nobody will answer. And reading the exit code is what turns a silent failure into something a script can act on, since an empty output file and a successful run look identical otherwise.
Test on a fragment before the whole file
Trying a command on a two hour video is how an afternoon disappears into a mistake visible in the first ten seconds.
ffmpeg -ss 60 -t 20 -i in.mp4 -c copy sample.mp4 ffmpeg -i sample.mp4 -c:v libx264 -crf 23 -preset medium test.mp4 ffprobe -v error -show_entries format=duration,size -of default=nw=1 test.mp4
Cut a representative fragment by copying rather than encoding, then run the real command against that. Everything you are testing behaves the same way and the loop takes seconds.
Take the sample from the middle rather than the opening, since titles and static frames encode differently from the content that follows and give a misleading impression of both quality and speed.
Read the summary line at the end
Every run prints a final line that answers two questions people usually guess at.
ffmpeg -i in.mp4 -c:v libx264 -crf 23 out.mp4 2>&1 | tail -3
It states how many kilobytes went to video and how many to audio. On a short clip at a high audio setting, audio is frequently a meaningful share, and reducing it is the easier saving.
The speed figure is expressed as a multiple of real time. Anything below one means the encode takes longer than the video lasts, which is the number that determines whether a queue keeps up.
Keep the output quiet enough to read
Default output is verbose, and inside a script that noise buries the one line that matters.
ffmpeg -hide_banner -loglevel error -stats -i in.mp4 -c:v libx264 -crf 23 out.mp4 ffmpeg -hide_banner -loglevel warning -i in.mp4 -c copy out.mkv 2>> ~/logs/ffmpeg.log
Suppressing the banner and lowering the level leaves errors and progress. Redirecting to a log keeps the record without filling a terminal.
Do not silence it entirely. A job that fails quietly produces an empty file and a successful looking run, and the log line explaining why is the only thing that would have told you.
Know which options must come before the input
Order matters in a way that produces silent behaviour differences rather than errors.
ffmpeg -ss 60 -i in.mp4 -t 10 -c copy fast.mp4 ffmpeg -i in.mp4 -ss 60 -t 10 -c copy slow.mp4
Placing the seek before the input jumps directly and is fast. Placing it after decodes everything up to that point first, which on a long file is the difference between a second and several minutes.
The same principle applies generally: options before the input describe how to read it, and options after describe what to produce. A command that behaves unexpectedly is frequently one where something is on the wrong side.
Keep the reference somewhere you will find it
The commands worth remembering are the ones you use monthly rather than daily, and those are exactly the ones that get looked up each time.
alias ffprobe-info="ffprobe -v error -show_entries format=duration,size,bit_rate:stream=codec_name,width,height -of default=nw=1" ffprobe-info input.mp4
Wrapping the ones you repeat into short names removes the lookup entirely, and the definition is a record of what the options were for.
Keep the file with your own notes rather than relying on shell history. History is per machine and is lost when the machine is rebuilt, which is when you most want the commands.