Support descriptors serialization
This commit is contained in:
parent
fa917dafce
commit
00a28859c7
7 changed files with 528 additions and 59 deletions
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
103
src/info.rs
103
src/info.rs
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
21
src/time.rs
21
src/time.rs
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue