FFmpeg Toolkit
Production-ready patterns for video and audio manipulation. Combines best practices from 9 community skills into one comprehensive reference.
When to Use
- User wants to trim, cut, concat, or re-encode video/audio files
- Converting between formats (MP4, WebM, ProRes, MKV, etc.)
- Encoding for a specific platform (YouTube, TikTok, Instagram, LinkedIn)
- Extracting frames, generating GIFs, or creating thumbnails
- Analyzing video quality (PSNR, SSIM, VMAF)
- User says "ffmpeg", "transcode", "trim video", "convert video", or "compress video"
How It Works
Production-ready FFmpeg/FFprobe command patterns organized by operation type:
- Probe & inspect — extract metadata, duration, resolution, codecs
- Edit/transform — trim, concat, convert, scale, effects, audio ops
- Delivery presets — platform encoding (YouTube/Twitter/LinkedIn/IG), HLS streaming, quality metrics, hardware acceleration
- Automation — batch processing, validation helpers, multi-platform export
Use the section matching the user's goal, then adapt codec/container/filter flags to source constraints.
Examples
# Trim a clip (fast, stream copy)
ffmpeg -ss 00:01:30 -to 00:02:45 -i input.mp4 -c copy output.mp4
# Convert to web-friendly H.264
ffmpeg -i input.avi -c:v libx264 -crf 23 -c:a aac -b:a 128k output.mp4
# Concatenate clips (same codec)
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
# Extract audio
ffmpeg -i video.mp4 -vn -c:a libmp3lame -q:a 2 audio.mp3
Prerequisites
- FFmpeg 5.0+:
ffmpeg -version
- Hardware accel:
ffmpeg -hwaccels
- Install:
brew install ffmpeg (macOS) | sudo apt install ffmpeg (Debian) | sudo dnf install ffmpeg (Fedora)
1. Probe & Inspect
# Full metadata (JSON)
ffprobe -v quiet -print_format json -show_format -show_streams "input.mp4"
# Duration only
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "input.mp4"
# Resolution
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "input.mp4"
# FPS
ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 "input.mp4"
# Codec info
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,profile,pix_fmt -of default "input.mp4"
# Validate file has video stream
ffprobe -v error -select_streams v:0 -show_entries stream=codec_type -of csv=p=0 "file.mp4" 2>/dev/null
# Returns "video" if valid
2. Trim & Cut
# Fast trim (stream copy, keyframe-aligned — not frame-accurate)
# Both -ss and -to before -i so they reference input timestamps
ffmpeg -ss 00:01:30 -to 00:02:45 -i input.mp4 -c copy output.mp4
# Frame-accurate trim (re-encodes, -ss after -i for precise seek)
ffmpeg -i input.mp4 -ss 00:01:30 -t 00:01:15 -c:v libx264 -crf 18 -c:a aac output.mp4
# Extract by frame range
ffmpeg -i input.mp4 -vf "select=between(n\,100\,500)" -vsync vfr output.mp4
# First N seconds
ffmpeg -i input.mp4 -t 30 -c copy first30.mp4
# Last N seconds (requires knowing duration)
# dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4)
# start=$(echo "$dur - 30" | bc)
# ffmpeg -ss $start -i input.mp4 -c copy last30.mp4
Note: -ss before -i = fast seek (keyframe). -ss after -i = slow but frame-accurate.
3. Concatenation
# Same codecs — concat demuxer (fast, no re-encode)
# Create filelist.txt:
# file 'clip1.mp4'
# file 'clip2.mp4'
# file 'clip3.mp4'
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
# Different codecs — concat filter (re-encodes)
ffmpeg -i clip1.mp4 -i clip2.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1" output.mp4
# Three inputs
ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1" output.mp4
4. Format Conversion
# Any → H.264 MP4 (web-friendly universal)
ffmpeg -i input.avi -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 128k output.mp4
# Any → H.265/HEVC (smaller files, newer compatibility)
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -c:a aac -b:a 128k output.mp4
# MP4 → ProRes MOV (for Final Cut Pro)
ffmpeg -i input.mp4 -c:v prores_ks -profile:v 3 -c:a pcm_s16le output.mov
# MP4 → WebM/VP9 (web, open format)
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus output.webm
# MKV → MP4 (remux, no re-encode)
ffmpeg -i input.mkv -c copy -movflags faststart output.mp4
# GIF → MP4 (Remotion-compatible)
ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p \
-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" output.mp4
ProRes Profiles (Final Cut Pro)
| Profile |
Flag |
Quality |
Use Case |
| Proxy |
-profile:v 0 |
Low |
Offline editing |
| LT |
-profile:v 1 |
Medium |
Light grading |
| Standard |
-profile:v 2 |
High |
General editing |
| HQ |
-profile:v 3 |
Very High |
Final delivery |
| 4444 |
-profile:v 4 |
Highest |
VFX/compositing |
Codec Selection Guide
| Use Case |
Video Codec |
Audio Codec |
Container |
| Web delivery |
libx264 |
aac |
mp4 |
| Smaller web |
libx265 |
aac |
mp4 |
| Open web |
libvpx-vp9 |
libopus |
webm |
| Final Cut Pro |
prores_ks |
pcm_s16le |
mov |
| Archive/lossless |
ffv1 |
flac |
mkv |
| Quick preview |
libx264 -preset ultrafast |
aac |
mp4 |
| Social media |
libx264 -crf 20 |
aac -b:a 192k |
mp4 |
5. Scaling & Resolution
# Exact resolution
ffmpeg -i input.mp4 -vf "scale=1920:1080" output.mp4
# Scale width, auto height (maintain aspect ratio)
ffmpeg -i input.mp4 -vf "scale=1920:-1" output.mp4
# Even dimensions (required for H.264)
ffmpeg -i input.mp4 -vf "scale=1920:-2" output.mp4
# Fit within bounds (no upscale)
ffmpeg -i input.mp4 -vf "scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease" output.mp4
# Pad to exact size (letterbox/pillarbox)
ffmpeg -i input.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black" output.mp4
# Crop to aspect ratio (center crop)
ffmpeg -i input.mp4 -vf "crop=ih*16/9:ih" output.mp4
6. Audio Operations
# Extract audio only
ffmpeg -i video.mp4 -vn -c:a libmp3lame -q:a 2 audio.mp3
ffmpeg -i video.mp4 -vn -c:a pcm_s16le audio.wav
ffmpeg -i video.mp4 -vn -c:a aac -b:a 192k audio.m4a
ffmpeg -i video.mp4 -vn -c:a flac audio.flac
# Replace audio track
ffmpeg -i video.mp4 -i new_audio.mp3 -c:v copy -map 0:v:0 -map 1:a:0 output.mp4
# Mix/overlay audio tracks
ffmpeg -i video.mp4 -i music.mp3 \
-filter_complex "[0:a][1:a]amerge=inputs=2[a]" -map 0:v -map "[a]" -c:v copy output.mp4
# Adjust volume
ffmpeg -i input.mp4 -af "volume=1.5" output.mp4
ffmpeg -i input.mp4 -af "volume=0.5" output.mp4 # reduce
# Normalize audio (EBU R128 loudness)
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 output.mp4
# Convert audio format
ffmpeg -i audio.m4a -c:a libmp3lame -q:a 2 audio.mp3
# Fade audio in/out
ffmpeg -i input.mp4 -af "afade=t=in:st=0:d=2,afade=t=out:st=58:d=2" output.mp4
# Remove audio
ffmpeg -i input.mp4 -an -c:v copy output_silent.mp4
7. Video Effects & Filters
# Fade in/out (video)
ffmpeg -i input.mp4 -vf "fade=t=in:st=0:d=1,fade=t=out:st=9:d=1" output.mp4
# Speed up (2x) — video + audio
ffmpeg -i input.mp4 \
-filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" \
-map "[v]" -map "[a]" output.mp4
# Speed up (4x) — chain atempo (max 2.0 per filter)
ffmpeg -i input.mp4 \
-filter_complex "[0:v]setpts=0.25*PTS[v];[0:a]atempo=2.0,atempo=2.0[a]" \
-map "[v]" -map "[a]" output.mp4
# Slow down (0.5x)
ffmpeg -i input.mp4 \
-filter_complex "[0:v]setpts=2.0*PTS[v];[0:a]atempo=0.5[a]" \
-map "[v]" -map "[a]" output.mp4
# Text overlay (centered title)
ffmpeg -i input.mp4 \
-vf "drawtext=text='Title':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=50" output.mp4
# Color correction
ffmpeg -i input.mp4 -vf "eq=brightness=0.1:saturation=1.2:contrast=1.1" output.mp4
# Rotate 90° clockwise
ffmpeg -i input.mp4 -vf "transpose=1" output.mp4
# Picture-in-picture
ffmpeg -i main.mp4 -i overlay.mp4 \
-filter_complex "[1:v]scale=320:-1[pip];[0:v][pip]overlay=W-w-10:H-h-10" output.mp4
# Stabilize (two-pass)
ffmpeg -i shaky.mp4 -vf vidstabdetect -f null -
ffmpeg -i shaky.mp4 -vf vidstabtransform output.mp4
Speed Reference
| Speed |
setpts |
atempo |
| 0.25x (slow) |
4.0*PTS |
atempo=0.5,atempo=0.5 |
| 0.5x |
2.0*PTS |
atempo=0.5 |
| 1.5x |
0.667*PTS |
atempo=1.5 |
| 2x |
0.5*PTS |
atempo=2.0 |
| 4x |
0.25*PTS |
atempo=2.0,atempo=2.0 |
| 8x |
0.125*PTS |
atempo=2.0,atempo=2.0,atempo=2.0 |
8. GIF Creation
# High-quality GIF (two-pass with palette)
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -i input.mp4 -i palette.png \
-filter_complex "[0:v]fps=15,scale=480:-1:flags=lanczos[v];[v][1:v]paletteuse" output.gif
# Quick GIF (lower quality, single pass)
ffmpeg -i input.mp4 -vf "fps=10,scale=320:-1" -t 5 output.gif
# GIF from time range
ffmpeg -ss 5 -t 3 -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -ss 5 -t 3 -i input.mp4 -i palette.png \
-filter_complex "[0:v]fps=15,scale=480:-1:flags=lanczos[v];[v][1:v]paletteuse" output.gif
9. Thumbnails & Frame Extraction
# Single thumbnail at timestamp
ffmpeg -ss 00:00:10 -i input.mp4 -vframes 1 -q:v 2 thumb.jpg
# Thumbnail every N seconds
ffmpeg -i input.mp4 -vf "fps=1/10" thumb_%04d.jpg
# Keyframe extraction (scene changes)
ffmpeg -i input.mp4 -vf "select=eq(pict_type\,I)" -vsync vfr keyframe_%04d.jpg
# Scene detection (configurable threshold 0.0-1.0)
ffmpeg -i input.mp4 -vf "select='gt(scene,0.3)'" -vsync vfr scene_%04d.jpg
# Contact sheet / sprite sheet
ffmpeg -i input.mp4 -vf "fps=1/10,scale=160:-1,tile=5x4" contact_sheet.jpg
# Extract frame at specific resolution
ffmpeg -ss 5 -i input.mp4 -vframes 1 -vf "scale=1280:-1" frame.jpg
10. Platform-Specific Encoding
YouTube (recommended)
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 18 \
-c:a aac -b:a 192k -ar 48000 \
-movflags +faststart -pix_fmt yuv420p youtube.mp4
Twitter/X (max 512MB, 140s, 1920x1200)
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 \
-vf "scale='min(1920,iw)':'min(1200,ih)':force_original_aspect_ratio=decrease" \
-c:a aac -b:a 128k -t 140 -movflags +faststart twitter.mp4
LinkedIn (max 5GB, 10 min, prefer 1080p)
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 20 \
-vf "scale=-2:1080" -c:a aac -b:a 192k \
-movflags +faststart -t 600 linkedin.mp4
Instagram Reels (9:16, max 90s)
ffmpeg -i input.mp4 -c:v libx264 -crf 20 \
-vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black" \
-c:a aac -b:a 128k -t 90 -movflags +faststart reel.mp4
Web Embed (small, fast-loading)
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 28 \
-vf "scale=-2:720" -c:a aac -b:a 96k \
-movflags +faststart -maxrate 2M -bufsize 4M web.mp4
Platform Quick Reference
| Platform |
Max Res |
Max Size |
Max Duration |
Aspect |
| YouTube |
8K |
256GB |
12h |
16:9 |
| Twitter/X |
1920x1200 |
512MB |
2:20 |
16:9/1:1 |
| LinkedIn |
4096x2304 |
5GB |
10min |
16:9/1:1 |
| Instagram Reels |
1080x1920 |
— |
90s |
9:16 |
| TikTok |
1080x1920 |
— |
10min |
9:16 |
11. HLS Streaming
# Create HLS stream from video
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f hls \
-hls_time 10 -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:a -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 \
-f hls -hls_time 10 -hls_list_size 0 \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0 v:1,a:1" \
stream_%v/playlist.m3u8
# Download HLS stream
ffmpeg -i "https://example.com/playlist.m3u8" -c copy output.mp4
# Download with auth headers
ffmpeg -headers "Authorization: Bearer TOKEN\r\n" \
-i "https://example.com/playlist.m3u8" \
-c copy -protocol_whitelist http,https,tcp,tls,crypto output.mp4
12. Quality Metrics
# PSNR (Peak Signal-to-Noise Ratio) — higher is better
ffmpeg -i compressed.mp4 -i original.mp4 -lavfi psnr -f null -
# SSIM (Structural Similarity) — closer to 1.0 is better
ffmpeg -i compressed.mp4 -i original.mp4 -lavfi ssim -f null -
# VMAF (Netflix perceptual quality) — 0-100 scale
# Default model (FFmpeg 5.0+, uses built-in vmaf_v0.6.1)
ffmpeg -i compressed.mp4 -i original.mp4 -lavfi libvmaf -f null -
Quality Interpretation
| Metric |
Excellent |
Good |
Fair |
Poor |
| PSNR |
>40 dB |
35-40 |
30-35 |
<30 |
| SSIM |
>0.95 |
0.90-0.95 |
0.80-0.90 |
<0.80 |
| VMAF |
>90 |
75-90 |
60-75 |
<60 |
13. Hardware Acceleration
# macOS (VideoToolbox)
ffmpeg -hwaccel videotoolbox -i input.mp4 -c:v h264_videotoolbox -q:v 50 output.mp4
# Linux NVIDIA (NVENC)
ffmpeg -hwaccel cuda -i input.mp4 -c:v h264_nvenc -preset p4 -cq 23 output.mp4
# Intel Quick Sync (QSV)
ffmpeg -hwaccel qsv -i input.mp4 -c:v h264_qsv -global_quality 23 output.mp4
# Check available hardware acceleration
ffmpeg -hwaccels
# Check available encoders
ffmpeg -encoders | grep -E "videotoolbox|nvenc|qsv"
14. Multi-Pass Encoding
# Two-pass for target bitrate (best quality at file size)
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 1 -f null /dev/null
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 2 -c:a aac -b:a 192k output.mp4
# Target file size calculation
# target_bitrate = (target_size_MB * 8192) / duration_seconds - audio_bitrate
# Example: 50MB file, 120s video, 128k audio:
# video_bitrate = (50 * 8192) / 120 - 128 ≈ 3285 kbps
15. Video Analysis (Sub-Agent Pattern)
For analyzing video content by extracting frames and using AI vision:
Frame Extraction Strategy (by duration)
| Duration |
Strategy |
Command |
| 0-60s |
1 frame/2s |
ffmpeg -i input.mp4 -vf "fps=0.5" frames/%04d.jpg |
| 1-10min |
Scene detection |
ffmpeg -i input.mp4 -vf "select='gt(scene,0.3)'" -vsync vfr frames/%04d.jpg |
| 10-30min |
Keyframes only |
ffmpeg -i input.mp4 -vf "select=eq(pict_type\,I)" -vsync vfr frames/%04d.jpg |
| 30min+ |
Thumbnail filter |
ffmpeg -i input.mp4 -vf "fps=1/30,scale=640:-1" frames/%04d.jpg |
Workflow
- Create temp dir:
/tmp/video-analysis-$(date +%s)
- Extract metadata with
ffprobe
- Choose frame extraction strategy based on duration
- Cap at ~60 frames max. If >100 frames, increase scene threshold or reduce fps
- Sub-agent delegation: Split frames into batches of 8-10, spawn parallel sub-agents to analyze each batch using vision. Each writes
batch_N_analysis.md. Main agent reads only text summaries — saves ~90% context.
- Synthesize: metadata table + timeline segments + key moments (3-7) + summary (2-5 sentences)
- Cleanup temp directory
Higher Detail Mode
- Double frame rate from strategy table
- Lower scene detection threshold to 0.2
- Add
scale=1920:-1 for better text/detail capture
16. Batch Processing
# Convert all files in directory
for f in *.avi; do
ffmpeg -i "$f" -c:v libx264 -crf 23 -c:a aac "${f%.avi}.mp4"
done
# Batch with GNU parallel (faster)
ls *.avi | parallel -j4 'ffmpeg -i {} -c:v libx264 -crf 23 -c:a aac {.}.mp4'
# Multi-platform export from single source
export_all() {
local input="$1"
local base="${input%.*}"
ffmpeg -i "$input" -c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k -movflags +faststart "${base}_youtube.mp4" &
ffmpeg -i "$input" -c:v libx264 -crf 23 -vf "scale='min(1920,iw)':'min(1200,ih)':force_original_aspect_ratio=decrease" -c:a aac -b:a 128k -t 140 -movflags +faststart "${base}_twitter.mp4" &
ffmpeg -i "$input" -c:v libx264 -crf 20 -vf "scale=-2:1080" -c:a aac -b:a 192k -movflags +faststart "${base}_linkedin.mp4" &
wait
echo "All exports complete"
}
# Usage: export_all input.mp4
17. Common Patterns & Tips
Quality Guidelines
| Use Case |
CRF |
Preset |
Notes |
| Archival |
15-18 |
slow |
Large files, maximum quality |
| Production |
18-22 |
medium |
Good balance |
| Web/sharing |
23-28 |
medium |
Smaller files |
| Preview/draft |
30-35 |
ultrafast |
Fast encoding, lower quality |
Useful Flags
| Flag |
Purpose |
-movflags +faststart |
Web playback (moov atom at start) |
-pix_fmt yuv420p |
Maximum compatibility |
-threads 0 |
Auto-detect CPU threads |
-max_muxing_queue_size 1024 |
Fix muxing queue overflow |
-y |
Overwrite output without asking |
-n |
Never overwrite |
-hide_banner |
Suppress version info |
-progress pipe:1 |
Machine-readable progress |
Validation Helper
validate_video() {
local file="$1"
if ffprobe -v error -select_streams v:0 -show_entries stream=codec_type -of csv=p=0 "$file" 2>/dev/null | grep -q "video"; then
echo "✓ Valid: $(ffprobe -v error -show_entries format=duration,size -of default=noprint_wrappers=1 "$file" 2>/dev/null)"
return 0
else
echo "✗ Invalid or missing video stream"
return 1
fi
}
Related Tools
- Whisper/WhisperX — Transcription from extracted audio
- yt-dlp — Download source video (
yt-dlp -f bestvideo+bestaudio URL)
- ImageMagick — Static image manipulation (use ffmpeg for video frames, ImageMagick for post-processing stills)
- Remotion — Programmatic video rendering (ffmpeg for encoding final output)