Tech Guides
01

Quick Reference

The most common one-liners you will reach for daily.

Format Conversion

MKV to MP4
ffmpeg -i input.mkv -c copy output.mp4

Remux without re-encoding. Near-instant.

MOV to MP4 (re-encode)
ffmpeg -i input.mov -c:v libx264 \
  -crf 23 -c:a aac output.mp4

Re-encode to H.264 + AAC for broad compatibility.

WebM from MP4
ffmpeg -i input.mp4 -c:v libvpx-vp9 \
  -crf 30 -b:v 0 -c:a libopus output.webm

VP9 + Opus for web delivery.

GIF from Video
ffmpeg -i input.mp4 -vf \
  "fps=12,scale=480:-1:flags=lanczos,\
split[s0][s1];[s0]palettegen[p];\
[s1][p]paletteuse" output.gif

High-quality GIF with optimized palette.

Resize & Crop

Scale to 720p
ffmpeg -i input.mp4 -vf "scale=-2:720" \
  -c:v libx264 -crf 23 output.mp4

Width auto-calculated, divisible by 2.

Crop center square
ffmpeg -i input.mp4 -vf \
  "crop=min(iw\,ih):min(iw\,ih)" \
  output.mp4

Crop to largest centered square.

Extract & Trim

Extract audio
ffmpeg -i input.mp4 -vn -c:a copy audio.aac
# Or convert to MP3:
ffmpeg -i input.mp4 -vn -q:a 2 audio.mp3
Trim by time
# From 1:30 to 2:45
ffmpeg -ss 00:01:30 -to 00:02:45 \
  -i input.mp4 -c copy trimmed.mp4

Place -ss before -i for fast seek.

Extract frames
# One frame per second
ffmpeg -i input.mp4 -vf "fps=1" \
  frames/frame_%04d.png
Thumbnail at timestamp
ffmpeg -ss 00:00:10 -i input.mp4 \
  -frames:v 1 thumbnail.jpg
reel 02
02

Input/Output Model

How FFmpeg reads, routes, and writes media streams.

The Processing Pipeline

Every FFmpeg invocation follows this flow:

input file  -->  demuxer  -->  decoder  -->  [filters]  -->  encoder  -->  muxer  -->  output file
               (container)   (codec)     (transform)     (codec)    (container)

With -c copy, the decoder/encoder steps are skipped entirely (stream copy mode). This is fast but prevents any filtering.

Multiple Inputs & Stream Selection

# Two inputs, select streams explicitly
ffmpeg -i video.mp4 -i audio.m4a \
  -map 0:v:0 -map 1:a:0 \
  -c copy output.mp4

# Map notation breakdown:
#   -map 0:v:0   = input 0, first video stream
#   -map 1:a:0   = input 1, first audio stream
#   -map 0       = all streams from input 0
#   -map 0:s     = all subtitle streams from input 0
Auto Stream Selection
Without -map, FFmpeg picks one stream per type (video, audio, subtitle) from the first input that has it, choosing the "best" by bitrate/resolution. Use -map when you need precise control.

Per-Stream Codec Options

Option Meaning Example
-c:v Video codec -c:v libx264
-c:a Audio codec -c:a aac
-c:s Subtitle codec -c:s mov_text
-c copy Copy all streams (no re-encode) -c copy
-c:v copy -c:a aac Copy video, re-encode audio Mix copy and encode
-b:v Video bitrate -b:v 4M
-b:a Audio bitrate -b:a 192k

Common Global Options

Flag Purpose
-yOverwrite output without asking
-nNever overwrite (fail if output exists)
-hide_bannerSuppress build/config info
-v quietSuppress all output except errors
-statsShow encoding progress only
-t 30Limit output duration to 30 seconds
-ss HH:MM:SSSeek to position (before -i = fast seek)
-to HH:MM:SSStop at position
-f fmtForce input/output format
-threads 0Auto-detect thread count
reel 03
03

Video Encoding

Codecs, quality targets, and encoding strategies for every use case.

CRF vs Bitrate

CRF (Constant Rate Factor) targets a consistent visual quality. Lower CRF = higher quality, larger file. Bitrate mode targets a file size. Use CRF for archival and local playback; use bitrate for streaming with bandwidth constraints.

Mode Flag Best For Example
CRF (constant quality) -crf N Local playback, archival -crf 23
ABR (average bitrate) -b:v N Streaming, file size target -b:v 4M
CBR (constant bitrate) -b:v N -minrate N -maxrate N -bufsize 2N Live streaming -b:v 4M -maxrate 4M -bufsize 8M
Constrained CRF -crf N -maxrate M -bufsize 2M Quality with ceiling -crf 23 -maxrate 5M -bufsize 10M

H.264 (libx264)

The most widely supported codec. Works everywhere: browsers, phones, smart TVs, and media players.

# High quality archival
ffmpeg -i input.mov -c:v libx264 -crf 18 -preset slow \
  -c:a aac -b:a 192k output.mp4

# Fast web export
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset medium \
  -profile:v high -level 4.1 \
  -movflags +faststart -c:a aac output.mp4
Preset Speed Compression
ultrafastFastestLargest files
superfast
veryfast
faster
fast
mediumDefaultGood balance
slow
slower
veryslowSlowestSmallest files
CRF sweet spots for H.264
18 = visually lossless. 23 = default (good quality). 28 = smaller, noticeable loss. Range: 0-51.

H.265 / HEVC (libx265)

~50% better compression than H.264 at the same quality. Slower to encode. Not universally supported in browsers (Safari yes, Chrome partial).

ffmpeg -i input.mov -c:v libx265 -crf 28 -preset medium \
  -tag:v hvc1 -c:a aac output.mp4

# -tag:v hvc1 is required for Apple/QuickTime compatibility
# CRF 28 in x265 ≈ CRF 23 in x264 quality

VP9 (libvpx-vp9)

Google's open codec. Broadly supported in browsers. Good for WebM delivery.

# Two-pass VP9 for best quality
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 2M \
  -pass 1 -an -f null /dev/null

ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 2M \
  -pass 2 -c:a libopus -b:a 128k output.webm

# CRF mode (simpler)
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 \
  -c:a libopus output.webm

AV1 (libsvtav1)

Next-gen open codec. Best compression, but slowest encoding. Growing browser support.

# SVT-AV1 (fastest AV1 encoder)
ffmpeg -i input.mp4 -c:v libsvtav1 -crf 30 \
  -preset 6 -svtav1-params tune=0 \
  -c:a libopus output.mp4

# Presets: 0 (slowest/best) to 13 (fastest)
# CRF range: 0-63, default 35

Codec Comparison

Codec Encoder Quality/Size Speed Browser Support
H.264 libx264 Good Fast Universal
H.265 libx265 Better Slower Partial
VP9 libvpx-vp9 Better Slow Most (not Safari)
AV1 libsvtav1 Best Slowest Growing
reel 04
04

Audio Encoding

Codecs, quality settings, and volume normalization.

Audio Codecs

Codec Encoder Container Typical Use Quality Flag
AAC aac MP4, M4A Video, streaming -b:a 192k
MP3 libmp3lame MP3 Music, podcasts -q:a 2 (VBR ~190kbps)
Opus libopus WebM, OGG VoIP, web, streaming -b:a 128k
FLAC flac FLAC Lossless archival -compression_level 8
Vorbis libvorbis OGG, WebM Legacy web audio -q:a 6

Common Audio Operations

# Convert to high-quality MP3
ffmpeg -i input.wav -c:a libmp3lame -q:a 0 output.mp3

# Convert to AAC with specific bitrate
ffmpeg -i input.wav -c:a aac -b:a 256k output.m4a

# Lossless FLAC from WAV
ffmpeg -i input.wav -c:a flac output.flac

# Change sample rate
ffmpeg -i input.wav -ar 44100 output.wav

# Mono to stereo / stereo to mono
ffmpeg -i input.wav -ac 1 mono.wav
ffmpeg -i input.wav -ac 2 stereo.wav

Volume Normalization

FFmpeg offers two normalization filters. loudnorm (EBU R128) is the industry standard for broadcast/streaming.

# EBU R128 loudness normalization (two-pass for best results)

# Pass 1: analyze
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json \
  -f null /dev/null 2>> analysis.json

# Pass 2: apply (use measured values from pass 1)
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11:\
measured_I=-23.5:measured_TP=-4.2:measured_LRA=8.1:\
measured_thresh=-34.0:linear=true \
  -c:v copy output.mp4

# Simple single-pass normalization (good enough for most cases)
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 \
  -c:v copy output.mp4
# Simple volume adjustment
ffmpeg -i input.mp4 -af "volume=1.5" louder.mp4     # 150% volume
ffmpeg -i input.mp4 -af "volume=-3dB" quieter.mp4   # reduce by 3dB
ffmpeg -i input.mp4 -af "volume=6dB" louder.mp4     # boost by 6dB
EBU R128 Targets
-16 LUFS is the standard for streaming platforms (YouTube, Spotify). -24 LUFS is typical for broadcast TV. TP=-1.5 sets the true peak ceiling.
reel 05
05

Filters & Filtergraphs

Transform video and audio with FFmpeg's powerful filter system.

Simple Filters (-vf / -af)

For single-input, single-output chains. Filters are comma-separated.

# Chain multiple video filters
ffmpeg -i input.mp4 -vf "scale=1280:720,fps=30,eq=brightness=0.06" \
  output.mp4

# Chain audio filters
ffmpeg -i input.mp4 -af "highpass=f=200,lowpass=f=3000,volume=2" \
  output.mp4

Essential Video Filters

Filter Usage Purpose
scalescale=1280:720Resize video
cropcrop=640:480:0:0Crop to w:h at x:y
fpsfps=24Change framerate
padpad=1920:1080:(ow-iw)/2:(oh-ih)/2Add borders (letterbox)
transposetranspose=1Rotate 90 CW
hflip / vfliphflipMirror horizontally/vertically
eqeq=brightness=0.06:saturation=1.3Brightness/contrast/saturation
deinterlaceyadifDeinterlace video
drawtextdrawtext=text='Hello':fontsize=48:x=10:y=10Burn text into video
overlayoverlay=W-w-10:10Overlay image/video
setptssetpts=0.5*PTSSpeed up (0.5x = 2x speed)

Essential Audio Filters

Filter Usage Purpose
volumevolume=1.5Adjust volume
loudnormloudnorm=I=-16EBU R128 normalization
highpasshighpass=f=200Remove low frequencies
lowpasslowpass=f=3000Remove high frequencies
afadeafade=t=in:d=2Fade in/out
atempoatempo=2.0Speed up audio (0.5-100)
aresamplearesample=44100Resample audio
silenceremovesilenceremove=1:0:-50dBStrip silence

Complex Filtergraphs (-filter_complex)

For multiple inputs/outputs, splitting streams, or combining streams. Uses labeled pads in square brackets.

# Picture-in-picture
ffmpeg -i main.mp4 -i overlay.mp4 -filter_complex \
  "[1:v]scale=320:240[pip]; \
   [0:v][pip]overlay=W-w-10:10" \
  -c:a copy output.mp4

# Side-by-side comparison
ffmpeg -i left.mp4 -i right.mp4 -filter_complex \
  "[0:v]scale=640:480[l]; \
   [1:v]scale=640:480[r]; \
   [l][r]hstack=inputs=2" \
  output.mp4

# Crossfade between two clips
ffmpeg -i clip1.mp4 -i clip2.mp4 -filter_complex \
  "[0:v][1:v]xfade=transition=fade:duration=1:offset=4[v]; \
   [0:a][1:a]acrossfade=d=1[a]" \
  -map "[v]" -map "[a]" output.mp4

# Add watermark
ffmpeg -i input.mp4 -i logo.png -filter_complex \
  "[1:v]scale=100:-1,format=rgba,colorchannelmixer=aa=0.5[wm]; \
   [0:v][wm]overlay=W-w-20:20" \
  -c:a copy output.mp4
Filtergraph syntax
[input_label]filter=params[output_label] -- Semicolons separate filter chains. Commas chain filters within a single stream. Labels in [] connect chains together.

Speed Changes

# 2x speed (video + audio)
ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" \
  -af "atempo=2.0" fast.mp4

# 0.5x slow motion
ffmpeg -i input.mp4 -vf "setpts=2.0*PTS" \
  -af "atempo=0.5" slow.mp4

# 4x speed (atempo max is 100, but chain for clarity)
ffmpeg -i input.mp4 -vf "setpts=0.25*PTS" \
  -af "atempo=2.0,atempo=2.0" fast4x.mp4
reel 06
06

Streaming

HLS, DASH, and RTMP for live and on-demand delivery.

HLS (HTTP Live Streaming)

Apple's adaptive streaming protocol. Produces .m3u8 playlist + .ts segments. Universally supported.

# Basic HLS output
ffmpeg -i input.mp4 -c:v libx264 -crf 23 \
  -c:a aac -b:a 128k \
  -hls_time 6 -hls_list_size 0 \
  -hls_segment_filename "segment_%03d.ts" \
  playlist.m3u8

# Multi-bitrate HLS (adaptive)
ffmpeg -i input.mp4 \
  -map 0:v -map 0:v -map 0:v -map 0:a \
  -c:v libx264 -c:a aac \
  -b:v:0 5M -s:v:0 1920x1080 \
  -b:v:1 2M -s:v:1 1280x720 \
  -b:v:2 800k -s:v:2 640x360 \
  -b:a 128k \
  -var_stream_map "v:0,a:0 v:1,a:0 v:2,a:0" \
  -master_pl_name master.m3u8 \
  -hls_time 6 -hls_list_size 0 \
  -hls_segment_filename "v%v/seg_%03d.ts" \
  v%v/index.m3u8
Option Purpose
-hls_time 6Target segment duration (seconds)
-hls_list_size 0Keep all segments in playlist (VOD)
-hls_list_size 5Rolling window of 5 segments (live)
-hls_flags delete_segmentsDelete old segments
-hls_playlist_type vodMark as VOD (adds EXT-X-ENDLIST)

DASH (Dynamic Adaptive Streaming)

MPEG-DASH is the open standard alternative to HLS. Produces .mpd manifest + .m4s segments.

ffmpeg -i input.mp4 -c:v libx264 -crf 23 \
  -c:a aac -b:a 128k \
  -f dash -seg_duration 4 \
  -init_seg_name "init_$RepresentationID$.m4s" \
  -media_seg_name "seg_$RepresentationID$_$Number$.m4s" \
  manifest.mpd

RTMP (Live Streaming)

Push live video to platforms like YouTube, Twitch, or a custom server.

# Stream screen to RTMP
ffmpeg -f x11grab -s 1920x1080 -i :0.0 \
  -f pulse -i default \
  -c:v libx264 -preset veryfast -b:v 3M \
  -c:a aac -b:a 128k \
  -f flv rtmp://live.twitch.tv/app/YOUR_STREAM_KEY

# Re-stream a file as live
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -b:v 2500k \
  -maxrate 2500k -bufsize 5000k \
  -c:a aac -b:a 128k \
  -f flv rtmp://server/live/stream
The -re flag
-re reads input at native framerate (real-time). Without it, FFmpeg processes as fast as possible, which overwhelms live servers.
reel 07
07

Hardware Acceleration

Offload encoding and decoding to GPU for 5-20x speedups.

NVIDIA NVENC

Requires an NVIDIA GPU with Turing or newer (GTX 16xx+, RTX series).

# H.264 with NVENC
ffmpeg -hwaccel cuda -hwaccel_output_format cuda \
  -i input.mp4 \
  -c:v h264_nvenc -preset p4 -cq 23 \
  -c:a copy output.mp4

# H.265 with NVENC
ffmpeg -hwaccel cuda -hwaccel_output_format cuda \
  -i input.mp4 \
  -c:v hevc_nvenc -preset p5 -cq 28 \
  -tag:v hvc1 -c:a copy output.mp4

# AV1 with NVENC (RTX 40 series)
ffmpeg -hwaccel cuda -hwaccel_output_format cuda \
  -i input.mp4 \
  -c:v av1_nvenc -cq 30 \
  -c:a copy output.mp4
NVENC Preset Speed Quality
p1FastestLowest
p4BalancedGood
p7SlowestBest

Intel Quick Sync (QSV)

Available on Intel CPUs with integrated graphics (6th gen+).

# H.264 with QSV
ffmpeg -hwaccel qsv -hwaccel_output_format qsv \
  -i input.mp4 \
  -c:v h264_qsv -global_quality 23 \
  -c:a copy output.mp4

# H.265 with QSV
ffmpeg -hwaccel qsv -hwaccel_output_format qsv \
  -i input.mp4 \
  -c:v hevc_qsv -global_quality 28 \
  -c:a copy output.mp4

Apple VideoToolbox

macOS only. Uses the Apple silicon media engine or Intel Quick Sync.

# H.264 with VideoToolbox
ffmpeg -i input.mp4 \
  -c:v h264_videotoolbox -q:v 65 \
  -c:a copy output.mp4

# H.265 with VideoToolbox
ffmpeg -i input.mp4 \
  -c:v hevc_videotoolbox -q:v 65 \
  -tag:v hvc1 -c:a copy output.mp4

VAAPI (Linux)

Video Acceleration API for Linux. Works with Intel and AMD GPUs.

ffmpeg -hwaccel vaapi -hwaccel_output_format vaapi \
  -hwaccel_device /dev/dri/renderD128 \
  -i input.mp4 \
  -c:v h264_vaapi -qp 23 \
  -c:a copy output.mp4

Check Available HW Encoders

# List all available encoders
ffmpeg -encoders 2>/dev/null | grep -E "nvenc|qsv|vaapi|videotoolbox"

# List available hardware acceleration methods
ffmpeg -hwaccels
Quality Trade-off
Hardware encoders are 5-20x faster but produce slightly larger files at the same visual quality compared to software encoders. For archival, prefer software. For live/bulk encoding, use hardware.
reel 08
08

Batch Processing & Scripting

Patterns for processing many files efficiently.

Bash Loops

# Convert all MKV files to MP4
for f in *.mkv; do
  ffmpeg -i "$f" -c:v libx264 -crf 23 -c:a aac \
    "${f%.mkv}.mp4"
done

# Batch resize to 720p
mkdir -p resized
for f in *.mp4; do
  ffmpeg -i "$f" -vf "scale=-2:720" \
    -c:v libx264 -crf 23 "resized/$f"
done

# Extract audio from all videos
for f in *.mp4; do
  ffmpeg -i "$f" -vn -c:a libmp3lame -q:a 2 \
    "${f%.mp4}.mp3"
done

Parallel Processing

# Using GNU parallel (limit to 4 concurrent jobs)
ls *.mkv | parallel -j4 \
  'ffmpeg -i {} -c:v libx264 -crf 23 -c:a aac {.}.mp4'

# Using xargs
find . -name "*.avi" -print0 | \
  xargs -0 -P4 -I{} ffmpeg -i {} -c:v libx264 -crf 23 {}.mp4

Concat (Join) Files

# Method 1: concat demuxer (same codec, recommended)
# Create a file list:
cat > filelist.txt <<EOF
file 'clip1.mp4'
file 'clip2.mp4'
file 'clip3.mp4'
EOF

ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4

# Method 2: concat filter (different codecs/resolutions)
ffmpeg -i clip1.mp4 -i clip2.mp4 -filter_complex \
  "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" output.mp4

# Generate filelist from directory
for f in clip_*.mp4; do echo "file '$f'"; done > filelist.txt

Progress Monitoring

# Write progress to a file (for GUIs / scripts)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 \
  -progress progress.log -y output.mp4

# Parse progress in a script
tail -f progress.log | grep --line-buffered "out_time="

# Get total duration for percentage calculation
duration=$(ffprobe -v quiet -show_entries \
  format=duration -of csv=p=0 input.mp4)

Useful Script Patterns

#!/bin/bash
# Encode with logging and error handling

encode() {
  local input="$1"
  local output="${input%.*}_encoded.mp4"

  echo "Encoding: $input"
  if ffmpeg -hide_banner -i "$input" \
    -c:v libx264 -crf 23 -preset medium \
    -c:a aac -b:a 128k \
    -movflags +faststart \
    -y "$output" 2>> encode.log; then
    echo "  Done: $output"
  else
    echo "  FAILED: $input" | tee -a failures.log
  fi
}

export -f encode

find . -name "*.mov" -print0 | \
  xargs -0 -P$(nproc) -I{} bash -c 'encode "{}"'
reel 09
09

ffprobe

Inspect and analyze media files without touching a single frame.

Basic Inspection

# Quick summary
ffprobe -hide_banner input.mp4

# Show all streams
ffprobe -show_streams input.mp4

# Show format/container info
ffprobe -show_format input.mp4

# Show everything
ffprobe -show_format -show_streams input.mp4

Targeted Queries

# Get duration in seconds
ffprobe -v quiet -show_entries format=duration \
  -of csv=p=0 input.mp4

# Get resolution
ffprobe -v quiet -select_streams v:0 \
  -show_entries stream=width,height \
  -of csv=p=0 input.mp4

# Get video codec
ffprobe -v quiet -select_streams v:0 \
  -show_entries stream=codec_name \
  -of csv=p=0 input.mp4

# Get framerate
ffprobe -v quiet -select_streams v:0 \
  -show_entries stream=r_frame_rate \
  -of csv=p=0 input.mp4

# Get bitrate
ffprobe -v quiet -show_entries format=bit_rate \
  -of csv=p=0 input.mp4

# Get audio sample rate and channels
ffprobe -v quiet -select_streams a:0 \
  -show_entries stream=sample_rate,channels \
  -of csv=p=0 input.mp4

JSON Output

# Full probe as JSON (great for scripting)
ffprobe -v quiet -print_format json \
  -show_format -show_streams input.mp4

# Parse with jq
ffprobe -v quiet -print_format json \
  -show_streams input.mp4 | \
  jq '.streams[] | select(.codec_type=="video") |
    {codec: .codec_name, width, height, duration}'

Frame-Level Analysis

# Show keyframe (I-frame) timestamps
ffprobe -select_streams v -show_frames \
  -show_entries frame=pict_type,pts_time \
  -of csv input.mp4 | grep ",I,"

# Count total frames
ffprobe -v quiet -count_frames -select_streams v:0 \
  -show_entries stream=nb_read_frames \
  -of csv=p=0 input.mp4

# Show packet sizes (for bitrate analysis)
ffprobe -select_streams v -show_packets \
  -show_entries packet=pts_time,size \
  -of csv input.mp4

Batch Probe Script

#!/bin/bash
# List all videos with their resolution, codec, and duration

printf "%-40s %-10s %-12s %s\n" "File" "Codec" "Resolution" "Duration"
printf "%s\n" "$(printf '=%.0s' {1..80})"

for f in *.mp4 *.mkv *.mov 2>/dev/null; do
  [ -f "$f" ] || continue
  codec=$(ffprobe -v quiet -select_streams v:0 \
    -show_entries stream=codec_name -of csv=p=0 "$f")
  res=$(ffprobe -v quiet -select_streams v:0 \
    -show_entries stream=width,height -of csv=p=0 "$f")
  dur=$(ffprobe -v quiet -show_entries format=duration \
    -of csv=p=0 "$f" | cut -d. -f1)
  mins=$((dur / 60))
  secs=$((dur % 60))
  printf "%-40s %-10s %-12s %d:%02d\n" "$f" "$codec" "$res" "$mins" "$secs"
done
Output formats (-of)
csv for simple scripts, json for complex parsing, flat for key-value pairs, ini for config-style output. Add p=0 to csv to suppress section headers.