Skip to content

v1.6.2

Choose a tag to compare

@TheBluWiz TheBluWiz released this 14 Sep 03:22
· 4 commits to main since this release

[1.6.2] - 2026-09-13

Fixed

  • Frame-rate integrity guard false-positived on variable-frame-rate sources — muxm refused to ship a correctly-encoded, perfectly-synced file with Frame-rate integrity check failed: source 600.0000 fps but output 58 fps … refusing to ship a desynced encode (die 45), on sources where ffprobe's r_frame_rate is a VFR container's raw timing base rather than the content's real average cadence. Confirmed on a real macOS screen-recording .mov reporting r_frame_rate=600/1 while avg_frame_rate and frame-count÷duration both independently agreed the content actually ran ~58 fps — init_src_fps() was trusting r_frame_rate alone for SRC_FPS, and the post-mux verify step was doing the same for the muxed output, so a correct 58fps encode got compared against a source number the file never actually played at. Both now fall back to avg_frame_rate when it diverges from r_frame_rate by more than 2%, via a new shared predicate, _fps_prefers_avg(); true CFR sources, where the two already agree, are unaffected. The guard's own pass/fail comparison was extracted into _fps_integrity_ok() so it is unit-testable independent of a real encode — a genuine desync turns out to be hard to manufacture honestly (an early synthetic VFR fixture coincidentally passed even against the pre-fix code, since source and output happened to share the same wrong nominal value) — and a dedicated case (23.976 vs 25, the historical raw-ES-stamping desync this guard exists to catch) locks in that the fallback didn't blunt its original purpose. Verified against the real 228s capture, a 20s trim, and several synthetic fixtures, all previously failing at exit 45 and now succeeding.

  • The same false positive reproduced on Matroska/WebM containers, on both the source and the output side, even after the fix above — this ffprobe build never computes a real avg_frame_rate for Matroska at all; it just echoes r_frame_rate back, leaving _fps_prefers_avg nothing genuine to compare against, so an MKV/WebM source or --output-ext mkv output could still trip the guard. Added _fps_measure_avg_decimal(): a real packet count (-count_packets — demux-only, no decoding, ~0.1s measured on a 473MB file) divided by the video stream's own duration, not the container's .format.duration — an early version of this fix used the container duration and was itself subtly wrong, since audio commonly runs a few milliseconds to tens of milliseconds longer than video (encoder priming/padding), which silently skewed the computed average low. Matroska only exposes the video-specific duration as a tags.DURATION string (HH:MM:SS.nnnnnnnnn), so the function parses either that or a plain numeric duration. Gated on format_name containing matroska, so MP4/MOV — which already get a trustworthy avg_frame_rate for free — pay no extra cost. Wired into both init_src_fps() and the verify step.

  • Frame-rate integrity guard false-positived on an AVI source with a dropped frame, re-encoded to MKV (containers suite, "passthrough fallback" test: Frame-rate integrity check failed: source 24.0000 fps but output 23.0548 fps … refusing to ship a desynced encode, die 45, no derived .mkv) — a regression from the 1.6.2 Matroska measurement fix. init_src_fps() measured the true average frame rate only for Matroska/WebM sources and trusted ffprobe's avg_frame_rate for everything else; on AVI that field is the header's advertised frame count over the header's rate, so a stream with 24 real packets spread over 25 frame slots (ffmpeg's AVI muxer writes the 1-second lavfi fixture that way) still reads an exact 24/1. The verify step, meanwhile, does measure a Matroska output — 24 packets over 1.041s ≈ 23.05 fps — so the guard compared a measured output average against a header-derived source rate, found them 4% apart, and rejected a faithfully re-encoded, perfectly-synced file. The container gate is now a single shared predicate, _fps_container_needs_measured_avg(): only the MP4/MOV family (whose avg_frame_rate really is nb_frames÷duration) is trusted as-is; Matroska/WebM (echoes r_frame_rate), AVI (header value), and MPEG-TS/FLV/ASF (probe-window estimate) are measured with _fps_measure_avg_decimal() on both the source and the output side, so the comparison is always like-for-like — measured-vs-measured or nominal-vs-nominal, never mixed. True CFR sources still measure within 2% of their nominal rate and keep the exact rational for DV wrap stamping; an unknown/empty format_name keeps avg_frame_rate (no extra pass). The pre-existing containers regression test passes again, and now prints muxm's own error line when the derived file is missing instead of a bare "none found". New coverage: unit tests for the gate, init_src_fps on a mocked AVI source (measured average wins / agreeing CFR keeps the rational / measurement unavailable keeps the nominal), and an end-to-end AVI-with-a-dropped-frame → MKV scenario in the video suite, skip-gated on the fixture actually reproducing the gap.

Changed

  • Version bumped to 1.6.2. docs/muxm.1 and completions/muxm-completion.bash regenerated via tools/gen-docs.sh. Test suite: 691/692 unit tests and 79/79 video-pipeline tests pass (0 failures across both), including new coverage for _fps_prefers_avg, _fps_integrity_ok, init_src_fps's VFR fallback, and four end-to-end VFR scenarios (MP4→MP4, MP4→MKV, MKV→MP4) alongside the existing CFR fps preservation regression test, unaffected throughout.