diff --git a/src/commands.rs b/src/commands.rs index 13259c9..56a68b3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,24 +1,29 @@ use crate::time::SpliceTime; -use crate::{ClockTimeExt, CueError, TransportPacketWrite}; -use bitstream_io::{BigEndian, BitWrite, BitWriter}; +use crate::ClockTimeExt; use std::io; use std::io::Write; -use std::ops::{Deref, DerefMut}; -use std::time::Duration; #[cfg(feature = "serde")] use serde::Serialize; -pub trait SpliceCommand: TransportPacketWrite { +pub trait SpliceCommand { fn splice_command_type(&self) -> SpliceCommandType; + + fn write_to(&mut self, buffer: &mut W) -> anyhow::Result + where + W: io::Write; } #[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct SpliceNull {} -impl TransportPacketWrite for SpliceNull { - fn write_to(&self, _: &mut W) -> anyhow::Result +impl SpliceCommand for SpliceNull { + fn splice_command_type(&self) -> SpliceCommandType { + SpliceCommandType::SpliceNull + } + + fn write_to(&mut self, _: &mut W) -> anyhow::Result where W: io::Write, { @@ -26,21 +31,12 @@ impl TransportPacketWrite for SpliceNull { } } -impl SpliceCommand for SpliceNull { - fn splice_command_type(&self) -> SpliceCommandType { - SpliceCommandType::SpliceNull - } -} - +#[derive(Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize), serde(transparent))] #[repr(transparent)] pub struct TimeSignal(SpliceTime); impl TimeSignal { - pub fn new() -> Self { - TimeSignal(SpliceTime::new()) - } - pub fn set_pts(&mut self, pts: Option) where T: ClockTimeExt, @@ -49,9 +45,12 @@ impl TimeSignal { } } -impl TransportPacketWrite for TimeSignal { - #[inline] - fn write_to(&self, buffer: &mut W) -> anyhow::Result +impl SpliceCommand for TimeSignal { + fn splice_command_type(&self) -> SpliceCommandType { + SpliceCommandType::TimeSignal + } + + fn write_to(&mut self, buffer: &mut W) -> anyhow::Result where W: Write, { @@ -59,18 +58,12 @@ impl TransportPacketWrite for TimeSignal { } } -impl SpliceCommand for TimeSignal { - fn splice_command_type(&self) -> SpliceCommandType { - SpliceCommandType::TimeSignal - } -} - impl From for TimeSignal where T: ClockTimeExt, { fn from(pts: T) -> Self { - let mut t = Self::new(); + let mut t = Self::default(); t.set_pts(Some(pts)); t } @@ -115,32 +108,3 @@ impl From for u8 { } } } - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - use assert_json_diff::assert_json_eq; - - #[cfg(feature = "serde")] - #[test] - fn serialize_splice_null() -> Result<()> { - let splice_null = SpliceNull::default(); - assert_json_eq!(serde_json::to_value(&splice_null)?, serde_json::json!({})); - Ok(()) - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_time_signal() -> Result<()> { - let time_signal = TimeSignal::new(); - assert_json_eq!( - serde_json::to_value(&time_signal)?, - serde_json::json!({ - "time_specified_flag": false, - "pts_time": 0.0 - }) - ); - Ok(()) - } -} diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index 3936020..88dec97 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -1,12 +1,8 @@ mod segmentation; -use crate::{CueError, TransportPacketWrite}; pub use segmentation::*; use std::io; -#[cfg(feature = "serde")] -use serde::Serialize; - #[derive(Debug, Clone, PartialEq)] pub enum SpliceDescriptor { Avail, @@ -16,32 +12,6 @@ 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; diff --git a/src/descriptors/segmentation.rs b/src/descriptors/segmentation.rs index 18bddf2..8b34a20 100644 --- a/src/descriptors/segmentation.rs +++ b/src/descriptors/segmentation.rs @@ -1,9 +1,6 @@ -use crate::{BytesWritten, ClockTimeExt, CueError, TransportPacketWrite}; -use anyhow::Context; +use crate::{BytesWritten, ClockTimeExt}; use ascii::AsciiString; use bitstream_io::{BigEndian, BitRecorder, BitWrite, BitWriter}; -use std::ffi::CStr; -use std::io::Write; use std::{fmt, io}; use crate::descriptors::{SpliceDescriptorExt, SpliceDescriptorTag}; @@ -33,191 +30,6 @@ pub struct SegmentationDescriptor { descriptor_length: Option, } -#[cfg(feature = "serde")] -mod serde_serialization { - use super::*; - use crate::ticks_to_secs; - use crate::time::format_duration; - use ascii::AsciiStr; - use serde::ser::{Error, Serialize, SerializeStruct, Serializer}; - use std::fmt::{format, LowerHex}; - use std::time::Duration; - - 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 { - let duration_secs = ticks_to_secs(self.segmentation_duration); - state.serialize_field("segmentation_duration", &self.segmentation_duration)?; - state.serialize_field("segmentation_duration_secs", &duration_secs)?; - state.serialize_field( - "segmentation_duration_human", - &format_duration(Duration::from_secs_f64(duration_secs)).to_string(), - )?; - } - 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) | 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, we skip the first byte (which is the length) - _ => serializer.serialize_str(&format!("0x{}", hex::encode(&data[1..]))), - } - } - } -} - impl SegmentationDescriptor { pub fn set_segmentation_event_id(&mut self, segmentation_event_id: u32) { self.segmentation_event_id = segmentation_event_id; @@ -287,6 +99,78 @@ impl SegmentationDescriptor { self.sub_segments_expected = sub_segments_expected; } + pub fn segmentation_event_id(&self) -> u32 { + self.segmentation_event_id + } + + pub fn segmentation_event_cancel_indicator(&self) -> bool { + self.segmentation_event_cancel_indicator + } + + pub fn program_segmentation_flag(&self) -> bool { + self.program_segmentation_flag + } + + pub fn segmentation_duration_flag(&self) -> bool { + self.segmentation_duration_flag + } + + pub fn delivery_not_restricted_flag(&self) -> bool { + self.delivery_not_restricted_flag + } + + pub fn web_delivery_allowed_flag(&self) -> bool { + self.web_delivery_allowed_flag + } + + pub fn no_regional_blackout_flag(&self) -> bool { + self.no_regional_blackout_flag + } + + pub fn archive_allowed_flag(&self) -> bool { + self.archive_allowed_flag + } + + pub fn device_restrictions(&self) -> DeviceRestrictions { + self.device_restrictions + } + + pub fn components(&self) -> &Vec { + &self.components + } + + pub fn segmentation_duration(&self) -> u64 { + self.segmentation_duration + } + + pub fn segmentation_upid(&self) -> &SegmentationUpid { + &self.segmentation_upid + } + + pub fn segmentation_type(&self) -> SegmentationType { + self.segmentation_type + } + + pub fn segment_num(&self) -> u8 { + self.segment_num + } + + pub fn segments_expected(&self) -> u8 { + self.segments_expected + } + + pub fn sub_segment_num(&self) -> u8 { + self.sub_segment_num + } + + pub fn sub_segments_expected(&self) -> u8 { + self.sub_segments_expected + } + + pub fn descriptor_length(&self) -> Option { + self.descriptor_length + } + pub(crate) fn write_to(&mut self, buffer: &mut W) -> anyhow::Result where W: io::Write, @@ -535,17 +419,17 @@ impl SegmentationUpid { } } - fn segmentation_upid_length(&self) -> u8 { + pub 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, + ISCI(_) => 8, + AdID(_) => 12, + UMID(_) => 32, ISANDeprecated(_) => 8, ISAN(_) => 12, - TID(s) => 8, + TID(_) => 8, AiringID(_) => 8, ADI(s) => s.len() as u8, EIDR(_) => 12, @@ -560,7 +444,7 @@ impl SegmentationUpid { } } - fn write_to(&self, out: &mut BitRecorder) -> anyhow::Result<()> { + pub(crate) fn write_to(&self, out: &mut BitRecorder) -> anyhow::Result<()> { use SegmentationUpid::*; let mut recorder = BitRecorder::::new(); @@ -614,13 +498,13 @@ impl SegmentationUpid { #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize))] -struct Component { +pub struct Component { component_tag: u8, pts_offset: u64, } impl Component { - fn write_to(&self, recorder: &mut BitRecorder) -> io::Result<()> { + pub(crate) fn write_to(&self, recorder: &mut BitRecorder) -> io::Result<()> { recorder.write(8, self.component_tag)?; recorder.write(7, 0x7f)?; recorder.write(33, self.pts_offset) @@ -686,7 +570,7 @@ impl Default for SegmentationType { } impl SegmentationType { - fn id(&self) -> u8 { + pub fn id(&self) -> u8 { use SegmentationType::*; match self { NotIndicated => 0x00, @@ -739,7 +623,7 @@ impl SegmentationType { } /// Reflects definitions on the Table 23 of the spec. - fn syntax(&self) -> SegmentationTypeSyntax { + pub fn syntax(&self) -> SegmentationTypeSyntax { use SegmentationFieldSyntax::*; use SegmentationType::*; @@ -1147,26 +1031,46 @@ impl TryFrom for SegmentationType { } } -enum SegmentationFieldSyntax { +#[cfg_attr(feature = "serde", derive(Serialize))] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum SegmentationFieldSyntax { Fixed(u8), ZeroOrNonZero, NonZero, NotUsed, } -struct SegmentationTypeSyntax { +#[cfg_attr(feature = "serde", derive(Serialize))] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct SegmentationTypeSyntax { segment_num: SegmentationFieldSyntax, segments_expected: SegmentationFieldSyntax, sub_segment_num: SegmentationFieldSyntax, sub_segments_expected: SegmentationFieldSyntax, } +impl SegmentationTypeSyntax { + pub fn segment_num(&self) -> SegmentationFieldSyntax { + self.segment_num + } + + pub fn segments_expected(&self) -> SegmentationFieldSyntax { + self.segments_expected + } + + pub fn sub_segment_num(&self) -> SegmentationFieldSyntax { + self.sub_segment_num + } + + pub fn sub_segments_expected(&self) -> SegmentationFieldSyntax { + self.sub_segments_expected + } +} + #[cfg(test)] mod tests { use super::*; use anyhow::Result; - use assert_json_diff::assert_json_eq; - use std::time::Duration; #[test] fn write_segmentation_upid_airing_id() -> Result<()> { @@ -1214,87 +1118,4 @@ 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": 27630000, - "segmentation_duration_secs": 307.0, - "segmentation_duration_human": "5m 7s", - "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 7687744..8f25922 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,9 +1,7 @@ use crate::commands::{SpliceCommand, SpliceCommandType}; use crate::descriptors::SpliceDescriptor; -use crate::{CueError, TransportPacketWrite}; use bitstream_io::{BigEndian, BitWrite, BitWriter}; use crc::{Crc, CRC_32_MPEG_2}; -use std::fmt; use std::fmt::{Display, Formatter}; pub const MPEG_2: Crc = Crc::::new(&CRC_32_MPEG_2); @@ -14,58 +12,58 @@ where C: SpliceCommand, S: EncodingState, { - state: SpliceInfoState, - encoded: S, + pub(crate) state: SpliceInfoState, + pub(crate) encoded: S, } #[derive(Debug, Clone, PartialEq)] -struct SpliceInfoState +pub(crate) struct SpliceInfoState where C: SpliceCommand, { /// This is an 8-bit field. Its value shall be 0xFC. - table_id: u8, + pub(crate) table_id: u8, /// The section_syntax_indicator is a 1-bit field that should always be set to ‘0’, indicating /// that MPEG short sections are to be used. - section_syntax_indicator: bool, + pub(crate) section_syntax_indicator: bool, /// This is a 1-bit flag that shall be set to 0. - private_indicator: bool, + pub(crate) private_indicator: bool, /// A two-bit field that indicates if the content preparation system has created a Stream /// Access Point (SAP) at the signaled point in the stream. SAP types are defined in /// ISO 14496-12, Annex I. The semantics of SAP types are further informatively elaborated /// in ISO/IEC 23009-1 DASH, Section 4.5.2. - sap_type: SAPType, // 2 bits + pub(crate) sap_type: SAPType, // 2 bits - protocol_version: u8, - encrypted_packet: bool, - encryption_algorithm: EncryptionAlgorithm, - pts_adjustment: u64, // 33 bits - cw_index: u8, - tier: u16, // 12 bits + pub(crate) protocol_version: u8, + pub(crate) encrypted_packet: bool, + pub(crate) encryption_algorithm: EncryptionAlgorithm, + pub(crate) pts_adjustment: u64, // 33 bits + pub(crate) cw_index: u8, + pub(crate) tier: u16, // 12 bits - splice_command: C, + pub(crate) splice_command: C, - descriptors: Vec, + pub(crate) descriptors: Vec, } pub trait EncodingState {} #[derive(Debug, Clone, Copy, PartialEq)] -struct NotEncoded; +pub(crate) struct NotEncoded; impl EncodingState for NotEncoded {} #[derive(Debug, Clone, PartialEq)] -struct EncodedData { - section_length: u16, - splice_command_length: u16, - splice_command_type: SpliceCommandType, - descriptor_loop_length: u16, - crc32: u32, - final_data: Vec, +pub(crate) struct EncodedData { + pub section_length: u16, + pub splice_command_length: u16, + pub splice_command_type: SpliceCommandType, + pub descriptor_loop_length: u16, + pub crc32: u32, + pub final_data: Vec, } impl EncodingState for EncodedData {} @@ -74,7 +72,7 @@ impl SpliceInfoSection where C: SpliceCommand, { - fn new(splice_command: C) -> Self { + pub fn new(splice_command: C) -> Self { Self { state: SpliceInfoState { table_id: 0xFC, @@ -277,82 +275,12 @@ impl From for u8 { } } -#[cfg(feature = "serde")] -mod serde_serialization { - use super::*; - use crate::ticks_to_secs; - use crate::time::format_duration; - use serde::ser::{Serialize, SerializeStruct, Serializer}; - use std::fmt::LowerHex; - use std::time::Duration; - - impl Serialize for SpliceInfoSection - where - C: SpliceCommand + Serialize, - { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - #[inline] - fn as_hex(value: T) -> String - where - T: LowerHex, - { - format!("0x{:x}", value) - } - - 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", - &self.state.section_syntax_indicator, - )?; - state.serialize_field("private_indicator", &self.state.private_indicator)?; - state.serialize_field("sap_type", &as_hex(self.state.sap_type as u8))?; - state.serialize_field("section_length", &self.encoded.section_length)?; - state.serialize_field("protocol_version", &self.state.protocol_version)?; - state.serialize_field("encrypted_packet", &self.state.encrypted_packet)?; - state.serialize_field( - "encryption_algorithm", - &u8::from(self.state.encryption_algorithm), - )?; - state.serialize_field("pts_adjustment", &self.state.pts_adjustment)?; - let pts_adjustment_secs = ticks_to_secs(self.state.pts_adjustment); - state.serialize_field("pts_adjustment_secs", &pts_adjustment_secs)?; - state.serialize_field( - "pts_adjustment_human", - &format_duration(Duration::from_secs_f64(pts_adjustment_secs)).to_string(), - )?; - 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)?; - state.serialize_field( - "splice_command_type", - &u8::from(self.encoded.splice_command_type), - )?; - state.serialize_field("splice_command_name", &self.encoded.splice_command_type)?; - state.serialize_field("splice_command", &self.state.splice_command)?; - state.serialize_field( - "descriptor_loop_length", - &self.encoded.descriptor_loop_length, - )?; - state.serialize_field("descriptors", &self.state.descriptors)?; - state.serialize_field("crc_32", &as_hex(self.encoded.crc32))?; - state.end() - } - } -} - #[cfg(test)] mod tests { use super::*; use crate::commands::*; use crate::descriptors::{SegmentationDescriptor, SegmentationType, SegmentationUpid}; - use crate::ClockTimeExt; use anyhow::Result; - use assert_json_diff::assert_json_eq; - use std::time::Duration; #[test] fn write_splice_null_as_base64() -> Result<()> { @@ -420,111 +348,4 @@ mod tests { ); Ok(()) } - - #[cfg(feature = "serde")] - #[test] - fn compliance_spec_14_1_example_time_signal_as_json() -> Result<()> { - 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": 54, - "protocol_version": 0, - "encrypted_packet": false, - "encryption_algorithm": 0, - "pts_adjustment": 0, - "pts_adjustment_secs": 0.0, - "pts_adjustment_human": "0s", - "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": 32, - "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": 27630000, - "segmentation_duration_secs": 307.0, - "segmentation_duration_human": "5m 7s", - "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": "0xb6c1a0f1" - }"#, - )?; - - assert_json_eq!( - serde_json::to_value(&spec_14_1_example_time_signal()?)?, - expected_json - ); - - Ok(()) - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_as_json() -> Result<()> { - let splice = SpliceInfoSection::new(SpliceNull::default()); - - 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": 17, - "protocol_version": 0, - "encrypted_packet": false, - "encryption_algorithm": 0, - "pts_adjustment": 0, - "pts_adjustment_secs": 0.0, - "pts_adjustment_human": "0s", - "cw_index": "0x0", - "tier": "0xfff", - "splice_command_length": 0, - "splice_command_type": 0, - "splice_command_name": "SpliceNull", - "splice_command": {}, - "descriptor_loop_length": 0, - "descriptors": [], - "crc_32": "0x7a4fbfff" - }) - ); - - Ok(()) - } } diff --git a/src/lib.rs b/src/lib.rs index 10f9ca5..73cbca3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,22 +3,18 @@ use std::io; use std::time::Duration; use thiserror::Error; -#[cfg(feature = "serde")] -use serde::{Serialize, Serializer}; - mod commands; mod descriptors; mod info; mod time; -pub use commands::SpliceNull; -pub use info::{EncryptionAlgorithm, SAPType, SpliceInfoSection}; +#[cfg(feature = "serde")] +mod serde; -pub trait TransportPacketWrite { - fn write_to(&self, buffer: &mut W) -> anyhow::Result - where - W: io::Write; -} +pub use commands::SpliceNull; +pub use descriptors::*; +pub use info::{EncryptionAlgorithm, SAPType, SpliceInfoSection}; +pub use time::SpliceTime; #[derive(Error, Debug)] #[error("Could not execute operation due to {0}")] @@ -44,11 +40,6 @@ impl ClockTimeExt for Duration { } } -/// Truncate to 6 decimal positions, as shown in the spec. -fn ticks_to_secs(value: u64) -> f64 { - (value as f64 / 90_000.0 * 1_000_000.0).ceil() as f64 / 1_000_000.0 -} - trait BytesWritten { fn bytes_written(&self) -> u32; } @@ -60,14 +51,6 @@ impl BytesWritten for BitRecorder { } } -#[cfg(feature = "serde")] -fn serialize_time(value: &u64, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_f64(ticks_to_secs(*value)) -} - #[cfg(test)] mod tests { use super::*; @@ -83,12 +66,4 @@ mod tests { let time = Duration::from_secs_f64(21388.766756); assert_eq!(time.to_90k(), 0x072bd0050); } - - #[test] - fn test_ticks_to_secs() { - let time = Duration::from_secs_f64(21388.766756); - assert_eq!(time.to_90k(), 0x072bd0050); - assert_eq!(ticks_to_secs(0x072bd0050), 21388.766756); - assert_eq!(ticks_to_secs(time.to_90k()), 21388.766756); - } } diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..806ef37 --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,637 @@ +use crate::commands::SpliceCommand; +use crate::descriptors::{SegmentationDescriptor, SegmentationUpid, SpliceDescriptorExt}; +use crate::info::EncodedData; +use crate::{SpliceDescriptor, SpliceInfoSection, SpliceTime}; +use ascii::AsciiStr; +use bitstream_io::{BigEndian, BitRecorder, BitWriter}; +use serde::ser::{Error, SerializeStruct}; +use serde::{Serialize, Serializer}; +use std::fmt; +use std::fmt::LowerHex; +use std::time::Duration; + +/// Truncate to 6 decimal positions, as shown in the spec. +fn ticks_to_secs(value: u64) -> f64 { + (value as f64 / 90_000.0 * 1_000_000.0).ceil() as f64 / 1_000_000.0 +} + +impl Serialize for SegmentationDescriptor { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use crate::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() { + let duration_secs = ticks_to_secs(self.segmentation_duration()); + state.serialize_field("segmentation_duration", &self.segmentation_duration())?; + state.serialize_field("segmentation_duration_secs", &duration_secs)?; + state.serialize_field( + "segmentation_duration_human", + &format_duration(Duration::from_secs_f64(duration_secs)).to_string(), + )?; + } + 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) | 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, we skip the first byte (which is the length) + _ => serializer.serialize_str(&format!("0x{}", hex::encode(&data[1..]))), + } + } +} + +impl Serialize for SpliceTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let num_fields = if self.time_specified_flag() { 3 } else { 1 }; + + let mut state = serializer.serialize_struct("SpliceTime", num_fields)?; + state.serialize_field("time_specified_flag", &self.time_specified_flag())?; + if self.time_specified_flag() { + state.serialize_field("pts_time", &self.pts_time().unwrap_or(0))?; + state.serialize_field( + "pts_time_secs", + &ticks_to_secs(self.pts_time().unwrap_or(0)), + )?; + } + state.end() + } +} + +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)), + } + } +} + +impl Serialize for SpliceInfoSection +where + C: SpliceCommand + Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + #[inline] + fn as_hex(value: T) -> String + where + T: LowerHex, + { + format!("0x{:x}", value) + } + + 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", + &self.state.section_syntax_indicator, + )?; + state.serialize_field("private_indicator", &self.state.private_indicator)?; + state.serialize_field("sap_type", &as_hex(self.state.sap_type as u8))?; + state.serialize_field("section_length", &self.encoded.section_length)?; + state.serialize_field("protocol_version", &self.state.protocol_version)?; + state.serialize_field("encrypted_packet", &self.state.encrypted_packet)?; + state.serialize_field( + "encryption_algorithm", + &u8::from(self.state.encryption_algorithm), + )?; + state.serialize_field("pts_adjustment", &self.state.pts_adjustment)?; + let pts_adjustment_secs = ticks_to_secs(self.state.pts_adjustment); + state.serialize_field("pts_adjustment_secs", &pts_adjustment_secs)?; + state.serialize_field( + "pts_adjustment_human", + &format_duration(Duration::from_secs_f64(pts_adjustment_secs)).to_string(), + )?; + 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)?; + state.serialize_field( + "splice_command_type", + &u8::from(self.encoded.splice_command_type), + )?; + state.serialize_field("splice_command_name", &self.encoded.splice_command_type)?; + state.serialize_field("splice_command", &self.state.splice_command)?; + state.serialize_field( + "descriptor_loop_length", + &self.encoded.descriptor_loop_length, + )?; + state.serialize_field("descriptors", &self.state.descriptors)?; + state.serialize_field("crc_32", &as_hex(self.encoded.crc32))?; + state.end() + } +} + +// Copyright (c) 2016 The humantime Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/// Formats duration into a human-readable string +/// +/// Note: this format is guaranteed to have same value when using +/// parse_duration, but we can change some details of the exact composition +/// of the value. +pub(crate) fn format_duration(val: Duration) -> FormattedDuration { + FormattedDuration(val) +} + +fn item_plural(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u64) -> fmt::Result { + if value > 0 { + if *started { + f.write_str(" ")?; + } + write!(f, "{}{}", value, name)?; + if value > 1 { + f.write_str("s")?; + } + *started = true; + } + Ok(()) +} + +fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32) -> fmt::Result { + if value > 0 { + if *started { + f.write_str(" ")?; + } + write!(f, "{}{}", value, name)?; + *started = true; + } + Ok(()) +} + +/// A wrapper type that allows you to Display a Duration +#[derive(Debug, Clone)] +pub(crate) struct FormattedDuration(Duration); + +impl fmt::Display for FormattedDuration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let secs = self.0.as_secs(); + let nanos = self.0.subsec_nanos(); + + if secs == 0 && nanos == 0 { + f.write_str("0s")?; + return Ok(()); + } + + let years = secs / 31_557_600; // 365.25d + let ydays = secs % 31_557_600; + let months = ydays / 2_630_016; // 30.44d + let mdays = ydays % 2_630_016; + let days = mdays / 86400; + let day_secs = mdays % 86400; + let hours = day_secs / 3600; + let minutes = day_secs % 3600 / 60; + let seconds = day_secs % 60; + + let millis = nanos / 1_000_000; + let micros = nanos / 1000 % 1000; + let nanosec = nanos % 1000; + + let started = &mut false; + item_plural(f, started, "year", years)?; + item_plural(f, started, "month", months)?; + item_plural(f, started, "day", days)?; + item(f, started, "h", hours as u32)?; + item(f, started, "m", minutes as u32)?; + item(f, started, "s", seconds as u32)?; + item(f, started, "milli", millis)?; + item(f, started, "us", micros)?; + item(f, started, "ns", nanosec)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::commands::TimeSignal; + use crate::descriptors::SegmentationType; + use crate::{ClockTimeExt, SpliceNull}; + use anyhow::Result; + use assert_json_diff::assert_json_eq; + use std::time::Duration; + + #[test] + fn test_ticks_to_secs() { + let time = Duration::from_secs_f64(21388.766756); + assert_eq!(time.to_90k(), 0x072bd0050); + assert_eq!(ticks_to_secs(0x072bd0050), 21388.766756); + assert_eq!(ticks_to_secs(time.to_90k()), 21388.766756); + } + + #[test] + fn serialize_splice_null() -> Result<()> { + let splice_null = SpliceNull::default(); + assert_json_eq!(serde_json::to_value(&splice_null)?, serde_json::json!({})); + Ok(()) + } + + #[test] + fn serialize_time_signal_without_time() -> Result<()> { + let time_signal = TimeSignal::default(); + assert_json_eq!( + serde_json::to_value(&time_signal)?, + serde_json::json!({ + "time_specified_flag": false + }) + ); + Ok(()) + } + + #[test] + fn serialize_time_signal_with_time() -> Result<()> { + let time_signal = TimeSignal::from(Duration::from_secs(10)); + assert_json_eq!( + serde_json::to_value(&time_signal)?, + serde_json::json!({ + "time_specified_flag": true, + "pts_time": 900000, + "pts_time_secs": 10.0 + }) + ); + Ok(()) + } + + #[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": 27630000, + "segmentation_duration_secs": 307.0, + "segmentation_duration_human": "5m 7s", + "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(()) + } + + #[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(()) + } + + #[test] + fn compliance_spec_14_1_example_time_signal_as_json() -> Result<()> { + let mut splice = SpliceInfoSection::new(TimeSignal::from(0x072bd0050u64)); + splice.set_cw_index(0xff); + + 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(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); + + 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": 54, + "protocol_version": 0, + "encrypted_packet": false, + "encryption_algorithm": 0, + "pts_adjustment": 0, + "pts_adjustment_secs": 0.0, + "pts_adjustment_human": "0s", + "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": 1924989008, + "pts_time_secs": 21388.766756 + }, + "descriptor_loop_length": 32, + "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": 27630000, + "segmentation_duration_secs": 307.0, + "segmentation_duration_human": "5m 7s", + "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": "0xb6c1a0f1" + }"#, + )?; + + assert_json_eq!( + serde_json::to_value(&splice.into_encoded()?)?, + expected_json + ); + + Ok(()) + } + + #[test] + fn serialize_info_section_as_json() -> Result<()> { + let splice = SpliceInfoSection::new(SpliceNull::default()); + + 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": 17, + "protocol_version": 0, + "encrypted_packet": false, + "encryption_algorithm": 0, + "pts_adjustment": 0, + "pts_adjustment_secs": 0.0, + "pts_adjustment_human": "0s", + "cw_index": "0x0", + "tier": "0xfff", + "splice_command_length": 0, + "splice_command_type": 0, + "splice_command_name": "SpliceNull", + "splice_command": {}, + "descriptor_loop_length": 0, + "descriptors": [], + "crc_32": "0x7a4fbfff" + }) + ); + + Ok(()) + } +} diff --git a/src/time.rs b/src/time.rs index d89f5a6..e56fe9d 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,27 +1,19 @@ -use crate::{BytesWritten, ClockTimeExt, CueError, TransportPacketWrite}; +use crate::{BytesWritten, ClockTimeExt}; use bitstream_io::{BigEndian, BitRecorder, BitWrite, BitWriter}; -#[cfg(feature = "serde")] -use serde::{Serialize, Serializer}; -use std::time::Duration; -use std::{fmt, io}; +use std::io; -#[cfg_attr(feature = "serde", derive(Serialize))] +#[derive(Debug, Clone, PartialEq, Default)] pub struct SpliceTime { time_specified_flag: bool, - #[cfg_attr(feature = "serde", serde(serialize_with = "crate::serialize_time"))] pts_time: u64, + + // Size of the SpliceTime structure after encoding + pub(crate) bytes_length: Option, } impl SpliceTime { - pub fn new() -> Self { - Self { - time_specified_flag: false, - pts_time: 0, - } - } - pub fn from_ticks(ticks: u64) -> Self { - let mut splice_time = Self::new(); + let mut splice_time = Self::default(); splice_time.set_pts_time(Some(ticks)); splice_time } @@ -54,10 +46,8 @@ impl SpliceTime { None } } -} -impl TransportPacketWrite for SpliceTime { - fn write_to(&self, buffer: &mut W) -> anyhow::Result + pub(crate) fn write_to(&mut self, buffer: &mut W) -> anyhow::Result where W: io::Write, { @@ -74,130 +64,19 @@ impl TransportPacketWrite for SpliceTime { let mut buffer = BitWriter::endian(buffer, BigEndian); recorder.playback(&mut buffer)?; + self.bytes_length = Some(recorder.bytes_written()); + Ok(recorder.bytes_written()) } } impl From for SpliceTime - where - T: ClockTimeExt, +where + T: ClockTimeExt, { fn from(pts: T) -> Self { - let mut t = Self::new(); - t.set_pts(Some(pts)); + let mut t = Self::default(); + t.set_pts_time(Some(pts)); t } } - -// Copyright (c) 2016 The humantime Developers -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -/// Formats duration into a human-readable string -/// -/// Note: this format is guaranteed to have same value when using -/// parse_duration, but we can change some details of the exact composition -/// of the value. -/// -/// # Examples -/// -/// ``` -/// use std::time::Duration; -/// use humantime::format_duration; -/// -/// let val1 = Duration::new(9420, 0); -/// assert_eq!(format_duration(val1).to_string(), "2h 37m"); -/// let val2 = Duration::new(0, 32_000_000); -/// assert_eq!(format_duration(val2).to_string(), "32ms"); -/// ``` -pub(crate) fn format_duration(val: Duration) -> FormattedDuration { - FormattedDuration(val) -} - -fn item_plural(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u64) -> fmt::Result { - if value > 0 { - if *started { - f.write_str(" ")?; - } - write!(f, "{}{}", value, name)?; - if value > 1 { - f.write_str("s")?; - } - *started = true; - } - Ok(()) -} -fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32) -> fmt::Result { - if value > 0 { - if *started { - f.write_str(" ")?; - } - write!(f, "{}{}", value, name)?; - *started = true; - } - Ok(()) -} - -/// A wrapper type that allows you to Display a Duration -#[derive(Debug, Clone)] -pub(crate) struct FormattedDuration(Duration); - -impl FormattedDuration { - /// Returns a reference to the [`Duration`][] that is being formatted. - pub fn get_ref(&self) -> &Duration { - &self.0 - } -} - -impl fmt::Display for FormattedDuration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let secs = self.0.as_secs(); - let nanos = self.0.subsec_nanos(); - - if secs == 0 && nanos == 0 { - f.write_str("0s")?; - return Ok(()); - } - - let years = secs / 31_557_600; // 365.25d - let ydays = secs % 31_557_600; - let months = ydays / 2_630_016; // 30.44d - let mdays = ydays % 2_630_016; - let days = mdays / 86400; - let day_secs = mdays % 86400; - let hours = day_secs / 3600; - let minutes = day_secs % 3600 / 60; - let seconds = day_secs % 60; - - let millis = nanos / 1_000_000; - let micros = nanos / 1000 % 1000; - let nanosec = nanos % 1000; - - let started = &mut false; - item_plural(f, started, "year", years)?; - item_plural(f, started, "month", months)?; - item_plural(f, started, "day", days)?; - item(f, started, "h", hours as u32)?; - item(f, started, "m", minutes as u32)?; - item(f, started, "s", seconds as u32)?; - item(f, started, "milli", millis)?; - item(f, started, "us", micros)?; - item(f, started, "ns", nanosec)?; - Ok(()) - } -}