diff --git a/examples/probe3d_deint.rs b/examples/probe3d_deint.rs deleted file mode 100644 index d67c420..0000000 --- a/examples/probe3d_deint.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Prove the SSIF de-interleave: the base-view .m2ts extents are the base-view -// interleaved units INSIDE the SSIF region, so the dependent (right-eye MVC) -// view = SSIF sectors MINUS base sectors. Then confirm those dependent units -// decrypt under the same Unit Key. -// probe3d_deint [clip=00042] - -use libfreemkv::aacs::content::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, aacs_unit_encrypted, decrypt_unit}; -use libfreemkv::sector::SectorSource; -use libfreemkv::{FileSectorSource, read_filesystem}; -use std::path::Path; - -fn hex16(s: &str) -> [u8; 16] { - let s = s.trim().trim_start_matches("0x"); - let mut o = [0u8; 16]; - for i in 0..16 { - o[i] = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).expect("hex"); - } - o -} - -fn main() { - let a: Vec = std::env::args().collect(); - let iso = &a[1]; - let uk = hex16(&a[2]); - let clip = a.get(3).map(|s| s.as_str()).unwrap_or("00042"); - let mut r = FileSectorSource::open(Path::new(iso)).expect("open"); - let fs = read_filesystem(&mut r).expect("udf"); - - let base = fs - .file_extents(&mut r, &format!("/BDMV/STREAM/{clip}.m2ts")) - .expect("base extents"); - let ssif = fs - .file_extents(&mut r, &format!("/BDMV/STREAM/SSIF/{clip}.ssif")) - .expect("ssif extents"); - let gb = |sec: u64| sec as f64 * 2048.0 / 1e9; - let bsum: u64 = base.iter().map(|(_, c)| *c as u64).sum(); - let ssum: u64 = ssif.iter().map(|(_, c)| *c as u64).sum(); - println!("base: {} extents {:.2} GB", base.len(), gb(bsum)); - println!("ssif: {} extents {:.2} GB", ssif.len(), gb(ssum)); - - // Is the base range a subset of the SSIF range? (LBA span check) - let brange = ( - base.iter().map(|(l, _)| *l).min().unwrap_or(0), - base.iter().map(|(l, c)| l + c).max().unwrap_or(0), - ); - let srange = ( - ssif.iter().map(|(l, _)| *l).min().unwrap_or(0), - ssif.iter().map(|(l, c)| l + c).max().unwrap_or(0), - ); - println!( - "base LBA span [{},{}) ssif LBA span [{},{}) base⊆ssif={}", - brange.0, - brange.1, - srange.0, - srange.1, - brange.0 >= srange.0 && brange.1 <= srange.1 - ); - - // dependent = ssif − base (per SSIF extent, subtract overlapping base ranges). - let mut b: Vec<(u32, u32)> = base.iter().map(|&(l, c)| (l, l + c)).collect(); - b.sort(); - let mut dep: Vec<(u32, u32)> = Vec::new(); - for &(sl, sc) in &ssif { - let (s, e) = (sl, sl + sc); - let mut cur = s; - for &(bs, be) in b.iter().filter(|&&(bs, be)| be > s && bs < e) { - if bs > cur { - dep.push((cur, bs)); - } - cur = cur.max(be); - } - if cur < e { - dep.push((cur, e)); - } - } - let dsum: u64 = dep.iter().map(|(s, e)| (*e - *s) as u64).sum(); - println!( - "\ndependent (ssif − base): {} ranges {:.2} GB (expected ≈ {:.2} GB)", - dep.len(), - gb(dsum), - gb(ssum - bsum) - ); - - // Decrypt-test dependent-view units at several points across the ranges. - let (mut tested, mut enc, mut dec) = (0u32, 0u32, 0u32); - for &(s, e) in dep.iter().filter(|(s, e)| e - s >= ALIGNED_UNIT_SECTORS).take(200) { - let lba = s; - let mut buf = vec![0u8; ALIGNED_UNIT_LEN]; - if r.read_sectors(lba, ALIGNED_UNIT_SECTORS as u16, &mut buf, false).is_ok() { - tested += 1; - if aacs_unit_encrypted(&buf) { - enc += 1; - } - if decrypt_unit(&mut buf, &uk) { - dec += 1; - } - } - } - println!("dependent-view unit decrypt: tested={tested} cpi-encrypted={enc} decrypted={dec}"); -} diff --git a/examples/probe3d_struct.rs b/examples/probe3d_struct.rs deleted file mode 100644 index 4f63b88..0000000 --- a/examples/probe3d_struct.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Read-only 3D structure probe: does the disc carry a dependent (MVC) view, -// where, and how is it described? Uses freemkv's own UDF reader (7z can't parse -// these ISOs). probe3d_struct [clip=00098] - -use libfreemkv::sector::SectorSource; -use libfreemkv::{FileSectorSource, read_filesystem}; -use std::path::Path; - -fn be16(b: &[u8], o: usize) -> u16 { - u16::from_be_bytes([b[o], b[o + 1]]) -} -fn be32(b: &[u8], o: usize) -> u32 { - u32::from_be_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]) -} - -fn main() { - let args: Vec = std::env::args().collect(); - let iso = &args[1]; - let clip = args.get(2).map(|s| s.as_str()).unwrap_or("00098"); - let mut r = FileSectorSource::open(Path::new(iso)).expect("open iso"); - let fs = read_filesystem(&mut r).expect("read udf"); - - // Base view vs dependent-interleave file. - for (label, path) in [ - ("base .m2ts", format!("/BDMV/STREAM/{clip}.m2ts")), - ("SSIF", format!("/BDMV/STREAM/SSIF/{clip}.ssif")), - ] { - match fs.file_extents(&mut r, &path) { - Ok(exts) => { - let sectors: u64 = exts.iter().map(|(_, c)| *c as u64).sum(); - println!( - "{label:<11}: EXISTS {:>5} extents {:>7.2} GB {path}", - exts.len(), - sectors as f64 * 2048.0 / 1e9 - ); - } - Err(_) => println!("{label:<11}: MISSING {path}"), - } - } - - // MPLS ExtensionData → the 3D STN_table_SS lives here. - let mpls = match fs.read_file(&mut r, &format!("/BDMV/PLAYLIST/{clip}.mpls")) { - Ok(m) => m, - Err(e) => { - println!("\nmpls read failed: {e}"); - return; - } - }; - println!("\nmpls: {} bytes", mpls.len()); - let ext_addr = be32(&mpls, 16) as usize; // ExtensionData_start_address - if ext_addr == 0 || ext_addr + 12 > mpls.len() { - println!("ExtensionData_start_address = {ext_addr} → NO extension data (not a 3D/SS playlist)"); - return; - } - let ed = &mpls[ext_addr..]; - let ed_len = be32(ed, 0) as usize; - let n_entries = ed[11] as usize; // len(4)+data_block_start(4)+reserved(3)+count(1) - println!("ExtensionData @ {ext_addr}: length={ed_len} entries={n_entries}"); - for i in 0..n_entries { - let o = 12 + i * 12; - if o + 12 > ed.len() { - break; - } - let id1 = be16(ed, o); - let id2 = be16(ed, o + 2); - let addr = be32(ed, o + 4) as usize; // relative to MPLS file start - let len = be32(ed, o + 8) as usize; - let tag = match (id1, id2) { - (1, 1) => "PiP metadata", - (1, 2) => "SubPath entries (SS)", - (2, 1) => "STN_table_SS?", - (2, 2) => "STN_table_SS (MVC dependent view)", - _ => "?", - }; - print!(" entry {i}: ID1={id1:#06x} ID2={id2:#06x} addr={addr} len={len} [{tag}] hex:"); - let blk = &mpls[addr.min(mpls.len())..(addr + 40).min(mpls.len())]; - for b in blk { - print!(" {b:02x}"); - } - println!(); - } -} diff --git a/src/disc/mod.rs b/src/disc/mod.rs index 701767a..8fa8c81 100644 --- a/src/disc/mod.rs +++ b/src/disc/mod.rs @@ -811,7 +811,11 @@ impl Codec { use crate::consts::coding_type as c; match ct { c::HEVC => Codec::Hevc, - c::H264 => Codec::H264, + // 0x1B base-view AVC and 0x20 MVC dependent-view (Blu-ray 3D right + // eye) are both H.264. Mapping 0x20 to video is what makes the PMT + // scan enumerate the dependent view as a second H.264 stream (its + // own PID in the SSIF) instead of dropping it — the basis of 3D. + c::H264 | c::H264_MVC => Codec::H264, c::VC1 => Codec::Vc1, c::MPEG2_VIDEO => Codec::Mpeg2, c::TRUEHD => Codec::TrueHd,