Possibility to write splice info section
This commit is contained in:
parent
f3936e2e78
commit
5e9522b845
6 changed files with 450 additions and 77 deletions
|
@ -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
44
README.md
Normal 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
68
src/commands.rs
Normal 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
133
src/descriptors.rs
Normal 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
189
src/info.rs
Normal 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());
|
||||
}
|
||||
}
|
92
src/lib.rs
92
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<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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue