Say when a title set is dropped from a DVD scan
parse_vmg skipped any title set whose parse failed, with no log and no counter. A real disc enumerated 38 titles from one image and 10 from another, and the 28 discarded failures were invisible — the symptom read as a scan difference rather than as dropped reads, which is most of why it took so long to localise. Behaviour is unchanged: a disc may legitimately carry placeholder TT_SRPT entries, so one failure is still not fatal. It now warns per skip with the title set and the error, and once at the end with kept-versus-declared. Also adds an opt-in diagnostic that reads every VTS IFO from an image, to separate a read failure from a parse failure. It reports all 13 sets reading identically from both a CSS image and its decrypted copy, which is what proves the 38-to-10 loss is downstream of these reads.
This commit is contained in:
@@ -824,3 +824,29 @@ fn dump_titles_for_an_image() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Diagnostic (opt-in): can each VTS IFO be READ from an image?
|
||||||
|
///
|
||||||
|
/// `parse_vmg` skips a title set whose `parse_vts` fails, and `parse_vts`
|
||||||
|
/// begins by reading `/VIDEO_TS/VTS_nn_0.IFO`. This isolates the read.
|
||||||
|
#[test]
|
||||||
|
#[ignore = "diagnostic: needs FMKV_IMAGE"]
|
||||||
|
fn dump_vts_ifo_reads_for_an_image() {
|
||||||
|
let Ok(path) = std::env::var("FMKV_IMAGE") else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut img =
|
||||||
|
crate::io::file_sector_source::FileSectorSource::open(std::path::Path::new(&path))
|
||||||
|
.expect("open");
|
||||||
|
let fs = udf::read_filesystem(&mut img).expect("udf");
|
||||||
|
for n in 1..=20u32 {
|
||||||
|
let p = format!("/VIDEO_TS/VTS_{n:02}_0.IFO");
|
||||||
|
match fs.read_file(&mut img, &p) {
|
||||||
|
Ok(b) => {
|
||||||
|
let magic = String::from_utf8_lossy(&b[..12.min(b.len())]).to_string();
|
||||||
|
println!("VTS {n:02}: read ok, {} bytes, magic={magic:?}", b.len());
|
||||||
|
}
|
||||||
|
Err(e) => println!("VTS {n:02}: READ FAILED: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+26
-2
@@ -421,16 +421,40 @@ pub fn parse_vmg(reader: &mut dyn SectorSource, udf: &UdfFs) -> Result<DvdInfo>
|
|||||||
|
|
||||||
// Parse each VTS IFO
|
// Parse each VTS IFO
|
||||||
let mut title_sets = Vec::new();
|
let mut title_sets = Vec::new();
|
||||||
|
let mut skipped = 0usize;
|
||||||
for (&vts_number, titles_info) in &title_set_map {
|
for (&vts_number, titles_info) in &title_set_map {
|
||||||
match parse_vts(reader, udf, vts_number, titles_info) {
|
match parse_vts(reader, udf, vts_number, titles_info) {
|
||||||
Ok(ts) => title_sets.push(ts),
|
Ok(ts) => title_sets.push(ts),
|
||||||
Err(_) => {
|
Err(e) => {
|
||||||
// Skip unreadable title sets — some DVDs have placeholder entries.
|
// Some discs carry placeholder TT_SRPT entries for title sets
|
||||||
|
// that are not really there, so one failure is not fatal. But
|
||||||
|
// the failure must not be INVISIBLE: every skipped set is a
|
||||||
|
// title the user will never see, and swallowing the reason made
|
||||||
|
// a disc that enumerated 38 titles from one image and 10 from
|
||||||
|
// another look like a scan difference rather than 28 dropped
|
||||||
|
// reads.
|
||||||
|
skipped += 1;
|
||||||
|
tracing::warn!(
|
||||||
|
target: "freemkv::scan",
|
||||||
|
vts = vts_number,
|
||||||
|
titles = titles_info.len(),
|
||||||
|
error = %e,
|
||||||
|
"title set could not be parsed; its titles are omitted"
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if skipped > 0 {
|
||||||
|
tracing::warn!(
|
||||||
|
target: "freemkv::scan",
|
||||||
|
skipped,
|
||||||
|
kept = title_sets.len(),
|
||||||
|
declared = title_set_map.len(),
|
||||||
|
"some title sets were omitted from the scan"
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(DvdInfo { title_sets })
|
Ok(DvdInfo { title_sets })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user