Initial segmentation descriptor implementation

This commit is contained in:
Rafael Caricio 2022-05-02 19:45:05 +02:00
parent 3ffcca82c0
commit 1f1ee312c0
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
7 changed files with 825 additions and 74 deletions

View file

@ -13,8 +13,8 @@ crc = "3.0"
thiserror = "1" thiserror = "1"
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }
hex = { version = "0.4", default-features = false, features = ["alloc"] } hex = { version = "0.4", default-features = false, features = ["alloc"] }
anyhow = "1"
[dev-dependencies] [dev-dependencies]
serde_json = "1" serde_json = "1"
anyhow = "1"
assert-json-diff = "2" assert-json-diff = "2"

View file

@ -14,6 +14,7 @@ pub trait SpliceCommand: TransportPacketWrite {
} }
#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpliceNull {} pub struct SpliceNull {}
impl SpliceNull { impl SpliceNull {
@ -23,7 +24,7 @@ impl SpliceNull {
} }
impl TransportPacketWrite for SpliceNull { impl TransportPacketWrite for SpliceNull {
fn write_to<W>(&self, _: &mut W) -> Result<(), CueError> fn write_to<W>(&self, _: &mut W) -> anyhow::Result<()>
where where
W: io::Write, W: io::Write,
{ {
@ -53,7 +54,7 @@ impl TimeSignal {
impl TransportPacketWrite for TimeSignal { impl TransportPacketWrite for TimeSignal {
#[inline] #[inline]
fn write_to<W>(&self, buffer: &mut W) -> Result<(), CueError> fn write_to<W>(&self, buffer: &mut W) -> anyhow::Result<()>
where where
W: Write, W: Write,
{ {
@ -67,8 +68,14 @@ impl SpliceCommand for TimeSignal {
} }
} }
impl From<Duration> for TimeSignal {
fn from(duration: Duration) -> Self {
Self(duration.into())
}
}
#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum SpliceCommandType { pub enum SpliceCommandType {
SpliceNull, SpliceNull,
SpliceSchedule, SpliceSchedule,

View file

@ -18,22 +18,16 @@ pub enum SpliceDescriptor {
Unknown(u8, u32, Vec<u8>), Unknown(u8, u32, Vec<u8>),
} }
impl SpliceDescriptor { pub(crate) trait SpliceDescriptorExt {
fn splice_descriptor_tag(&self) -> u8 { fn splice_descriptor_tag(&self) -> u8;
use SpliceDescriptor::*;
match self { fn identifier(&self) -> u32 {
Segmentation(_) => SpliceDescriptorTag::Segmentation.into(), 0x43554549 // ASCII "CUEI"
Avail => SpliceDescriptorTag::Avail.into(),
DTMF => SpliceDescriptorTag::DTMF.into(),
Time => SpliceDescriptorTag::Time.into(),
Audio => SpliceDescriptorTag::Audio.into(),
Unknown(tag, _, _) => *tag,
}
} }
} }
impl TransportPacketWrite for SpliceDescriptor { impl TransportPacketWrite for SpliceDescriptor {
fn write_to<W>(&self, buffer: &mut W) -> Result<(), CueError> fn write_to<W>(&self, buffer: &mut W) -> anyhow::Result<()>
where where
W: io::Write, W: io::Write,
{ {

View file

@ -1,49 +1,138 @@
use crate::{CueError, TransportPacketWrite}; use crate::{CueError, TransportPacketWrite};
use anyhow::Context;
use bitstream_io::{BigEndian, BitWrite, BitWriter};
use std::ffi::CStr;
use std::fmt::{Display, Formatter};
use std::io; use std::io;
use std::io::Write;
use crate::descriptors::{SpliceDescriptorExt, SpliceDescriptorTag};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::Serialize; use serde::Serialize;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", derive(Serialize))]
pub struct SegmentationDescriptor { pub struct SegmentationDescriptor {
identifier: u32,
segmentation_event_id: u32, segmentation_event_id: u32,
segmentation_event_cancel_indicator: bool, segmentation_event_cancel_indicator: bool,
program_segmentation: Vec<Component>, program_segmentation_flag: bool,
delivery_restricted: Option<DeliveryRestriction>, segmentation_duration_flag: bool,
segmentation_duration: Option<u64>, delivery_not_restricted_flag: bool,
segmentation_upid: SegmentationUpid,
}
impl TransportPacketWrite for SegmentationDescriptor {
fn write_to<W>(&self, buffer: &mut W) -> Result<(), CueError>
where
W: io::Write,
{
todo!()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
struct DeliveryRestriction {
web_delivery_allowed_flag: bool, web_delivery_allowed_flag: bool,
no_regional_blackout_flag: bool, no_regional_blackout_flag: bool,
archive_allowed_flag: bool, archive_allowed_flag: bool,
device_restrictions: DeviceRestrictions, device_restrictions: DeviceRestrictions,
components: Vec<Component>,
segmentation_duration: u64,
segmentation_upid: SegmentationUpid,
segmentation_type: SegmentationType,
segment_num: u8,
segments_expected: u8,
sub_segment_num: u8,
sub_segments_expected: u8,
}
impl TransportPacketWrite for SegmentationDescriptor {
fn write_to<W>(&self, buffer: &mut W) -> anyhow::Result<()>
where
W: io::Write,
{
use SegmentationFieldSyntax::*;
let mut data = Vec::new();
let mut internal_buffer = BitWriter::endian(&mut data, BigEndian);
internal_buffer.write(32, self.identifier())?;
internal_buffer.write(32, self.segmentation_event_id)?;
internal_buffer.write_bit(self.segmentation_event_cancel_indicator)?;
internal_buffer.write(7, 0x7f)?;
if !self.segmentation_event_cancel_indicator {
internal_buffer.write_bit(self.program_segmentation_flag)?;
internal_buffer.write_bit(self.segmentation_duration_flag)?;
internal_buffer.write_bit(self.delivery_not_restricted_flag)?;
if !self.delivery_not_restricted_flag {
internal_buffer.write_bit(self.web_delivery_allowed_flag)?;
internal_buffer.write_bit(self.no_regional_blackout_flag)?;
internal_buffer.write_bit(self.archive_allowed_flag)?;
internal_buffer.write(2, self.device_restrictions as u8)?;
} else {
internal_buffer.write(5, 0x1f)?;
}
if !self.program_segmentation_flag {
internal_buffer.write(8, self.components.len() as u8)?;
for component in &self.components {
component.write_to(&mut internal_buffer)?;
}
}
if self.segmentation_duration_flag {
internal_buffer.write(40, self.segmentation_duration)?;
}
internal_buffer.write(8, u8::from(self.segmentation_upid.segmentation_upid_type()))?;
self.segmentation_upid.write_to(&mut internal_buffer)?;
internal_buffer.write(8, self.segmentation_type.id())?;
let s = self.segmentation_type.syntax();
match s.segment_num {
Fixed(n) => internal_buffer.write(8, n)?,
NonZero | ZeroOrNonZero => internal_buffer.write(8, self.segment_num)?, // needs to check for non-zero
NotUsed => internal_buffer.write(8, 0u8)?,
}
match s.segments_expected {
Fixed(n) => internal_buffer.write(8, n)?,
NonZero | ZeroOrNonZero => internal_buffer.write(8, self.segments_expected)?, // needs to check for non-zero
NotUsed => internal_buffer.write(8, 0u8)?,
}
match s.sub_segment_num {
Fixed(n) => internal_buffer.write(8, n)?,
NonZero | ZeroOrNonZero => internal_buffer.write(8, self.sub_segment_num)?, // needs to check for non-zero
NotUsed => {}
}
match s.sub_segments_expected {
Fixed(n) => internal_buffer.write(8, n)?,
NonZero | ZeroOrNonZero => internal_buffer.write(8, self.sub_segments_expected)?, // needs to check for non-zero
NotUsed => {}
}
}
internal_buffer.flush()?;
let mut buffer = BitWriter::endian(buffer, BigEndian);
buffer.write(8, self.splice_descriptor_tag())?;
buffer.write(8, data.len() as u8)?;
buffer.write_bytes(data.as_slice())?;
Ok(())
}
}
impl SpliceDescriptorExt for SegmentationDescriptor {
fn splice_descriptor_tag(&self) -> u8 {
SpliceDescriptorTag::Segmentation.into()
}
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", derive(Serialize))]
#[repr(u8)]
enum DeviceRestrictions { enum DeviceRestrictions {
RestrictGroup0 = 0x00, /// This Segment is restricted for a class of devices defined by an out of band message that
RestrictGroup1 = 0x01, /// describes which devices are excluded.
RestrictGroup2 = 0x10, RestrictGroup0 = 0b00,
None = 0x11,
/// This Segment is restricted for a class of devices defined by an out of band message that
/// describes which devices are excluded.
RestrictGroup1 = 0b01,
/// This Segment is restricted for a class of devices defined by an out of band message that
/// describes which devices are excluded.
RestrictGroup2 = 0b10,
/// This Segment has no device restrictions.
None = 0b11,
} }
enum SegmentationUpidType { #[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum SegmentationUpidType {
NotUsed, NotUsed,
UserDefinedDeprecated, UserDefinedDeprecated,
ISCI, ISCI,
@ -52,7 +141,7 @@ enum SegmentationUpidType {
ISANDeprecated, ISANDeprecated,
ISAN, ISAN,
TID, TID,
TI, AiringID,
ADI, ADI,
EIDR, EIDR,
ATSCContentIdentifier, ATSCContentIdentifier,
@ -62,12 +151,67 @@ enum SegmentationUpidType {
URI, URI,
UUID, UUID,
SCR, SCR,
Reserved, Reserved(u8),
}
impl From<SegmentationUpidType> for u8 {
fn from(s: SegmentationUpidType) -> Self {
use SegmentationUpidType::*;
match s {
NotUsed => 0x00,
UserDefinedDeprecated => 0x01,
ISCI => 0x02,
AdID => 0x03,
UMID => 0x04,
ISANDeprecated => 0x05,
ISAN => 0x06,
TID => 0x07,
AiringID => 0x08,
ADI => 0x09,
EIDR => 0x0A,
ATSCContentIdentifier => 0x0B,
MPU => 0x0C,
MID => 0x0D,
ADSInformation => 0x0E,
URI => 0x0F,
UUID => 0x10,
SCR => 0x11,
Reserved(x) => x,
}
}
}
impl Display for SegmentationUpidType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use SegmentationUpidType::*;
match self {
NotUsed => write!(f, "Not Used"),
UserDefinedDeprecated => write!(f, "User Defined Deprecated"),
ISCI => write!(f, "ISCI"),
AdID => write!(f, "Ad-ID"),
UMID => write!(f, "UMID"),
ISANDeprecated => write!(f, "ISAN Deprecated"),
ISAN => write!(f, "ISAN"),
TID => write!(f, "TID"),
AiringID => write!(f, "AiringID"),
ADI => write!(f, "ADI"),
EIDR => write!(f, "EIDR"),
ATSCContentIdentifier => write!(f, "ATSC Content Identifier"),
MPU => write!(f, "MPU()"),
MID => write!(f, "MID()"),
ADSInformation => write!(f, "ADS Information"),
URI => write!(f, "URI"),
UUID => write!(f, "UUID"),
SCR => write!(f, "SCR"),
Reserved(_) => write!(f, "Reserved"),
}
}
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", derive(Serialize))]
enum SegmentationUpid { #[non_exhaustive]
pub enum SegmentationUpid {
NotUsed, NotUsed,
UserDefinedDeprecated, UserDefinedDeprecated,
ISCI, ISCI,
@ -76,7 +220,7 @@ enum SegmentationUpid {
ISANDeprecated, ISANDeprecated,
ISAN, ISAN,
TID, TID,
TI, AiringID(u64),
ADI, ADI,
EIDR, EIDR,
ATSCContentIdentifier, ATSCContentIdentifier,
@ -86,7 +230,71 @@ enum SegmentationUpid {
URI, URI,
UUID, UUID,
SCR, SCR,
Reserved, Reserved(u8),
}
impl SegmentationUpid {
pub fn segmentation_upid_type(&self) -> SegmentationUpidType {
use SegmentationUpid::*;
match self {
NotUsed => SegmentationUpidType::NotUsed,
UserDefinedDeprecated => SegmentationUpidType::UserDefinedDeprecated,
ISCI => SegmentationUpidType::ISCI,
AdID => SegmentationUpidType::AdID,
UMID => SegmentationUpidType::UMID,
ISANDeprecated => SegmentationUpidType::ISANDeprecated,
ISAN => SegmentationUpidType::ISAN,
TID => SegmentationUpidType::TID,
AiringID(_) => SegmentationUpidType::AiringID,
ADI => SegmentationUpidType::ADI,
EIDR => SegmentationUpidType::EIDR,
ATSCContentIdentifier => SegmentationUpidType::ATSCContentIdentifier,
MPU => SegmentationUpidType::MPU,
MID => SegmentationUpidType::MID,
ADSInformation => SegmentationUpidType::ADSInformation,
URI => SegmentationUpidType::URI,
UUID => SegmentationUpidType::UUID,
SCR => SegmentationUpidType::SCR,
Reserved(r) => SegmentationUpidType::Reserved(*r),
}
}
fn write_to<W>(&self, out: &mut BitWriter<W, BigEndian>) -> io::Result<()>
where
W: io::Write,
{
use SegmentationUpid::*;
let mut data = Vec::new();
let mut buffer = BitWriter::endian(&mut data, BigEndian);
match self {
// URI(uri) => {
// let raw_value = CStr::from_bytes_with_nul("https://link.com".as_bytes()).unwrap();
// buffer.write_bytes(raw_value.to_bytes())?;
// }
AiringID(aid) => {
// 8 bytes is 64 bits
buffer.write(64, *aid)?;
}
_ => {}
}
buffer.flush()?;
match self {
// All variants with variable length use the same write logic
UserDefinedDeprecated | URI | AiringID(_) => {
out.write(8, data.len() as u8)?;
out.write_bytes(data.as_slice())?;
}
_ => {
out.write(8, 0x0)?;
}
}
Ok(())
}
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -95,3 +303,533 @@ struct Component {
component_tag: u8, component_tag: u8,
pts_offset: u64, pts_offset: u64,
} }
impl Component {
fn write_to<W>(&self, buffer: &mut BitWriter<W, BigEndian>) -> io::Result<()>
where
W: io::Write,
{
buffer.write(8, self.component_tag)?;
buffer.write(7, 0x7f)?;
buffer.write(33, self.pts_offset)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[non_exhaustive]
pub enum SegmentationType {
NotIndicated,
ContentIdentification,
ProgramStart,
ProgramEnd,
ProgramEarlyTermination,
ProgramBreakaway,
ProgramResumption,
ProgramRunoverPlanned,
ProgramRunoverUnplanned,
ProgramOverlapStart,
ProgramBlackoutOverride,
ProgramJoin,
ChapterStart,
ChapterEnd,
BreakStart,
BreakEnd,
OpeningCreditStartDeprecated,
OpeningCreditEndDeprecated,
ClosingCreditStartDeprecated,
ClosingCreditEndDeprecated,
ProviderAdvertisementStart,
ProviderAdvertisementEnd,
DistributorAdvertisementStart,
DistributorAdvertisementEnd,
ProviderPlacementOpportunityStart,
ProviderPlacementOpportunityEnd,
DistributorPlacementOpportunityStart,
DistributorPlacementOpportunityEnd,
ProviderOverlayPlacementOpportunityStart,
ProviderOverlayPlacementOpportunityEnd,
DistributorOverlayPlacementOpportunityStart,
DistributorOverlayPlacementOpportunityEnd,
ProviderPromoStart,
ProviderPromoEnd,
DistributorPromoStart,
DistributorPromoEnd,
UnscheduledEventStart,
UnscheduledEventEnd,
AlternateContentOpportunityStart,
AlternateContentOpportunityEnd,
ProviderAdBlockStart,
ProviderAdBlockEnd,
DistributorAdBlockStart,
DistributorAdBlockEnd,
NetworkStart,
NetworkEnd,
}
impl SegmentationType {
fn id(&self) -> u8 {
use SegmentationType::*;
match self {
NotIndicated => 0x00,
ContentIdentification => 0x01,
ProgramStart => 0x10,
ProgramEnd => 0x11,
ProgramEarlyTermination => 0x12,
ProgramBreakaway => 0x13,
ProgramResumption => 0x14,
ProgramRunoverPlanned => 0x15,
ProgramRunoverUnplanned => 0x16,
ProgramOverlapStart => 0x17,
ProgramBlackoutOverride => 0x18,
ProgramJoin => 0x19,
ChapterStart => 0x20,
ChapterEnd => 0x21,
BreakStart => 0x22,
BreakEnd => 0x23,
OpeningCreditStartDeprecated => 0x24,
OpeningCreditEndDeprecated => 0x25,
ClosingCreditStartDeprecated => 0x26,
ClosingCreditEndDeprecated => 0x27,
ProviderAdvertisementStart => 0x30,
ProviderAdvertisementEnd => 0x31,
DistributorAdvertisementStart => 0x32,
DistributorAdvertisementEnd => 0x33,
ProviderPlacementOpportunityStart => 0x34,
ProviderPlacementOpportunityEnd => 0x35,
DistributorPlacementOpportunityStart => 0x36,
DistributorPlacementOpportunityEnd => 0x37,
ProviderOverlayPlacementOpportunityStart => 0x38,
ProviderOverlayPlacementOpportunityEnd => 0x39,
DistributorOverlayPlacementOpportunityStart => 0x3A,
DistributorOverlayPlacementOpportunityEnd => 0x3B,
ProviderPromoStart => 0x3C,
ProviderPromoEnd => 0x3D,
DistributorPromoStart => 0x3E,
DistributorPromoEnd => 0x3F,
UnscheduledEventStart => 0x40,
UnscheduledEventEnd => 0x41,
AlternateContentOpportunityStart => 0x42,
AlternateContentOpportunityEnd => 0x43,
ProviderAdBlockStart => 0x44,
ProviderAdBlockEnd => 0x45,
DistributorAdBlockStart => 0x46,
DistributorAdBlockEnd => 0x47,
NetworkStart => 0x50,
NetworkEnd => 0x51,
}
}
/// Reflects definitions on the Table 23 of the spec.
fn syntax(&self) -> SegmentationTypeSyntax {
use SegmentationFieldSyntax::*;
use SegmentationType::*;
match self {
NotIndicated => SegmentationTypeSyntax {
segment_num: Fixed(0),
segments_expected: Fixed(0),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ContentIdentification => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramStart => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramEnd => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramEarlyTermination => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramBreakaway => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramResumption => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramRunoverPlanned => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramRunoverUnplanned => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramOverlapStart => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramBlackoutOverride => SegmentationTypeSyntax {
segment_num: Fixed(0),
segments_expected: Fixed(0),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProgramJoin => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ChapterStart => SegmentationTypeSyntax {
segment_num: NonZero,
segments_expected: NonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ChapterEnd => SegmentationTypeSyntax {
segment_num: NonZero,
segments_expected: NonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
BreakStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
BreakEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
OpeningCreditStartDeprecated => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
OpeningCreditEndDeprecated => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ClosingCreditStartDeprecated => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ClosingCreditEndDeprecated => SegmentationTypeSyntax {
segment_num: Fixed(1),
segments_expected: Fixed(1),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderAdvertisementStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderAdvertisementEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorAdvertisementStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorAdvertisementEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderPlacementOpportunityStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: ZeroOrNonZero,
sub_segments_expected: ZeroOrNonZero,
},
ProviderPlacementOpportunityEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorPlacementOpportunityStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: ZeroOrNonZero,
sub_segments_expected: ZeroOrNonZero,
},
DistributorPlacementOpportunityEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderOverlayPlacementOpportunityStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: ZeroOrNonZero,
sub_segments_expected: ZeroOrNonZero,
},
ProviderOverlayPlacementOpportunityEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorOverlayPlacementOpportunityStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: ZeroOrNonZero,
sub_segments_expected: ZeroOrNonZero,
},
DistributorOverlayPlacementOpportunityEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderPromoStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderPromoEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorPromoStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorPromoEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
UnscheduledEventStart => SegmentationTypeSyntax {
segment_num: Fixed(0),
segments_expected: Fixed(0),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
UnscheduledEventEnd => SegmentationTypeSyntax {
segment_num: Fixed(0),
segments_expected: Fixed(0),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
AlternateContentOpportunityStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
AlternateContentOpportunityEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderAdBlockStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
ProviderAdBlockEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorAdBlockStart => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
DistributorAdBlockEnd => SegmentationTypeSyntax {
segment_num: ZeroOrNonZero,
segments_expected: ZeroOrNonZero,
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
NetworkStart => SegmentationTypeSyntax {
segment_num: Fixed(0),
segments_expected: Fixed(0),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
NetworkEnd => SegmentationTypeSyntax {
segment_num: Fixed(0),
segments_expected: Fixed(0),
sub_segment_num: NotUsed,
sub_segments_expected: NotUsed,
},
}
}
}
impl TryFrom<u8> for SegmentationType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
use SegmentationType::*;
match value {
0x00 => Ok(NotIndicated),
0x01 => Ok(ContentIdentification),
0x10 => Ok(ProgramStart),
0x11 => Ok(ProgramEnd),
0x12 => Ok(ProgramEarlyTermination),
0x13 => Ok(ProgramBreakaway),
0x14 => Ok(ProgramResumption),
0x15 => Ok(ProgramRunoverPlanned),
0x16 => Ok(ProgramRunoverUnplanned),
0x17 => Ok(ProgramOverlapStart),
0x18 => Ok(ProgramBlackoutOverride),
0x19 => Ok(ProgramJoin),
0x20 => Ok(ChapterStart),
0x21 => Ok(ChapterEnd),
0x22 => Ok(BreakStart),
0x23 => Ok(BreakEnd),
0x24 => Ok(OpeningCreditStartDeprecated),
0x25 => Ok(OpeningCreditEndDeprecated),
0x26 => Ok(ClosingCreditStartDeprecated),
0x27 => Ok(ClosingCreditEndDeprecated),
0x30 => Ok(ProviderAdvertisementStart),
0x31 => Ok(ProviderAdvertisementEnd),
0x32 => Ok(DistributorAdvertisementStart),
0x33 => Ok(DistributorAdvertisementEnd),
0x34 => Ok(ProviderPlacementOpportunityStart),
0x35 => Ok(ProviderPlacementOpportunityEnd),
0x36 => Ok(DistributorPlacementOpportunityStart),
0x37 => Ok(DistributorPlacementOpportunityEnd),
0x38 => Ok(ProviderOverlayPlacementOpportunityStart),
0x39 => Ok(ProviderOverlayPlacementOpportunityEnd),
0x3A => Ok(DistributorOverlayPlacementOpportunityStart),
0x3B => Ok(DistributorOverlayPlacementOpportunityEnd),
0x3C => Ok(ProviderPromoStart),
0x3D => Ok(ProviderPromoEnd),
0x3E => Ok(DistributorPromoStart),
0x3F => Ok(DistributorPromoEnd),
0x40 => Ok(UnscheduledEventStart),
0x41 => Ok(UnscheduledEventEnd),
0x42 => Ok(AlternateContentOpportunityStart),
0x43 => Ok(AlternateContentOpportunityEnd),
0x44 => Ok(ProviderAdBlockStart),
0x45 => Ok(ProviderAdBlockEnd),
0x46 => Ok(DistributorAdBlockStart),
0x47 => Ok(DistributorAdBlockEnd),
0x50 => Ok(NetworkStart),
0x51 => Ok(NetworkEnd),
_ => Err(()),
}
}
}
enum SegmentationFieldSyntax {
Fixed(u8),
ZeroOrNonZero,
NonZero,
NotUsed,
}
struct SegmentationTypeSyntax {
segment_num: SegmentationFieldSyntax,
segments_expected: SegmentationFieldSyntax,
sub_segment_num: SegmentationFieldSyntax,
sub_segments_expected: SegmentationFieldSyntax,
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use std::time::Duration;
#[test]
fn write_segmentation_upid_airing_id() -> Result<()> {
let mut data = Vec::new();
let mut buffer = BitWriter::endian(&mut data, BigEndian);
let segmentation_upid = SegmentationUpid::AiringID(0x2ca0a18a);
segmentation_upid.write_to(&mut buffer)?;
// length (1 byte) + data (8 bytes)
assert_eq!(data.len(), 9);
let hex = hex::encode(data[1..].to_vec());
assert_eq!(hex, "000000002ca0a18a".to_string());
Ok(())
}
#[test]
fn write_segmentation_descriptor() -> Result<()> {
let mut data = Vec::new();
let segmentation_descriptor = SegmentationDescriptor {
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: DeviceRestrictions::None,
components: vec![],
segmentation_duration: 27630000,
segmentation_upid: SegmentationUpid::AiringID(0x2ca0a18a),
segmentation_type: SegmentationType::ProviderPlacementOpportunityStart,
segment_num: 2,
segments_expected: 0,
sub_segment_num: 154,
sub_segments_expected: 201,
};
segmentation_descriptor.write_to(&mut data)?;
let hex = hex::encode(data.as_slice());
assert_eq!(
hex,
"021e435545494800008e7fcf0001a599b00808000000002ca0a18a3402009ac9".to_string()
);
Ok(())
}
}

View file

@ -1,9 +1,9 @@
use std::fmt;
use crate::commands::{SpliceCommand, SpliceCommandType}; use crate::commands::{SpliceCommand, SpliceCommandType};
use crate::descriptors::SpliceDescriptor; use crate::descriptors::SpliceDescriptor;
use crate::{CueError, TransportPacketWrite}; use crate::{CueError, TransportPacketWrite};
use bitstream_io::{BigEndian, BitWrite, BitWriter}; use bitstream_io::{BigEndian, BitWrite, BitWriter};
use crc::{Crc, CRC_32_MPEG_2}; use crc::{Crc, CRC_32_MPEG_2};
use std::fmt;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
pub const MPEG_2: Crc<u32> = Crc::<u32>::new(&CRC_32_MPEG_2); pub const MPEG_2: Crc<u32> = Crc::<u32>::new(&CRC_32_MPEG_2);
@ -106,6 +106,10 @@ where
self.state.tier = tier; self.state.tier = tier;
} }
pub fn set_cw_index(&mut self, cw_index: u8) {
self.state.cw_index = cw_index;
}
pub fn add_descriptor(&mut self, descriptor: SpliceDescriptor) { pub fn add_descriptor(&mut self, descriptor: SpliceDescriptor) {
self.state.descriptors.push(descriptor); self.state.descriptors.push(descriptor);
} }
@ -127,7 +131,7 @@ impl<C> SpliceInfoSection<C, NotEncoded>
where where
C: SpliceCommand, C: SpliceCommand,
{ {
pub fn into_encoded(self) -> Result<SpliceInfoSection<C, EncodedData>, CueError> { pub fn into_encoded(self) -> anyhow::Result<SpliceInfoSection<C, EncodedData>> {
// Write splice command to a temporary buffer // Write splice command to a temporary buffer
let mut splice_data = Vec::new(); let mut splice_data = Vec::new();
self.state.splice_command.write_to(&mut splice_data)?; self.state.splice_command.write_to(&mut splice_data)?;
@ -180,7 +184,7 @@ where
if self.state.encrypted_packet { if self.state.encrypted_packet {
// TODO: alignment stuffing here, in case of DES encryption this needs to be 8 bytes aligned // TODO: alignment stuffing here, in case of DES encryption this needs to be 8 bytes aligned
// encrypted_packet_crc32: // encrypted_packet_crc32:
buffer.write(32, 0)?; buffer.write(32, u32::MAX)?;
} }
let crc32 = MPEG_2.checksum(data.as_slice()); let crc32 = MPEG_2.checksum(data.as_slice());
buffer.write(32, crc32)?; buffer.write(32, crc32)?;
@ -369,6 +373,7 @@ mod tests {
#[test] #[test]
fn compliance_spec_14_1_example_time_signal_as_base64() -> Result<()> { 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_ticks(0x072bd0050));
splice.set_cw_index(0xff);
// splice.add_descriptor(SegmentationDescriptor::new().into()); // splice.add_descriptor(SegmentationDescriptor::new().into());
assert_eq!( assert_eq!(
@ -382,12 +387,12 @@ mod tests {
#[test] #[test]
fn compliance_spec_14_1_example_time_signal_as_hex() -> Result<()> { 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_ticks(0x072bd0050));
splice.set_cw_index(0xff);
// splice.add_descriptor(SegmentationDescriptor::new().into()); // splice.add_descriptor(SegmentationDescriptor::new().into());
// 0xFC3034000000000000FFFFF00506FE72BD0050001E021C435545494800008E7FCF0001A599B00808000000002CA0A18A3402009AC9D17E
assert_eq!( assert_eq!(
splice.into_encoded()?.to_hex(), splice.into_encoded()?.to_hex(),
"0xfc301600000000000000fff005068072bd00500000e9dfc26c".to_string() "0xfc3034000000000000fffff00506fe72bd0050001e021c435545494800008e7fcf0001a599b00808000000002ca0a18a3402009ac9d17e".to_string()
); );
Ok(()) Ok(())
} }
@ -396,6 +401,7 @@ mod tests {
#[test] #[test]
fn compliance_spec_14_1_example_time_signal_as_json() -> Result<()> { 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_ticks(0x072bd0050));
splice.set_cw_index(0xff);
// splice.add_descriptor(SegmentationDescriptor::new().into()); // splice.add_descriptor(SegmentationDescriptor::new().into());
assert_json_eq!( assert_json_eq!(
@ -410,7 +416,7 @@ mod tests {
"encrypted_packet": false, "encrypted_packet": false,
"encryption_algorithm": 0, "encryption_algorithm": 0,
"pts_adjustment": 0.0, "pts_adjustment": 0.0,
"cw_index": "0x0", "cw_index": "0xff",
"tier": "0xfff", "tier": "0xfff",
"splice_command_length": 5, "splice_command_length": 5,
"splice_command_type": 6, "splice_command_type": 6,
@ -421,7 +427,7 @@ mod tests {
}, },
"descriptor_loop_length": 0, "descriptor_loop_length": 0,
"descriptors": [], "descriptors": [],
"crc_32": "0xe9dfc26c" "crc_32": "0x2184b03d"
}) })
); );

View file

@ -14,7 +14,7 @@ pub use commands::SpliceNull;
pub use info::{EncryptionAlgorithm, SAPType, SpliceInfoSection}; pub use info::{EncryptionAlgorithm, SAPType, SpliceInfoSection};
pub trait TransportPacketWrite { pub trait TransportPacketWrite {
fn write_to<W>(&self, buffer: &mut W) -> Result<(), CueError> fn write_to<W>(&self, buffer: &mut W) -> anyhow::Result<()>
where where
W: io::Write; W: io::Write;
} }

View file

@ -42,18 +42,24 @@ impl SpliceTime {
} }
impl TransportPacketWrite for SpliceTime { impl TransportPacketWrite for SpliceTime {
fn write_to<W>(&self, buffer: &mut W) -> Result<(), CueError> fn write_to<W>(&self, buffer: &mut W) -> anyhow::Result<()>
where where
W: io::Write, W: io::Write,
{ {
let mut buffer = BitWriter::endian(buffer, BigEndian); let mut buffer = BitWriter::endian(buffer, BigEndian);
buffer.write_bit(self.time_specified_flag)?; buffer.write_bit(self.time_specified_flag)?;
if self.time_specified_flag { if self.time_specified_flag {
buffer.write(6, 0x00)?; buffer.write(6, 0x3f)?;
buffer.write(33, self.pts_time)?; buffer.write(33, self.pts_time)?;
} else { } else {
buffer.write(7, 0x00)?; buffer.write(7, 0x7f)?;
} }
Ok(()) Ok(())
} }
} }
impl From<Duration> for SpliceTime {
fn from(duration: Duration) -> Self {
Self::from_ticks(duration.to_90k())
}
}