Skip to main content

Crate scte35_splice

Crate scte35_splice 

Source
Expand description

§scte35-splice — ANSI/SCTE 35 2023r1 splice information

Spec-cited parser and builder for the SCTE 35 Digital Program Insertion cueing message (splice_info_section, table_id 0xFC), with the workspace’s symmetric Parse/Serialize discipline: every wire type round-trips byte-for-byte.

The implemented edition is ANSI/SCTE 35 2023r1 (the single-document edition; SCTE has since split the standard into 35-1 / 35-2). Layouts are transcribed in scte35-splice/docs/scte_35.md and cited per module.

§Coverage

§Quick start

use scte35_splice::{SpliceInfoSection, commands::AnyCommand};
use broadcast_common::{Parse, Serialize};

// A minimal time_signal() section with no descriptors, built and emitted.
let ts = scte35_splice::commands::TimeSignal {
    splice_time: scte35_splice::time::SpliceTime::with_pts(0x0_0012_3456),
};
let section = SpliceInfoSection::new_clear(AnyCommand::TimeSignal(ts), &[]);
let bytes = section.to_bytes();
assert_eq!(bytes[0], 0xFC); // table_id

// ...and parsed straight back.
let parsed = SpliceInfoSection::parse(&bytes).unwrap();
assert!(matches!(parsed.clear.as_ref().unwrap().command, AnyCommand::TimeSignal(_)));

§dvb-si integration

SCTE 35 sections ride on a PID labelled in the PMT by a registration descriptor carrying the format_identifier "CUEI" (which dvb-si already parses). Once you have the 0xFC section bytes, route them here:

use scte35_splice::SpliceInfoSection;
use broadcast_common::{Parse, Serialize};

// A splice_null() section produced by this crate stands in for bytes a
// dvb-si demux would hand you from the SCTE 35 PID.
let section = SpliceInfoSection::new_clear(
    scte35_splice::commands::AnyCommand::SpliceNull(Default::default()),
    &[],
);
let on_the_wire = section.to_bytes();

// table_id 0xFC marks it as a splice_info_section; parse it.
assert_eq!(on_the_wire[0], scte35_splice::section::TABLE_ID);
let parsed = SpliceInfoSection::parse(&on_the_wire).unwrap();
assert_eq!(parsed.descriptors().count(), 0);

§Reserved-bit & CRC policy

Reserved bits are written as 1 on serialize (the spec’s convention) and ignored on parse. splice_info_section uses the MPEG-2 CRC-32 (broadcast_common::crc32_mpeg2, §9.6.1): parse verifies it, serialize recomputes it.

§Examples

Two runnable examples ship with this crate (cargo run -p scte35-splice --example <name>).

§parse_splice_insert

//! Basic: parse a real `splice_info_section` carrying a `splice_insert()`.
//!
//! Run with: `cargo run -p scte35-splice --example parse_splice_insert`
//!
//! Bytes are a well-known SCTE 35 reference vector (a `splice_insert` with
//! event_id 0x4800008F), inlined so the example is self-contained.

use broadcast_common::Parse;
use scte35_splice::{commands::AnyCommand, SpliceInfoSection};

#[rustfmt::skip]
const SPLICE_INSERT: [u8; 50] = [
    0xFC, 0x30, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x14, 0x05, 0x48,
    0x00, 0x00, 0x8F, 0x7F, 0xEF, 0xFE, 0x73, 0x69, 0xC0, 0x2E, 0xFE, 0x00, 0x52, 0xCC, 0xF5,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x08, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x01,
    0x35, 0x62, 0xDB, 0xA3, 0x0A,
];

fn main() {
    let s = SpliceInfoSection::parse(&SPLICE_INSERT).expect("CRC + parse");

    println!("protocol_version : {}", s.protocol_version);
    println!("pts_adjustment   : {}", s.pts_adjustment);
    println!("tier             : {:#05X}", s.tier);

    let clear = s.clear.as_ref().expect("an unencrypted (clear) section");
    match &clear.command {
        AnyCommand::SpliceInsert(si) => {
            println!("command          : splice_insert");
            println!("splice_event_id  : {:#010X}", si.splice_event_id);
        }
        other => println!("command          : {other:?}"),
    }
}

§round_trip_and_descriptors

//! Advanced: parse a `splice_info_section`, walk its descriptor loop, and
//! prove the serializer is byte-exact (the project's round-trip invariant).
//!
//! Run with: `cargo run -p scte35-splice --example round_trip_and_descriptors`

use broadcast_common::{Parse, Serialize};
use scte35_splice::SpliceInfoSection;

#[rustfmt::skip]
const SPLICE_INSERT: [u8; 50] = [
    0xFC, 0x30, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x14, 0x05, 0x48,
    0x00, 0x00, 0x8F, 0x7F, 0xEF, 0xFE, 0x73, 0x69, 0xC0, 0x2E, 0xFE, 0x00, 0x52, 0xCC, 0xF5,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x08, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x01,
    0x35, 0x62, 0xDB, 0xA3, 0x0A,
];

fn main() {
    let s = SpliceInfoSection::parse(&SPLICE_INSERT).expect("CRC + parse");

    let mut n = 0;
    for d in s.descriptors() {
        n += 1;
        match d {
            Ok(desc) => println!("descriptor #{n}: {desc:?}"),
            Err(e) => println!("descriptor #{n}: <malformed: {e}>"),
        }
    }
    println!("{n} splice descriptor(s) in the loop");

    // Round-trip: re-serialize and require byte-identical output (CRC included).
    let bytes = s.to_bytes();
    assert_eq!(bytes.len(), s.serialized_len());
    assert_eq!(
        bytes, SPLICE_INSERT,
        "serialize must reproduce the input exactly"
    );
    println!("round-trip: {} bytes, byte-identical ✔", bytes.len());
}

Re-exports§

pub use error::Error;
pub use error::Result;
pub use section::ClearPayload;
pub use section::SpliceInfoSection;
pub use traits::CommandDef;
pub use traits::SpliceDescriptorDef;

Modules§

commands
Splice commands — ANSI/SCTE 35 2023r1 §9.7, Tables 8-13.
descriptors
Splice descriptors — ANSI/SCTE 35 2023r1 §10, Tables 16-28.
dvb_ta
DVB Targeted Advertising — binary SCTE 35 profile (ETSI TS 103 752-1 V1.2.1).
error
Error type returned by every parser + builder in this crate.
section
splice_info_section() — ANSI/SCTE 35 2023r1 §9.6, Table 5 (table_id 0xFC).
time
Time structures and 90 kHz helpers — ANSI/SCTE 35 2023r1 §9.8.1 (Table 14) and §9.8.2 (Table 15).
traits
SCTE-35-specific dispatch traits. Parse / Serialize come from broadcast_common and are imported directly at call sites.