From 6fd2babc114f5745691615cb4c7f922fec8b7b72 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Mon, 2 May 2022 00:08:28 +0200 Subject: [PATCH] Initial support for TimeSignal --- src/commands.rs | 65 +++++++++++++++++++++++++++++++++ src/info.rs | 95 ++++++++++++++++++++++++++++++++++++++++++------- src/lib.rs | 45 +++++++++++++++++++++++ src/time.rs | 59 ++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 13 deletions(-) create mode 100644 src/time.rs diff --git a/src/commands.rs b/src/commands.rs index 6df2e4b..29f234a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,5 +1,10 @@ +use crate::time::SpliceTime; use crate::{CueError, TransportPacketWrite}; +use bitstream_io::{BigEndian, BitWrite, BitWriter}; use std::io; +use std::io::Write; +use std::ops::{Deref, DerefMut}; +use std::time::Duration; #[cfg(feature = "serde")] use serde::Serialize; @@ -32,6 +37,37 @@ impl SpliceCommand for SpliceNull { } } +#[cfg_attr(feature = "serde", derive(Serialize), serde(transparent))] +#[repr(transparent)] +pub struct TimeSignal(SpliceTime); + +impl TimeSignal { + pub fn new() -> Self { + TimeSignal(SpliceTime::new()) + } + + pub fn from_ticks(pts_time: u64) -> Self { + TimeSignal(SpliceTime::from_ticks(pts_time)) + } +} + +impl TransportPacketWrite for TimeSignal { + #[inline] + fn write_to(&self, buffer: &mut W) -> Result<(), CueError> + where + W: Write, + { + self.0.write_to(buffer) + } +} + +impl SpliceCommand for TimeSignal { + fn splice_command_type(&self) -> SpliceCommandType { + SpliceCommandType::TimeSignal + } +} + +#[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Debug, Clone, Copy)] pub enum SpliceCommandType { SpliceNull, @@ -70,3 +106,32 @@ impl From for u8 { } } } + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + use assert_json_diff::assert_json_eq; + + #[cfg(feature = "serde")] + #[test] + fn serialize_splice_null() -> Result<()> { + let splice_null = SpliceNull::new(); + assert_json_eq!(serde_json::to_value(&splice_null)?, serde_json::json!({})); + Ok(()) + } + + #[cfg(feature = "serde")] + #[test] + fn serialize_time_signal() -> Result<()> { + let time_signal = TimeSignal::new(); + assert_json_eq!( + serde_json::to_value(&time_signal)?, + serde_json::json!({ + "time_specified_flag": false, + "pts_time": 0.0 + }) + ); + Ok(()) + } +} diff --git a/src/info.rs b/src/info.rs index 9357e16..42a2a8d 100644 --- a/src/info.rs +++ b/src/info.rs @@ -199,15 +199,16 @@ impl SpliceInfoSection where C: SpliceCommand, { - pub fn as_base64(&self) -> Result { - Ok(base64::encode(self.encoded.final_data.as_slice())) + pub fn as_base64(&self) -> String { + base64::encode(self.as_bytes()) } - pub fn as_hex(&self) -> Result { - Ok(format!( - "0x{}", - hex::encode(self.encoded.final_data.as_slice()) - )) + pub fn as_hex(&self) -> String { + format!("0x{}", hex::encode(self.as_bytes())) + } + + pub fn as_bytes(&self) -> &[u8] { + self.encoded.final_data.as_slice() } } @@ -270,6 +271,7 @@ impl From for u8 { #[cfg(feature = "serde")] mod serde_serialization { use super::*; + use crate::ticks_to_secs; use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::fmt::LowerHex; @@ -289,7 +291,7 @@ mod serde_serialization { format!("0x{:x}", value) } - let mut state = serializer.serialize_struct("SpliceInfoSection", 17)?; + let mut state = serializer.serialize_struct("SpliceInfoSection", 18)?; state.serialize_field("table_id", &as_hex(self.state.table_id))?; state.serialize_field( "section_syntax_indicator", @@ -304,7 +306,7 @@ mod serde_serialization { "encryption_algorithm", &u8::from(self.state.encryption_algorithm), )?; - state.serialize_field("pts_adjustment", &self.state.pts_adjustment)?; + state.serialize_field("pts_adjustment", &ticks_to_secs(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)?; @@ -312,6 +314,7 @@ mod serde_serialization { "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", @@ -327,16 +330,19 @@ mod serde_serialization { #[cfg(test)] mod tests { use super::*; - use crate::commands::SpliceNull; + use crate::commands::*; + use crate::descriptors::SegmentationDescriptor; + use crate::ClockTimeExt; use anyhow::Result; use assert_json_diff::assert_json_eq; + use std::time::Duration; #[test] fn write_splice_null_as_base64() -> Result<()> { let splice = SpliceInfoSection::new(SpliceNull::new()); assert_eq!( - splice.into_encoded()?.as_base64()?, + splice.into_encoded()?.as_base64(), "/DARAAAAAAAAAP/wAAAAAHpPv/8=".to_string() ); @@ -348,13 +354,75 @@ mod tests { let splice = SpliceInfoSection::new(SpliceNull::new()); assert_eq!( - splice.into_encoded()?.as_hex()?, + splice.into_encoded()?.as_hex(), "0xfc301100000000000000fff0000000007a4fbfff".to_string() ); Ok(()) } + #[test] + fn compliance_spec_14_1_example_time_signal_as_base64() -> Result<()> { + let mut splice = SpliceInfoSection::new(TimeSignal::from_ticks(0x072bd0050)); + // splice.add_descriptor(SegmentationDescriptor::new().into()); + + assert_eq!( + splice.into_encoded()?.as_base64(), + "/DA0AAAAAAAA///wBQb+cr0AUAAeAhxDVUVJSAAAjn/PAAGlmbAICAAAAAAsoKGKNAIAmsnRfg==" + .to_string() + ); + Ok(()) + } + + #[test] + fn compliance_spec_14_1_example_time_signal_as_hex() -> Result<()> { + let mut splice = SpliceInfoSection::new(TimeSignal::from_ticks(0x072bd0050)); + // splice.add_descriptor(SegmentationDescriptor::new().into()); + + // 0xFC3034000000000000FFFFF00506FE72BD0050001E021C435545494800008E7FCF0001A599B00808000000002CA0A18A3402009AC9D17E + assert_eq!( + splice.into_encoded()?.as_hex(), + "0xfc301600000000000000fff005068072bd00500000e9dfc26c".to_string() + ); + Ok(()) + } + + #[cfg(feature = "serde")] + #[test] + fn compliance_spec_14_1_example_time_signal_as_json() -> Result<()> { + let mut splice = SpliceInfoSection::new(TimeSignal::from_ticks(0x072bd0050)); + // splice.add_descriptor(SegmentationDescriptor::new().into()); + + 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": "0x0", + "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": "0xe9dfc26c" + }) + ); + + Ok(()) + } + #[cfg(feature = "serde")] #[test] fn serialize_as_json() -> Result<()> { @@ -371,11 +439,12 @@ mod tests { "protocol_version": 0, "encrypted_packet": false, "encryption_algorithm": 0, - "pts_adjustment": 0, + "pts_adjustment": 0.0, "cw_index": "0x0", "tier": "0xfff", "splice_command_length": 0, "splice_command_type": 0, + "splice_command_name": "SpliceNull", "splice_command": {}, "descriptor_loop_length": 0, "descriptors": [], diff --git a/src/lib.rs b/src/lib.rs index 43f75e7..d045ce4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,14 @@ use std::io; +use std::time::Duration; use thiserror::Error; +#[cfg(feature = "serde")] +use serde::{Serialize, Serializer}; + mod commands; mod descriptors; mod info; +mod time; pub use commands::SpliceNull; pub use info::{EncryptionAlgorithm, SAPType, SpliceInfoSection}; @@ -19,3 +24,43 @@ pub trait TransportPacketWrite { pub enum CueError { Io(#[from] io::Error), } + +pub trait ClockTimeExt { + fn as_90k(&self) -> u64; +} + +impl ClockTimeExt for Duration { + fn as_90k(&self) -> u64 { + (self.as_secs_f64() * 90_000.0) as u64 + } +} + +#[cfg(feature = "serde")] +fn serialize_time(value: &u64, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_f64(ticks_to_secs(*value)) +} + +/// Truncate to 6 decimal positions, as shown in the spec. +pub fn ticks_to_secs(value: u64) -> f64 { + (value as f64 / 90_000.0 * 1_000_000.0).ceil() as f64 / 1_000_000.0 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clock_time() { + let duration = Duration::from_secs(1); + assert_eq!(duration.as_90k(), 90_000); + } + + #[test] + fn test_spec_example() { + let time = Duration::from_secs_f64(21388.766756); + assert_eq!(time.as_90k(), 0x072bd0050); + } +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..c801646 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,59 @@ +use crate::{ClockTimeExt, CueError, TransportPacketWrite}; +use bitstream_io::{BigEndian, BitWrite, BitWriter}; +#[cfg(feature = "serde")] +use serde::{Serialize, Serializer}; +use std::io; +use std::time::Duration; + +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct SpliceTime { + time_specified_flag: bool, + #[cfg_attr(feature = "serde", serde(serialize_with = "crate::serialize_time"))] + pts_time: u64, +} + +impl SpliceTime { + pub fn new() -> Self { + Self { + time_specified_flag: false, + pts_time: 0, + } + } + + pub fn from_ticks(ticks: u64) -> Self { + let mut splice_time = Self::new(); + splice_time.set_pts_time(Some(ticks)); + splice_time + } + + #[inline] + pub fn set_pts_time(&mut self, pts_time: Option) { + match pts_time { + None => { + self.time_specified_flag = false; + self.pts_time = 0; + } + Some(ticks) => { + self.time_specified_flag = true; + self.pts_time = ticks; + } + } + } +} + +impl TransportPacketWrite for SpliceTime { + fn write_to(&self, buffer: &mut W) -> Result<(), CueError> + where + W: io::Write, + { + let mut buffer = BitWriter::endian(buffer, BigEndian); + buffer.write_bit(self.time_specified_flag)?; + if self.time_specified_flag { + buffer.write(6, 0x00)?; + buffer.write(33, self.pts_time)?; + } else { + buffer.write(7, 0x00)?; + } + Ok(()) + } +}