Read the vendor forced-subtitle field as the enumeration it is
One vendor's playlists.xml carries a per-subtitle-slot cell that looks
like a boolean, and it was parsed as one: value 1 meant forced, anything
else meant not forced. Across every image in the corpus that uses this
format the cell takes four values, and 1 is not the forced one.
Decoding three of those discs and counting every PGS display set:
* 1 marks a FULL dialogue track that additionally contains some
forced-narrative signs. All nine cells bearing it on one disc are
full tracks of 949-1411 display sets; all seven on another are full
tracks of 1602-1651. Neither disc has a small track among them.
* 2 and 3 mark a DEDICATED forced-narrative track, in its own trailing
stream slot, duplicating a language that already holds a full track.
The two 2 slots measured are 15 and 10 display sets with every one
flagged forced; the four 3 slots are 7, 14, 23 and 59 against
1216-2655 on the tracks they duplicate.
So the old reading was wrong in both directions — it flagged full
dialogue tracks forced, which is how one language came to present as two
identical full subtitle tracks with one of them marked forced, and it
threw away the cells naming the real forced tracks.
Content could not have corrected this afterwards. Clearing a wrong
forced label needs a disc whose authoring sets forced_on_flag, and on
the measured disc carrying four genuine forced tracks not one display
set anywhere sets it — there, the vendor cell is the only evidence there
is. The classification is now explicit: only a dedicated forced slot
earns the flag, an unrecognised value never does, and the
contains-forced-signs value is dropped rather than weakened into a
forced label, since a wrong forced flag on a full dialogue track is the
user-visible defect while a missing hint costs nothing.
Four of the crate's own tests had been asserting the boolean reading;
their subject was positional alignment, so they keep it and now use a
real forced value. The other two parsers that emit a forced qualifier
from vendor metadata were audited and are structurally immune — in both,
the forced marker names a slot of its own rather than hanging off a full
track's entry, so the failure has no encoding there — and each is now
pinned by a test saying so.
This commit is contained in:
@@ -565,6 +565,60 @@ mod tests {
|
||||
assert_eq!(l.qualifier, LabelQualifier::Forced);
|
||||
}
|
||||
|
||||
/// Immunity pin against the defect measured in the `paramount` parser: a
|
||||
/// vendor "forced" marker that sits on a FULL dialogue track's own slot to
|
||||
/// mean "this track also contains forced signs", read as "this track is
|
||||
/// forced" and so flagging full dialogue tracks forced.
|
||||
///
|
||||
/// This grammar cannot express that. The forced marker is a component of a
|
||||
/// slot's OWN token, so a forced-narrative pass occupies a slot of its own
|
||||
/// (`{lang}_TXT_FOR_`, `{lang}_DUB_`) alongside the language's separate
|
||||
/// full-dialogue slot — it is never a parallel array indexed against the
|
||||
/// full tracks' slots, which is the shape that let one vendor's marker land
|
||||
/// on a dialogue track.
|
||||
///
|
||||
/// Mutation: give any full-dialogue component (`SDLG`, `TXT`, `SDH`,
|
||||
/// `STRI`, `SCOM`) a forced qualifier of its own.
|
||||
#[test]
|
||||
fn a_full_subtitle_token_is_never_forced_without_its_own_forced_component() {
|
||||
for token in [
|
||||
"eng_SDLG_",
|
||||
"eng_TXT_",
|
||||
"eng_SDH_",
|
||||
"eng_STRI_",
|
||||
"eng_SCOM_",
|
||||
] {
|
||||
let l = parse_token_inner(token, None)
|
||||
.unwrap_or_else(|| panic!("{token} must classify as a subtitle"));
|
||||
assert_eq!(l.stream_type, StreamLabelType::Subtitle);
|
||||
assert_ne!(
|
||||
l.qualifier,
|
||||
LabelQualifier::Forced,
|
||||
"{token} carries no forced component and must not be forced"
|
||||
);
|
||||
}
|
||||
// And a language's forced pass is a SEPARATE slot from its full track,
|
||||
// never a marker applied to the full track's slot.
|
||||
let mut flag = UnknownParts::default();
|
||||
let tokens = strs(&[
|
||||
"FPL_MainFeature",
|
||||
"PG Stream 1",
|
||||
"eng_SDLG_", // PG slot 2 — the full dialogue track
|
||||
"eng_TXT_FOR_", // PG slot 3 — its forced-narrative companion
|
||||
]);
|
||||
let labels = assign_labels(&tokens, &mut flag);
|
||||
let subs: Vec<_> = labels
|
||||
.iter()
|
||||
.filter(|l| l.stream_type == StreamLabelType::Subtitle)
|
||||
.map(|l| (l.stream_number, l.qualifier))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
subs,
|
||||
vec![(2, LabelQualifier::None), (3, LabelQualifier::Forced),],
|
||||
"the forced marker belongs to its own slot, not to the full track's"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_token_components_are_case_insensitive() {
|
||||
// Regression for the case-sensitive gate: a lowercase codec/
|
||||
|
||||
Reference in New Issue
Block a user