diff --git a/README.md b/README.md index 8365667..0b82afa 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ other purposes. More information can be found at ## Main Features - Parsing of SCTE-35 data -- Encoding of SCTE-35 data +- Encoding of SCTE-35 data (not yet implemented) - Serde integration for serialization into JSON or any other [serde supported formats](https://docs.rs/serde/1.0.137/serde/#data-formats). ## Implementation Overview @@ -24,11 +24,11 @@ Implemented parts of the standard are: - [ ] Splice Schedule - [x] Time Signal - [ ] Bandwidth Reservation - - [ ] Splice Time + - [x] Splice Time - Splice Descriptors: - [ ] Avail - [ ] DTMF - - [ ] Segmentation Descriptor + - [x] Segmentation Descriptor - [ ] MPU - [ ] MID - Encryption Information section @@ -38,4 +38,4 @@ Implemented parts of the standard are: - [ ] Triple DES EDE3 – ECB mode - [ ] Customized encryption algorithm - [ ] CRC encryption calculation - - [ ] CRC calculation \ No newline at end of file + - [x] CRC calculation \ No newline at end of file diff --git a/src/commands.rs b/src/commands.rs index cad5d63..1f95c4a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,5 +1,5 @@ use crate::time::SpliceTime; -use crate::{CueError, TransportPacketWrite}; +use crate::{ClockTimeExt, CueError, TransportPacketWrite}; use bitstream_io::{BigEndian, BitWrite, BitWriter}; use std::io; use std::io::Write; @@ -47,8 +47,11 @@ impl TimeSignal { TimeSignal(SpliceTime::new()) } - pub fn from_ticks(pts_time: u64) -> Self { - TimeSignal(SpliceTime::from_ticks(pts_time)) + pub fn set_pts(&mut self, pts: Option) + where + T: ClockTimeExt, + { + self.0.set_pts_time(pts); } } @@ -68,9 +71,14 @@ impl SpliceCommand for TimeSignal { } } -impl From for TimeSignal { - fn from(duration: Duration) -> Self { - Self(duration.into()) +impl From for TimeSignal +where + T: ClockTimeExt, +{ + fn from(pts: T) -> Self { + let mut t = Self::new(); + t.set_pts(Some(pts)); + t } } diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index b7e1f54..e124418 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -8,7 +8,6 @@ use std::io; use serde::Serialize; #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize))] pub enum SpliceDescriptor { Avail, DTMF, @@ -17,6 +16,32 @@ pub enum SpliceDescriptor { Audio, Unknown(u8, u32, Vec), } +#[cfg(feature = "serde")] +mod serde_serialization { + use super::*; + use serde::ser::{Error, Serialize, SerializeStruct, Serializer}; + + impl Serialize for SpliceDescriptor { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use SpliceDescriptor::*; + match self { + Segmentation(seg) => seg.serialize(serializer), + Unknown(tag, len, data) => { + let mut struc = serializer.serialize_struct("SpliceDescriptor", 3)?; + struc.serialize_field("tag", &format!("0x{:x}", tag))?; + struc.serialize_field("length", &len)?; + struc.serialize_field("data", &format!("0x{}", hex::encode(data).as_str()))?; + struc.end() + } + // TODO: add other descriptors + _ => serializer.serialize_str(&format!("{:?}", self)), + } + } + } +} pub(crate) trait SpliceDescriptorExt { fn splice_descriptor_tag(&self) -> u8; @@ -26,8 +51,8 @@ pub(crate) trait SpliceDescriptorExt { } } -impl TransportPacketWrite for SpliceDescriptor { - fn write_to(&self, buffer: &mut W) -> anyhow::Result<()> +impl SpliceDescriptor { + pub(crate) fn write_to(&mut self, buffer: &mut W) -> anyhow::Result<()> where W: io::Write, { @@ -40,6 +65,17 @@ impl TransportPacketWrite for SpliceDescriptor { SpliceDescriptor::Unknown(_, _, _) => unimplemented!(), } } + + pub(crate) fn len(&self) -> u8 { + match self { + SpliceDescriptor::Avail => 0, + SpliceDescriptor::DTMF => 0, + SpliceDescriptor::Segmentation(segmentation) => segmentation.len(), + SpliceDescriptor::Time => 0, + SpliceDescriptor::Audio => 0, + SpliceDescriptor::Unknown(_, _, _) => 0, + } + } } impl From for SpliceDescriptor { diff --git a/src/descriptors/segmentation.rs b/src/descriptors/segmentation.rs index 5377e93..d0c1a42 100644 --- a/src/descriptors/segmentation.rs +++ b/src/descriptors/segmentation.rs @@ -1,18 +1,16 @@ -use crate::{BytesWritten, CueError, TransportPacketWrite}; +use crate::{BytesWritten, ClockTimeExt, CueError, TransportPacketWrite}; use anyhow::Context; use ascii::AsciiString; use bitstream_io::{BigEndian, BitRecorder, BitWrite, BitWriter}; use std::ffi::CStr; -use std::fmt::{Display, Formatter}; -use std::io; use std::io::Write; +use std::{fmt, io}; use crate::descriptors::{SpliceDescriptorExt, SpliceDescriptorTag}; #[cfg(feature = "serde")] use serde::Serialize; #[derive(Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(Serialize))] pub struct SegmentationDescriptor { segmentation_event_id: u32, segmentation_event_cancel_indicator: bool, @@ -31,6 +29,192 @@ pub struct SegmentationDescriptor { segments_expected: u8, sub_segment_num: u8, sub_segments_expected: u8, + + descriptor_length: u8, +} + +#[cfg(feature = "serde")] +mod serde_serialization { + use super::*; + use crate::ticks_to_secs; + use ascii::AsciiStr; + use serde::ser::{Error, Serialize, SerializeStruct, Serializer}; + use std::fmt::{format, LowerHex}; + + impl Serialize for SegmentationDescriptor { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use SegmentationFieldSyntax::*; + + #[inline] + fn as_hex(value: T) -> String + where + T: LowerHex, + { + format!("0x{:02x}", value) + } + + let segmentation_syntax = self.segmentation_type.syntax(); + + // predict number of fields in the struct + let mut num_fields = 6; + if !self.segmentation_event_cancel_indicator { + num_fields += 12; + if !self.delivery_not_restricted_flag { + num_fields += 4; + } + if self.segmentation_duration_flag { + num_fields += 2; + } + match segmentation_syntax.sub_segment_num { + Fixed(_) | NonZero | ZeroOrNonZero => { + num_fields += 1; + } + NotUsed => {} + } + match segmentation_syntax.sub_segments_expected { + Fixed(_) | NonZero | ZeroOrNonZero => { + num_fields += 1; + } + NotUsed => {} + } + } + + let mut state = serializer.serialize_struct("SegmentationDescriptor", num_fields)?; + state.serialize_field("name", "Segmentation Descriptor")?; + state.serialize_field( + "splice_descriptor_tag", + &as_hex(self.splice_descriptor_tag()), + )?; + state.serialize_field("descriptor_length", &self.descriptor_length)?; + let id = self.identifier().to_be_bytes(); + state.serialize_field( + "identifier", + AsciiStr::from_ascii(id.as_slice()) + .expect("ascii characters") + .as_str(), + )?; + state.serialize_field("segmentation_event_id", &as_hex(self.segmentation_event_id))?; + state.serialize_field( + "segmentation_event_cancel_indicator", + &self.segmentation_event_cancel_indicator, + )?; + + if !self.segmentation_event_cancel_indicator { + state.serialize_field( + "program_segmentation_flag", + &self.program_segmentation_flag, + )?; + state.serialize_field( + "segmentation_duration_flag", + &self.segmentation_duration_flag, + )?; + state.serialize_field( + "delivery_not_restricted_flag", + &self.delivery_not_restricted_flag, + )?; + if !self.delivery_not_restricted_flag { + state.serialize_field( + "web_delivery_allowed_flag", + &self.web_delivery_allowed_flag, + )?; + state.serialize_field( + "no_regional_blackout_flag", + &self.no_regional_blackout_flag, + )?; + state.serialize_field("archive_allowed_flag", &self.archive_allowed_flag)?; + state.serialize_field("device_restrictions", &self.device_restrictions)?; + } + state.serialize_field("components", &self.components)?; + if self.segmentation_duration_flag { + state.serialize_field( + "segmentation_duration", + &ticks_to_secs(self.segmentation_duration), + )?; + state.serialize_field( + "segmentation_duration_ticks", + &self.segmentation_duration, + )?; + } + state.serialize_field( + "segmentation_upid_type", + &as_hex(u8::from(self.segmentation_upid.segmentation_upid_type())), + )?; + state.serialize_field( + "segmentation_upid_type_name", + &format!("{}", self.segmentation_upid.segmentation_upid_type()), + )?; + state.serialize_field( + "segmentation_upid_length", + &self.segmentation_upid.segmentation_upid_length(), + )?; + state.serialize_field("segmentation_upid", &self.segmentation_upid)?; + state.serialize_field( + "segmentation_message", + &format!("{}", self.segmentation_type), + )?; + state.serialize_field("segmentation_type_id", &self.segmentation_type.id())?; + state.serialize_field("segment_num", &self.segment_num)?; + state.serialize_field("segments_expected", &self.segments_expected)?; + match segmentation_syntax.sub_segment_num { + Fixed(v) => { + state.serialize_field("sub_segment_num", &v)?; + } + NonZero | ZeroOrNonZero => { + state.serialize_field("sub_segment_num", &self.sub_segment_num)?; + } + NotUsed => {} + } + match segmentation_syntax.sub_segments_expected { + Fixed(v) => { + state.serialize_field("sub_segments_expected", &v)?; + } + NonZero | ZeroOrNonZero => { + state.serialize_field( + "sub_segments_expected", + &self.sub_segments_expected, + )?; + } + NotUsed => {} + } + } + state.end() + } + } + + impl Serialize for SegmentationUpid { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use SegmentationUpid::*; + + let mut recorder = BitRecorder::::new(); + self.write_to(&mut recorder) + .map_err(|err| S::Error::custom(format!("{}", err)))?; + + let mut data = Vec::new(); + let mut buffer = BitWriter::endian(&mut data, BigEndian); + recorder + .playback(&mut buffer) + .map_err(|err| S::Error::custom(format!("{}", err)))?; + + // TODO: serialize as struct when variant is MPU and MID + match self { + // if field is represented as a character, then show with textual representation + ISCI(v) | AdID(v) | TID(v) | AdID(v) | ADSInformation(v) | URI(v) | SCR(v) => { + serializer.serialize_str(v.as_str()) + } + // if field is represented as a number, then show as hex + ISAN(v) | EIDR(v) | UUID(v) => serializer.serialize_str(&format!("0x{:x}", v)), + ISANDeprecated(v) | AiringID(v) => serializer.serialize_str(&format!("0x{:x}", v)), + // everything else show as hex + _ => serializer.serialize_str(&format!("0x{}", hex::encode(data[1..].to_vec()))), + } + } + } } impl SegmentationDescriptor { @@ -73,9 +257,9 @@ impl SegmentationDescriptor { self.device_restrictions = device_restrictions; } - pub fn set_segmentation_duration(&mut self, segmentation_duration: u64) { + pub fn set_segmentation_duration(&mut self, segmentation_duration: impl ClockTimeExt) { self.set_segmentation_duration_flag(true); - self.segmentation_duration = segmentation_duration; + self.segmentation_duration = segmentation_duration.to_90k(); } pub fn set_segmentation_upid(&mut self, segmentation_upid: SegmentationUpid) { @@ -101,10 +285,12 @@ impl SegmentationDescriptor { pub fn set_sub_segments_expected(&mut self, sub_segments_expected: u8) { self.sub_segments_expected = sub_segments_expected; } -} -impl TransportPacketWrite for SegmentationDescriptor { - fn write_to(&self, buffer: &mut W) -> anyhow::Result<()> + pub(crate) fn len(&self) -> u8 { + self.descriptor_length + } + + pub(crate) fn write_to(&mut self, buffer: &mut W) -> anyhow::Result<()> where W: io::Write, { @@ -166,6 +352,7 @@ impl TransportPacketWrite for SegmentationDescriptor { let mut buffer = BitWriter::endian(buffer, BigEndian); buffer.write(8, self.splice_descriptor_tag())?; buffer.write(8, recorder.bytes_written() as u8)?; + self.descriptor_length = recorder.bytes_written() as u8; recorder.playback(&mut buffer)?; Ok(()) @@ -205,6 +392,7 @@ impl Default for DeviceRestrictions { } #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] #[non_exhaustive] pub enum SegmentationUpidType { NotUsed, @@ -261,8 +449,8 @@ impl From for u8 { } } -impl Display for SegmentationUpidType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SegmentationUpidType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { use SegmentationUpidType::*; match self { NotUsed => write!(f, "Not Used"), @@ -289,7 +477,6 @@ impl Display for SegmentationUpidType { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize))] #[non_exhaustive] pub enum SegmentationUpid { NotUsed, @@ -345,6 +532,31 @@ impl SegmentationUpid { } } + fn segmentation_upid_length(&self) -> u8 { + use SegmentationUpid::*; + match self { + NotUsed => 0, + UserDefinedDeprecated(s) => s.len() as u8, + ISCI(s) => 8, + AdID(s) => 12, + UMID(s) => 32, + ISANDeprecated(_) => 8, + ISAN(_) => 12, + TID(s) => 8, + AiringID(_) => 8, + ADI(s) => s.len() as u8, + EIDR(_) => 12, + ATSCContentIdentifier(s) => s.len() as u8, + MPU => 0, + MID => 0, + ADSInformation(s) => s.len() as u8, + URI(s) => s.len() as u8, + UUID(_) => 16, + SCR(s) => s.len() as u8, + Reserved(_) => 0, + } + } + fn write_to(&self, out: &mut BitRecorder) -> anyhow::Result<()> { use SegmentationUpid::*; @@ -809,6 +1021,72 @@ impl SegmentationType { } } +impl fmt::Display for SegmentationType { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use SegmentationType::*; + match self { + NotIndicated => write!(f, "Not Indicated"), + ContentIdentification => write!(f, "Content Identification"), + ProgramStart => write!(f, "Program Start"), + ProgramEnd => write!(f, "Program End"), + ProgramEarlyTermination => write!(f, "Program Early Termination"), + ProgramBreakaway => write!(f, "Program Breakaway"), + ProgramResumption => write!(f, "Program Resumption"), + ProgramRunoverPlanned => write!(f, "Program Runover Planned"), + ProgramRunoverUnplanned => write!(f, "Program Runover Unplanned"), + ProgramOverlapStart => write!(f, "Program Overlap Start"), + ProgramBlackoutOverride => write!(f, "Program Blackout Override"), + ProgramJoin => write!(f, "Program Join"), + ChapterStart => write!(f, "Chapter Start"), + ChapterEnd => write!(f, "Chapter End"), + BreakStart => write!(f, "Break Start"), + BreakEnd => write!(f, "Break End"), + OpeningCreditStartDeprecated => write!(f, "Opening Credit Start (Deprecated)"), + OpeningCreditEndDeprecated => write!(f, "Opening Credit End (Deprecated)"), + ClosingCreditStartDeprecated => write!(f, "Closing Credit Start (Deprecated)"), + ClosingCreditEndDeprecated => write!(f, "Closing Credit End (Deprecated)"), + ProviderAdvertisementStart => write!(f, "Provider Advertisement Start"), + ProviderAdvertisementEnd => write!(f, "Provider Advertisement End"), + DistributorAdvertisementStart => write!(f, "Distributor Advertisement Start"), + DistributorAdvertisementEnd => write!(f, "Distributor Advertisement End"), + ProviderPlacementOpportunityStart => write!(f, "Provider Placement Opportunity Start"), + ProviderPlacementOpportunityEnd => write!(f, "Provider Placement Opportunity End"), + DistributorPlacementOpportunityStart => { + write!(f, "Distributor Placement Opportunity Start") + } + DistributorPlacementOpportunityEnd => { + write!(f, "Distributor Placement Opportunity End") + } + ProviderOverlayPlacementOpportunityStart => { + write!(f, "Provider Overlay Placement Opportunity Start") + } + ProviderOverlayPlacementOpportunityEnd => { + write!(f, "Provider Overlay Placement Opportunity End") + } + DistributorOverlayPlacementOpportunityStart => { + write!(f, "Distributor Overlay Placement Opportunity Start") + } + DistributorOverlayPlacementOpportunityEnd => { + write!(f, "Distributor Overlay Placement Opportunity End") + } + ProviderPromoStart => write!(f, "Provider Promo Start"), + ProviderPromoEnd => write!(f, "Provider Promo End"), + DistributorPromoStart => write!(f, "Distributor Promo Start"), + DistributorPromoEnd => write!(f, "Distributor Promo End"), + UnscheduledEventStart => write!(f, "Unscheduled Event Start"), + UnscheduledEventEnd => write!(f, "Unscheduled Event End"), + AlternateContentOpportunityStart => write!(f, "Alternate Content Opportunity Start"), + AlternateContentOpportunityEnd => write!(f, "Alternate Content Opportunity End"), + ProviderAdBlockStart => write!(f, "Provider Ad Block Start"), + ProviderAdBlockEnd => write!(f, "Provider Ad Block End"), + DistributorAdBlockStart => write!(f, "Distributor Ad Block Start"), + DistributorAdBlockEnd => write!(f, "Distributor Ad Block End"), + NetworkStart => write!(f, "Network Start"), + NetworkEnd => write!(f, "Network End"), + } + } +} + impl TryFrom for SegmentationType { type Error = (); @@ -884,6 +1162,7 @@ struct SegmentationTypeSyntax { mod tests { use super::*; use anyhow::Result; + use assert_json_diff::assert_json_eq; use std::time::Duration; #[test] @@ -932,4 +1211,86 @@ mod tests { Ok(()) } + + #[cfg(feature = "serde")] + #[test] + fn serialize_segmentation_to_json() -> Result<()> { + let mut descriptor = SegmentationDescriptor::default(); + descriptor.set_segmentation_event_id(0x4800008e); + descriptor.set_program_segmentation_flag(true); + descriptor.set_segmentation_duration_flag(true); + descriptor.set_no_regional_blackout_flag(true); + descriptor.set_archive_allowed_flag(true); + descriptor.set_segmentation_duration(Duration::from_secs_f32(307.0)); + descriptor.set_segmentation_duration(27630000); + descriptor.set_segmentation_upid(SegmentationUpid::AiringID(0x2ca0a18a)); + descriptor.set_segmentation_type(SegmentationType::ProviderPlacementOpportunityStart); + descriptor.set_segment_num(2); + descriptor.set_sub_segment_num(154); + descriptor.set_sub_segments_expected(201); + + // We need to write to calculate the length + let mut data = Vec::new(); + descriptor.write_to(&mut data)?; + + let expected_json: serde_json::Value = serde_json::from_str( + r#"{ + "name": "Segmentation Descriptor", + "splice_descriptor_tag": "0x02", + "descriptor_length": 30, + "identifier": "CUEI", + "segmentation_event_id": "0x4800008e", + "segmentation_event_cancel_indicator": false, + "program_segmentation_flag": true, + "segmentation_duration_flag": true, + "delivery_not_restricted_flag": false, + "web_delivery_allowed_flag": false, + "no_regional_blackout_flag": true, + "archive_allowed_flag": true, + "device_restrictions": "None", + "components": [], + "segmentation_duration": 307.0, + "segmentation_duration_ticks": 27630000, + "segmentation_upid_type": "0x08", + "segmentation_upid_type_name": "AiringID", + "segmentation_upid_length": 8, + "segmentation_upid": "0x2ca0a18a", + "segmentation_message": "Provider Placement Opportunity Start", + "segmentation_type_id": 52, + "segment_num": 2, + "segments_expected": 0, + "sub_segment_num": 154, + "sub_segments_expected": 201 + }"#, + )?; + + assert_json_eq!(serde_json::to_value(&descriptor)?, expected_json); + Ok(()) + } + + #[cfg(feature = "serde")] + #[test] + fn serialize_segmentation_with_cancel_indicator_to_json() -> Result<()> { + let mut descriptor = SegmentationDescriptor::default(); + descriptor.set_segmentation_event_id(0x4800008e); + descriptor.set_segmentation_event_cancel_indicator(true); + + // We need to write to calculate the length + let mut data = Vec::new(); + descriptor.write_to(&mut data)?; + + let expected_json: serde_json::Value = serde_json::from_str( + r#"{ + "name": "Segmentation Descriptor", + "splice_descriptor_tag": "0x02", + "descriptor_length": 9, + "identifier": "CUEI", + "segmentation_event_id": "0x4800008e", + "segmentation_event_cancel_indicator": true + }"#, + )?; + + assert_json_eq!(serde_json::to_value(&descriptor)?, expected_json); + Ok(()) + } } diff --git a/src/info.rs b/src/info.rs index 8d03b4b..b161728 100644 --- a/src/info.rs +++ b/src/info.rs @@ -131,15 +131,17 @@ impl SpliceInfoSection where C: SpliceCommand, { - pub fn into_encoded(self) -> anyhow::Result> { + pub fn into_encoded(mut self) -> anyhow::Result> { // Write splice command to a temporary buffer let mut splice_data = Vec::new(); self.state.splice_command.write_to(&mut splice_data)?; // Write the descriptors to a temporary buffer let mut descriptor_data = Vec::new(); - for descriptor in &self.state.descriptors { + let mut descriptor_loop_length = 0; + for descriptor in &mut self.state.descriptors { descriptor.write_to(&mut descriptor_data)?; + descriptor_loop_length += descriptor.len() as u16; } // Start writing the final output to a temporary buffer @@ -154,7 +156,7 @@ where // splice command length and descriptors which are also known by now const FIXED_INFO_SIZE_BYTES: usize = (8 + 1 + 6 + 33 + 8 + 12 + 12 + 8 + 16 + 32) / 8; let mut section_length = - (FIXED_INFO_SIZE_BYTES + splice_data.len() + descriptor_data.len()) as u16; + (FIXED_INFO_SIZE_BYTES + splice_data.len() + descriptor_loop_length as usize) as u16; if self.state.encrypted_packet { section_length += 4; } @@ -171,7 +173,6 @@ where let splice_command_type = self.state.splice_command.splice_command_type(); buffer.write(8, u8::from(splice_command_type))?; buffer.write_bytes(splice_data.as_slice())?; - let descriptor_loop_length = descriptor_data.len() as u16; buffer.write(16, descriptor_loop_length)?; buffer.write_bytes(descriptor_data.as_slice())?; buffer.flush()?; @@ -300,7 +301,7 @@ mod serde_serialization { format!("0x{:x}", value) } - let mut state = serializer.serialize_struct("SpliceInfoSection", 18)?; + let mut state = serializer.serialize_struct("SpliceInfoSection", 19)?; state.serialize_field("table_id", &as_hex(self.state.table_id))?; state.serialize_field( "section_syntax_indicator", @@ -316,6 +317,7 @@ mod serde_serialization { &u8::from(self.state.encryption_algorithm), )?; state.serialize_field("pts_adjustment", &ticks_to_secs(self.state.pts_adjustment))?; + state.serialize_field("pts_adjustment_ticks", &self.state.pts_adjustment)?; state.serialize_field("cw_index", &as_hex(self.state.cw_index))?; state.serialize_field("tier", &as_hex(self.state.tier))?; state.serialize_field("splice_command_length", &self.encoded.splice_command_length)?; @@ -372,7 +374,7 @@ mod tests { #[test] fn compliance_spec_14_1_example_time_signal_as_base64() -> Result<()> { - let mut splice = SpliceInfoSection::new(TimeSignal::from_ticks(0x072bd0050)); + let mut splice = SpliceInfoSection::new(TimeSignal::from(0x072bd0050u64)); splice.set_cw_index(0xff); let mut descriptor = SegmentationDescriptor::default(); @@ -400,7 +402,7 @@ mod tests { #[test] fn compliance_spec_14_1_example_time_signal_as_hex() -> Result<()> { - let mut splice = SpliceInfoSection::new(TimeSignal::from_ticks(0x072bd0050)); + let mut splice = SpliceInfoSection::new(TimeSignal::from(0x072bd0050u64)); splice.set_cw_index(0xff); let mut descriptor = SegmentationDescriptor::default(); @@ -428,7 +430,7 @@ mod tests { #[cfg(feature = "serde")] #[test] fn compliance_spec_14_1_example_time_signal_as_json() -> Result<()> { - let mut splice = SpliceInfoSection::new(TimeSignal::from_ticks(0x072bd0050)); + let mut splice = SpliceInfoSection::new(TimeSignal::from(0x072bd0050u64)); splice.set_cw_index(0xff); let mut descriptor = SegmentationDescriptor::default(); @@ -446,31 +448,69 @@ mod tests { splice.add_descriptor(descriptor.into()); + let expected_json: serde_json::Value = serde_json::from_str( + r#"{ + "table_id": "0xfc", + "section_syntax_indicator": false, + "private_indicator": false, + "sap_type": "0x3", + "section_length": 52, + "protocol_version": 0, + "encrypted_packet": false, + "encryption_algorithm": 0, + "pts_adjustment": 0.0, + "pts_adjustment_ticks": 0, + "cw_index": "0xff", + "tier": "0xfff", + "splice_command_length": 5, + "splice_command_type": 6, + "splice_command_name": "TimeSignal", + "splice_command": { + "name": "Time Signal", + "command_type": 6, + "command_length": 5, + "time_specified_flag": true, + "pts_time": 21388.766756, + "pts_time_ticks": 1924989008 + }, + "descriptor_loop_length": 30, + "descriptors": [ + { + "name": "Segmentation Descriptor", + "splice_descriptor_tag": "0x02", + "descriptor_length": 30, + "identifier": "CUEI", + "segmentation_event_id": "0x4800008e", + "segmentation_event_cancel_indicator": false, + "program_segmentation_flag": true, + "segmentation_duration_flag": true, + "delivery_not_restricted_flag": false, + "web_delivery_allowed_flag": false, + "no_regional_blackout_flag": true, + "archive_allowed_flag": true, + "device_restrictions": "None", + "components": [], + "segmentation_duration": 307.0, + "segmentation_duration_ticks": 27630000, + "segmentation_upid_type": "0x08", + "segmentation_upid_type_name": "AiringID", + "segmentation_upid_length": 8, + "segmentation_upid": "0x2ca0a18a", + "segmentation_message": "Provider Placement Opportunity Start", + "segmentation_type_id": 52, + "segment_num": 2, + "segments_expected": 0, + "sub_segment_num": 154, + "sub_segments_expected": 201 + } + ], + "crc_32": "0x926218f0" + }"#, + )?; + assert_json_eq!( serde_json::to_value(&splice.into_encoded()?)?, - serde_json::json!({ - "table_id": "0xfc", - "section_syntax_indicator": false, - "private_indicator": false, - "sap_type": "0x3", - "section_length": 22, - "protocol_version": 0, - "encrypted_packet": false, - "encryption_algorithm": 0, - "pts_adjustment": 0.0, - "cw_index": "0xff", - "tier": "0xfff", - "splice_command_length": 5, - "splice_command_type": 6, - "splice_command_name": "TimeSignal", - "splice_command": { - "time_specified_flag": true, - "pts_time": 21388.766756, - }, - "descriptor_loop_length": 0, - "descriptors": [], - "crc_32": "0x2184b03d" - }) + expected_json ); Ok(()) @@ -493,6 +533,7 @@ mod tests { "encrypted_packet": false, "encryption_algorithm": 0, "pts_adjustment": 0.0, + "pts_adjustment_ticks": 0, "cw_index": "0x0", "tier": "0xfff", "splice_command_length": 0, diff --git a/src/lib.rs b/src/lib.rs index ce2a32c..686bb64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,15 @@ pub trait ClockTimeExt { fn to_90k(&self) -> u64; } +impl ClockTimeExt for u64 { + #[inline] + fn to_90k(&self) -> u64 { + *self + } +} + impl ClockTimeExt for Duration { + #[inline] fn to_90k(&self) -> u64 { (self.as_secs_f64() * 90_000.0).floor() as u64 } diff --git a/src/time.rs b/src/time.rs index 13c0ce2..26f2b02 100644 --- a/src/time.rs +++ b/src/time.rs @@ -27,18 +27,33 @@ impl SpliceTime { } #[inline] - pub fn set_pts_time(&mut self, pts_time: Option) { + pub fn set_pts_time(&mut self, pts_time: Option) + where + T: ClockTimeExt, + { match pts_time { None => { self.time_specified_flag = false; self.pts_time = 0; } - Some(ticks) => { + Some(duration) => { self.time_specified_flag = true; - self.pts_time = ticks; + self.pts_time = duration.to_90k(); } } } + + pub fn time_specified_flag(&self) -> bool { + self.time_specified_flag + } + + pub fn pts_time(&self) -> Option { + if self.time_specified_flag { + Some(self.pts_time) + } else { + None + } + } } impl TransportPacketWrite for SpliceTime {