use crate::bitstream::TsStreamBuffer;
use crate::stream::TsGraphicsStream;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Frame {
pub started: bool,
pub forced: bool,
pub finished: bool,
}
pub fn scan(stream: &mut TsGraphicsStream, buffer: &mut TsStreamBuffer, tag: &mut Option<String>) {
let segment_type = buffer.read_byte(false);
match segment_type {
0x15 => *tag = read_ods(stream, buffer), 0x16 => read_pcs(stream, buffer), 0x80 => {
stream.last_frame.finished = true;
}
_ => {}
}
stream.base.is_vbr = true;
}
fn read_ods(stream: &mut TsGraphicsStream, buffer: &mut TsStreamBuffer) -> Option<String> {
let _ = buffer.read_bits2(16, false); let _ = buffer.read_bits2(16, false);
if stream.last_frame.finished {
return None;
}
if stream.last_frame.forced {
stream.forced_captions = stream.forced_captions.wrapping_add(1);
Some("F".to_owned())
} else {
stream.captions = stream.captions.wrapping_add(1);
Some("N".to_owned())
}
}
fn read_pcs(stream: &mut TsGraphicsStream, buffer: &mut TsStreamBuffer) {
let _ = buffer.read_bits2(16, false); if stream.base.is_initialized {
let _ = buffer.read_bits2(16, false);
let _ = buffer.read_bits2(16, false);
} else {
stream.width = i32::from(buffer.read_bits2(16, false));
stream.height = i32::from(buffer.read_bits2(16, false));
stream.base.is_initialized = true;
}
let _ = buffer.read_byte(false);
let composition_number = i32::from(buffer.read_bits2(16, false));
let _ = buffer.read_byte(false); let _ = buffer.read_bits2(16, false);
let num_composition_objects = buffer.read_byte(false);
for _ in 0..num_composition_objects {
let _ = buffer.read_bits2(16, false); let _ = buffer.read_byte(false); let flags = buffer.read_byte(false); let _ = buffer.read_bits2(16, false); let _ = buffer.read_bits2(16, false); if (flags & 0x80) == 0x80 {
let _ = buffer.read_bits2(16, false); let _ = buffer.read_bits2(16, false); let _ = buffer.read_bits2(16, false); let _ = buffer.read_bits2(16, false); }
let frame = Frame { started: true, forced: (flags & 0x40) == 0x40, finished: false };
stream.last_frame = frame;
stream.caption_ids.entry(composition_number).or_insert(frame);
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::{any, prop_assert, proptest};
use super::{Frame, read_ods, read_pcs, scan};
use crate::bitstream::TsStreamBuffer;
use crate::stream::{TsGraphicsStream, TsStreamType};
fn buf(data: &[u8]) -> TsStreamBuffer {
let mut b = TsStreamBuffer::new();
b.add(data, 0, data.len());
b.begin_read();
b
}
fn run(stream: &mut TsGraphicsStream, data: &[u8]) -> Option<String> {
let mut b = buf(data);
let mut tag = None;
scan(stream, &mut b, &mut tag);
tag
}
fn pcs(width: u16, height: u16, comp_number: u16, objects: &[u8]) -> Vec<u8> {
let mut v = vec![0x16]; v.extend_from_slice(&[0, 0]); v.extend_from_slice(&width.to_be_bytes());
v.extend_from_slice(&height.to_be_bytes());
v.push(0); v.extend_from_slice(&comp_number.to_be_bytes());
v.push(0); v.extend_from_slice(&[0, 0]); v.push(u8::try_from(objects.len()).unwrap()); for &flags in objects {
v.extend_from_slice(&[0, 0]); v.push(0); v.push(flags); v.extend_from_slice(&[0, 0]); v.extend_from_slice(&[0, 0]); if (flags & 0x80) == 0x80 {
v.extend_from_slice(&[0_u8; 8]); }
}
v
}
fn ods() -> Vec<u8> {
vec![0x15, 0, 0, 0, 0]
}
#[test]
fn pcs_records_resolution_once_and_seeds_the_frame() {
let mut s = TsGraphicsStream::default();
run(&mut s, &pcs(1920, 1080, 7, &[0x00]));
assert_eq!((s.width, s.height), (1920, 1080));
assert!(s.base.is_initialized);
assert!(s.base.is_vbr);
assert_eq!(s.last_frame, Frame { started: true, forced: false, finished: false });
assert_eq!(s.caption_ids.len(), 1);
assert_eq!(s.caption_ids.get(&7).copied(), Some(s.last_frame));
run(&mut s, &pcs(1280, 720, 9, &[0x40]));
assert_eq!((s.width, s.height), (1920, 1080)); assert_eq!(s.last_frame, Frame { started: true, forced: true, finished: false });
assert_eq!(s.caption_ids.len(), 2);
assert!(s.caption_ids.contains_key(&9));
}
#[test]
fn pcs_with_no_objects_leaves_frame_and_captions_untouched() {
let mut s = TsGraphicsStream {
last_frame: Frame { started: true, forced: true, finished: true },
..TsGraphicsStream::default()
};
run(&mut s, &pcs(1920, 1080, 1, &[]));
assert_eq!((s.width, s.height), (1920, 1080));
assert_eq!(s.last_frame, Frame { started: true, forced: true, finished: true });
assert!(s.caption_ids.is_empty());
}
#[test]
fn pcs_dedups_same_composition_number_and_keeps_the_first_frame() {
let mut s = TsGraphicsStream::default();
run(&mut s, &pcs(1920, 1080, 5, &[0x40, 0x00]));
assert_eq!(s.caption_ids.len(), 1);
assert_eq!(s.caption_ids.get(&5).map(|f| f.forced), Some(true)); assert!(!s.last_frame.forced); assert!(s.last_frame.started);
}
#[test]
fn multi_object_uncropped_pcs_reads_each_forced_flag_at_the_right_offset() {
let mut s = TsGraphicsStream::default();
run(&mut s, &pcs(1920, 1080, 1, &[0x00, 0x40]));
assert!(s.last_frame.forced);
let tag = run(&mut s, &ods());
assert_eq!(tag.as_deref(), Some("F"));
assert_eq!((s.captions, s.forced_captions), (0, 1));
}
#[test]
fn cropped_object_consumes_its_crop_fields_before_the_next_object() {
let mut s = TsGraphicsStream::default();
run(&mut s, &pcs(1920, 1080, 1, &[0x80, 0x40]));
assert!(s.last_frame.forced); assert_eq!(s.caption_ids.get(&1).map(|f| f.forced), Some(false)); }
#[test]
fn ods_counts_a_normal_caption_and_tags_n() {
let mut s = TsGraphicsStream::default();
run(&mut s, &pcs(1920, 1080, 1, &[0x00])); let tag = run(&mut s, &ods());
assert_eq!(tag.as_deref(), Some("N"));
assert_eq!((s.captions, s.forced_captions), (1, 0));
let tag = run(&mut s, &ods());
assert_eq!(tag.as_deref(), Some("N"));
assert_eq!((s.captions, s.forced_captions), (2, 0));
}
#[test]
fn ods_counts_a_forced_caption_and_tags_f() {
let mut s = TsGraphicsStream::default();
run(&mut s, &pcs(1920, 1080, 1, &[0x40])); let tag = run(&mut s, &ods());
assert_eq!(tag.as_deref(), Some("F"));
assert_eq!((s.captions, s.forced_captions), (0, 1));
}
#[test]
fn end_marker_finishes_the_frame_and_later_ods_is_not_counted() {
let mut s = TsGraphicsStream::default();
run(&mut s, &pcs(1920, 1080, 1, &[0x00]));
run(&mut s, &[0x80]);
assert!(s.last_frame.finished);
let tag = run(&mut s, &ods());
assert_eq!(tag, None);
assert_eq!((s.captions, s.forced_captions), (0, 0));
}
#[test]
fn unknown_segment_only_sets_vbr() {
let mut s = TsGraphicsStream::default();
let before = s.clone();
let tag = run(&mut s, &[0x00, 0xFF, 0xFF]);
assert_eq!(tag, None);
assert_eq!(s.width, before.width);
assert_eq!(s.captions, before.captions);
assert_eq!(s.last_frame, before.last_frame);
assert!(s.caption_ids.is_empty());
assert!(s.base.is_vbr);
}
#[test]
fn force_flag_masks_only_bit_0x40() {
let object = |flag: u8| {
let mut v = vec![0x16]; v.extend_from_slice(&[0_u8; 12]); v.push(1); v.extend_from_slice(&[0, 0, 0]); v.push(flag); v.extend_from_slice(&[0_u8; 12]); let mut s = TsGraphicsStream::default();
run(&mut s, &v);
s.last_frame.forced
};
assert!(object(0x40));
assert!(object(0xFF));
assert!(!object(0x00));
assert!(!object(0xBF));
}
#[test]
fn read_ods_and_read_pcs_are_reachable_directly() {
let mut s = TsGraphicsStream::default();
let mut b = buf(&[0, 0, 0, 0]); let tag = read_ods(&mut s, &mut b);
assert_eq!(tag.as_deref(), Some("N"));
assert_eq!(s.captions, 1);
let mut s = TsGraphicsStream::default();
let mut b = buf(&[]); read_pcs(&mut s, &mut b);
assert_eq!((s.width, s.height), (0, 0));
assert!(s.base.is_initialized);
}
#[test]
fn description_after_a_scanned_caption_run() {
let mut s = TsGraphicsStream::default();
s.base.stream_type = TsStreamType::PresentationGraphics;
run(&mut s, &pcs(1920, 1080, 1, &[0x40]));
run(&mut s, &ods()); run(&mut s, &[0x80]);
run(&mut s, &pcs(0, 0, 2, &[0x00]));
run(&mut s, &ods()); run(&mut s, &pcs(0, 0, 3, &[0x00]));
run(&mut s, &ods()); assert_eq!((s.captions, s.forced_captions), (2, 1));
assert_eq!(s.description(), "1920x1080 / 2 Captions (1 Forced Caption)");
}
proptest! {
#[test]
fn scan_never_panics_on_arbitrary_bytes(data in any::<Vec<u8>>()) {
let mut s = TsGraphicsStream::default();
s.base.stream_type = TsStreamType::PresentationGraphics;
let mut b = buf(&data);
let mut tag = None;
scan(&mut s, &mut b, &mut tag);
prop_assert!(s.base.is_vbr);
}
}
}