Rafael Caricio
501e060979
Move all code related to the "serde" feature to a separated module. Add missing getter methods to some structs. Add missing derive to public structs. Makes all tests pass. Signed-off-by: Rafael Caricio <rafael@caricio.com>
638 lines
24 KiB
Rust
638 lines
24 KiB
Rust
use crate::commands::SpliceCommand;
|
|
use crate::descriptors::{SegmentationDescriptor, SegmentationUpid, SpliceDescriptorExt};
|
|
use crate::info::EncodedData;
|
|
use crate::{SpliceDescriptor, SpliceInfoSection, SpliceTime};
|
|
use ascii::AsciiStr;
|
|
use bitstream_io::{BigEndian, BitRecorder, BitWriter};
|
|
use serde::ser::{Error, SerializeStruct};
|
|
use serde::{Serialize, Serializer};
|
|
use std::fmt;
|
|
use std::fmt::LowerHex;
|
|
use std::time::Duration;
|
|
|
|
/// Truncate to 6 decimal positions, as shown in the spec.
|
|
fn ticks_to_secs(value: u64) -> f64 {
|
|
(value as f64 / 90_000.0 * 1_000_000.0).ceil() as f64 / 1_000_000.0
|
|
}
|
|
|
|
impl Serialize for SegmentationDescriptor {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
use crate::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() {
|
|
let duration_secs = ticks_to_secs(self.segmentation_duration());
|
|
state.serialize_field("segmentation_duration", &self.segmentation_duration())?;
|
|
state.serialize_field("segmentation_duration_secs", &duration_secs)?;
|
|
state.serialize_field(
|
|
"segmentation_duration_human",
|
|
&format_duration(Duration::from_secs_f64(duration_secs)).to_string(),
|
|
)?;
|
|
}
|
|
state.serialize_field(
|
|
"segmentation_upid_type",
|
|
&as_hex(u8::from(self.segmentation_upid().segmentation_upid_type())),
|
|
)?;
|
|
state.serialize_field(
|
|
"segmentation_upid_type_name",
|
|
&format!("{}", self.segmentation_upid().segmentation_upid_type()),
|
|
)?;
|
|
state.serialize_field(
|
|
"segmentation_upid_length",
|
|
&self.segmentation_upid().segmentation_upid_length(),
|
|
)?;
|
|
state.serialize_field("segmentation_upid", &self.segmentation_upid())?;
|
|
state.serialize_field(
|
|
"segmentation_message",
|
|
&format!("{}", self.segmentation_type()),
|
|
)?;
|
|
state.serialize_field("segmentation_type_id", &self.segmentation_type().id())?;
|
|
state.serialize_field("segment_num", &self.segment_num())?;
|
|
state.serialize_field("segments_expected", &self.segments_expected())?;
|
|
match segmentation_syntax.sub_segment_num() {
|
|
Fixed(v) => {
|
|
state.serialize_field("sub_segment_num", &v)?;
|
|
}
|
|
NonZero | ZeroOrNonZero => {
|
|
state.serialize_field("sub_segment_num", &self.sub_segment_num())?;
|
|
}
|
|
NotUsed => {}
|
|
}
|
|
match segmentation_syntax.sub_segments_expected() {
|
|
Fixed(v) => {
|
|
state.serialize_field("sub_segments_expected", &v)?;
|
|
}
|
|
NonZero | ZeroOrNonZero => {
|
|
state
|
|
.serialize_field("sub_segments_expected", &self.sub_segments_expected())?;
|
|
}
|
|
NotUsed => {}
|
|
}
|
|
}
|
|
state.end()
|
|
}
|
|
}
|
|
|
|
impl Serialize for SegmentationUpid {
|
|
fn serialize<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) | ADSInformation(v) | URI(v) | SCR(v) => {
|
|
serializer.serialize_str(v.as_str())
|
|
}
|
|
// if field is represented as a number, then show as hex
|
|
ISAN(v) | EIDR(v) | UUID(v) => serializer.serialize_str(&format!("0x{:x}", v)),
|
|
ISANDeprecated(v) | AiringID(v) => serializer.serialize_str(&format!("0x{:x}", v)),
|
|
// everything else show as hex, we skip the first byte (which is the length)
|
|
_ => serializer.serialize_str(&format!("0x{}", hex::encode(&data[1..]))),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for SpliceTime {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let num_fields = if self.time_specified_flag() { 3 } else { 1 };
|
|
|
|
let mut state = serializer.serialize_struct("SpliceTime", num_fields)?;
|
|
state.serialize_field("time_specified_flag", &self.time_specified_flag())?;
|
|
if self.time_specified_flag() {
|
|
state.serialize_field("pts_time", &self.pts_time().unwrap_or(0))?;
|
|
state.serialize_field(
|
|
"pts_time_secs",
|
|
&ticks_to_secs(self.pts_time().unwrap_or(0)),
|
|
)?;
|
|
}
|
|
state.end()
|
|
}
|
|
}
|
|
|
|
impl Serialize for SpliceDescriptor {
|
|
fn serialize<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)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C> Serialize for SpliceInfoSection<C, EncodedData>
|
|
where
|
|
C: SpliceCommand + Serialize,
|
|
{
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
#[inline]
|
|
fn as_hex<T>(value: T) -> String
|
|
where
|
|
T: LowerHex,
|
|
{
|
|
format!("0x{:x}", value)
|
|
}
|
|
|
|
let mut state = serializer.serialize_struct("SpliceInfoSection", 19)?;
|
|
state.serialize_field("table_id", &as_hex(self.state.table_id))?;
|
|
state.serialize_field(
|
|
"section_syntax_indicator",
|
|
&self.state.section_syntax_indicator,
|
|
)?;
|
|
state.serialize_field("private_indicator", &self.state.private_indicator)?;
|
|
state.serialize_field("sap_type", &as_hex(self.state.sap_type as u8))?;
|
|
state.serialize_field("section_length", &self.encoded.section_length)?;
|
|
state.serialize_field("protocol_version", &self.state.protocol_version)?;
|
|
state.serialize_field("encrypted_packet", &self.state.encrypted_packet)?;
|
|
state.serialize_field(
|
|
"encryption_algorithm",
|
|
&u8::from(self.state.encryption_algorithm),
|
|
)?;
|
|
state.serialize_field("pts_adjustment", &self.state.pts_adjustment)?;
|
|
let pts_adjustment_secs = ticks_to_secs(self.state.pts_adjustment);
|
|
state.serialize_field("pts_adjustment_secs", &pts_adjustment_secs)?;
|
|
state.serialize_field(
|
|
"pts_adjustment_human",
|
|
&format_duration(Duration::from_secs_f64(pts_adjustment_secs)).to_string(),
|
|
)?;
|
|
state.serialize_field("cw_index", &as_hex(self.state.cw_index))?;
|
|
state.serialize_field("tier", &as_hex(self.state.tier))?;
|
|
state.serialize_field("splice_command_length", &self.encoded.splice_command_length)?;
|
|
state.serialize_field(
|
|
"splice_command_type",
|
|
&u8::from(self.encoded.splice_command_type),
|
|
)?;
|
|
state.serialize_field("splice_command_name", &self.encoded.splice_command_type)?;
|
|
state.serialize_field("splice_command", &self.state.splice_command)?;
|
|
state.serialize_field(
|
|
"descriptor_loop_length",
|
|
&self.encoded.descriptor_loop_length,
|
|
)?;
|
|
state.serialize_field("descriptors", &self.state.descriptors)?;
|
|
state.serialize_field("crc_32", &as_hex(self.encoded.crc32))?;
|
|
state.end()
|
|
}
|
|
}
|
|
|
|
// Copyright (c) 2016 The humantime Developers
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
/// Formats duration into a human-readable string
|
|
///
|
|
/// Note: this format is guaranteed to have same value when using
|
|
/// parse_duration, but we can change some details of the exact composition
|
|
/// of the value.
|
|
pub(crate) fn format_duration(val: Duration) -> FormattedDuration {
|
|
FormattedDuration(val)
|
|
}
|
|
|
|
fn item_plural(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u64) -> fmt::Result {
|
|
if value > 0 {
|
|
if *started {
|
|
f.write_str(" ")?;
|
|
}
|
|
write!(f, "{}{}", value, name)?;
|
|
if value > 1 {
|
|
f.write_str("s")?;
|
|
}
|
|
*started = true;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32) -> fmt::Result {
|
|
if value > 0 {
|
|
if *started {
|
|
f.write_str(" ")?;
|
|
}
|
|
write!(f, "{}{}", value, name)?;
|
|
*started = true;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// A wrapper type that allows you to Display a Duration
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct FormattedDuration(Duration);
|
|
|
|
impl fmt::Display for FormattedDuration {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let secs = self.0.as_secs();
|
|
let nanos = self.0.subsec_nanos();
|
|
|
|
if secs == 0 && nanos == 0 {
|
|
f.write_str("0s")?;
|
|
return Ok(());
|
|
}
|
|
|
|
let years = secs / 31_557_600; // 365.25d
|
|
let ydays = secs % 31_557_600;
|
|
let months = ydays / 2_630_016; // 30.44d
|
|
let mdays = ydays % 2_630_016;
|
|
let days = mdays / 86400;
|
|
let day_secs = mdays % 86400;
|
|
let hours = day_secs / 3600;
|
|
let minutes = day_secs % 3600 / 60;
|
|
let seconds = day_secs % 60;
|
|
|
|
let millis = nanos / 1_000_000;
|
|
let micros = nanos / 1000 % 1000;
|
|
let nanosec = nanos % 1000;
|
|
|
|
let started = &mut false;
|
|
item_plural(f, started, "year", years)?;
|
|
item_plural(f, started, "month", months)?;
|
|
item_plural(f, started, "day", days)?;
|
|
item(f, started, "h", hours as u32)?;
|
|
item(f, started, "m", minutes as u32)?;
|
|
item(f, started, "s", seconds as u32)?;
|
|
item(f, started, "milli", millis)?;
|
|
item(f, started, "us", micros)?;
|
|
item(f, started, "ns", nanosec)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::commands::TimeSignal;
|
|
use crate::descriptors::SegmentationType;
|
|
use crate::{ClockTimeExt, SpliceNull};
|
|
use anyhow::Result;
|
|
use assert_json_diff::assert_json_eq;
|
|
use std::time::Duration;
|
|
|
|
#[test]
|
|
fn test_ticks_to_secs() {
|
|
let time = Duration::from_secs_f64(21388.766756);
|
|
assert_eq!(time.to_90k(), 0x072bd0050);
|
|
assert_eq!(ticks_to_secs(0x072bd0050), 21388.766756);
|
|
assert_eq!(ticks_to_secs(time.to_90k()), 21388.766756);
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_splice_null() -> Result<()> {
|
|
let splice_null = SpliceNull::default();
|
|
assert_json_eq!(serde_json::to_value(&splice_null)?, serde_json::json!({}));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_time_signal_without_time() -> Result<()> {
|
|
let time_signal = TimeSignal::default();
|
|
assert_json_eq!(
|
|
serde_json::to_value(&time_signal)?,
|
|
serde_json::json!({
|
|
"time_specified_flag": false
|
|
})
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_time_signal_with_time() -> Result<()> {
|
|
let time_signal = TimeSignal::from(Duration::from_secs(10));
|
|
assert_json_eq!(
|
|
serde_json::to_value(&time_signal)?,
|
|
serde_json::json!({
|
|
"time_specified_flag": true,
|
|
"pts_time": 900000,
|
|
"pts_time_secs": 10.0
|
|
})
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_segmentation_to_json() -> Result<()> {
|
|
let mut descriptor = SegmentationDescriptor::default();
|
|
descriptor.set_segmentation_event_id(0x4800008e);
|
|
descriptor.set_program_segmentation_flag(true);
|
|
descriptor.set_segmentation_duration_flag(true);
|
|
descriptor.set_no_regional_blackout_flag(true);
|
|
descriptor.set_archive_allowed_flag(true);
|
|
descriptor.set_segmentation_duration(Duration::from_secs_f32(307.0));
|
|
descriptor.set_segmentation_duration(27630000);
|
|
descriptor.set_segmentation_upid(SegmentationUpid::AiringID(0x2ca0a18a));
|
|
descriptor.set_segmentation_type(SegmentationType::ProviderPlacementOpportunityStart);
|
|
descriptor.set_segment_num(2);
|
|
descriptor.set_sub_segment_num(154);
|
|
descriptor.set_sub_segments_expected(201);
|
|
|
|
// We need to write to calculate the length
|
|
let mut data = Vec::new();
|
|
descriptor.write_to(&mut data)?;
|
|
|
|
let expected_json: serde_json::Value = serde_json::from_str(
|
|
r#"{
|
|
"name": "Segmentation Descriptor",
|
|
"splice_descriptor_tag": "0x02",
|
|
"descriptor_length": 30,
|
|
"identifier": "CUEI",
|
|
"segmentation_event_id": "0x4800008e",
|
|
"segmentation_event_cancel_indicator": false,
|
|
"program_segmentation_flag": true,
|
|
"segmentation_duration_flag": true,
|
|
"delivery_not_restricted_flag": false,
|
|
"web_delivery_allowed_flag": false,
|
|
"no_regional_blackout_flag": true,
|
|
"archive_allowed_flag": true,
|
|
"device_restrictions": "None",
|
|
"components": [],
|
|
"segmentation_duration": 27630000,
|
|
"segmentation_duration_secs": 307.0,
|
|
"segmentation_duration_human": "5m 7s",
|
|
"segmentation_upid_type": "0x08",
|
|
"segmentation_upid_type_name": "AiringID",
|
|
"segmentation_upid_length": 8,
|
|
"segmentation_upid": "0x2ca0a18a",
|
|
"segmentation_message": "Provider Placement Opportunity Start",
|
|
"segmentation_type_id": 52,
|
|
"segment_num": 2,
|
|
"segments_expected": 0,
|
|
"sub_segment_num": 154,
|
|
"sub_segments_expected": 201
|
|
}"#,
|
|
)?;
|
|
|
|
assert_json_eq!(serde_json::to_value(&descriptor)?, expected_json);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_segmentation_with_cancel_indicator_to_json() -> Result<()> {
|
|
let mut descriptor = SegmentationDescriptor::default();
|
|
descriptor.set_segmentation_event_id(0x4800008e);
|
|
descriptor.set_segmentation_event_cancel_indicator(true);
|
|
|
|
// We need to write to calculate the length
|
|
let mut data = Vec::new();
|
|
descriptor.write_to(&mut data)?;
|
|
|
|
let expected_json: serde_json::Value = serde_json::from_str(
|
|
r#"{
|
|
"name": "Segmentation Descriptor",
|
|
"splice_descriptor_tag": "0x02",
|
|
"descriptor_length": 9,
|
|
"identifier": "CUEI",
|
|
"segmentation_event_id": "0x4800008e",
|
|
"segmentation_event_cancel_indicator": true
|
|
}"#,
|
|
)?;
|
|
|
|
assert_json_eq!(serde_json::to_value(&descriptor)?, expected_json);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn compliance_spec_14_1_example_time_signal_as_json() -> Result<()> {
|
|
let mut splice = SpliceInfoSection::new(TimeSignal::from(0x072bd0050u64));
|
|
splice.set_cw_index(0xff);
|
|
|
|
let mut descriptor = SegmentationDescriptor::default();
|
|
descriptor.set_segmentation_event_id(0x4800008e);
|
|
descriptor.set_program_segmentation_flag(true);
|
|
descriptor.set_segmentation_duration_flag(true);
|
|
descriptor.set_no_regional_blackout_flag(true);
|
|
descriptor.set_archive_allowed_flag(true);
|
|
descriptor.set_segmentation_duration(27630000);
|
|
descriptor.set_segmentation_upid(SegmentationUpid::AiringID(0x2ca0a18a));
|
|
descriptor.set_segmentation_type(SegmentationType::ProviderPlacementOpportunityStart);
|
|
descriptor.set_segment_num(2);
|
|
descriptor.set_sub_segment_num(154);
|
|
descriptor.set_sub_segments_expected(201);
|
|
|
|
splice.add_descriptor(descriptor.into());
|
|
|
|
let expected_json: serde_json::Value = serde_json::from_str(
|
|
r#"{
|
|
"table_id": "0xfc",
|
|
"section_syntax_indicator": false,
|
|
"private_indicator": false,
|
|
"sap_type": "0x3",
|
|
"section_length": 54,
|
|
"protocol_version": 0,
|
|
"encrypted_packet": false,
|
|
"encryption_algorithm": 0,
|
|
"pts_adjustment": 0,
|
|
"pts_adjustment_secs": 0.0,
|
|
"pts_adjustment_human": "0s",
|
|
"cw_index": "0xff",
|
|
"tier": "0xfff",
|
|
"splice_command_length": 5,
|
|
"splice_command_type": 6,
|
|
"splice_command_name": "TimeSignal",
|
|
"splice_command": {
|
|
"time_specified_flag": true,
|
|
"pts_time": 1924989008,
|
|
"pts_time_secs": 21388.766756
|
|
},
|
|
"descriptor_loop_length": 32,
|
|
"descriptors": [
|
|
{
|
|
"name": "Segmentation Descriptor",
|
|
"splice_descriptor_tag": "0x02",
|
|
"descriptor_length": 30,
|
|
"identifier": "CUEI",
|
|
"segmentation_event_id": "0x4800008e",
|
|
"segmentation_event_cancel_indicator": false,
|
|
"program_segmentation_flag": true,
|
|
"segmentation_duration_flag": true,
|
|
"delivery_not_restricted_flag": false,
|
|
"web_delivery_allowed_flag": false,
|
|
"no_regional_blackout_flag": true,
|
|
"archive_allowed_flag": true,
|
|
"device_restrictions": "None",
|
|
"components": [],
|
|
"segmentation_duration": 27630000,
|
|
"segmentation_duration_secs": 307.0,
|
|
"segmentation_duration_human": "5m 7s",
|
|
"segmentation_upid_type": "0x08",
|
|
"segmentation_upid_type_name": "AiringID",
|
|
"segmentation_upid_length": 8,
|
|
"segmentation_upid": "0x2ca0a18a",
|
|
"segmentation_message": "Provider Placement Opportunity Start",
|
|
"segmentation_type_id": 52,
|
|
"segment_num": 2,
|
|
"segments_expected": 0,
|
|
"sub_segment_num": 154,
|
|
"sub_segments_expected": 201
|
|
}
|
|
],
|
|
"crc_32": "0xb6c1a0f1"
|
|
}"#,
|
|
)?;
|
|
|
|
assert_json_eq!(
|
|
serde_json::to_value(&splice.into_encoded()?)?,
|
|
expected_json
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_info_section_as_json() -> Result<()> {
|
|
let splice = SpliceInfoSection::new(SpliceNull::default());
|
|
|
|
assert_json_eq!(
|
|
serde_json::to_value(&splice.into_encoded()?)?,
|
|
serde_json::json!({
|
|
"table_id": "0xfc",
|
|
"section_syntax_indicator": false,
|
|
"private_indicator": false,
|
|
"sap_type": "0x3",
|
|
"section_length": 17,
|
|
"protocol_version": 0,
|
|
"encrypted_packet": false,
|
|
"encryption_algorithm": 0,
|
|
"pts_adjustment": 0,
|
|
"pts_adjustment_secs": 0.0,
|
|
"pts_adjustment_human": "0s",
|
|
"cw_index": "0x0",
|
|
"tier": "0xfff",
|
|
"splice_command_length": 0,
|
|
"splice_command_type": 0,
|
|
"splice_command_name": "SpliceNull",
|
|
"splice_command": {},
|
|
"descriptor_loop_length": 0,
|
|
"descriptors": [],
|
|
"crc_32": "0x7a4fbfff"
|
|
})
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|