Place Blu-ray clips by the playlist's marks, not by guessing at PTS jumps
A seamless-branching title's PlayItems do not chain contiguously: one clip's OUT can sit after the next clip's IN, where the disc stores the join twice, or before it, where the playlist skips material. The mux never saw those marks — its own header said so — and inferred seams from PTS jumps instead. Inference cannot recover this. A forward jump is ambiguous: it means the playlist skipped, or it means frames were lost to damaged media, and compressing the latter would falsify timing on exactly the rips that most need it faithful. An overlap smaller than the B-frame reorder threshold is invisible to inference entirely, and its duplicate content then collided in the muxer, where the monotonic nudge flattened a run of audio onto the tick floor and put sound ahead of picture for the rest of the film. Measured on one 11-PlayItem title: the file declared 7893.385 s, which is what the playlist says the title is, and carried packets to 8029.298 s. Both numbers came from the same program on the same disc. Four skips totalling 135.9 s became dead timeline, and a 1.79 s overlap put audio 1.8 s ahead at the half-hour mark. Five of forty-seven titles were affected; every single-clip title was exact. So the marks are read. Each clip contributes exactly out - in, laid end to end, so the output runs as long as the playlist says and a join never rewinds. Titles without usable marks — DVD, HD-DVD, file sources — keep the inference path unchanged, and clips that already chain contiguously produce a constant offset, which is pinned by a test.
This commit is contained in:
+23
-1
@@ -1349,6 +1349,22 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
/// BlockAdditional under the track's `mvcC` mapping. Such a frame is always a
|
||||
/// `BlockGroup` (never a SimpleBlock), with a `ReferenceBlock` when it is not
|
||||
/// a keyframe. `None` for every non-3D frame.
|
||||
/// Drive seam correction from the title's PlayItem marks instead of
|
||||
/// inferring it from PTS jumps.
|
||||
///
|
||||
/// A multi-clip Blu-ray playlist joins its clips with overlaps and skips
|
||||
/// that PTS inspection cannot recover: a forward jump is indistinguishable
|
||||
/// from frames lost to damaged media, and an overlap smaller than the
|
||||
/// reorder threshold is invisible. Given the marks, each clip is placed at
|
||||
/// the sum of the earlier clips' durations, so the output runs exactly as
|
||||
/// long as the playlist says the title is.
|
||||
///
|
||||
/// No-op for a title with fewer than two clips or without usable marks —
|
||||
/// DVD, HD-DVD and file sources keep the inference path.
|
||||
pub fn set_clips(&mut self, clips: &[crate::disc::Clip]) {
|
||||
self.continuity = TimelineContinuity::with_clips(clips);
|
||||
}
|
||||
|
||||
pub fn write_frame(
|
||||
&mut self,
|
||||
track_idx: usize,
|
||||
@@ -1391,7 +1407,13 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
// band of non-monotonic block timestamps. Only the PRIMARY video track
|
||||
// drives the boundary decision; every other track (audio, subtitle, DV
|
||||
// EL) rides the current offset. No-op for single-clip titles.
|
||||
let pts_ns = self.continuity.adjust(pts_ns, drives_epoch);
|
||||
// `None` means the playlist does not include this frame — material
|
||||
// outside every clip's IN/OUT marks, which only a seam-plan-driven
|
||||
// title can report. Dropping it is the point: emitting it is what put
|
||||
// duplicate content on the timeline at a join.
|
||||
let Some(pts_ns) = self.continuity.map(pts_ns, drives_epoch) else {
|
||||
return Ok(());
|
||||
};
|
||||
let raw_ticks = pts_ns / TIMESTAMP_SCALE_NS;
|
||||
|
||||
// Cluster boundaries normally coincide with a video keyframe so every
|
||||
|
||||
Reference in New Issue
Block a user