use oximedia_container::mux::mpegts::scte35::SpliceInsertConfig;
use oximedia_container::{
emit_splice_insert, emit_splice_null, emit_time_signal, parse_splice_info_section,
Scte35Config, Scte35Parser, SpliceCommand, SpliceInfoSection,
};
use std::time::Duration;
fn parse_both(data: &[u8]) -> SpliceInfoSection {
let section_a =
parse_splice_info_section(data).expect("parse_splice_info_section should succeed");
let mut parser = Scte35Parser::new(Scte35Config::default());
let section_b = parser
.parse(data)
.expect("Scte35Parser::parse should succeed");
assert_eq!(
section_a.splice_command, section_b.splice_command,
"free function and method must agree on splice_command"
);
assert_eq!(
section_a.crc32, section_b.crc32,
"free function and method must agree on crc32"
);
section_a
}
#[test]
fn splice_null_roundtrip() {
let bytes = emit_splice_null();
assert!(!bytes.is_empty(), "emit_splice_null must produce bytes");
let section = parse_both(&bytes);
assert_eq!(
section.splice_command,
SpliceCommand::Null,
"splice_null must roundtrip as SpliceCommand::Null"
);
assert!(!section.encrypted, "section must not be marked encrypted");
assert_eq!(section.protocol_version, 0);
}
#[test]
fn time_signal_immediate_roundtrip() {
let bytes = emit_time_signal(None);
assert!(!bytes.is_empty());
let section = parse_both(&bytes);
if let SpliceCommand::TimeSignal(t) = §ion.splice_command {
assert!(
!t.time_specified,
"immediate time signal must have time_specified=false"
);
assert!(t.pts_time.is_none());
} else {
panic!("Expected TimeSignal, got {:?}", section.splice_command);
}
}
#[test]
fn time_signal_with_pts_roundtrip() {
let one_second = Duration::from_secs(1);
let bytes = emit_time_signal(Some(one_second));
assert!(!bytes.is_empty());
let section = parse_both(&bytes);
if let SpliceCommand::TimeSignal(t) = §ion.splice_command {
assert!(t.time_specified, "should have time_specified=true");
let pts = t.pts_time.expect("should have a pts_time");
assert_eq!(pts, 90_000, "1 second should encode as 90,000 at 90 kHz");
} else {
panic!("Expected TimeSignal, got {:?}", section.splice_command);
}
}
#[test]
fn splice_insert_immediate_roundtrip() {
let cfg = SpliceInsertConfig {
event_id: 42,
out_of_network: true,
duration: None,
auto_return: false,
splice_pts: None, unique_program_id: 1,
};
let bytes = emit_splice_insert(&cfg);
assert!(!bytes.is_empty());
let section = parse_both(&bytes);
if let SpliceCommand::Insert(ins) = §ion.splice_command {
assert_eq!(ins.event_id, 42);
assert!(!ins.event_cancel);
assert!(ins.out_of_network);
assert!(ins.program_splice, "program_splice must be set");
assert!(ins.immediate);
assert!(
ins.splice_time.is_none(),
"immediate splice has no splice_time"
);
assert_eq!(ins.unique_program_id, 1);
} else {
panic!("Expected Insert, got {:?}", section.splice_command);
}
}
#[test]
fn splice_insert_with_duration_roundtrip() {
let cfg = SpliceInsertConfig {
event_id: 99,
out_of_network: true,
duration: Some(Duration::from_secs(30)),
auto_return: true,
splice_pts: None, unique_program_id: 7,
};
let bytes = emit_splice_insert(&cfg);
let section = parse_both(&bytes);
if let SpliceCommand::Insert(ins) = §ion.splice_command {
assert_eq!(ins.event_id, 99);
let bd = ins.duration.as_ref().expect("should have break_duration");
assert_eq!(
bd.duration, 2_700_000,
"break duration must survive roundtrip"
);
assert!(bd.auto_return);
} else {
panic!("Expected Insert, got {:?}", section.splice_command);
}
}
#[test]
fn free_function_rejects_empty_input() {
let result = parse_splice_info_section(&[]);
assert!(result.is_err(), "empty slice must be rejected");
}
#[test]
fn free_function_rejects_wrong_table_id() {
let mut bytes = emit_splice_null();
bytes[0] = 0x00; let result = parse_splice_info_section(&bytes);
assert!(result.is_err(), "wrong table_id must be rejected");
}
#[test]
fn free_function_rejects_crc_mismatch() {
let mut bytes = emit_splice_null();
let last = bytes.len() - 1;
bytes[last] ^= 0xFF; let result = parse_splice_info_section(&bytes);
assert!(result.is_err(), "CRC mismatch must be rejected");
}
#[test]
fn parse_three_sections_in_sequence() {
let null_bytes = emit_splice_null();
let ts_bytes = emit_time_signal(Some(Duration::from_secs(5)));
let ins_bytes = emit_splice_insert(&SpliceInsertConfig {
event_id: 1,
out_of_network: false,
duration: None,
auto_return: false,
splice_pts: None,
unique_program_id: 0,
});
let sections: Vec<SpliceInfoSection> = [null_bytes, ts_bytes, ins_bytes]
.iter()
.map(|b| parse_splice_info_section(b).expect("each section should parse"))
.collect();
assert!(matches!(sections[0].splice_command, SpliceCommand::Null));
assert!(matches!(
sections[1].splice_command,
SpliceCommand::TimeSignal(_)
));
assert!(matches!(
sections[2].splice_command,
SpliceCommand::Insert(_)
));
}