FFmpeg Cheatsheet - Video, Audio & Format Conversion

Day-to-day ffmpeg is a lookup from intent to flags. Need it fast, copy the streams with -c copy and skip re-encoding entirely. Need a different container, still copy, because the codec is unchanged and only the wrapper moves. Need a smaller file, actually encode with libx264 and steer quality with -crf. Need content changes, add a filter graph, and accept that this forces real decoding so copy is no longer an option. Placement of flags causes most surprises: -ss before -i seeks to a keyframe quickly but imprecisely, while after -i it decodes precisely and costs time. A -1 in a scale filter means compute that dimension from the aspect ratio. When you only want one stream, -vn and -an drop the other. Groups cover conversion, trimming, stream extraction, compression, watermarks and subtitles, audio work, screen recording and streaming. Streaming input straight from a URL works too, which matters when you want a slice of a long recording without downloading all of it first.

SysOps·39 commands·Last updated 2026-07-21
ffmpegVideoAudioTranscoding

Typical Use Case

For creators and operators who need to turn raw footage into a deliverable file: transcoding, container changes, cutting clips, subtitles and watermarks, frame extraction, recording and streaming. The sheet is organized around one rule: inspect the streams first, then decide. Repackage with -c copy, re-encode only when you name -c:v / -c:a; frame extraction, cropping and scaling go through -vf; anything combining two inputs goes through -filter_complex. Use it to inspect footage before editing, to produce a version that respects a bitrate limit before shipping, and to build an HLS segmentation pipeline step by step.

Format Conversion 6

ffmpeg -i input.mp4 output.avi
Convert container; auto-select encoder
ffmpeg -i input.mp4 -c:v libx264 output.mp4
Re-encode with H.264
ffmpeg -i input.mp4 -c:v libx265 output.mp4
Encode to H.265 (smaller)
ffmpeg -i input.wav -c:a libmp3lame -q:a 2 output.mp3
WAV to MP3, -q:a 2 ~190kbps
ffmpeg -i input.mp4 -c:v copy -c:a copy output.mkv
Stream copy into MKV, very fast
ffmpeg -i input.gif -movflags faststart output.mp4
GIF to MP4, faststart for web

Trim & Crop 5

ffmpeg -i input.mp4 -ss 00:01:00 -to 00:02:00 -c copy clip.mp4
Cut 1:00-2:00, -c copy fast
ffmpeg -ss 10 -i input.mp4 -t 30 clip.mp4
Start at 10s, take 30s (-ss before -i)
ffmpeg -i input.mp4 -vf "crop=640:480:0:0" output.mp4
Crop to 640x480 at (0,0)
ffmpeg -i input.mp4 -vf "crop=iw/2:ih:0:0" output.mp4
Crop the left half
ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1:black" output.mp4
Scale and pad to 1280x720 with black bars

Extract & Separate 6

ffmpeg -i input.mp4 -vn audio.aac
Extract audio, -vn drops video
ffmpeg -i input.mp4 -an video_only.mp4
Drop audio, keep video (-an)
ffmpeg -i input.mp4 -vf "select=eq(pict_type\,I)" -vsync vfr thumb_%03d.png
Extract keyframes as images
ffmpeg -i input.mp4 -r 1 frame_%03d.png
Extract one frame per second
ffmpeg -i input.mp4 -map 0:s:0 subtitle.srt
Extract subtitle track to SRT
ffmpeg -i "image_%03d.png" -r 30 video.mp4
Images to video

Compression & Quality 6

ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
CRF compress; higher = lower quality (18-28)
ffmpeg -i input.mp4 -c:v libx264 -crf 18 -preset slow output.mp4
High quality; slow preset = smaller
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M output.mp4
Cap video bitrate at 1Mbps
ffmpeg -i input.mp4 -vf "scale=1280:-1" output.mp4
Scale width to 1280px
ffmpeg -i input.mp4 -c:a aac -b:a 128k output.mp4
Audio to AAC at 128kbps
ffmpeg -i input.mp4 -c:v libx264 -preset fast -tune zerolatency output.mp4
Low-latency encode for live streaming

Watermark & Subtitle 5

ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=10:10" output.mp4
Image watermark at top-left
ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=W-w-10:H-h-10" output.mp4
Watermark at bottom-right
ffmpeg -i input.mp4 -vf "subtitles=sub.srt" output.mp4
Burn SRT subtitles into video
ffmpeg -i input.mp4 -vf "drawtext=text=Hello:fontcolor=white:fontsize=24:x=10:y=10" output.mp4
Add a text watermark
ffmpeg -i input.mp4 -i sub.srt -c copy -c:s mov_text output.mp4
Soft (toggleable) subtitle in MP4

Audio 5

ffmpeg -i input.mp4 -c:a libmp3lame -b:a 192k output.mp3
To MP3 at 192kbps
ffmpeg -i input.mp3 -af "volume=2.0" output.mp3
Double the volume
ffmpeg -i input.mp4 -af "atempo=2.0" output.mp4
2x audio speed (pitch preserved)
ffmpeg -i input.mp3 -af "silenceremove=stop_periods=1:stop_duration=1:stop_threshold=-50dB" output.mp3
Remove silent segments
ffmpeg -i "concat:a.mp3|b.mp3" -c copy output.mp3
Concatenate audio files

Capture & Streaming 6

ffmpeg -f avfoundation -i "0:" -r 30 -t 60 screen.mp4
macOS screen record 60s (avfoundation)
ffmpeg -f x11grab -i :0.0 -r 25 screen.mp4
Linux screen record (X11)
ffmpeg -f v4l2 -i /dev/video0 -t 10 webcam.mp4
Record webcam for 10s
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://live.example.com/live
RTMP live push
ffmpeg -i http://example.com/stream.m3u8 -c copy output.mp4
Download an HLS stream locally
ffmpeg -i input.mp4 -hls_time 10 -hls_list_size 0 output.m3u8
Slice video into an HLS playlist

Parameter matrix

ParameterEffectExample
-c copyCopy streams without re-encoding: keeps quality and speed when only the container changesffmpeg -i input.mp4 -c:v copy -c:a copy output.mkv
-ss (position) / -t / -toClip length: -ss before -i seeks fast, after -i seeks preciselyffmpeg -ss 10 -i input.mp4 -t 30 clip.mp4
-crf / -preset / -b:vQuality controls: higher CRF means smaller and uglier (18-28 typical), slower preset is smaller, -b:v pins bitrateffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
-vf / -filter_complexFilter graphs: chain single-input filters with -vf, use -filter_complex for overlay and multi-input graphsffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=10:10" output.mp4

Common pitfalls

SymptomAudio drifts out of sync, ending early or running progressively late.

CauseEditing the picture with -vf changes the video length while the audio stream keeps the original duration.

FixAdd -shortest so output follows the shorter stream, or add -t / -af atrim after the filter to align them.

SymptomThe same filter chain gives a completely different result when reordered.

CauseA filter graph is a left-to-right pipeline; the order decides what each filter consumes and produces.

FixScale before padding (scale=...,pad=...); never put pad ahead of scale.

SymptomRepackaging only degrades quality and bloats the file size.

CauseWithout -c copy, ffmpeg re-encodes every stream with its default encoders.

FixAlways add -c:v copy -c:a copy for container changes, and confirm the target container accepts those codecs (MP4 subtitles need -c:s mov_text).

SymptomSubtitles are missing in the player, or burned-in text renders as boxes.

CauseSoft subtitles need an explicit subtitle codec, and subtitles/drawtext cannot find a font without fontsdir.

FixEmbed with -c copy -c:s mov_text, burn with -vf "subtitles=sub.srt", and add fontsdir pointing at a font directory.

Troubleshooting

  1. 1Inspect which streams the input actually has

    ffmpeg -i input.mp4 -c copy -c:a copy output.mkv

    With no output target ffmpeg prints stream info and exits, which makes it the quickest health check.

  2. 2Confirm the filter chain is ordered correctly

    ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1:black" output.mp4

    Scale then pad gives a proportional resize with letterboxing; the reverse order makes pad use pre-scale dimensions.

  3. 3Segment the video into an HLS playlist

    ffmpeg -i input.mp4 -hls_time 10 -hls_list_size 0 output.m3u8

    hls_time sets the segment length and hls_list_size 0 keeps every segment (use 1-6 for a sliding window).

Command Examples

Change container without re-encoding: -c copy

ffmpeg -i input.mp4 -c:v copy -c:a copy output.mkv

When you only remux, -c copy is mandatory. Writing -c:v libx264 instead re-encodes the entire video, taking orders of magnitude longer than a stream copy and losing a generation of quality for nothing. This single flag is the usual answer to "why does a format conversion take ten minutes".

Fast trimming: put -ss before -i

ffmpeg -ss 10 -i input.mp4 -t 30 -c copy clip.mp4

With -ss before -i, ffmpeg seeks to the timestamp before decoding, so clipping 30 seconds out of an hour is instant. After -i it decodes from the start, which is slow and less exact. Frame-accurate cuts need -ss after -i, and then -c copy must go.

Scale proportionally and pad to 1280x720

ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1:black" output.mp4

This is the standard fix for vertical footage on a 16:9 player: force_original_aspect_ratio=decrease scales it down without exceeding the frame, then pad with -1:-1 centres it and fills the remainder. A bare scale=1280:720 stretches the picture, which is the most common source of "the video looks wrong" reports.

Overlay a watermark in the bottom-right corner

ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=W-w-10:H-h-10" output.mp4

overlay belongs inside -filter_complex, and the order of the two -i inputs decides base versus overlay: the first one is the background. A transparent PNG watermark needs no extra option; to fade the watermark you must declare the pixel format explicitly, for example by chaining ,format=yuva420p.

Burn in subtitles and prep for fast start

ffmpeg -i input.mp4 -vf "subtitles=sub.srt" -c:v libx264 -crf 23 -movflags faststart output.mp4

Burning subtitles with -vf subtitles re-encodes the video, so -c copy is no longer an option. Pair it with -movflags faststart to move the moov atom to the head so progressive playback does not stall. If the font is missing, pass fontsdir or force_style explicitly, otherwise CJK glyphs render as boxes.

Common Pitfalls

  • Omitting -c copy triggers a full re-encode: a 4K source through libx264 takes tens of minutes, pins the CPU, and throws away a generation of quality. For remux, trim and concat jobs always try -c copy first.
  • Filter order is processing order, and swapping it changes the result: scale must precede pad (shrink, then fill), and crop must come first to read original dimensions. Written as pad,scale, the black bars get scaled down too.
  • subtitles and drawtext depend on system fonts and libass; when no font is found the fallback is used and CJK text commonly renders as boxes or fails outright. Reproduce across machines with -vf "subtitles=sub.srt:fontsdir=/usr/share/fonts".
  • ffmpeg overwrites existing output files silently by default. A typo in an output path inside a batch script destroys the original footage — add -n, or write to a new filename, as a habit.
  • -ss placement changes the result: before -i it is a fast keyframe seek, so the cut can land a few frames off and is affected by GOP boundaries; after -i it is frame-accurate. If you need exact cuts, pay the decode cost instead of mixing both.
  • The concat filter and the concat demuxer make different assumptions: `-f concat` requires every listed file to have identical codec parameters, otherwise the result goes silent or garbled halfway through. Normalise the encodes first, then concatenate.

Tips

  • -c copy only remuxes streams without re-encoding — very fast, but no filtering.
  • Recommended CRF is 18-28: 18 near-lossless, 23 default, 28 smaller.
  • -ss before -i is fast but imprecise; after -i is precise but slower.
  • In scale, -1 means proportional; scale=1280:-1 sets width 1280, height auto.
  • Slower -preset means better compression: ultrafast … veryslow.
  • For live push, -re reads at the native rate to avoid overwhelming the server.

FAQ

When generating HLS segments, how should I set -hls_time and -hls_list_size?

The canonical form on this sheet is ffmpeg -i input.mp4 -hls_time 10 -hls_list_size 0 output.m3u8. -hls_time 10 cuts a segment every 10 seconds; keep that value aligned with the keyframe interval of your encoded output, because cuts only join seamlessly when they land on keyframes, and too short a segment bloats the index. -hls_list_size 0 keeps every segment in the playlist, which is what you want for a catch-up style VOD list. For live streaming with a sliding window instead, pass a positive number — 6 keeps only the six most recent segments.

How do I save an entire m3u8 live stream to a local file?

Use ffmpeg -i http://example.com/stream.m3u8 -c copy output.mp4. The -c copy is the important part: the segments behind the playlist are already in a playable encoding, so stream copy avoids re-encoding, runs at close to network speed and loses nothing in quality. The command has to run start to finish — ffmpeg follows the playlist as it grows and only finalises the file when the stream ends. If the resulting container is wrong, do a separate -c copy remux afterwards rather than changing codecs here.

There are two ways to pull frames out. Which one fits which job?

For a fixed rate use -r: ffmpeg -i input.mp4 -r 1 frame_%03d.png writes one image per second, which suits thumbnail grids and list-page covers. For keyframes use a filter using select=eq(pict_type\,I) with -vsync vfr, which emits only I-frames, so the count follows the source keyframe density — usually far fewer images, each of them independently decodable. Reach for -r when you want predictable, small thumbnails; reach for select with -vsync vfr when you want semantically meaningful cuts at scene changes.

When shrinking a file, should I reach for -crf or -b:v?

They govern different things. -crf is constant quality: the encoder decides per-frame how many bits the picture deserves, as in ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4, the default. Crank it up and the file shrinks markedly; run it twice on the same source and you get the same result, which makes it the right choice for archival masters and anything you will edit later. -b:v is constant bitrate — you name a number and the encoder hits it, as in ffmpeg -i input.mp4 -c:v libx264 -b:v 1M output.mp4, which suits delivery targets with hard bitrate ceilings and decoders that want to predict bandwidth. When you need both predictability and quality, compose them: encode once with -crf 18 -preset slow, then cap the finished file at the delivery bitrate.

Why does the frame rate go on the output side when building a video from images?

In ffmpeg -i "image_%03d.png" -r 30 video.mp4 the -r 30 is an output frame rate: ffmpeg reads the image sequence as an unbounded stream of frames and lays it out at 30fps, so total duration is simply image count divided by 30 — forty pictures become a clip barely over a second long. On the input side, %03d is a zero-padded three-digit index, which means the files must be numbered contiguously; a gap or a sequence that starts elsewhere makes ffmpeg stop partway through.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us