⚠️ 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 -passlogfile name. 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 -passlogfile path.

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.