ffmpeg
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 |
| 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:-1for 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)
More from leegonzales/aiskills
veo3-prompter
Craft professional video prompts for Google Veo 3.1 using cinematic techniques, audio direction, and timestamp choreography. Use when generating AI videos, creating video prompts, or working with Veo 3.
41goals-graph
Query and update Lee's goals graph through natural language. Translates conversational questions and updates into goals_query.py commands.
19codebase-navigator
Semantic code search using osgrep for understanding codebases, finding implementations, and navigating large projects. Use when asked "where is", "how does", "find the code that", or any question about code location or implementation.
6writing-partner
Collaborative essay writing that preserves authenticity through structured interview, thread tracking, and voice calibration. Transforms AI from text generator into intellectual prosthesis. Use when writing essays, blog posts, or any content where voice matters more than speed.
4prose-polish
Evaluate and elevate writing effectiveness through multi-dimensional quality assessment. Analyzes craft, coherence, authority, purpose, and voice with genre-calibrated thresholds. Use for refining drafts, diagnosing quality issues, generating quality content, or teaching writing principles.
4claude-project-docs
Generate concise CLAUDE.md files and agent documentation following best practices. Use when setting up a new project for Claude Code, auditing existing CLAUDE.md, or creating progressive disclosure documentation structure.
3