disc: detect forced PGS subtitles from stream content for info

Give `info` the same forced-subtitle verdict the muxer derives during a
rip, so the two agree. A shared classifier (mux::codec::pgs::ForcedTracker)
folds a PGS track's display sets — forced iff every one carries the
forced_on_flag — and is used by BOTH the MKV writer and a new scan-time
probe that reads the title's PGS streams (reusing the TS demuxer and PGS
parser). The probe only overrides a track it actually observed content
for, so an undecrypted/unread stream keeps its vendor-derived flag. Gated
behind ScanOptions::probe_forced_subtitles (off for the rip path, which
detects forced while muxing without a second read).
This commit is contained in:
Matthew Jackson
2026-07-19 14:23:04 -07:00
parent 2ccb5c9d01
commit 3841ae2250
4 changed files with 280 additions and 13 deletions
+6 -13
View File
@@ -704,12 +704,9 @@ struct Ac3ChannelFixup {
struct PgsForcedFixup {
/// Absolute file offset of the 1-byte `FlagForced` value in the Tracks element.
value_offset: u64,
/// Whether the track has displayed at least one subtitle (a display PCS).
has_display: bool,
/// Whether EVERY display set so far carried the forced_on_flag. Starts true;
/// cleared by the first non-forced display set. With `has_display`, a value of
/// true at `finish()` means the whole track is forced narrative.
all_forced: bool,
/// Shared forced-narrative classifier fed the track's display sets. The same
/// type drives the `info`-time forced probe, so both classify identically.
tracker: super::codec::pgs::ForcedTracker,
}
/// TimestampScale: nanoseconds per Matroska timestamp tick. 0.1 ms (100_000 ns).
@@ -984,8 +981,7 @@ impl<W: Write + Seek> MkvMuxer<W> {
i,
PgsForcedFixup {
value_offset,
has_display: false,
all_forced: true,
tracker: super::codec::pgs::ForcedTracker::new(),
},
);
} else if track.is_forced {
@@ -1560,10 +1556,7 @@ impl<W: Write + Seek> MkvMuxer<W> {
// non-forced set appears. `finish()` promotes FlagForced only for a track
// that displayed subtitles and had every one forced.
if let Some(fixup) = self.pgs_forced_fixups.get_mut(&track_idx) {
if let Some(forced) = super::codec::pgs::display_set_is_forced(data) {
fixup.has_display = true;
fixup.all_forced &= forced;
}
fixup.tracker.observe(data);
}
Ok(())
@@ -1606,7 +1599,7 @@ impl<W: Write + Seek> MkvMuxer<W> {
let forced_offsets: Vec<u64> = self
.pgs_forced_fixups
.values()
.filter(|f| f.has_display && f.all_forced)
.filter(|f| f.tracker.is_forced())
.map(|f| f.value_offset)
.collect();
if !forced_offsets.is_empty() {