Sample PGS across the title instead of reading its head
The content-based forced-subtitle probe spent its whole 256 MiB budget
on the first sectors of a title. A feature's subtitles begin minutes in,
so the probe read the opening logos, hit the budget, observed no display
set at all and contributed nothing to any verdict — the vendor label was
always the only input.
The forced predicate is asymmetric: one non-forced display set disproves
forced permanently, while proving forced needs the whole track, and
genuine forced tracks are tiny where full tracks are huge. So the same
budget is now SPREAD over each extent in ~16 MiB windows placed on the
AACS unit grid, sized in proportion to the extent, ending at the extent's
end. Cost is unchanged; placement is not.
Also:
* Per-track early exit. A track that is disproven (and whose label
needs no correcting) stops asking for budget; an extent that owes
evidence only for such tracks is skipped outright, and evidence
already in the cache is never demuxed a second time.
* Content may now DEMOTE a wrong vendor forced flag, in the probe and
in the muxer, behind one shared guard: absence of forced_on_flag
only means something if some other track demonstrably uses it, and
the track must have the shape of a full dialogue track rather than
of a forced-narrative one. On a disc where no track sets the flag,
nothing is demotable.
* A sampled or budget-cut extent's evidence is memoised with the
COVERAGE behind it. It used to be filed under the extent's full key
and replayed to playlists that would have read far more of the clip,
turning a prefix into an absence claim about the whole extent.
This commit is contained in:
+977
-89
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,8 @@ pub fn display_set_is_forced(frame_data: &[u8]) -> Option<bool> {
|
|||||||
pub struct ForcedTracker {
|
pub struct ForcedTracker {
|
||||||
has_display: bool,
|
has_display: bool,
|
||||||
all_forced: bool,
|
all_forced: bool,
|
||||||
|
displays: u32,
|
||||||
|
forced_displays: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ForcedTracker {
|
impl Default for ForcedTracker {
|
||||||
@@ -76,10 +78,87 @@ impl Default for ForcedTracker {
|
|||||||
Self {
|
Self {
|
||||||
has_display: false,
|
has_display: false,
|
||||||
all_forced: true,
|
all_forced: true,
|
||||||
|
displays: 0,
|
||||||
|
forced_displays: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The disc-shaped facts about ONE subtitle track that a demotion decision
|
||||||
|
/// rests on — how many display sets were seen, and how many of them carried the
|
||||||
|
/// HDMV `forced_on_flag`.
|
||||||
|
///
|
||||||
|
/// Split out from [`ForcedTracker`] so the two places that can contradict a
|
||||||
|
/// vendor label (the scan-time probe, which accumulates per-extent evidence,
|
||||||
|
/// and the muxer, which holds a live tracker per track) feed the SAME rule.
|
||||||
|
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
|
||||||
|
pub struct ForcedFacts {
|
||||||
|
/// Display sets observed on this track.
|
||||||
|
pub displays: u32,
|
||||||
|
/// How many of them carried `forced_on_flag`.
|
||||||
|
pub forced_displays: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A track must have shown at least this many display sets before "none of them
|
||||||
|
/// was forced" is allowed to contradict a vendor forced label.
|
||||||
|
///
|
||||||
|
/// Absence is weak evidence on a handful of sets: a genuine forced-narrative
|
||||||
|
/// track is SMALL (measured shape: tens of display sets for a whole feature), so
|
||||||
|
/// a couple of unflagged sets is exactly what one looks like on a disc whose
|
||||||
|
/// authoring never sets the flag.
|
||||||
|
pub const DEMOTE_MIN_DISPLAY_SETS: u32 = 8;
|
||||||
|
|
||||||
|
/// ...and it must carry at least this fraction (1/N) of the display sets of the
|
||||||
|
/// BUSIEST subtitle track on the disc.
|
||||||
|
///
|
||||||
|
/// This is the shape test that separates the two populations. Measured: a
|
||||||
|
/// dedicated forced track carries a low-tens count of display sets for a whole
|
||||||
|
/// feature, a full dialogue track carries one to two thousand — two orders of
|
||||||
|
/// magnitude apart. A track sitting within a quarter of the busiest track's
|
||||||
|
/// count is a full track, whatever its label says; a track at one percent of it
|
||||||
|
/// is the forced-narrative track its label claims and must keep that label.
|
||||||
|
pub const DEMOTE_MIN_DISPLAY_SHARE_DIVISOR: u32 = 4;
|
||||||
|
|
||||||
|
/// Whether content evidence is strong enough to CONTRADICT a vendor label that
|
||||||
|
/// says a track is forced — i.e. to demote 1 → 0.
|
||||||
|
///
|
||||||
|
/// Promotion (0 → 1) needs no such gate: it rests on positive evidence (every
|
||||||
|
/// display set carried `forced_on_flag`). Demotion rests on an ABSENCE, and an
|
||||||
|
/// absence is only meaningful if the flag is in use at all. Measured: discs
|
||||||
|
/// exist on which NO track carries `forced_on_flag`; there, "this track has no
|
||||||
|
/// forced display sets" is a fact about the authoring house, not about the
|
||||||
|
/// track, and demoting on it would strip a correct forced label from every
|
||||||
|
/// track on the disc.
|
||||||
|
///
|
||||||
|
/// So the rule is:
|
||||||
|
/// * a track that itself mixes forced and non-forced sets is self-evidently
|
||||||
|
/// not a forced-only track — demote, no further evidence needed; otherwise
|
||||||
|
/// * some OTHER track must demonstrably use the flag (`disc_uses_forced_flag`),
|
||||||
|
/// proving the authoring house sets it, AND this track must have the SHAPE of
|
||||||
|
/// a full track ([`DEMOTE_MIN_DISPLAY_SETS`] and
|
||||||
|
/// [`DEMOTE_MIN_DISPLAY_SHARE_DIVISOR`]) rather than of a forced-narrative one.
|
||||||
|
///
|
||||||
|
/// `busiest_displays` is the largest `displays` over every subtitle track judged
|
||||||
|
/// together (the same title's tracks for the probe, the same file's tracks for
|
||||||
|
/// the muxer).
|
||||||
|
pub fn demotable(facts: ForcedFacts, disc_uses_forced_flag: bool, busiest_displays: u32) -> bool {
|
||||||
|
if facts.displays == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Mixed: forced sets AND non-forced sets on the same track. The flag is in
|
||||||
|
// use right here, so its absence on the other sets is real evidence.
|
||||||
|
if facts.forced_displays > 0 && facts.forced_displays < facts.displays {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !disc_uses_forced_flag || facts.displays < DEMOTE_MIN_DISPLAY_SETS {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// `displays >= busiest / DIVISOR`, multiplied out (u64: `displays` is a
|
||||||
|
// disc-derived count, so the product must not be able to wrap).
|
||||||
|
u64::from(facts.displays) * u64::from(DEMOTE_MIN_DISPLAY_SHARE_DIVISOR)
|
||||||
|
>= u64::from(busiest_displays)
|
||||||
|
}
|
||||||
|
|
||||||
impl ForcedTracker {
|
impl ForcedTracker {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
@@ -91,6 +170,22 @@ impl ForcedTracker {
|
|||||||
if let Some(forced) = display_set_is_forced(frame_data) {
|
if let Some(forced) = display_set_is_forced(frame_data) {
|
||||||
self.has_display = true;
|
self.has_display = true;
|
||||||
self.all_forced &= forced;
|
self.all_forced &= forced;
|
||||||
|
// Saturating: the counts drive a shape comparison between tracks, so
|
||||||
|
// a pathological stream must pin them, never wrap (and never panic
|
||||||
|
// on an overflow in a debug build).
|
||||||
|
self.displays = self.displays.saturating_add(1);
|
||||||
|
if forced {
|
||||||
|
self.forced_displays = self.forced_displays.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The counts behind the verdict: how many display sets were seen and how
|
||||||
|
/// many carried `forced_on_flag`. Feeds [`demotable`].
|
||||||
|
pub fn facts(&self) -> ForcedFacts {
|
||||||
|
ForcedFacts {
|
||||||
|
displays: self.displays,
|
||||||
|
forced_displays: self.forced_displays,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,4 +811,80 @@ mod tests {
|
|||||||
let f = parser.parse(&make_pes(pcs_bytes(0), Some(180000)));
|
let f = parser.parse(&make_pes(pcs_bytes(0), Some(180000)));
|
||||||
assert_eq!(f[0].data, display, "display PCS data emitted verbatim");
|
assert_eq!(f[0].data, display, "display PCS data emitted verbatim");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── the demotion guard ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn facts(displays: u32, forced: u32) -> ForcedFacts {
|
||||||
|
ForcedFacts {
|
||||||
|
displays,
|
||||||
|
forced_displays: forced,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The case the guard exists for: a disc whose authoring never sets
|
||||||
|
/// `forced_on_flag`. Nothing about the absence of a flag nobody uses can
|
||||||
|
/// contradict a vendor label, however many display sets confirm the absence.
|
||||||
|
#[test]
|
||||||
|
fn nothing_is_demotable_on_a_disc_that_never_sets_the_flag() {
|
||||||
|
for displays in [1u32, DEMOTE_MIN_DISPLAY_SETS, 2_000, u32::MAX] {
|
||||||
|
assert!(
|
||||||
|
!demotable(facts(displays, 0), false, displays),
|
||||||
|
"{displays} unflagged display sets on a flagless disc prove nothing"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A track that itself mixes forced and non-forced display sets needs no
|
||||||
|
/// corroboration: the flag is demonstrably in use ON THIS TRACK, so it is a
|
||||||
|
/// full track carrying occasional forced signs — not a forced-only track.
|
||||||
|
#[test]
|
||||||
|
fn a_mixed_track_is_demotable_on_its_own_evidence() {
|
||||||
|
assert!(demotable(facts(4, 1), false, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the flag in use elsewhere on the disc, the shape decides. Measured:
|
||||||
|
/// a forced-narrative track carries tens of display sets, a full dialogue
|
||||||
|
/// track one to two thousand.
|
||||||
|
#[test]
|
||||||
|
fn shape_decides_once_the_disc_is_known_to_use_the_flag() {
|
||||||
|
assert!(
|
||||||
|
demotable(facts(2_000, 0), true, 2_000),
|
||||||
|
"the busiest track on the disc, with no forced set on it, is a full track"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!demotable(facts(20, 0), true, 2_000),
|
||||||
|
"a track at one percent of the busiest is the forced track its label claims"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!demotable(facts(DEMOTE_MIN_DISPLAY_SETS - 1, 0), true, 8),
|
||||||
|
"too few display sets for their absence of flags to mean anything"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
demotable(facts(DEMOTE_MIN_DISPLAY_SETS, 0), true, 8),
|
||||||
|
"at the threshold, with the shape of the busiest track, it is demotable"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Never on no evidence at all: a track nobody observed cannot contradict
|
||||||
|
/// anything.
|
||||||
|
#[test]
|
||||||
|
fn an_unobserved_track_is_never_demotable() {
|
||||||
|
assert!(!demotable(facts(0, 0), true, 2_000));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saturating counters: a pathological stream must pin the counts, never wrap
|
||||||
|
/// them (and never panic on overflow in a debug build).
|
||||||
|
#[test]
|
||||||
|
fn display_counts_saturate_instead_of_wrapping() {
|
||||||
|
let mut t = ForcedTracker::new();
|
||||||
|
t.displays = u32::MAX;
|
||||||
|
t.forced_displays = u32::MAX;
|
||||||
|
let mut pcs = vec![0u8; 18];
|
||||||
|
pcs[0] = SEGMENT_PCS;
|
||||||
|
pcs[PCS_NUM_OBJECTS_OFFSET] = 1;
|
||||||
|
pcs[PCS_FIRST_OBJECT_FLAGS_OFFSET] = PCS_FORCED_ON_FLAG;
|
||||||
|
t.observe(&pcs);
|
||||||
|
assert_eq!(t.facts().displays, u32::MAX);
|
||||||
|
assert_eq!(t.facts().forced_displays, u32::MAX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+186
-12
@@ -741,7 +741,8 @@ pub struct MkvMuxer<W: Write + Seek> {
|
|||||||
/// display set carried the HDMV `forced_on_flag` (a dedicated forced/narrative
|
/// display set carried the HDMV `forced_on_flag` (a dedicated forced/narrative
|
||||||
/// track). At `finish()` the reserved byte is promoted to 1 for such tracks —
|
/// track). At `finish()` the reserved byte is promoted to 1 for such tracks —
|
||||||
/// so forced subs are flagged even on discs without vendor label metadata.
|
/// so forced subs are flagged even on discs without vendor label metadata.
|
||||||
/// Only ever promotes (0→1); a scan/vendor forced flag is never demoted.
|
/// A vendor forced flag is cleared only under the cross-track guard in
|
||||||
|
/// `finish()` (see `super::codec::pgs::demotable`).
|
||||||
pgs_forced_fixups: std::collections::HashMap<usize, PgsForcedFixup>,
|
pgs_forced_fixups: std::collections::HashMap<usize, PgsForcedFixup>,
|
||||||
/// `--log-level 3` opening-frame capture: the first ~100 coded frames per
|
/// `--log-level 3` opening-frame capture: the first ~100 coded frames per
|
||||||
/// track are written (raw) to a `<output>.opening.bin` side file with a
|
/// track are written (raw) to a `<output>.opening.bin` side file with a
|
||||||
@@ -767,6 +768,10 @@ struct Ac3ChannelFixup {
|
|||||||
struct PgsForcedFixup {
|
struct PgsForcedFixup {
|
||||||
/// Absolute file offset of the 1-byte `FlagForced` value in the Tracks element.
|
/// Absolute file offset of the 1-byte `FlagForced` value in the Tracks element.
|
||||||
value_offset: u64,
|
value_offset: u64,
|
||||||
|
/// The value written up-front — the scan/vendor-label flag. Kept so
|
||||||
|
/// `finish()` can tell a promotion from a demotion and rewrite only the byte
|
||||||
|
/// that actually changes.
|
||||||
|
initial_forced: bool,
|
||||||
/// Shared forced-narrative classifier fed the track's display sets. The same
|
/// Shared forced-narrative classifier fed the track's display sets. The same
|
||||||
/// type drives the `info`-time forced probe, so both classify identically.
|
/// type drives the `info`-time forced probe, so both classify identically.
|
||||||
tracker: super::codec::pgs::ForcedTracker,
|
tracker: super::codec::pgs::ForcedTracker,
|
||||||
@@ -1045,6 +1050,7 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
|||||||
i,
|
i,
|
||||||
PgsForcedFixup {
|
PgsForcedFixup {
|
||||||
value_offset,
|
value_offset,
|
||||||
|
initial_forced: track.is_forced,
|
||||||
tracker: super::codec::pgs::ForcedTracker::new(),
|
tracker: super::codec::pgs::ForcedTracker::new(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1686,22 +1692,64 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
|||||||
// Close final cluster
|
// Close final cluster
|
||||||
self.end_cluster()?;
|
self.end_cluster()?;
|
||||||
|
|
||||||
// Promote FlagForced for PGS subtitle tracks that proved to be forced
|
// Correct FlagForced for PGS subtitle tracks from what the mux actually
|
||||||
// narrative (displayed subtitles, every one forced). In-place single-byte
|
// saw. In-place single-byte rewrite of the reserved value, then restore
|
||||||
// rewrite of the reserved value, then restore the append position for the
|
// the append position for the Cues that follow.
|
||||||
// Cues that follow. Only promotes (0→1); a track already forced from the
|
//
|
||||||
// scan/vendor flag stays forced.
|
// PROMOTE (0→1) a track that proved to be forced narrative: it displayed
|
||||||
let forced_offsets: Vec<u64> = self
|
// subtitles and every one carried `forced_on_flag`. Positive evidence,
|
||||||
|
// no further justification needed.
|
||||||
|
//
|
||||||
|
// DEMOTE (1→0) a track whose vendor label claims forced but whose content
|
||||||
|
// contradicts it — the case a whole-file mux is uniquely entitled to
|
||||||
|
// judge, because unlike the scan-time probe it has seen EVERY display set
|
||||||
|
// on the track. Gated by
|
||||||
|
// [`super::codec::pgs::demotable`]: an absence of `forced_on_flag` proves
|
||||||
|
// nothing on a disc whose authoring never sets it, so the gate demands
|
||||||
|
// that some track here demonstrably does, and that this track have the
|
||||||
|
// shape of a full dialogue track rather than of a forced-narrative one.
|
||||||
|
let disc_uses_forced_flag = self
|
||||||
.pgs_forced_fixups
|
.pgs_forced_fixups
|
||||||
.values()
|
.values()
|
||||||
.filter(|f| f.tracker.is_forced())
|
.any(|f| f.tracker.facts().forced_displays > 0);
|
||||||
.map(|f| f.value_offset)
|
let busiest = self
|
||||||
|
.pgs_forced_fixups
|
||||||
|
.values()
|
||||||
|
.map(|f| f.tracker.facts().displays)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let rewrites: Vec<(u64, u8)> = self
|
||||||
|
.pgs_forced_fixups
|
||||||
|
.values()
|
||||||
|
.filter_map(|f| {
|
||||||
|
if f.tracker.is_forced() && !f.initial_forced {
|
||||||
|
return Some((f.value_offset, 1u8));
|
||||||
|
}
|
||||||
|
if f.initial_forced
|
||||||
|
&& !f.tracker.is_forced()
|
||||||
|
&& super::codec::pgs::demotable(
|
||||||
|
f.tracker.facts(),
|
||||||
|
disc_uses_forced_flag,
|
||||||
|
busiest,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
tracing::info!(
|
||||||
|
target: "mux",
|
||||||
|
displays = f.tracker.facts().displays,
|
||||||
|
forced_displays = f.tracker.facts().forced_displays,
|
||||||
|
busiest,
|
||||||
|
"PGS track labelled forced showed no forced display sets on a disc that uses the flag; clearing FlagForced"
|
||||||
|
);
|
||||||
|
return Some((f.value_offset, 0u8));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
if !forced_offsets.is_empty() {
|
if !rewrites.is_empty() {
|
||||||
let here = self.writer.stream_position()?;
|
let here = self.writer.stream_position()?;
|
||||||
for off in forced_offsets {
|
for (off, value) in rewrites {
|
||||||
self.writer.seek(std::io::SeekFrom::Start(off))?;
|
self.writer.seek(std::io::SeekFrom::Start(off))?;
|
||||||
self.writer.write_all(&[1u8])?;
|
self.writer.write_all(&[value])?;
|
||||||
}
|
}
|
||||||
self.writer.seek(std::io::SeekFrom::Start(here))?;
|
self.writer.seek(std::io::SeekFrom::Start(here))?;
|
||||||
}
|
}
|
||||||
@@ -3210,6 +3258,132 @@ mod tests {
|
|||||||
pcs
|
pcs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every `FlagForced` value byte in the file, in track order — the two-PGS-track
|
||||||
|
/// tests need per-track values, not just the first.
|
||||||
|
fn all_flag_forced_values(data: &[u8]) -> Vec<u8> {
|
||||||
|
let needle = ebml::FLAG_FORCED.to_be_bytes();
|
||||||
|
let needle = &needle[2..]; // FlagForced is a 2-byte EBML ID
|
||||||
|
data.windows(needle.len())
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, w)| *w == needle)
|
||||||
|
.filter_map(|(i, _)| data.get(i + 3).copied())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A PGS subtitle track carrying the vendor/scan forced flag `forced`.
|
||||||
|
fn pgs_subtitle_track(pid: u16, forced: bool) -> MkvTrack {
|
||||||
|
MkvTrack::subtitle(&crate::disc::SubtitleStream {
|
||||||
|
pid,
|
||||||
|
codec: Codec::Pgs,
|
||||||
|
language: "eng".into(),
|
||||||
|
forced,
|
||||||
|
qualifier: crate::disc::LabelQualifier::None,
|
||||||
|
codec_data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrong vendor forced label is CLEARED by the content — but only where the
|
||||||
|
/// content can carry that argument. During a full mux every display set on the
|
||||||
|
/// track is seen, so "this track has hundreds of display sets and not one of
|
||||||
|
/// them is forced" is as complete as evidence gets; and a sibling track that
|
||||||
|
/// does carry `forced_on_flag` proves the authoring house sets it, so the
|
||||||
|
/// absence on this track means something.
|
||||||
|
///
|
||||||
|
/// Before this, `finish()` only ever promoted 0→1, so a track wrongly labelled
|
||||||
|
/// forced stayed forced in the output no matter what the disc contained.
|
||||||
|
#[test]
|
||||||
|
fn mkv_pgs_wrong_forced_label_is_cleared_when_a_sibling_uses_the_flag() {
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
let shared = Arc::new(Mutex::new(Cursor::new(Vec::new())));
|
||||||
|
let tracks = [
|
||||||
|
make_video_track(),
|
||||||
|
pgs_subtitle_track(0x1200, true), // mislabelled full track
|
||||||
|
pgs_subtitle_track(0x1201, false), // genuine forced track
|
||||||
|
];
|
||||||
|
let mut muxer =
|
||||||
|
MkvMuxer::new(SharedWriter(shared.clone()), &tracks, None, 60.0, &[]).unwrap();
|
||||||
|
muxer
|
||||||
|
.write_frame(0, 0, true, &[0u8; 16], Some(40_000_000), None)
|
||||||
|
.unwrap();
|
||||||
|
for i in 0..12 {
|
||||||
|
muxer
|
||||||
|
.write_frame(
|
||||||
|
1,
|
||||||
|
1_000_000 * (i + 1),
|
||||||
|
true,
|
||||||
|
&pgs_display_set(false),
|
||||||
|
Some(2_000_000),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
for i in 0..3 {
|
||||||
|
muxer
|
||||||
|
.write_frame(
|
||||||
|
2,
|
||||||
|
1_000_000 * (i + 1),
|
||||||
|
true,
|
||||||
|
&pgs_display_set(true),
|
||||||
|
Some(2_000_000),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
muxer.finish().unwrap();
|
||||||
|
|
||||||
|
let data = shared.lock().unwrap().clone().into_inner();
|
||||||
|
assert_eq!(
|
||||||
|
all_flag_forced_values(&data),
|
||||||
|
vec![0, 1],
|
||||||
|
"the mislabelled track loses FlagForced; the genuinely forced one keeps it"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ...and the guard that stops that from being reckless. On a disc whose
|
||||||
|
/// authoring never sets `forced_on_flag` — they exist — no track has any
|
||||||
|
/// forced display set, so "no forced display set here" is a fact about the
|
||||||
|
/// authoring, not about the track. The vendor label is then the only
|
||||||
|
/// information there is and must survive.
|
||||||
|
#[test]
|
||||||
|
fn mkv_pgs_forced_label_survives_a_disc_that_never_sets_the_flag() {
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
let shared = Arc::new(Mutex::new(Cursor::new(Vec::new())));
|
||||||
|
let tracks = [
|
||||||
|
make_video_track(),
|
||||||
|
pgs_subtitle_track(0x1200, true),
|
||||||
|
pgs_subtitle_track(0x1201, false),
|
||||||
|
];
|
||||||
|
let mut muxer =
|
||||||
|
MkvMuxer::new(SharedWriter(shared.clone()), &tracks, None, 60.0, &[]).unwrap();
|
||||||
|
muxer
|
||||||
|
.write_frame(0, 0, true, &[0u8; 16], Some(40_000_000), None)
|
||||||
|
.unwrap();
|
||||||
|
for track in 1..=2 {
|
||||||
|
for i in 0..12 {
|
||||||
|
muxer
|
||||||
|
.write_frame(
|
||||||
|
track,
|
||||||
|
1_000_000 * (i + 1),
|
||||||
|
true,
|
||||||
|
&pgs_display_set(false),
|
||||||
|
Some(2_000_000),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
muxer.finish().unwrap();
|
||||||
|
|
||||||
|
let data = shared.lock().unwrap().clone().into_inner();
|
||||||
|
assert_eq!(
|
||||||
|
all_flag_forced_values(&data),
|
||||||
|
vec![1, 0],
|
||||||
|
"with the flag unused disc-wide, both labels stand as authored"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mkv_pgs_forced_promoted_when_all_display_sets_forced() {
|
fn mkv_pgs_forced_promoted_when_all_display_sets_forced() {
|
||||||
// End-to-end: a PGS subtitle track whose every display set is forced is
|
// End-to-end: a PGS subtitle track whose every display set is forced is
|
||||||
|
|||||||
Reference in New Issue
Block a user