Round 6: a large backstep only crosses when it would be misplaced

The round-5 fix advanced the clip cursor on ANY backward step over 3s. That
also fires for a corrupt PTS, and for a legitimate STC discontinuity inside one
clip — and nothing moves the cursor back, because a forward step matches
neither past_out nor stepped_back. Every later frame then sits below the new
clip IN and is dropped: on the fixture table that is ~17 minutes of one track
gone, and the only volume gate compares total drops against ALL tracks frames,
so it exits 0.

The branch now also requires the frame to be INSIDE the current clip marks,
which is the only case that would otherwise be silently placed at the old
offset — the rewind. A frame outside them needs no help: the containment check
drops and counts it, the cursor stays put, and the next good frame is placed
normally. Both behaviours have a test, each confirmed to fail without the guard.

Separately, the dir:// PES input path scanned the folder and never applied the
encryption verdict scan_dir exists to produce, so the same folder ripped
through one door and failed through the other asking for a key it does not
need. That logic now lives in one function, session::apply_folder_encryption_verdict,
called by both.
This commit is contained in:
Matthew Jackson
2026-08-06 11:02:48 -07:00
parent dd9e92ed52
commit 6b67c52249
3 changed files with 99 additions and 10 deletions
+22 -6
View File
@@ -392,9 +392,12 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
// when the mux output is being written to the same disk.
let reader = crate::io::file_sector_source::FileSectorSource::open(path)?;
let probe_path = path.clone();
let stream = image_input(reader, opts, move || {
crate::io::file_sector_source::FileSectorSource::open(&probe_path).ok()
})?;
let stream = image_input(
reader,
opts,
move || crate::io::file_sector_source::FileSectorSource::open(&probe_path).ok(),
false,
)?;
Ok(Box::new(stream))
}
// `dir://` as a SOURCE: an extracted disc folder, presented as a
@@ -408,9 +411,12 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
validate_file_path(path, "dir")?;
let reader = crate::dirimage::DirImage::open(path)?;
let probe_path = path.clone();
let stream = image_input(reader, opts, move || {
crate::dirimage::DirImage::open(&probe_path).ok()
})?;
let stream = image_input(
reader,
opts,
move || crate::dirimage::DirImage::open(&probe_path).ok(),
true,
)?;
Ok(Box::new(stream))
}
StreamUrl::M2ts { ref path } => {
@@ -467,6 +473,9 @@ fn image_input<S, F>(
mut reader: S,
opts: &InputOptions,
reopen: F,
// A FOLDER's `encrypted` flag comes from tree shape and can be wrong; an
// image's cannot. See `session::apply_folder_encryption_verdict`.
is_folder: bool,
) -> io::Result<PipelinedPesStream>
where
S: SectorSource + Send + 'static,
@@ -476,6 +485,13 @@ where
let mut disc =
crate::disc::Disc::scan_image(&mut reader, capacity, &crate::disc::ScanOptions::default())
.map_err(|e| -> io::Error { e.into() })?;
// Without this a folder reached here with a tree-shape verdict while
// `session::scan_dir` reached the opposite one from its CONTENT, so the
// same folder ripped through one door and failed through the other.
if is_folder {
crate::session::apply_folder_encryption_verdict(&mut reader, &mut disc)
.map_err(|e| -> io::Error { e.into() })?;
}
// Apply the caller-resolved keys (lookup-free); decrypt_keys() then
// yields them for the stream below. Propagate a failed application
// rather than silently muxing an undecryptable stream.