DVD: correct PAL/NTSC, anamorphic aspect, and SD colour
Fix three DVD video-attribute bugs surfaced by a PAL disc detected as NTSC: - PAL/NTSC: parse video_format from VTS_V_ATR bits 5-4, not bits 1-0 (the old mask read permitted_df, so PAL 576i/25fps was mis-detected as NTSC 480i/29.97). Named consts replace the magic bit positions. - Anamorphic aspect: write MKV DisplayWidth/Height from the disc's display_aspect (16:9 720x576 -> 1024x576) instead of square pixels, so 16:9 DVDs no longer render as 4:3. - Colour: stamp SD colorimetry (PAL=BT.470BG, NTSC=SMPTE-170M) instead of BT.709 (HD). Adds VideoStream.display_aspect (threaded through every muxer) plus TvSystem/DvdAspect/ColorSpace plumbing, with regression tests. Removes the deprecated Disc mux set_halt bridge (use with_halt).
This commit is contained in:
@@ -137,6 +137,10 @@ impl Disc {
|
||||
2 => ColorSpace::Bt2020,
|
||||
_ => ColorSpace::Unknown,
|
||||
},
|
||||
// Blu-ray HD/UHD video is square-pixel; display aspect
|
||||
// equals the pixel grid (16:9). Anamorphic SD-on-BD is
|
||||
// not special-cased here.
|
||||
display_aspect: None,
|
||||
secondary: s.secondary,
|
||||
// No user-facing English in the library (numeric-code
|
||||
// rule): the Dolby Vision enhancement layer is signalled
|
||||
|
||||
+79
-6
@@ -24,12 +24,25 @@ impl Disc {
|
||||
pid: 0xE0, // DVD video PID (standard MPEG PS video stream)
|
||||
codec: ts.video.codec,
|
||||
resolution: ts.video.resolution,
|
||||
frame_rate: match ts.video.standard.as_str() {
|
||||
"PAL" => FrameRate::F25,
|
||||
_ => FrameRate::F29_97,
|
||||
frame_rate: match ts.video.standard {
|
||||
crate::ifo::TvSystem::Pal => FrameRate::F25,
|
||||
crate::ifo::TvSystem::Ntsc => FrameRate::F29_97,
|
||||
},
|
||||
hdr: HdrFormat::Sdr,
|
||||
color_space: ColorSpace::Bt709,
|
||||
// DVD is SD, not HD: PAL is BT.470BG, NTSC is SMPTE-170M.
|
||||
// Stamping BT.709 (HD) mis-tags the colour primaries/transfer.
|
||||
color_space: match ts.video.standard {
|
||||
crate::ifo::TvSystem::Pal => ColorSpace::Bt470bg,
|
||||
crate::ifo::TvSystem::Ntsc => ColorSpace::Smpte170m,
|
||||
},
|
||||
// DVD pixels are anamorphic 720x480/576; the real display shape
|
||||
// is the IFO aspect flag, not the pixel grid. Carry it so the
|
||||
// MKV muxer writes a correct 16:9 / 4:3 DisplayWidth/Height
|
||||
// instead of the square-pixel 3:2 / 5:4 it would otherwise emit.
|
||||
display_aspect: Some(match ts.video.aspect {
|
||||
crate::ifo::DvdAspect::R16x9 => (16, 9),
|
||||
crate::ifo::DvdAspect::R4x3 => (4, 3),
|
||||
}),
|
||||
secondary: false,
|
||||
label: String::new(),
|
||||
});
|
||||
@@ -527,8 +540,14 @@ mod tests {
|
||||
fn scan_dvd_titles_pal_frame_rate_and_video_pid() {
|
||||
let mut disc = MemDisc::new();
|
||||
let vmg = build_vmg(&[(1, 1, 1)]);
|
||||
// video b0 low 2 bits = 1 → PAL.
|
||||
let vts = build_vts(0, 0x01, &[], &[], &[(0, 9)], false);
|
||||
let vts = build_vts(
|
||||
0,
|
||||
crate::ifo::v_atr_byte(crate::ifo::VIDEO_FORMAT_PAL, crate::ifo::ASPECT_4X3),
|
||||
&[],
|
||||
&[],
|
||||
&[(0, 9)],
|
||||
false,
|
||||
);
|
||||
let udf = build_video_ts_fs(
|
||||
&mut disc,
|
||||
&[
|
||||
@@ -558,6 +577,60 @@ mod tests {
|
||||
assert_eq!(v.pid, 0xE0, "DVD video PID is fixed 0xE0");
|
||||
assert_eq!(v.frame_rate, FrameRate::F25, "PAL → 25 fps");
|
||||
assert_eq!(v.resolution, Resolution::R576i, "PAL → 576i");
|
||||
assert_eq!(
|
||||
v.color_space,
|
||||
ColorSpace::Bt470bg,
|
||||
"PAL DVD is SD BT.470BG, not BT.709"
|
||||
);
|
||||
}
|
||||
|
||||
/// NTSC DVD video is SD SMPTE-170M colorimetry (not BT.709). Mirror of the
|
||||
/// PAL test with `VIDEO_FORMAT_NTSC` → 480i / 29.97 / SMPTE-170M.
|
||||
#[test]
|
||||
fn scan_dvd_titles_ntsc_color_is_smpte170m() {
|
||||
let mut disc = MemDisc::new();
|
||||
let vmg = build_vmg(&[(1, 1, 1)]);
|
||||
let vts = build_vts(
|
||||
0,
|
||||
crate::ifo::v_atr_byte(crate::ifo::VIDEO_FORMAT_NTSC, crate::ifo::ASPECT_4X3),
|
||||
&[],
|
||||
&[],
|
||||
&[(0, 9)],
|
||||
false,
|
||||
);
|
||||
let udf = build_video_ts_fs(
|
||||
&mut disc,
|
||||
&[
|
||||
FileSpec {
|
||||
name: "VIDEO_TS.IFO".into(),
|
||||
icb_lba: 60,
|
||||
data_lba: 5000,
|
||||
contents: vmg,
|
||||
},
|
||||
FileSpec {
|
||||
name: "VTS_01_0.IFO".into(),
|
||||
icb_lba: 62,
|
||||
data_lba: 6000,
|
||||
contents: vts,
|
||||
},
|
||||
],
|
||||
);
|
||||
let t = &Disc::scan_dvd_titles(&mut disc, &udf)[0];
|
||||
let v = t
|
||||
.streams
|
||||
.iter()
|
||||
.find_map(|s| match s {
|
||||
Stream::Video(v) => Some(v),
|
||||
_ => None,
|
||||
})
|
||||
.expect("video stream");
|
||||
assert_eq!(v.frame_rate, FrameRate::F29_97, "NTSC → 29.97 fps");
|
||||
assert_eq!(v.resolution, Resolution::R480i, "NTSC → 480i");
|
||||
assert_eq!(
|
||||
v.color_space,
|
||||
ColorSpace::Smpte170m,
|
||||
"NTSC DVD is SD SMPTE-170M, not BT.709"
|
||||
);
|
||||
}
|
||||
|
||||
/// AC-3 audio gets sub_stream_id 0x80 → PID routed via dvd_audio_pid
|
||||
|
||||
@@ -182,6 +182,13 @@ pub struct VideoStream {
|
||||
pub hdr: HdrFormat,
|
||||
/// Color space
|
||||
pub color_space: ColorSpace,
|
||||
/// Intended display aspect ratio as `(num, den)` when the coded pixels are
|
||||
/// **anamorphic** (display shape ≠ pixel grid) — e.g. DVD 720x576 shown as
|
||||
/// 16:9 → `Some((16, 9))`. `None` means square pixels: the display aspect
|
||||
/// equals the pixel dimensions (HD/UHD, BD). Consumed by the MKV muxer to
|
||||
/// write DisplayWidth/DisplayHeight; passthrough muxers (TS/M2TS) ignore it
|
||||
/// because the aspect already lives in the elementary stream.
|
||||
pub display_aspect: Option<(u32, u32)>,
|
||||
/// Whether this is a secondary stream (PiP, Dolby Vision EL)
|
||||
pub secondary: bool,
|
||||
/// Extra label (e.g. "Dolby Vision EL")
|
||||
@@ -366,6 +373,13 @@ pub enum HdrFormat {
|
||||
pub enum ColorSpace {
|
||||
Bt709,
|
||||
Bt2020,
|
||||
/// SD PAL/576-line colorimetry (ITU-R BT.470 System B/G — primaries 5,
|
||||
/// transfer 5, matrix 5). DVDs are SD, not HD: stamping BT.709 mis-tags
|
||||
/// their colour.
|
||||
Bt470bg,
|
||||
/// SD NTSC/480-line colorimetry (SMPTE 170M / BT.601-525 — primaries 6,
|
||||
/// transfer 6, matrix 6).
|
||||
Smpte170m,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@@ -843,6 +857,8 @@ impl ColorSpace {
|
||||
match self {
|
||||
ColorSpace::Bt709 => "BT.709",
|
||||
ColorSpace::Bt2020 => "BT.2020",
|
||||
ColorSpace::Bt470bg => "BT.470BG",
|
||||
ColorSpace::Smpte170m => "SMPTE 170M",
|
||||
ColorSpace::Unknown => "",
|
||||
}
|
||||
}
|
||||
@@ -850,6 +866,8 @@ impl ColorSpace {
|
||||
const ALL_CS: &[(&'static str, ColorSpace)] = &[
|
||||
("bt709", ColorSpace::Bt709),
|
||||
("bt2020", ColorSpace::Bt2020),
|
||||
("bt470bg", ColorSpace::Bt470bg),
|
||||
("smpte170m", ColorSpace::Smpte170m),
|
||||
("unknown", ColorSpace::Unknown),
|
||||
];
|
||||
|
||||
@@ -3500,6 +3518,7 @@ mod tests {
|
||||
frame_rate: FrameRate::F23_976,
|
||||
hdr: HdrFormat::Sdr,
|
||||
color_space: ColorSpace::Bt709,
|
||||
display_aspect: None,
|
||||
secondary: false,
|
||||
label: String::new(),
|
||||
})],
|
||||
|
||||
Reference in New Issue
Block a user