Repository navigation
v1.6.2
[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'sr_frame_rateis a VFR container's raw timing base rather than the content's real average cadence. Confirmed on a real macOS screen-recording.movreportingr_frame_rate=600/1whileavg_frame_rateand frame-count÷duration both independently agreed the content actually ran ~58 fps —init_src_fps()was trustingr_frame_ratealone forSRC_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 toavg_frame_ratewhen it diverges fromr_frame_rateby 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_ratefor Matroska at all; it just echoesr_frame_rateback, leaving_fps_prefers_avgnothing genuine to compare against, so an MKV/WebM source or--output-ext mkvoutput 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 atags.DURATIONstring (HH:MM:SS.nnnnnnnnn), so the function parses either that or a plain numeric duration. Gated onformat_namecontainingmatroska, so MP4/MOV — which already get a trustworthyavg_frame_ratefor free — pay no extra cost. Wired into bothinit_src_fps()and the verify step. -
Frame-rate integrity guard false-positived on an AVI source with a dropped frame, re-encoded to MKV (
containerssuite, "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'savg_frame_ratefor 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 exact24/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 (whoseavg_frame_ratereally isnb_frames÷duration) is trusted as-is; Matroska/WebM (echoesr_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/emptyformat_namekeepsavg_frame_rate(no extra pass). The pre-existingcontainersregression 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_fpson 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 thevideosuite, skip-gated on the fixture actually reproducing the gap.
Changed
- Version bumped to 1.6.2.
docs/muxm.1andcompletions/muxm-completion.bashregenerated viatools/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 CFRfps preservationregression test, unaffected throughout.