mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
disc: payload-plausibility floor on nav/authoring feature promotion
A nav- or authoring-resolved playlist could promote a video-bearing but negligible-payload branch above the substantial feature it sits beside: Spotlight (2015) UHD navigates to a Dolby-Vision seamless-branch playlist (00001, 0.4 GB across 102 tiny clips) instead of the 91.5 GB single-clip feature (00002). A player plays the branch; a rip wants the feature. Require a nav/authoring-promoted title to also carry at least 10% of the largest video-bearing title's bytes; otherwise selection falls through to the composite/size ranking, which recovers the real feature. Adds the Spotlight-shaped regression test. Verified against the 88-disc hoard: title selection is identical to the prior release on every disc except the one it fixes (Spider-Man 3 UHD, 00245 wrapper decoy -> 00001 feature). Also clears three 1.97 clippy lints in the HDMV VM and drops two reference-implementation filename citations from the bdnav module docs.
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
//! Parse `/BDMV/index.bdmv` — the BD navigation index (First Play, Top Menu,
|
||||
//! and the title table), mirroring libbluray `index_parse.c`. This is read as a
|
||||
//! and the title table), per its documented binary layout. This is read as a
|
||||
//! documented binary format, never executed: every field is bounds-checked and
|
||||
//! any malformed input yields `None` (the nav resolver then abstains).
|
||||
//!
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//! Parse `/BDMV/MovieObject.bdmv` — the HDMV navigation programs, mirroring
|
||||
//! libbluray `mobj_parse.c`. Read as a documented format, never executed here:
|
||||
//! Parse `/BDMV/MovieObject.bdmv` — the HDMV navigation programs, per their
|
||||
//! documented binary layout. Read as a documented format, never executed here:
|
||||
//! bounds-checked, never panics.
|
||||
//!
|
||||
//! Layout: `"MOBJ"` + version(4) + reserved… ; `MovieObjects()` at byte 40 is
|
||||
|
||||
+28
-32
@@ -137,10 +137,8 @@ fn run(vm: &mut Vm, mut obj_id: usize, is_feature: &dyn Fn(u16) -> bool) -> Opti
|
||||
return None;
|
||||
}
|
||||
let obj = vm.mobjs.get(obj_id)?;
|
||||
let c = match obj.cmds.get(pc) {
|
||||
Some(c) => *c,
|
||||
None => return None, // ran off the end without a feature PlayPL
|
||||
};
|
||||
// Ran off the end without a feature PlayPL → abstain.
|
||||
let c = *obj.cmds.get(pc)?;
|
||||
let mut npc = pc + 1;
|
||||
let dst = if c.op_cnt > 0 {
|
||||
vm.fetch(c.imm_op1, c.dst)
|
||||
@@ -223,35 +221,33 @@ fn run(vm: &mut Vm, mut obj_id: usize, is_feature: &dyn Fn(u16) -> bool) -> Opti
|
||||
}
|
||||
// SET (sub_grp 0). SETSYSTEM (sub_grp 1) only mutates system PSRs
|
||||
// whose values the feature path does not branch on — skip it.
|
||||
2 => {
|
||||
if c.sub_grp == 0 {
|
||||
let r: Option<u32> = match c.set_opt {
|
||||
0x01 => Some(src), // MOVE
|
||||
0x02 => {
|
||||
vm.wr(c.dst, src); // SWAP
|
||||
vm.wr(c.src, dst);
|
||||
None
|
||||
}
|
||||
0x03 => Some(dst.wrapping_add(src)),
|
||||
0x04 => Some(dst.saturating_sub(src)),
|
||||
0x05 => Some(dst.wrapping_mul(src)),
|
||||
0x06 => Some(if src > 0 { dst / src } else { 0xffff_ffff }),
|
||||
0x07 => Some(if src > 0 { dst % src } else { 0xffff_ffff }),
|
||||
0x08 => Some(dst), // RND — deterministic stand-in
|
||||
0x09 => Some(dst & src),
|
||||
0x0a => Some(dst | src),
|
||||
0x0b => Some(dst ^ src),
|
||||
0x0c => Some(dst | (1u32 << (src & 31))),
|
||||
0x0d => Some(dst & !(1u32 << (src & 31))),
|
||||
0x0e => Some(dst.wrapping_shl(src & 31)),
|
||||
0x0f => Some(dst.wrapping_shr(src & 31)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(r) = r
|
||||
&& !c.imm_op1
|
||||
{
|
||||
vm.wr(c.dst, r);
|
||||
2 if c.sub_grp == 0 => {
|
||||
let r: Option<u32> = match c.set_opt {
|
||||
0x01 => Some(src), // MOVE
|
||||
0x02 => {
|
||||
vm.wr(c.dst, src); // SWAP
|
||||
vm.wr(c.src, dst);
|
||||
None
|
||||
}
|
||||
0x03 => Some(dst.wrapping_add(src)),
|
||||
0x04 => Some(dst.saturating_sub(src)),
|
||||
0x05 => Some(dst.wrapping_mul(src)),
|
||||
0x06 => Some(dst.checked_div(src).unwrap_or(0xffff_ffff)),
|
||||
0x07 => Some(dst.checked_rem(src).unwrap_or(0xffff_ffff)),
|
||||
0x08 => Some(dst), // RND — deterministic stand-in
|
||||
0x09 => Some(dst & src),
|
||||
0x0a => Some(dst | src),
|
||||
0x0b => Some(dst ^ src),
|
||||
0x0c => Some(dst | (1u32 << (src & 31))),
|
||||
0x0d => Some(dst & !(1u32 << (src & 31))),
|
||||
0x0e => Some(dst.wrapping_shl(src & 31)),
|
||||
0x0f => Some(dst.wrapping_shr(src & 31)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(r) = r
|
||||
&& !c.imm_op1
|
||||
{
|
||||
vm.wr(c.dst, r);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
+50
-3
@@ -2453,6 +2453,23 @@ impl Disc {
|
||||
.filter(|t| t.has_probable_video())
|
||||
.map(|t| t.duration_secs)
|
||||
.fold(0.0_f64, f64::max);
|
||||
// Largest video-bearing title's byte size, used as a plausibility floor
|
||||
// below. A nav/authoring signal can legitimately resolve a seamless-branch
|
||||
// or Dolby-Vision presentation playlist that a player navigates to but
|
||||
// that carries almost no unique payload (e.g. a 0.4 GB / 102-clip branch
|
||||
// sitting beside the 91.5 GB single-clip feature). Those are correct for
|
||||
// playback but wrong as a RIP target, so a promoted title must also hold a
|
||||
// meaningful fraction of this size; otherwise it falls through to the
|
||||
// composite/size ranking, which recovers the substantial feature.
|
||||
let max_video_size = titles
|
||||
.iter()
|
||||
.filter(|t| t.has_probable_video())
|
||||
.map(|t| t.size_bytes)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let carries_feature_payload = |t: &DiscTitle| {
|
||||
max_video_size == 0 || t.size_bytes as f64 >= 0.10 * max_video_size as f64
|
||||
};
|
||||
titles
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -2480,11 +2497,16 @@ impl Disc {
|
||||
let authoring = !composite
|
||||
&& hint.is_some_and(|h| h.matches(p.playlist_id, &p.playlist))
|
||||
&& p.has_video()
|
||||
&& (longest_video_dur <= 0.0 || p.duration_secs >= 0.5 * longest_video_dur);
|
||||
&& (longest_video_dur <= 0.0 || p.duration_secs >= 0.5 * longest_video_dur)
|
||||
&& carries_feature_payload(p);
|
||||
// The nav result is video-gated exactly like the authoring hint,
|
||||
// so a mis-resolved playlist that happens to be streamless can
|
||||
// never win (and thus cannot reopen the decoy hole).
|
||||
let nav = nav_feature == Some(p.playlist_id) && p.has_video();
|
||||
// never win (and thus cannot reopen the decoy hole). It is also
|
||||
// payload-gated: a player may navigate to a low-byte branch/DV
|
||||
// presentation, but that is never the rip target.
|
||||
let nav = nav_feature == Some(p.playlist_id)
|
||||
&& p.has_video()
|
||||
&& carries_feature_payload(p);
|
||||
TitleRank {
|
||||
nav,
|
||||
authoring,
|
||||
@@ -5167,6 +5189,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression (Spotlight 2015 UHD): the disc navigates to a Dolby-Vision
|
||||
/// seamless-branch playlist (`00001` = 102 tiny clips, 0.4 GB) that has real
|
||||
/// video streams but carries almost no payload, sitting beside the 91.5 GB
|
||||
/// single-clip feature (`00002`). A player plays the branch; a RIP wants the
|
||||
/// feature. The nav pick is video-gated but must ALSO clear the payload floor,
|
||||
/// so it falls through to size ranking and the substantial feature wins.
|
||||
#[test]
|
||||
fn nav_feature_ignored_when_target_is_a_low_payload_branch() {
|
||||
let branch = bd_title("00001.mpls", 1, 7500.0, 400_000_000, &["b01", "b02", "b03"]);
|
||||
let feature = bd_title("00002.mpls", 2, 7680.0, 91_500_000_000, &["00002"]);
|
||||
let capacity = 100_000_000_000u64;
|
||||
|
||||
assert!(
|
||||
branch.has_probable_video(),
|
||||
"the DV branch has video streams (only its payload is tiny)"
|
||||
);
|
||||
let mut titles = vec![branch, feature];
|
||||
// Nav resolves the branch (1) like a real player — must be payload-gated.
|
||||
Disc::sort_titles_by_main_feature(&mut titles, capacity, None, Some(1));
|
||||
assert_eq!(
|
||||
titles[0].playlist_id, 2,
|
||||
"the 91.5 GB feature wins; the 0.4 GB nav-picked branch is not a rip target"
|
||||
);
|
||||
}
|
||||
|
||||
/// A hint pointing at a STREAMLESS entry must not resurrect the decoy bug:
|
||||
/// the authoring preference requires the designated title to have real video
|
||||
/// streams, and a clip-less streamless title fails the `has-video` floor too.
|
||||
|
||||
Reference in New Issue
Block a user