Support descriptors serialization

This commit is contained in:
Rafael Caricio 2022-05-03 16:53:28 +02:00
parent fa917dafce
commit 00a28859c7
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
7 changed files with 528 additions and 59 deletions

View file

@ -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
- [x] CRC calculation

View file

@ -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<T>(&mut self, pts: Option<T>)
where
T: ClockTimeExt,
{
self.0.set_pts_time(pts);
}
}
@ -68,9 +71,14 @@ impl SpliceCommand for TimeSignal {
}
}
impl From<Duration> for TimeSignal {
fn from(duration: Duration) -> Self {
Self(duration.into())
impl<T> From<T> for TimeSignal
where
T: ClockTimeExt,
{
fn from(pts: T) -> Self {
let mut t = Self::new();
t.set_pts(Some(pts));
t
}
}

View file

@ -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<u8>),
}
#[cfg(feature = "serde")]
mod serde_serialization {
use super::*;
use serde::ser::{Error, Serialize, SerializeStruct, Serializer};
impl Serialize for SpliceDescriptor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<W>(&self, buffer: &mut W) -> anyhow::Result<()>
impl SpliceDescriptor {
pub(crate) fn write_to<W>(&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<SegmentationDescriptor> for SpliceDescriptor {

View file

@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use SegmentationFieldSyntax::*;
#[inline]
fn as_hex<T>(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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use SegmentationUpid::*;
let mut recorder = BitRecorder::<u32, BigEndian>::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<W>(&self, buffer: &mut W) -> anyhow::Result<()>
pub(crate) fn len(&self) -> u8 {
self.descriptor_length
}
pub(crate) fn write_to<W>(&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<SegmentationUpidType> 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<u32, BigEndian>) -> 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<u8> 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(())
}
}

View file

@ -131,15 +131,17 @@ impl<C> SpliceInfoSection<C, NotEncoded>
where
C: SpliceCommand,
{
pub fn into_encoded(self) -> anyhow::Result<SpliceInfoSection<C, EncodedData>> {
pub fn into_encoded(mut self) -> anyhow::Result<SpliceInfoSection<C, EncodedData>> {
// 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,

View file

@ -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
}

View file

@ -27,18 +27,33 @@ impl SpliceTime {
}
#[inline]
pub fn set_pts_time(&mut self, pts_time: Option<u64>) {
pub fn set_pts_time<T>(&mut self, pts_time: Option<T>)
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<u64> {
if self.time_specified_flag {
Some(self.pts_time)
} else {
None
}
}
}
impl TransportPacketWrite for SpliceTime {