⚠️ Check before you run (risk: pass 2 reading an old passlog)
- If an old passlog file is left over, pass 2 reads its data. When it came from another video, pass 2 either fails or finishes without an error at the wrong bitrate.
- In batch jobs, give every encode its own
-passlogfilename. A shared name gets overwritten partway through the batch. - Delete the passlog files when each encode has finished.
- Run pass 1 and pass 2 in the same folder, or give both commands the same
-passlogfilepath.
CRF Mode vs. Two-Pass Bitrate Mode
libx264 and libx265 have two main ways to control quality and bitrate:
| Mode | Option | Use When |
|---|---|---|
| CRF (Constant Rate Factor) | -crf 18–28 |
You want steady quality and the file size can vary |
| Two-pass bitrate | -b:v N -pass 1/2 |
You need a set file size or bitrate |
CRF is simpler and is the usual choice for archiving and streaming. Use two-pass when the file must fit a size limit, such as an email attachment limit or a disc.
How Two-Pass Works
Pass 1 (analysis): FFmpeg encodes the video quickly and writes a log file that records how complex each frame is. No video file is written; only the log matters.
Pass 2 (encoding): FFmpeg reads the log to see which scenes need more bits and which need fewer. It then encodes the final file and shares out the bitrate to match.
By default, the log is ffmpeg2pass-0.log in the current folder (plus ffmpeg2pass-0.log.mbtree for H.264).
Basic Two-Pass H.264 Example
Pass 1 analyzes the video and writes only the log:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 1 \
-passlogfile /tmp/passlog_myproject -an -f null /dev/null
Pass 2 uses the log to encode the real output:
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 2 \
-passlogfile /tmp/passlog_myproject -c:a aac -b:a 128k output.mp4
Delete the passlog afterwards:
rm -f /tmp/passlog_myproject-*.log /tmp/passlog_myproject-*.log.mbtree
-pass 1/-pass 2: chooses the analysis pass or the encoding pass.-passlogfile: sets the log path. The default name,ffmpeg2pass-0.log, collides in batch jobs, so always set it.-an: skips audio in pass 1. The log only needs to know how complex the video is.-f null /dev/null: throws the pass 1 output away, because only the log is needed.
Use the same -passlogfile path in both passes. If pass 2 cannot find the log, libx264 prints ratecontrol_init: can't open stats file and FFmpeg stops with Error while opening encoder.
Targeting a Specific File Size
To work out the bitrate for a target file size:
total_bitrate_kbps = (target_size_MB × 8000) ÷ duration_seconds
video_bitrate_kbps = total_bitrate_kbps − audio_bitrate_kbps
Example: a 4-minute (240-second) video with 128 kbps audio that must fit in 50 MB:
total = (50 × 8000) ÷ 240 ≈ 1666.67 kbps
calculated video ceiling = 1666.67 − 128 ≈ 1538.67 kbps
The examples below use 1500 kbps to leave some container headroom.
Here 1 MB is 1,000,000 bytes. Video at 1500 kbps plus audio at 128 kbps for 240 seconds comes to 48.84 MB, before the container adds its own overhead. Check that the finished file is under 50 MB.
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
Every encode in a folder uses the same default log name, ffmpeg2pass-0.log. If you run several two-pass encodes there at the same time, or run all the pass 1s before the pass 2s, another file’s pass 1 overwrites the log, and pass 2 reads data from the wrong video. Running pass 1 and then pass 2 for one file at a time works with the default name. A unique passlogfile name for each encode keeps the jobs independent:
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
After the encode, check that the output is as long as the input:
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
FFmpeg writes the log to ./ffmpeg2pass-0.log unless you set another path with -passlogfile:
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)
H.265 uses the same two steps:
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
These commands set the pass and the log path inside -x265-params (pass=1, stats=), which also works in older FFmpeg versions. From FFmpeg 8.1, libx265 also accepts -pass 1/-pass 2 with -passlogfile NAME, as H.264 does; the log is then NAME-0.log plus NAME-0.log.cutree. Everything else works the same way.
Adding Encoder Presets
You can add a preset to both passes:
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, so that the log matches what pass 2 does.
When to Use Two-Pass
| Scenario | Recommendation |
|---|---|
| Archiving at steady visual quality | CRF mode (-crf 23) |
| The file must fit a size limit | Two-pass bitrate mode |
| Streaming with a bitrate cap | -maxrate plus -bufsize (two-pass only hits the average and sets no cap; without -bufsize, -maxrate is ignored) |
| Quick transcodes or previews | CRF mode (single pass) |
Common Pitfalls
Log File Not Found in Pass 2
If pass 2 runs in a different folder from pass 1, it cannot find ffmpeg2pass-0.log. Run both in the same folder, or use -passlogfile /path/to/log in both passes.
Audio in Pass 1
Pass 1 with -an skips audio, and that is correct: the log only needs data about the video. Encoding audio in pass 1 only costs time.
Same -b:v in Both Passes
Use the same -b:v value in both passes. With different values, the pass 1 log was made for a different bitrate than pass 2 uses, and the result is unreliable.