Skip to content

Reuse the first pass's stats when a retry re-encodes - #145

Merged
zfleeman merged 1 commit into
mainfrom
zf/reuse-first-pass
Nov 6, 2026
Merged

zfleeman merged 1 commit into
mainfrom
zf/reuse-first-pass

Conversation

@zfleeman

@zfleeman zfleeman commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Closes #139.

When an encode lands over the target, twopass_loop() re-encodes at a lower bitrate. Each retry used to run both passes from scratch in a new temp folder. The first pass only studies the video and writes stats, and a retry changes nothing but the bitrate, so it wrote the same stats again. Now a retry skips the first pass and runs the second pass with the stats from the first attempt.

This only affects software codecs. Hardware encoders (*_nvenc, *_videotoolbox) run a single pass already.

Changes

  • twopass.py: TwoPass.run() takes two optional arguments. log_dir is the folder for the stats and progress files (without one, run() makes and deletes its own temp folder as before, e.g. for --dry-run). reuse_first_pass skips the first pass and prints "Reusing the first pass's stats". A pass's progress file is deleted before the pass starts, so a retry's progress bar doesn't briefly show the last attempt's progress.
  • __main__.py: twopass_loop() creates one TemporaryDirectory for all attempts and passes reuse_first_pass=True from the second attempt on. EncodeJob.start_pass() counted a new attempt when a first or single pass started. A retry now starts with a second pass, so a second pass right after another second pass also counts as a new attempt. The retry's progress bar runs from 50% to 100%, since the first half is already done.
  • README.md: a sentence in the codec notes about faster retries, with the numbers below.

Checked

10 seconds of a 1080p60 Call of Duty clip. Each codec encoded at 6 MiB, then retried at 5.1 MiB twice: once reusing the first attempt's stats, and once from scratch.

Codec Retry with reused stats Retry from scratch
x264 5.07 MiB in 4.0 s 5.13 MiB in 6.9 s
x265 4.85 MiB in 10.4 s 4.92 MiB in 23.0 s
vp9 5.12 MiB in 44.1 s 5.12 MiB in 60.3 s

The reused retries land about as close to the target as full re-encodes. With verbose logging, x265 logs slice=P but 2pass stats say B and a B-frame warning on every plain two-pass encode, with or without this change, and a reused retry adds no new warnings.

A CLI run at -s 5.1 with x264 went over on the first attempt (5.12 MB), reused the stats, landed at 5.02 MB, and left no ff4d- temp folders behind.

AV1 isn't tested: this machine's ffmpeg build doesn't include libaom-av1. libaom's first-pass stats don't depend on the target bitrate either, so it should behave like VP9.

Tests

  • test_twopass.py: with reuse_first_pass, only the second pass runs and it points at the first attempt's stats (-passlogfile for x264 and VP9, stats= for x265). A caller's log_dir is used and not deleted. Hardware codecs are unchanged. A retry's progress file starts empty.
  • test_main.py: twopass_loop() passes reuse_first_pass as False, True, True across three attempts with one shared stats folder, which is deleted afterward. EncodeJob.status() counts attempt 2 for a retry that starts with the second pass, and for hardware retries.

@zfleeman
zfleeman merged commit 64fca79 into main Nov 6, 2026
6 checks passed
@zfleeman
zfleeman deleted the zf/reuse-first-pass branch November 6, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reuse the first pass's stats when a retry re-encodes

1 participant