Recipe data lives in ../assets/encoding-presets.json (query it; don't re-derive). This file is the why behind those numbers.
CRF encodes to a perceptual quality level; size falls where it falls. Use CRF for everything except a hard size/bandwidth budget (then two-pass, below).
| Encoder | Range | Visually lossless | Good delivery | Small | Notes |
|---|---|---|---|---|---|
| libx264 | 0–51 | 17–18 | 20–23 | 26–28 | +6 ≈ half the size |
| libx265 | 0–51 | 20–21 | 23–26 | 28–30 | x265 CRF ≈ x264 CRF + 3 for similar quality |
| libsvtav1 | 0–63 | 25–28 | 30–35 | 38–45 | scale differs — do not map 1:1 from x264 |
| libvpx-vp9 | 0–63 | 24–28 | 31–36 | 40+ | needs -b:v 0 for pure CRF mode |
VP9 trap: -crf 32 alone is constrained quality; pure CRF needs
-c:v libvpx-vp9 -crf 32 -b:v 0.
Preset changes size at the same quality, not the quality itself (CRF pins that).
ultrafast..placebo. slow is the sweet spot for delivery;
fast/medium for intermediates; never placebo (≈1% gain, 2× time over veryslow).0–13, lower = slower. 6 balanced, 4 quality-leaning,
8–10 for drafts.-tune film (live action grain), -tune animation (flat areas + lines),
-tune grain (preserve heavy grain — also consider this for film scans),
-tune stillimage, -tune zerolatency (streaming only — disables lookahead).
Don't set tune at all when unsure.
-pix_fmt yuv420p10le reduces banding in gradients (skies, dark scenes) even for
8-bit sources, at ~5% size cost. x265 and SVT-AV1 handle it natively; for H.264 it
breaks too many players — keep H.264 8-bit. HDR requires 10-bit
(see color-hdr.md).
Target bitrate = (size_MB × 8192 ÷ seconds) − audio_kbps.
# 700 MB target for a 1h video with 128k audio → (700*8192/3600)-128 ≈ 1465k
ffmpeg -y -i in.mp4 -c:v libx264 -b:v 1465k -preset slow -pass 1 -an -f null -
ffmpeg -i in.mp4 -c:v libx264 -b:v 1465k -preset slow -pass 2 \
-c:a aac -b:a 128k -movflags +faststart out.mp4
Pass 1 writes ffmpeg2pass-0.log in the CWD — run both passes from the same
directory. On Windows -f null - works in PowerShell; no need for NUL.
| Codec | Use | Bitrates |
|---|---|---|
| libopus | Best per-bit; anything not chained to MP4-only players | voice 24–32k mono, music 96–128k stereo |
| aac (native) | MP4 delivery default; fine at ≥128k stereo | 128–192k |
| libmp3lame | Legacy compat only | -q:a 2 (~190k VBR) |
| flac / pcm_s16le | Archival / editing intermediates | lossless |
Opus-in-MP4 exists but player support is patchy — Opus belongs in webm/mka/opus.
Long-GOP H.264/HEVC is miserable to scrub/cut repeatedly. For multi-step edit pipelines, transcode once to an all-intra mezzanine and work on that:
ffmpeg -i in.mp4 -c:v libx264 -crf 14 -preset fast -g 1 -c:a pcm_s16le mezz.mov
(-g 1 = every frame a keyframe: any cut point is copy-safe, scrubbing is instant.
ProRes via -c:v prores_ks -profile:v 3 if the destination is an NLE.)
FFV1 level 3 in MKV is the preservation standard (lossless, checksummed, seekable):
ffmpeg -i in.mp4 -c:v ffv1 -level 3 -g 1 -slicecrc 1 -c:a flac archive.mkv
Verify the round trip with -f framemd5 (see
analysis-validation.md).
CRF first, then check, then two-pass only if over:
ffmpeg -i in.mp4 -c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p \
-c:a aac -b:a 128k -movflags +faststart try.mp4
# over budget? compute bitrate for the cap and two-pass (above), or step CRF +2