<!-- skill: FFmpeg Media | version: 1.0.0 | description: Convert/edit video+audio with ffmpeg — formats, compression, streaming, loudness | category: Video & Design -->

## Instructions
- Never re-encode without reason: `-c copy` for remux/cut (lossless, instant). Re-encode only to change codec/size.
- Web video: H.264 + AAC in MP4 (`-c:v libx264 -crf 23 -preset medium -c:a aac -movflags +faststart`). CRF 18-28 = quality dial (lower = bigger).
- Constant frame rate for editing (`-r 30`), fix phone VFR drift: `-vsync cfr`. Scale with `scale=1920:-2` (even dimensions for H.264).
- Audio: loudness first (`loudnorm` to -14 LUFS stereo for YouTube), then encode AAC 128-192k. Strip metadata you don't want to leak (`-map_metadata -1`).
- Always test with `-t 30` (30s sample) before the full encode. Two-pass only when exact file size matters.
- Batch with shell loops; `ffprobe` (JSON) to inspect before touching anything.
- Subtitles: burn (`subtitles=file.srt` filter) for social feeds, soft-mux (`-c:s mov_text`) for players; keep the `.srt` master beside the video.
- Thumbnails/contact sheets: `-vf fps=1/10,scale=320:-1,tile=4x4` builds a preview grid in one pass — review hours of footage as one image.
- Concat rule: same codec/params → concat demuxer with `-c copy`; anything else → re-encode to a common intermediate first or A/V drifts at joins.
- Streaming prep: HLS (`-hls_time 6 -hls_playlist_type vod`) or DASH from the master; multi-rendition ladders (1080/720/480) with matching GOPs (`-g 48 -keyint_min 48`) for clean switching.
- Hardware encode on weak machines: `h264_v4l2m2m`/`h264_vaapi` on Pi/Linux where available; software `libx264 -preset veryfast` as the fallback — test 30s for quality delta first.
## Examples
- Phone video for editing: `ffmpeg -i in.mp4 -c:v libx264 -r 30 -vsync cfr -c:a aac edit.mp4`
- Extract podcast audio: `ffmpeg -i video.mp4 -vn -c:a libmp3lame -q:a 4 audio.mp3`
- GIF preview: `ffmpeg -i in.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" out.gif`
- Silence-cut podcast: `silenceremove=start_periods=1:stop_duration=1:stop_threshold=-40dB` pass before loudness normalization.
## Workflows
1. `ffprobe` the source (codecs, fps, resolution, duration).
2. Decide: remux (`-c copy`), cut (`-ss` BEFORE `-i` for speed), or re-encode.
3. Test 30s, verify A/V sync + size, then full run.
4. Loudness-normalize audio deliverables, check final with a second ffprobe.
5. Keep the master; compress copies are disposable.
6. Log the exact command in a `render-log.txt` beside outputs — re-renders must be reproducible.
## Templates
```bash
# Inspect
ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate,duration -of default=noprint_wrappers=1 in.mp4
# Shrink for web (720p, faststart)
ffmpeg -i in.mp4 -vf scale=1280:-2 -c:v libx264 -crf 24 -preset fast -c:a aac -b:a 128k -movflags +faststart out.mp4
# Concat same-codec files losslessly
printf "file 'a.mp4'\nfile 'b.mp4'\n" > list.txt && ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
# Two-pass loudness normalize to -14 LUFS (YouTube/podcast stereo)
ffmpeg -i in.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11:print_format=json -f null - 2>&1 | tail -12
ffmpeg -i in.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11:measured_I=-18:measured_LRA=7:offset=1.2:linear=true -c:v copy -c:a aac -b:a 192k out.mp4
# Fast cut without re-encode (seek before input, copy codecs)
ffmpeg -ss 00:01:20 -to 00:03:45 -i in.mp4 -c copy clip.mp4
```
```bash
# contact-sheet review (one glanceable page per hour of footage)
ffmpeg -i in.mp4 -vf "fps=1/60,scale=320:-1,tile=5x6" sheet.png
# rotation-deterministic transcode (ignore phone rotate tags, set explicitly)
ffmpeg -noautorotate -i in.mp4 -vf "transpose=1,scale=1280:-2" -c:v libx264 -crf 22 -c:a aac out.mp4
```
## Tools
- SARAH `shell_run` runs ffmpeg directly; check `ffmpeg -version` (Pi: `sudo apt install ffmpeg`).
- Loudness JSON: `ffmpeg -i f.mp4 -af loudnorm=print_format=json -f null - 2>&1 | tail -12`
- MediaInfo (`apt install mediainfo`) for human-readable stream reports when ffprobe JSON is overkill.
## Advanced
- Filter graphs: `split` + `scale` + `hstack` for side-by-side before/after; `drawtext` (with escaped colons) for burned-in timecodes; `zscale` for proper HDR→SDR tone mapping instead of clipped conversions.
- Frame-accurate cuts need re-encode at the cut point — `-c copy` cuts land on keyframes; use `-ss` after `-i` + re-encode for exact cuts.
- Audio rescue: `afftdn` (FFT denoise, `nr=12`), `adeesser`, `acompressor=threshold=-18dB:ratio=3:attack=20:release=200`, `alimiter=limit=0.9` — in that order, gentle settings stacked beat one aggressive pass.
- Batch pattern: `for f in *.MOV; do ffmpeg -i "$f" -c:v libx264 -crf 22 -preset fast -c:a aac "${f%.*}_edit.mp4"; done` — quote everything, test on one file first.
- Troubleshooting: output 2x duration → VFR source, add `-vsync cfr -r 30`; `Non-monotonous DTS` warnings → timestamp breaks, remux through `-fflags +genpts`; AAC sounds metallic → source already lossy, raise bitrate (192k) instead of re-tuning; file plays locally but not on the web → missing `+faststart` (moov atom at end); concat A/V drift → mismatched timebases, normalize to one intermediate first.
- Rotation metadata trap: phone videos carry `rotate` tags — `-noautorotate` + explicit `transpose` keeps orientation deterministic across players.
- Contact-sheet review: `-vf "fps=1/60,scale=320:-1,tile=5x6" sheet.png` turns an hour of footage into one glanceable page before editing.
## Documentation
- ffmpeg docs: https://ffmpeg.org/documentation.html
- Encoding wiki: https://trac.ffmpeg.org/wiki/Encode/H.264
- Filter reference: https://ffmpeg.org/ffmpeg-filters.html
