scte35/src/descriptors/segmentation.rs
Rafael Caricio f0f60ebdfc
Roadblock on Deku usage
Here we hit a road block on using Deku for the SCTE35 spec. The Upid
type is more complex in terms of serialization which makes it very
complicated to use Deku.

Besides the problem above. It seems like using Deku will make
maintainance more complicated due to the externsive use of magic macros.
Which also does impact development. I still believe Deku is a great
project to be used in more straight forward binary formats.
2022-05-12 09:08:28 +02:00

1124 lines
41 KiB
Rust

use deku::prelude::*;
use crate::{BytesWritten, ClockTimeExt};
use ascii::AsciiString;
use bitstream_io::{BigEndian, BitRecorder, BitWrite, BitWriter};
use std::{fmt, io};
use crate::descriptors::{SpliceDescriptorExt, SpliceDescriptorTag};
#[cfg(feature = "serde")]
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SegmentationDescriptor {
segmentation_event_id: u32,
segmentation_event_cancel_indicator: bool,
program_segmentation_flag: bool,
segmentation_duration_flag: bool,
delivery_not_restricted_flag: bool,
web_delivery_allowed_flag: bool,
no_regional_blackout_flag: bool,
archive_allowed_flag: bool,
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,
descriptor_length: Option<u8>,
}
impl SegmentationDescriptor {
pub fn set_segmentation_event_id(&mut self, segmentation_event_id: u32) {
self.segmentation_event_id = segmentation_event_id;
}
pub fn set_segmentation_event_cancel_indicator(
&mut self,
segmentation_event_cancel_indicator: bool,
) {
self.segmentation_event_cancel_indicator = segmentation_event_cancel_indicator;
}
pub fn set_program_segmentation_flag(&mut self, program_segmentation_flag: bool) {
self.program_segmentation_flag = program_segmentation_flag;
}
pub fn set_segmentation_duration_flag(&mut self, segmentation_duration_flag: bool) {
self.segmentation_duration_flag = segmentation_duration_flag;
}
pub fn set_delivery_not_restricted_flag(&mut self, delivery_not_restricted_flag: bool) {
self.delivery_not_restricted_flag = delivery_not_restricted_flag;
}
pub fn set_web_delivery_allowed_flag(&mut self, web_delivery_allowed_flag: bool) {
self.web_delivery_allowed_flag = web_delivery_allowed_flag;
}
pub fn set_no_regional_blackout_flag(&mut self, no_regional_blackout_flag: bool) {
self.no_regional_blackout_flag = no_regional_blackout_flag;
}
pub fn set_archive_allowed_flag(&mut self, archive_allowed_flag: bool) {
self.archive_allowed_flag = archive_allowed_flag;
}
pub fn set_device_restrictions(&mut self, device_restrictions: DeviceRestrictions) {
self.device_restrictions = device_restrictions;
}
pub fn set_segmentation_duration(&mut self, segmentation_duration: impl ClockTimeExt) {
self.set_segmentation_duration_flag(true);
self.segmentation_duration = segmentation_duration.to_90k();
}
pub fn set_segmentation_upid(&mut self, segmentation_upid: SegmentationUpid) {
self.segmentation_upid = segmentation_upid;
}
pub fn set_segmentation_type(&mut self, segmentation_type: SegmentationType) {
self.segmentation_type = segmentation_type;
}
pub fn set_segment_num(&mut self, segment_num: u8) {
self.segment_num = segment_num;
}
pub fn set_segments_expected(&mut self, segments_expected: u8) {
self.segments_expected = segments_expected;
}
pub fn set_sub_segment_num(&mut self, sub_segment_num: u8) {
self.sub_segment_num = sub_segment_num;
}
pub fn set_sub_segments_expected(&mut self, sub_segments_expected: u8) {
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<Component> {
&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<u8> {
self.descriptor_length
}
pub(crate) fn write_to<W>(&mut self, buffer: &mut W) -> anyhow::Result<u32>
where
W: io::Write,
{
use SegmentationFieldSyntax::*;
let mut recorder: BitRecorder<u32, BigEndian> = BitRecorder::new();
recorder.write(32, self.identifier())?;
recorder.write(32, self.segmentation_event_id)?;
recorder.write_bit(self.segmentation_event_cancel_indicator)?;
recorder.write(7, 0x7f)?;
if !self.segmentation_event_cancel_indicator {
recorder.write_bit(self.program_segmentation_flag)?;
recorder.write_bit(self.segmentation_duration_flag)?;
recorder.write_bit(self.delivery_not_restricted_flag)?;
if !self.delivery_not_restricted_flag {
recorder.write_bit(self.web_delivery_allowed_flag)?;
recorder.write_bit(self.no_regional_blackout_flag)?;
recorder.write_bit(self.archive_allowed_flag)?;
recorder.write(2, self.device_restrictions as u8)?;
} else {
recorder.write(5, 0x1f)?;
}
if !self.program_segmentation_flag {
recorder.write(8, self.components.len() as u8)?;
for component in &self.components {
component.write_to(&mut recorder)?;
}
}
if self.segmentation_duration_flag {
recorder.write(40, self.segmentation_duration)?;
}
recorder.write(8, u8::from(self.segmentation_upid.segmentation_upid_type()))?;
self.segmentation_upid.write_to(&mut recorder)?;
recorder.write(8, self.segmentation_type.id())?;
let s = self.segmentation_type.syntax();
match s.segment_num {
Fixed(n) => recorder.write(8, n)?,
NonZero | ZeroOrNonZero => recorder.write(8, self.segment_num)?, // needs to check for non-zero
NotUsed => recorder.write(8, 0u8)?,
}
match s.segments_expected {
Fixed(n) => recorder.write(8, n)?,
NonZero | ZeroOrNonZero => recorder.write(8, self.segments_expected)?, // needs to check for non-zero
NotUsed => recorder.write(8, 0u8)?,
}
match s.sub_segment_num {
Fixed(n) => recorder.write(8, n)?,
NonZero | ZeroOrNonZero => recorder.write(8, self.sub_segment_num)?, // needs to check for non-zero
NotUsed => {}
}
match s.sub_segments_expected {
Fixed(n) => recorder.write(8, n)?,
NonZero | ZeroOrNonZero => recorder.write(8, self.sub_segments_expected)?, // needs to check for non-zero
NotUsed => {}
}
}
let descriptor_length = recorder.bytes_written() as u8;
// Actually write to the output buffer, now we know the total size we need to write out
let mut buffer = BitWriter::endian(buffer, BigEndian);
buffer.write(8, self.splice_descriptor_tag())?;
buffer.write(8, descriptor_length)?;
recorder.playback(&mut buffer)?;
// This field is used when serializing the Segmentation Descriptor with serde
self.descriptor_length = Some(descriptor_length);
// This is the full size of the descriptor, which includes the 2 bytes of the tag and the length
Ok(descriptor_length as u32 + 2)
}
}
impl SpliceDescriptorExt for SegmentationDescriptor {
fn splice_descriptor_tag(&self) -> u8 {
SpliceDescriptorTag::Segmentation.into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, DekuRead, DekuWrite)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[repr(u8)]
#[deku(type="u8", bits="2")]
pub enum DeviceRestrictions {
/// This Segment is restricted for a class of devices defined by an out of band message that
/// describes which devices are excluded.
RestrictGroup0 = 0b00,
/// 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,
}
impl Default for DeviceRestrictions {
fn default() -> Self {
DeviceRestrictions::None
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[non_exhaustive]
pub enum SegmentationUpidType {
NotUsed,
UserDefinedDeprecated,
ISCI,
AdID,
UMID,
ISANDeprecated,
ISAN,
TID,
AiringID,
ADI,
EIDR,
ATSCContentIdentifier,
MPU,
MID,
ADSInformation,
URI,
UUID,
SCR,
Reserved(u8),
}
impl Default for SegmentationUpidType {
fn default() -> Self {
SegmentationUpidType::NotUsed
}
}
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 fmt::Display for SegmentationUpidType {
fn fmt(&self, f: &mut fmt::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)]
#[non_exhaustive]
pub enum SegmentationUpid {
NotUsed,
UserDefinedDeprecated(AsciiString),
ISCI(AsciiString),
AdID(AsciiString),
UMID(AsciiString),
ISANDeprecated(u64),
ISAN(u128),
TID(AsciiString),
AiringID(u64),
ADI(AsciiString),
EIDR(u128),
ATSCContentIdentifier(AsciiString),
MPU,
MID,
ADSInformation(AsciiString),
URI(AsciiString),
UUID(u128),
SCR(AsciiString),
Reserved(u8),
}
impl Default for SegmentationUpid {
fn default() -> Self {
SegmentationUpid::NotUsed
}
}
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),
}
}
pub fn segmentation_upid_length(&self) -> u8 {
use SegmentationUpid::*;
match self {
NotUsed => 0,
UserDefinedDeprecated(s) => s.len() as u8,
ISCI(_) => 8,
AdID(_) => 12,
UMID(_) => 32,
ISANDeprecated(_) => 8,
ISAN(_) => 12,
TID(_) => 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,
}
}
pub(crate) fn write_to(&self, out: &mut BitRecorder<u32, BigEndian>) -> anyhow::Result<()> {
use SegmentationUpid::*;
let mut recorder = BitRecorder::<u32, BigEndian>::new();
match self {
AiringID(v) | ISANDeprecated(v) => {
// 8 byes is 64 bits
recorder.write(64, *v)?
}
ISAN(value) | EIDR(value) | UUID(value) => {
recorder.write_bytes(value.to_be_bytes().as_slice())?
}
UserDefinedDeprecated(value)
| ADI(value)
| ATSCContentIdentifier(value)
| ADSInformation(value)
| URI(value)
| SCR(value) => recorder.write_bytes(value.as_bytes())?,
ISCI(v) => {
let buf = v.as_bytes().iter().take(8).copied().collect::<Vec<_>>();
recorder.write_bytes(buf.as_slice())?;
}
AdID(v) | TID(v) => {
let buf = v.as_bytes().iter().take(12).copied().collect::<Vec<_>>();
recorder.write_bytes(buf.as_slice())?;
}
UMID(v) => {
let buf = v.as_bytes().iter().take(32).copied().collect::<Vec<_>>();
recorder.write_bytes(buf.as_slice())?;
}
MPU => todo!("Needs to implement MPU() record"),
MID => todo!("Needs to implement MID() record"),
NotUsed => {}
Reserved(_) => {}
}
match self {
NotUsed | Reserved(_) => {
out.write(8, 0u8)?;
}
// All variants with any contained value use the same logic
_ => {
out.write(8, recorder.bytes_written() as u8)?;
recorder.playback(out)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Component {
component_tag: u8,
pts_offset: u64,
}
impl Component {
pub(crate) fn write_to(&self, recorder: &mut BitRecorder<u32, BigEndian>) -> io::Result<()> {
recorder.write(8, self.component_tag)?;
recorder.write(7, 0x7f)?;
recorder.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 Default for SegmentationType {
fn default() -> Self {
SegmentationType::NotIndicated
}
}
impl SegmentationType {
pub 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.
pub 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 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 = ();
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(()),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum SegmentationFieldSyntax {
Fixed(u8),
ZeroOrNonZero,
NonZero,
NotUsed,
}
#[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;
#[test]
fn write_segmentation_upid_airing_id() -> Result<()> {
let mut data = Vec::new();
let mut buffer = BitWriter::endian(&mut data, BigEndian);
let mut recorder = BitRecorder::<u32, BigEndian>::new();
let segmentation_upid = SegmentationUpid::AiringID(0x2ca0a18a);
segmentation_upid.write_to(&mut recorder)?;
recorder.playback(&mut buffer)?;
// length (1 byte) + data (8 bytes)
assert_eq!(recorder.bytes_written(), 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 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);
descriptor.write_to(&mut data)?;
let hex = hex::encode(data.as_slice());
assert_eq!(
hex,
"021e435545494800008e7fcf0001a599b00808000000002ca0a18a3402009ac9".to_string()
);
Ok(())
}
}