Initial support for TimeSignal
This commit is contained in:
parent
cc43ed8994
commit
6fd2babc11
4 changed files with 251 additions and 13 deletions
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
95
src/info.rs
95
src/info.rs
|
@ -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": [],
|
||||
|
|
45
src/lib.rs
45
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<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
59
src/time.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue