Initial support for TimeSignal

This commit is contained in:
Rafael Caricio 2022-05-02 00:08:28 +02:00
parent cc43ed8994
commit 6fd2babc11
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
4 changed files with 251 additions and 13 deletions

View file

@ -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<W>(&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<SpliceCommandType> 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(())
}
}

View file

@ -199,15 +199,16 @@ impl<C> SpliceInfoSection<C, EncodedData>
where
C: SpliceCommand,
{
pub fn as_base64(&self) -> Result<String, CueError> {
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<String, CueError> {
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<EncryptionAlgorithm> 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": [],

View file

@ -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<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
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);
}
}

59
src/time.rs Normal file
View file

@ -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<u64>) {
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<W>(&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(())
}
}