Identify a frame clip by provenance, not by inferring it from timestamps

Four audit rounds each fixed one rule in SeamPlan::place and broke another,
because the question the rules were answering has no answer. Inside a
seamless-branching overlap clip k OUT comes AFTER clip k+1 IN — 57.8s of
overlap on the real fixture table — so a single timestamp is legitimately
inside two clips, and a clip file is not trimmed to its marks, so it also
carries material from before its own IN. No rule over timestamps can say which
clip a frame came from, and each attempt was right for one disc layout and
silently wrong for another: 65s of rewind, 17 minutes stranded, 28 minutes
dropped, 55s refused.

Frames already carry the byte offset they were read from (PesFrame::source,
stamped by the TS demuxer). Clip now carries the byte span its stream occupies
in the title feed, recorded while the extents are gathered. So the clip is a
LOOKUP: the offset falls in exactly one span. There is no decision to get wrong.

Every track of a clip lives in the same stream file and therefore shares one
span, so video, audio and subtitles agree by construction. Divergence between
them — each track guessing separately under its own tolerance — is how audio
and video ended up on different clips and drifted apart in the first place.

spans_trusted gates the whole path: unless the spans tile the feed contiguously
from zero, an offset means nothing and provenance is ignored in favour of the
mark heuristics, which is the 1.6.0 behaviour. A broken map degrades instead of
confidently selecting a wrong clip for every frame. A clip referenced twice
reuses its first span (the bytes are read once) and is still trusted.

Sources that stamp no provenance — a mkv:// remux, the deserialize hop — take
the heuristics, which is what they have always used and where they have always
been right, because they have no overlapping clips to be ambiguous about.

36 timeline tests, six of them new and covering: the overlap case marks cannot
see, all tracks agreeing, out-of-marks material dropped AND counted, a holed
span map, a discontiguous one, a repeated clip, and no provenance at all.
This commit is contained in:
Matthew Jackson
2026-08-06 23:08:13 -07:00
parent adc8ee37be
commit b9ca75f471
9 changed files with 383 additions and 48 deletions
+18
View File
@@ -85,6 +85,16 @@ impl Disc {
// Per-PlayItem Clip entries (differing in/out times) still get
// recorded.
let mut seen_clips: std::collections::HashSet<String> = std::collections::HashSet::new();
// Byte offset of the next extent within the TITLE'S FEED — the
// concatenation of `extents` in the order the mux reads them. Each
// clip's span is recorded so a frame's source offset identifies its
// clip by lookup rather than by guessing from timestamps, which is
// ambiguous inside an overlap. A clip referenced a SECOND time pushes
// no extents (see `first_ref`), so it reuses the span of its first
// reference — the same bytes, read once.
let mut feed_pos: u64 = 0;
let mut spans: std::collections::HashMap<String, (u64, u64)> =
std::collections::HashMap::new();
for play_item in &parsed.play_items {
let clip_dur = play_item.out_time.saturating_sub(play_item.in_time) as f64 / 45000.0;
@@ -133,19 +143,27 @@ impl Disc {
}),
};
if let Some(file_exts) = file_exts {
let span_start = feed_pos;
for (lba, sectors) in file_exts {
if sectors > 0 && lba > 0 {
extents.push(Extent {
start_lba: lba,
sector_count: sectors,
});
feed_pos = feed_pos.saturating_add(
sectors as u64 * crate::consts::SECTOR_BYTES as u64,
);
}
}
if feed_pos > span_start {
spans.insert(play_item.clip_id.clone(), (span_start, feed_pos));
}
}
}
}
clips.push(Clip {
feed_span: spans.get(&play_item.clip_id).copied(),
clip_id: play_item.clip_id.clone(),
in_time: play_item.in_time,
out_time: play_item.out_time,
+3
View File
@@ -497,6 +497,7 @@ fn compose_xpl_titles(
extents.extend_from_slice(exts);
size_bytes += *size;
parts.push(Clip {
feed_span: None,
clip_id: orig
.rsplit_once('.')
.map(|(b, _)| b)
@@ -639,6 +640,7 @@ impl Disc {
extents.extend_from_slice(exts);
size_bytes += *size;
parts.push(Clip {
feed_span: None,
clip_id: orig
.rsplit_once('.')
.map(|(b, _)| b)
@@ -690,6 +692,7 @@ impl Disc {
duration_secs: 0.0,
size_bytes: *size,
clips: vec![Clip {
feed_span: None,
clip_id,
in_time: 0,
out_time: 0,
+13
View File
@@ -173,6 +173,17 @@ pub struct Clip {
pub duration_secs: f64,
/// Source packet count (from CLPI, 0 if unavailable)
pub source_packets: u32,
/// Byte range this clip's stream occupies within the TITLE'S FEED — the
/// concatenation of the title's extents, in the order the mux reads them.
/// `None` when it could not be determined.
///
/// This is PROVENANCE, and it is what makes clip assignment a lookup
/// instead of a guess. During an overlap two clips' mark ranges both
/// contain the same timestamp, so no rule over timestamps alone can say
/// which clip a frame came from — four audit rounds each fixed one such
/// rule and broke another. A frame carries its source byte offset
/// (`PesFrame::source`), and that offset falls in exactly one clip's span.
pub feed_span: Option<(u64, u64)>,
}
/// A stream within a title.
@@ -4009,6 +4020,7 @@ mod tests {
t.size_bytes = size_bytes;
t.clips = (0..n_clips)
.map(|i| Clip {
feed_span: None,
clip_id: format!("{i:05}"),
in_time: 0,
out_time: 1,
@@ -4409,6 +4421,7 @@ mod tests {
size_bytes,
clips: (0..n_clips)
.map(|i| Clip {
feed_span: None,
clip_id: format!("{i:05}"),
in_time: 0,
out_time: 0,