From gr
Provides FFmpeg commands for video/audio post-production: conversion, scaling, compression, trimming, concatenation, AI post-processing. Excludes audio mixing and Remotion rendering.
npx claudepluginhub galbaz1/video-research-mcpThis skill uses the workspace's default tool permissions.
FFmpeg recipes for video post-production. Covers the operations you reach for daily: format conversion, scaling, trimming, concatenation, compression, and AI video post-processing.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Performs token-optimized structural code search using tree-sitter AST parsing to discover symbols, outline files, and unfold code without reading full files.
FFmpeg recipes for video post-production. Covers the operations you reach for daily: format conversion, scaling, trimming, concatenation, compression, and AI video post-processing.
For platform-specific export presets (YouTube, TikTok, Instagram, Twitter), see references/platform-presets.md.
Sequence determines correctness, not just quality. This is a physical constraint.
[1] Temporal denoise — remove inter-frame shimmer before any processing
[2] Scale — scale before sharpening avoids halos
[3] Sharpen — recover edge detail lost in generation/upscale
[4] Color grade (LUT) — normalize over-saturated AI palette
[5] Curves/EQ — fine-tune contrast and shadow lift
[6] Film grain — LAST before encode; grain before denoising destroys it
[7] Encode — libx265 -tune grain or AV1 FGS
Violations cause correctness failures: grain before denoising destroys it; interpolation after grain synthesis causes tearing.
Full chain as a single filtergraph:
ffmpeg -i input.mp4 \
-vf "hqdn3d=2:2:3:3,\
scale=1920:1080:flags=lanczos,\
unsharp=5:5:0.8:5:5:0.4,\
lut3d=grade.cube,\
curves=r='0/0 0.5/0.52 1/1':b='0/0.03 1/0.97',\
noise=alls=6:allf=t+u" \
-c:v libx265 -crf 18 -preset slow -tune grain \
-c:a copy output.mp4
Preferred for iterative grading: export a Hald CLUT, grade in Photoshop/GIMP, re-import. No re-encoding between iterations.
ffmpeg -i input.mp4 -i hald_clut_graded.png \
-filter_complex "[0:v][1:v] haldclut" \
-c:v libx265 -crf 18 output.mp4
| Target | Codec | When to use |
|---|---|---|
| Web/streaming | libx264 | Universal compatibility |
| Archive/master | libx265 | ~40% smaller at same quality |
| Modern distribution | libsvtav1 | Best compression, slower encode |
| Browser embed | libvpx-vp9 | WebM container, good compression |
| Flag | Purpose |
|---|---|
-movflags +faststart | Moves moov atom to front for web streaming |
-pix_fmt yuv420p | Ensures player compatibility |
-preset slow/medium/fast | Encode speed vs compression tradeoff |
-crf 18-28 | Quality control (lower = better, 23 is default) |
-profile:v high | H.264 feature set (high for quality, baseline for compat) |
| Use case | CRF | Preset | Notes |
|---|---|---|---|
| Archive/master | 18 | slow | Best quality, large files |
| Production | 20-22 | medium | Good balance |
| Web/preview | 23-25 | fast | Smaller files |
| Draft/quick | 28+ | veryfast | Fast encoding |
ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p \
-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" output.mp4
# Good quality, smaller file
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output.mp4
# Target file size (e.g., ~10MB for 60s = ~1.3Mbps)
ffmpeg -i input.mp4 -c:v libx264 -b:v 1300k -c:a aac -b:a 128k output.mp4
# Scale to 1080p (letterbox — maintain aspect ratio, add black bars)
ffmpeg -i input.mp4 \
-vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" \
output.mp4
# Scale to 1080p (crop to fill)
ffmpeg -i input.mp4 \
-vf "scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080" \
output.mp4
# Scale to width, auto height (even dimensions)
ffmpeg -i input.mp4 -vf "scale=1280:-2" output.mp4
# Extract to MP3
ffmpeg -i input.mp4 -vn -acodec libmp3lame -q:a 2 output.mp3
# Extract to AAC
ffmpeg -i input.mp4 -vn -acodec aac -b:a 192k output.m4a
# Extract to WAV (uncompressed)
ffmpeg -i input.mp4 -vn output.wav
# M4A to MP3
ffmpeg -i input.m4a -codec:a libmp3lame -qscale:a 2 output.mp3
# Adjust volume
ffmpeg -i input.mp3 -filter:a "volume=1.5" output.mp3
# Cut from timestamp for duration (recommended — re-encode for accuracy)
ffmpeg -i input.mp4 -ss 00:00:30 -t 00:00:15 -c:v libx264 -c:a aac output.mp4
# Cut between timestamps
ffmpeg -i input.mp4 -ss 00:00:30 -to 00:00:45 -c:v libx264 -c:a aac output.mp4
# Stream copy (faster but may lose frames at non-keyframe cut points)
ffmpeg -i input.mp4 -ss 00:00:30 -t 00:00:15 -c copy output.mp4
Re-encoding is recommended for trimming. Stream copy (-c copy) can silently drop video if the seek point misses a keyframe.
# Create file list
printf "file '%s'\n" clip1.mp4 clip2.mp4 clip3.mp4 > list.txt
# Concatenate (same codec/resolution — stream copy)
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
# Concatenate (mixed sources — re-encode)
ffmpeg -f concat -safe 0 -i list.txt -c:v libx264 -c:a aac output.mp4
# 2x speed (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
# 0.5x slow motion
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
# Video only (drop audio)
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" -an output.mp4
Speed math: To fit X seconds into Y seconds: speed = X / Y, setpts = 1/speed * PTS, atempo = speed.
Extreme speeds (>2x audio): Chain atempo filters (each limited to 0.5-2.0):
# 4x audio: atempo=2.0,atempo=2.0
# 8x audio: atempo=2.0,atempo=2.0,atempo=2.0
# Video fade in/out (1s each, 10s video)
ffmpeg -i input.mp4 \
-vf "fade=t=in:st=0:d=1,fade=t=out:st=9:d=1" -c:a copy output.mp4
# Audio fade in/out
ffmpeg -i input.mp4 \
-af "afade=t=in:st=0:d=1,afade=t=out:st=9:d=1" -c:v copy output.mp4
Always two-pass. Single-pass acts as a dynamic compressor; two-pass applies linear gain and preserves dynamic range.
# Pass 1: measure
ffmpeg -i input.mp4 \
-af "loudnorm=I=-16:LRA=11:TP=-1.5:print_format=json" \
-f null - 2>loudnorm_stats.json
# Pass 2: apply (substitute measured values from stats)
ffmpeg -i input.mp4 \
-af "loudnorm=I=-16:LRA=11:TP=-1.5:measured_I=-18.2:measured_LRA=9.1:measured_TP=-3.2:measured_thresh=-28.5:linear=true" \
output_normalized.mp4
Target levels: YouTube -14 LUFS TP -1 dBFS | Streaming -14 LUFS TP -2 dBFS | Broadcast EBU R128 -23 LUFS TP -1 dBFS.
# Get duration
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4
# Full info as JSON
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
# Validate output is playable
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 output.mp4
| Error | Fix |
|---|---|
| "height not divisible by 2" | Add -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" |
| Video won't play in browser | Use -movflags faststart -pix_fmt yuv420p -c:v libx264 |
| Audio out of sync after speed change | Use filter_complex with matched setpts + atempo |
| Output 0 bytes | Check full ffmpeg output — silent failures hide in stderr |
| "encoder not found" | Install FFmpeg with full codecs (brew install ffmpeg) |