Possibility to write splice info section

This commit is contained in:
Rafael Caricio 2022-05-01 00:49:45 +02:00
parent f3936e2e78
commit 5e9522b845
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
6 changed files with 450 additions and 77 deletions

View file

@ -7,5 +7,6 @@ edition = "2021"
license = "MIT"
[dependencies]
base64 = "0.13.0"
bitstream-io = "1.3.0"
crc = "3.0.0"

44
README.md Normal file
View file

@ -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

68
src/commands.rs Normal file
View file

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

133
src/descriptors.rs Normal file
View file

@ -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<Component>,
delivery_restricted: Option<DeliveryRestriction>,
segmentation_duration: Option<u64>,
segmentation_upid: SegmentationUpid,
}
impl TransportPacketWrite for SegmentationDescriptor {
fn write_to<W>(&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<u8> 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<SpliceDescriptorTag> 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,
}

189
src/info.rs Normal file
View file

@ -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<C>
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<Box<dyn SpliceDescriptor>>,
}
impl<C> SpliceInfoSection<C>
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<String, CueError> {
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<u8> 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<EncryptionAlgorithm> 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<C> TransportPacketWrite for SpliceInfoSection<C>
where
C: SpliceCommand,
{
fn write_to<W>(&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());
}
}

View file

@ -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<Vec<Component>>,
delivery_restricted: Option<DeliveryRestriction>,
segmentation_duration: Option<u64>,
segmentation_upid: SegmentationUpid,
mod commands;
mod descriptors;
mod info;
pub trait TransportPacketWrite {
fn write_to<W>(&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<io::Error> for CueError {
fn from(err: io::Error) -> CueError {
CueError::Io(err)
}
}