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 -passlogfile option 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 -passlogfile name — 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 -passlogfile path in both commands

CRF Mode vs. Two-Pass Bitrate Mode

FFmpeg’s libx264 and libx265 encoders offer two main quality/bitrate control modes:

ModeOptionUse When
CRF (Constant Rate Factor)-crf 18–28You want consistent quality, file size varies
Two-pass bitrate-b:v N -pass 1/2You 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 2 selects the analysis or encoding pass
  • -passlogfile sets an explicit log path (the default ffmpeg2pass-0.log collides in batch jobs; always set this explicitly)
  • -an suppresses audio in pass 1 (the log only needs video complexity data)
  • -f null /dev/null discards 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

ScenarioRecommendation
Archiving with consistent visual qualityUse CRF mode (-crf 23)
File must fit a specific size limitUse two-pass bitrate mode
Streaming delivery with guaranteed bitrateTwo-pass (or constrained bitrate with -maxrate)
Quick transcodes or previewsCRF 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.



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