From 5e9522b84516ff25ad4e896b3a9b58337539e961 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Sun, 1 May 2022 00:49:45 +0200 Subject: [PATCH] Possibility to write splice info section --- Cargo.toml | 1 + README.md | 44 +++++++++++ src/commands.rs | 68 ++++++++++++++++ src/descriptors.rs | 133 +++++++++++++++++++++++++++++++ src/info.rs | 189 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 92 ++++------------------ 6 files changed, 450 insertions(+), 77 deletions(-) create mode 100644 README.md create mode 100644 src/commands.rs create mode 100644 src/descriptors.rs create mode 100644 src/info.rs diff --git a/Cargo.toml b/Cargo.toml index e027653..39e56fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" license = "MIT" [dependencies] +base64 = "0.13.0" bitstream-io = "1.3.0" crc = "3.0.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..67cde9d --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# SCTE-35 lib and parser for Rust + +This library provide access to parse and encoding of data using the SCTE-35 standard. This standard is used by +cable providers and broadcasters to insert signaling information into the video stream for advertising and +other purposes. More information can be found at +[Digital Program Insertion Cueing Message for Cable](https://www.scte.org/documents/standards/scte-35/). + +## Main Features + +- Parsing of SCTE-35 data +- Encoding of SCTE-35 data +- `no_std` support +- Serde integration for serialization and deserialization in other formats + + +## Implementation Overview + +Implemented parts of the standard are: + + - [ ] Splice Info section + - Splice Command section: + - [ ] Splice Null + - [ ] Splice Insert + - [ ] Splice Schedule + - [ ] Time Signal + - [ ] Bandwidth Reservation + - [ ] Splice Time + - Splice Descriptors: + - [ ] Avail + - [ ] DTMF + - [ ] Segmentation Descriptor + - [ ] Cablelabs + - [ ] MPU + - [ ] MID + - [ ] ADS + - [ ] SCR + - Encryption Information section + - Encryption Algorithms: + - [ ] DES – ECB mode + - [ ] DES – CBC mode + - [ ] Triple DES EDE3 – ECB mode + - [ ] Customized encryption algorithm + - [ ] CRC encryption calculation + - [ ] CRC calculation \ No newline at end of file diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..41d2876 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,68 @@ +use crate::{CueError, TransportPacketWrite}; +use std::io; + +pub trait SpliceCommand: TransportPacketWrite { + fn splice_command_type(&self) -> u8; +} + +pub struct SpliceNull {} + +impl SpliceNull { + pub fn new() -> SpliceNull { + SpliceNull {} + } +} + +impl TransportPacketWrite for SpliceNull { + fn write_to(&self, _: &mut W) -> Result<(), CueError> + where + W: io::Write, + { + Ok(()) + } +} + +impl SpliceCommand for SpliceNull { + fn splice_command_type(&self) -> u8 { + SpliceCommandType::SpliceNull.into() + } +} + +#[derive(Debug, Clone, Copy)] +pub enum SpliceCommandType { + SpliceNull, + SpliceSchedule, + SpliceInsert, + TimeSignal, + BandwidthReservation, + PrivateCommand, + Reserved(u8), +} + +impl From for SpliceCommandType { + fn from(value: u8) -> SpliceCommandType { + match value { + 0x00 => SpliceCommandType::SpliceNull, + 0x04 => SpliceCommandType::SpliceSchedule, + 0x05 => SpliceCommandType::SpliceInsert, + 0x06 => SpliceCommandType::TimeSignal, + 0x07 => SpliceCommandType::BandwidthReservation, + 0xff => SpliceCommandType::PrivateCommand, + _ => SpliceCommandType::Reserved(value), + } + } +} + +impl From for u8 { + fn from(value: SpliceCommandType) -> u8 { + match value { + SpliceCommandType::SpliceNull => 0x00, + SpliceCommandType::SpliceSchedule => 0x04, + SpliceCommandType::SpliceInsert => 0x05, + SpliceCommandType::TimeSignal => 0x06, + SpliceCommandType::BandwidthReservation => 0x07, + SpliceCommandType::PrivateCommand => 0xff, + SpliceCommandType::Reserved(value) => value, + } + } +} diff --git a/src/descriptors.rs b/src/descriptors.rs new file mode 100644 index 0000000..d92cd0d --- /dev/null +++ b/src/descriptors.rs @@ -0,0 +1,133 @@ +use crate::{CueError, TransportPacketWrite}; +use std::io::Write; + +pub trait SpliceDescriptor { + fn splice_descriptor_tag(&self) -> u8; +} + +struct SegmentationDescriptor { + identifier: u32, + segmentation_event_id: u32, + segmentation_event_cancel_indicator: bool, + program_segmentation: Vec, + delivery_restricted: Option, + segmentation_duration: Option, + segmentation_upid: SegmentationUpid, +} + +impl TransportPacketWrite for SegmentationDescriptor { + fn write_to(&self, buffer: &mut W) -> Result<(), CueError> + where + W: Write, + { + todo!() + } +} + +impl SpliceDescriptor for SegmentationDescriptor { + fn splice_descriptor_tag(&self) -> u8 { + SpliceDescriptorTag::Segmentation.into() + } +} + +struct DeliveryRestriction { + web_delivery_allowed_flag: bool, + no_regional_blackout_flag: bool, + archive_allowed_flag: bool, + device_restrictions: DeviceRestrictions, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +enum SpliceDescriptorTag { + Avail, + DTMF, + Segmentation, + Time, + Audio, + Reserved(u8), + DVB(u8), +} + +impl From for SpliceDescriptorTag { + fn from(value: u8) -> Self { + match value { + 0x0 => SpliceDescriptorTag::Avail, + 0x1 => SpliceDescriptorTag::DTMF, + 0x2 => SpliceDescriptorTag::Segmentation, + 0x3 => SpliceDescriptorTag::Time, + 0x4 => SpliceDescriptorTag::Audio, + 0x5..=0xEF => SpliceDescriptorTag::Reserved(value), + 0xF0..=0xFF => SpliceDescriptorTag::DVB(value), + } + } +} + +impl From for u8 { + fn from(value: SpliceDescriptorTag) -> Self { + match value { + SpliceDescriptorTag::Avail => 0x0, + SpliceDescriptorTag::DTMF => 0x1, + SpliceDescriptorTag::Segmentation => 0x2, + SpliceDescriptorTag::Time => 0x3, + SpliceDescriptorTag::Audio => 0x4, + SpliceDescriptorTag::Reserved(value) => value, + SpliceDescriptorTag::DVB(value) => value, + } + } +} + +enum DeviceRestrictions { + RestrictGroup0 = 0x00, + RestrictGroup1 = 0x01, + RestrictGroup2 = 0x10, + None = 0x11, +} + +enum SegmentationUpidType { + NotUsed, + UserDefinedDeprecated, + ISCI, + AdID, + UMID, + ISANDeprecated, + ISAN, + TID, + TI, + ADI, + EIDR, + ATSCContentIdentifier, + MPU, + MID, + ADSInformation, + URI, + UUID, + SCR, + Reserved, +} + +enum SegmentationUpid { + NotUsed, + UserDefinedDeprecated, + ISCI, + AdID, + UMID, + ISANDeprecated, + ISAN, + TID, + TI, + ADI, + EIDR, + ATSCContentIdentifier, + MPU, + MID, + ADSInformation, + URI, + UUID, + SCR, + Reserved, +} + +struct Component { + component_tag: u8, + pts_offset: u64, +} diff --git a/src/info.rs b/src/info.rs new file mode 100644 index 0000000..c0b95c9 --- /dev/null +++ b/src/info.rs @@ -0,0 +1,189 @@ +use crate::commands::SpliceCommand; +use crate::descriptors::SpliceDescriptor; +use crate::{CueError, TransportPacketWrite}; +use bitstream_io::{BigEndian, BitWrite, BitWriter}; +use std::io; + +pub struct SpliceInfoSection +where + C: SpliceCommand, +{ + /// This is an 8-bit field. Its value shall be 0xFC. + table_id: u8, + + /// The section_syntax_indicator is a 1-bit field that should always be set to ‘0’, indicating + /// that MPEG short sections are to be used. + section_syntax_indicator: bool, + + /// This is a 1-bit flag that shall be set to 0. + private_indicator: bool, + + /// A two-bit field that indicates if the content preparation system has created a Stream + /// Access Point (SAP) at the signaled point in the stream. SAP types are defined in + /// ISO 14496-12, Annex I. The semantics of SAP types are further informatively elaborated + /// in ISO/IEC 23009-1 DASH, Section 4.5.2. + sap_type: SAPType, // 2 bits + + protocol_version: u8, + encrypted_packet: bool, + encryption_algorithm: EncryptionAlgorithm, + pts_adjustment: u64, // 33 bits + cw_index: u8, + tier: u16, // 12 bits + + splice_command: C, + + descriptors: Vec>, +} + +impl SpliceInfoSection +where + C: SpliceCommand, +{ + fn new(splice_command: C) -> Self { + Self { + table_id: 0xFC, + section_syntax_indicator: false, + private_indicator: false, + sap_type: SAPType::NotSpecified, + protocol_version: 0, + encrypted_packet: false, + encryption_algorithm: EncryptionAlgorithm::NotEncrypted, + pts_adjustment: 0, + cw_index: 0, + tier: 0, + splice_command, + descriptors: Vec::new(), + } + } + + pub fn as_base64(&self) -> Result { + let mut out = Vec::new(); + self.write_to(&mut out)?; + Ok(base64::encode(out.as_slice())) + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum SAPType { + Type1 = 0x00, + Type2 = 0x01, + Type3 = 0x02, + NotSpecified = 0x03, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum EncryptionAlgorithm { + NotEncrypted, + DESECBMode, + DESCBCMode, + TripleDESEDE3ECBMode, + Reserved(u8), // 4-31 + Private(u8), // 32-63 +} + +impl From for EncryptionAlgorithm { + fn from(value: u8) -> Self { + match value { + 0x00 => EncryptionAlgorithm::NotEncrypted, + 0x01 => EncryptionAlgorithm::DESECBMode, + 0x02 => EncryptionAlgorithm::DESCBCMode, + 0x03 => EncryptionAlgorithm::TripleDESEDE3ECBMode, + 0x04..=0x1F => EncryptionAlgorithm::Reserved(value), + 0x20..=0xFF => EncryptionAlgorithm::Private(value), + } + } +} + +impl From for u8 { + fn from(value: EncryptionAlgorithm) -> Self { + match value { + EncryptionAlgorithm::NotEncrypted => 0x00, + EncryptionAlgorithm::DESECBMode => 0x01, + EncryptionAlgorithm::DESCBCMode => 0x02, + EncryptionAlgorithm::TripleDESEDE3ECBMode => 0x03, + EncryptionAlgorithm::Reserved(value) => value, + EncryptionAlgorithm::Private(value) => value, + } + } +} + +impl TransportPacketWrite for SpliceInfoSection +where + C: SpliceCommand, +{ + fn write_to(&self, out: &mut W) -> Result<(), CueError> + where + W: io::Write, + { + // Write splice command to a temporary buffer + let mut splice_data = Vec::new(); + self.splice_command.write_to(&mut splice_data)?; + + // Write the descriptors to a temporary buffer + let mut descriptor_data = Vec::new(); + // for descriptor in &self.descriptors { + // descriptor.write_to(&mut descriptor_data)?; + // } + + // Start writing the final output to a temporary buffer + let mut data = Vec::new(); + let mut buffer = BitWriter::endian(&mut data, BigEndian); + buffer.write(8, self.table_id)?; + buffer.write_bit(self.section_syntax_indicator)?; + buffer.write_bit(self.private_indicator)?; + buffer.write(2, self.sap_type as u8)?; + + // We know the section length by computing all known fixed size elements from now plus the + // 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(); + if self.encrypted_packet { + section_length += 4; + } + buffer.write(12, section_length as u16)?; + buffer.write(8, self.protocol_version)?; + buffer.write_bit(self.encrypted_packet)?; + let encryption_algorithm: u8 = self.encryption_algorithm.into(); + buffer.write(6, encryption_algorithm)?; + buffer.write(33, self.pts_adjustment)?; + buffer.write(8, self.cw_index)?; + buffer.write(12, self.tier)?; + buffer.write(12, splice_data.len() as u16)?; + buffer.write(8, self.splice_command.splice_command_type())?; + buffer.write_bytes(splice_data.as_slice())?; + buffer.write(16, descriptor_data.len() as u16)?; + buffer.write_bytes(descriptor_data.as_slice())?; + buffer.flush()?; + + // Finally, write to out + let mut buffer = BitWriter::endian(out, BigEndian); + buffer.write_bytes(data.as_slice())?; + // CRC 32 + if self.encrypted_packet { + // TODO: alignment stuffing here, in case of DES encryption this needs to be 8 bytes aligned + // encrypted_packet_crc32: + buffer.write(32, 0)?; + } + // TODO: Calculate CRC32. Use the data information + // crc32: + buffer.write(32, 0)?; + buffer.flush()?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::commands::SpliceNull; + + #[test] + fn write_null_splice() { + let splice = SpliceInfoSection::new(SpliceNull::new()); + + assert_eq!(splice.as_base64().unwrap(), "".to_string()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 57cf70a..a8b01a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,84 +1,22 @@ +use std::io; -struct SegmentationDescriptor { - tag: u8, - descriptor_length: u8, - identifier: u32, - segmentation_event_id: u32, - segmentation_event_cancel_indicator: bool, - program_segmentation: Option>, - delivery_restricted: Option, - segmentation_duration: Option, - segmentation_upid: SegmentationUpid, +mod commands; +mod descriptors; +mod info; + +pub trait TransportPacketWrite { + fn write_to(&self, buffer: &mut W) -> Result<(), CueError> + where + W: io::Write; } -struct DeliveryRestriction { - web_delivery_allowed_flag: bool, - no_regional_blackout_flag: bool, - archive_allowed_flag: bool, - device_restrictions: DeviceRestrictions, +#[derive(Debug)] +pub enum CueError { + Io(io::Error), } -enum DeviceRestrictions { - RestrictGroup0 = 0x00, - RestrictGroup1 = 0x01, - RestrictGroup2 = 0x10, - None = 0x11, -} - -enum SegmentationUpidType { - NotUsed, - UserDefinedDeprecated, - ISCI, - AdID, - UMID, - ISANDeprecated, - ISAN, - TID, - TI, - ADI, - EIDR, - ATSCContentIdentifier, - MPU, - MID, - ADSInformation, - URI, - UUID, - SCR, - Reserved -} - -enum SegmentationUpid { - NotUsed, - UserDefinedDeprecated, - ISCI, - AdID, - UMID, - ISANDeprecated, - ISAN, - TID, - TI, - ADI, - EIDR, - ATSCContentIdentifier, - MPU, - MID, - ADSInformation, - URI, - UUID, - SCR, - Reserved -} - -struct Component { - component_tag: u8, - pts_offset: u64, -} - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); +impl From for CueError { + fn from(err: io::Error) -> CueError { + CueError::Io(err) } }