Skip to content

Add encode --verify & --fail-fast to catch damaged results - #378

Merged
alexheretic merged 2 commits into
alexheretic:mainfrom
pawelkwaczynski:verify-output
Aug 21, 2026
Merged

Add encode --verify & --fail-fast to catch damaged results#378
alexheretic merged 2 commits into
alexheretic:mainfrom
pawelkwaczynski:verify-output

Conversation

@pawelkwaczynski

Copy link
Copy Markdown
Contributor

Closes #376.

Two opt-in checks on the final encode, both off by default.

  • --verify decodes the temporary output in full before it is renamed into place, so a failed check leaves no output file. It fails on ffmpeg decode errors, on a duration mismatch with the input, or if the input had audio and the result does not.
  • --fail-fast passes ffmpeg -xerror to the encode, so a decode error in the input stops it instead of producing a damaged result.

They catch different failures. A source whose container claims 20s while the stream actually ends at 10s re-encodes "successfully" into a 10s file: --verify catches that on the duration check, and so does --fail-fast. A source with corrupt macroblocks in the middle produces a full length result that decodes cleanly, and only --fail-fast catches that one. --verify in turn covers damage introduced after decoding, which fail-fast cannot see.

A few notes on the details:

  • Both flags lean on ffmpeg's own -xerror rather than parsing its log output.
  • The comparisons against the input are skipped when the encode is customised with --vfilter, --enc, --enc-input, --acodec or --video-only, since the result is then not expected to match the input.
  • Verification maps video and audio only. Mapping everything makes the null muxer fail on subtitles and attachments, and the encode copies those through unchanged anyway.
  • --fail-fast applies to the final encode, not to sample encodes.

Nothing changes unless a flag is set. Tested on generated fixtures (truncated faststart mp4, byte flipped mp4) and on healthy inputs with audio, with subtitles, with attachments, raw h264 and still images. cargo test, cargo fmt --check and cargo clippy are clean.

Comment thread src/verify.rs Outdated
Comment thread src/verify.rs Outdated
/// Allowed difference between the input duration & the output duration.
///
/// Encoders may round the final frame duration, so an exact match can't be required.
const DURATION_TOLERANCE: Duration = Duration::from_secs(2);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document this in the --verify docs.

Comment thread src/verify.rs Outdated
Comment thread src/main.rs Outdated
Comment thread src/command/encode.rs Outdated
Comment thread src/command/encode.rs Outdated
Comment thread src/command/encode.rs Outdated
Comment thread src/command/args.rs Outdated
@alexheretic

Copy link
Copy Markdown
Owner

Thanks for the pr! I've left some comments. Overall looks pretty good to me, I don't see much in the way of merging this.

ffmpeg can exit successfully having written a result that is damaged or cut
short, usually because the input itself is damaged. Sample based VMAF does not
notice this when the samples land on healthy parts of the file.

--verify decodes the finished encode before it is moved into place and fails on
decode errors, a duration mismatch with the input or missing audio. The input
comparisons are skipped when the encode is customised with args that may
legitimately change the result.

--fail-fast passes ffmpeg -xerror to the encode, so a decode error in the input
stops it rather than leaving a damaged result behind.
Drop the matches_input heuristic that silently skipped checks for
customised encodes. Each check is now explicit and deterministic,
--verify enables both. The audio stream check is removed, there was
no evidence of ffmpeg ever dropping it.

Move the decode pass into the ffmpeg module, show verify as the last
third of the encode progress bar and document the 2s duration tolerance.
@pawelkwaczynski

Copy link
Copy Markdown
Contributor Author

Thanks for the review, all addressed in the follow-up commit (rebased on main):

  • --verify is now just a shorthand for --verify-decode + --verify-duration, each
    usable on its own. The matches_input heuristic is gone, so --video-only --verify
    and --acodec libopus --verify check the duration like any other encode. Someone
    trimming with --enc t=... uses --verify-decode alone (noted in the docs).
  • Dropped the audio stream check. Across ~460 production encodes I never saw ffmpeg
    silently drop audio, so I agree it doesn't earn a flag.
  • Decode pass moved to ffmpeg::decode, verify.rs removed.
  • Progress bar: with --verify-decode the bar length is 1.5x the input duration and
    verify fills the last third instead of resetting.
  • round_ms -> floor_ms, doc wording taken as suggested, 2s tolerance documented
    on the flag.

Sanity check on a faststart mp4 with the tail of mdat cut off: a plain encode exits 0
with a 3.85s result from a 6s input, --verify rejects it on duration, --fail-fast
stops the encode itself.

@alexheretic
alexheretic merged commit 25c1661 into alexheretic:main Aug 21, 2026
2 checks passed
@alexheretic

Copy link
Copy Markdown
Owner

Nice one, thanks!

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.

Optional output verification after encode (catch silently corrupted results)

2 participants