What You Will Learn
- The difference between CRF mode and two-pass bitrate mode
- How to run the two-pass pipeline: analysis pass then encoding pass
- How to target a specific file size using bitrate math
- The
-passlogfileoption for custom log file locations - H.264 and H.265 two-pass examples
Tested version: FFmpeg 6.1 (ubuntu-latest / CI-validated)
Target OS: Windows / macOS / Linux
⚠️ Safety Check — Read Before Running (Risk: stale passlog silently corrupts encode)
- A leftover passlog file causes pass 2 to read stale analysis data and silently produce a corrupted or incorrect encode
- In batch jobs, always give each encode its own unique
-passlogfilename — shared names are overwritten mid-batch - Delete the passlog files after each encode completes
- Run pass 1 and pass 2 in the same directory, or specify the same
-passlogfilepath in both commands
CRF Mode vs. Two-Pass Bitrate Mode
FFmpeg’s libx264 and libx265 encoders offer two main quality/bitrate control modes:
| Mode | Option | Use When |
|---|---|---|
| CRF (Constant Rate Factor) | -crf 18–28 | You want consistent quality, file size varies |
| Two-pass bitrate | -b:v N -pass 1/2 | You need to hit a specific file size or bitrate |
CRF is simpler and often preferred for archiving or streaming. Two-pass is the right choice when you must deliver a file that fits within a specific size budget (e.g., email attachment limits, disc capacity).
How Two-Pass Works
Pass 1 (Analysis): FFmpeg encodes the video quickly, writing a log file that records the complexity of each frame. No output video is produced — only the log file matters.
Pass 2 (Encoding): FFmpeg reads the log file to understand which scenes need more bits and which need fewer, then encodes the final output with the bitrate distributed according to the analysis.
By default the log is written to ffmpeg2pass-0.log (and ffmpeg2pass-0.log.mbtree for H.264) in the working directory. Always use -passlogfile with a unique name per encode in batch jobs.
Basic Two-Pass H.264 Example
Recommended: specify a unique passlogfile for every encode
Pass 1 — Analysis only, no output:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 1 \
-passlogfile /tmp/passlog_myproject -an -f null /dev/null
Pass 2 — Actual encode with analysis data:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 2 \
-passlogfile /tmp/passlog_myproject -c:a aac -b:a 128k output.mp4
Clean up the passlog after encoding:
rm -f /tmp/passlog_myproject.log /tmp/passlog_myproject.log.mbtree
-pass 1/-pass 2selects the analysis or encoding pass-passlogfilesets an explicit log path (the defaultffmpeg2pass-0.logcollides in batch jobs; always set this explicitly)-ansuppresses audio in pass 1 (the log only needs video complexity data)-f null /dev/nulldiscards the output (only the log file matters)
The -passlogfile path must be identical in both passes. If pass 2 cannot find the log, it fails with No such file or directory.
Targeting a Specific File Size
To calculate the bitrate for a target file size:
total_bitrate_kbps = (target_size_MB × 8192) ÷ duration_seconds
video_bitrate_kbps = total_bitrate_kbps − audio_bitrate_kbps
Example: Target 50 MB for a 4-minute (240-second) video with 128 kbps audio:
total = (50 × 8192) ÷ 240 ≈ 1707 kbps
video = 1707 − 128 = 1579 kbps ≈ 1.5M
Pass 1:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1500k -pass 1 \
-passlogfile /tmp/passlog_target -an -f null /dev/null
Pass 2:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1500k -pass 2 \
-passlogfile /tmp/passlog_target -c:a aac -b:a 128k output.mp4
Clean up:
rm -f /tmp/passlog_target.log /tmp/passlog_target.log.mbtree
Safe Passlog Management in Batch Jobs
When batch-encoding multiple files, the default log name ffmpeg2pass-0.log is shared across all encodes. Each file’s pass 1 overwrites the previous log, so pass 2 reads stale data from a different source file. Always use a unique passlogfile name per encode:
for f in *.mp4; do
base="${f%.mp4}"
# Unique passlogfile name using the base filename
passlog="/tmp/passlog_${base}_$(date +%s)"
# Pass 1
ffmpeg -i "$f" -c:v libx264 -b:v 1M -pass 1 \
-passlogfile "$passlog" -an -f null /dev/null
# Pass 2
ffmpeg -i "$f" -c:v libx264 -b:v 1M -pass 2 \
-passlogfile "$passlog" -c:a aac "${base}_out.mp4"
# Always clean up
rm -f "${passlog}.log" "${passlog}.log.mbtree"
done
Post-Encode Integrity Check
Verify that the output duration matches the input after a two-pass encode:
in_dur=$(ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 input.mp4)
out_dur=$(ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 output.mp4)
echo "Input: ${in_dur}s Output: ${out_dur}s"
# Investigate if the difference exceeds 0.5 seconds
Custom Log File Location
By default, FFmpeg writes the log to ./ffmpeg2pass-0.log. Use -passlogfile to specify a different path:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 1 -passlogfile /tmp/mylog -an -f null /dev/null
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 2 -passlogfile /tmp/mylog -c:a aac output.mp4
Clean up:
rm -f /tmp/mylog.log /tmp/mylog.log.mbtree
Two-Pass H.265 (libx265)
Two-pass encoding with H.265 follows the same structure:
ffmpeg -i input.mp4 -c:v libx265 -b:v 800k \
-x265-params "pass=1:stats=/tmp/passlog_h265.log" -an -f null /dev/null
ffmpeg -i input.mp4 -c:v libx265 -b:v 800k \
-x265-params "pass=2:stats=/tmp/passlog_h265.log" -c:a aac output.mp4
Clean up:
rm -f /tmp/passlog_h265.log /tmp/passlog_h265.log.cutree
Note: libx265 uses -x265-params pass=1 with stats= for the log path, unlike H.264’s -passlogfile. The behavior is otherwise identical.
Adding Encoder Presets
You can combine two-pass with encoder presets:
ffmpeg -i input.mp4 -c:v libx264 -preset slow -b:v 1M -pass 1 \
-passlogfile /tmp/passlog_slow -an -f null /dev/null
ffmpeg -i input.mp4 -c:v libx264 -preset slow -b:v 1M -pass 2 \
-passlogfile /tmp/passlog_slow -c:a aac -b:a 128k output.mp4
rm -f /tmp/passlog_slow.log /tmp/passlog_slow.log.mbtree
Use the same preset in both passes to ensure the log file and pass 2 analysis are consistent.
When to Use Two-Pass
| Scenario | Recommendation |
|---|---|
| Archiving with consistent visual quality | Use CRF mode (-crf 23) |
| File must fit a specific size limit | Use two-pass bitrate mode |
| Streaming delivery with guaranteed bitrate | Two-pass (or constrained bitrate with -maxrate) |
| Quick transcodes or previews | CRF mode (single pass) |
Common Pitfalls
Log File Not Found in Pass 2
If pass 2 runs in a different directory than pass 1, it cannot find ffmpeg2pass-0.log. Either run both in the same directory, or use -passlogfile /path/to/log in both passes.
Audio in Pass 1
Pass 1 with -an skips audio encoding, which is correct — the log only needs video complexity data. Including audio in pass 1 wastes time without benefit.
Same -b:v in Both Passes
Always use the same -b:v value in both passes. Using different values means the log from pass 1 targets a different bitrate than pass 2, producing unreliable results.
Related Articles
- Compressing Video — CRF and Bitrate Targets
- Video Format Conversion — Transcoding to MP4
- Changing Frame Rate
Tested with: ffmpeg 6.1.1 / Ubuntu 24.04 (GitHub Actions runner)
Primary sources: trac.ffmpeg.org/wiki/Encode/H.264 / ffmpeg.org/ffmpeg.html