Ahosting Logo
Knowledge Base

FFmpeg Commands Cheat Sheet for Beginners

Two rules that prevent most FFmpeg mistakesWhere the seek option goes· before minus i: jump straight there, fast, slightly less exact· after minus i: decode from the start, slow, exactCopy before you convert· if the codecs are already what you want, copy them· a remux takes seconds; a re-encode takes hours and loses qualityThe habit behind bothRun ffprobe on the input first. Most wasted FFmpeg time is spent converting something that didnot need converting.

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.