Make six silent mux failures observable
All six confirmed against the code. The governing rule this cluster serves: a
lossy or degraded outcome is never silent, because a corrupt rip the user does
not know about is the worst failure available.
**A 3D MKV re-mux silently lost one eye.** The BlockGroup read path had arms for
BLOCK / BLOCK_DURATION / REFERENCE_BLOCK only, so BLOCK_ADDITIONS fell into the
skip arm — while the writer does emit BlockAdditions > BlockMore > BlockAdditional
for the MVC dependent view. Reconstruction was judged out of scope and the
reasoning is recorded: PesFrame has no side-payload field and the header parser
never reads BlockAdditionMapping, so there is no dependent-view track to route the
AU to. Instead the loss is now LOUD — counted in bytes and events, warned once,
and surfaced through MkvStream's errors()/lost_bytes(), which the driver already
samples into MuxOutcome. One detail in the finding was wrong and is corrected: the
re-mux does NOT still advertise the mvcC mapping, because the header parser
ignores that element, so the output is a plain 2D H.264 track.
**An all-titles rip silently skipped real titles.** The header-buffer-cap
overflow returned Error::MkvInvalid, and is_skippable_title_stub matches exactly
E_MKV_INVALID | E_CSS_KEY_MISSING — verified here — so a 512 MiB-of-frames title
was classified as an empty nav/menu PGC stub and dropped. It now has its own
E9051 / MuxHeaderBufferExceeded { bytes }, outside the skippable set.
**The public pre-mux report contradicted the file.** Mp4Sink::finish() drops an
audio track it cannot describe, which I chose last round over failing an export
whose video is fine — but mp4_fit_report still listed that stream as included, so
the application's plan and the actual output disagreed. Fixed at both levels:
Mp4SkipReason is now non_exhaustive with NoSamples and UndescribableAudio,
Mp4Sink::final_report() describes the FILE rather than the plan, and for the
boxed dyn Stream path a defaulted Stream::undelivered_streams() carries the
information out to MuxOutcome::undelivered_streams with a driver-side warn.
**MP4 track ids could collide.** ids were assigned before the retain that drops
sample-less tracks, while next_id came from the post-retain count, so [1,3]
yielded next_id 3. Now max(track_id) + 1, saturating.
**Stream selection silently skipped its codec_privates prune** when the lists were
not the same length — but codec_privates is consumed POSITIONALLY and trailing
extras are documented as benign, so the length-equality guard was itself the bug.
The prune now runs unconditionally by index.
**The m2ts_mux scaffolding armed params_written on both the absent and the
unparseable codec_private arms** — the same defect already fixed in tsmux.rs.
Split into params_attempted (a latch, since retrying identical bytes cannot help)
and params_emitted, with a warn on each failure arm and an accessor so the
eventual wiring and its test can observe it.
Each fix verified red by mutating back to the prior behaviour: errors() 0 vs 1,
E6008 vs E9051, final_report [0,1] vs [0], next_track_id 3 vs [1,3], and the
selection prune resolving index 1 to the wrong track's record.
API surface deliberately widened: MuxOutcome gains a public field and Mp4Sink
becomes public. Nothing in-repo breaks. Note a behaviour change on the mkv://
input path — a 3D re-mux now reports non-zero loss, so a consumer treating
errors > 0 as disc damage will trip on it. That is intended: the outcome IS
degraded.
This commit is contained in:
+19
@@ -234,6 +234,21 @@ pub trait Stream: Send {
|
||||
fn lost_bytes(&self) -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
/// Sink side: `info().streams` indices this sink PLANNED to carry (and
|
||||
/// accepted frames for) but could not put in the finished container, valid
|
||||
/// after [`finish`](Self::finish). Empty for every sink that writes
|
||||
/// everything it accepted — which is all of them except `mp4://`, whose
|
||||
/// `finish()` must drop an audio track no frame of which yielded a parseable
|
||||
/// sample entry (an `stsd` cannot describe it).
|
||||
///
|
||||
/// This exists because such a drop otherwise contradicts the pre-mux plan the
|
||||
/// crate publishes (`mp4_fit_report`), leaving the caller reporting a
|
||||
/// successful export of a file missing a stream it was told would be there.
|
||||
/// The driver folds this into `MuxOutcome::undelivered_streams`.
|
||||
fn undelivered_streams(&self) -> Vec<usize> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps any output stream and counts bytes written.
|
||||
@@ -297,6 +312,10 @@ impl Stream for CountingStream {
|
||||
self.inner.errors()
|
||||
}
|
||||
|
||||
fn undelivered_streams(&self) -> Vec<usize> {
|
||||
self.inner.undelivered_streams()
|
||||
}
|
||||
|
||||
fn lost_bytes(&self) -> u64 {
|
||||
self.inner.lost_bytes()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user