# scte35-splice
[](https://crates.io/crates/scte35-splice)
[](https://docs.rs/scte35-splice)
[](../LICENSE-MIT)
> **Formerly `dvb-scte35`.** This crate was renamed from `dvb-scte35` (last
> released as 7.9.0 in the DVB lockstep) to `scte35-splice` (v1.0.0) and is now
> independently versioned. The deprecated `dvb-scte35` crate re-exports this one.
## Install
```toml
[dependencies]
scte35-splice = "1.0"
```
Spec-cited **ANSI/SCTE 35 2023r1** splice information (Digital Program
Insertion cueing) parser **and builder** in Rust, with the rust-dvb family's
symmetric `Parse`/`Serialize` round-trip discipline — every wire type
round-trips byte-for-byte.
> **Edition note.** This crate implements **ANSI/SCTE 35 2023r1**, the
> single-document edition of the standard. SCTE has since split the standard
> into **SCTE 35-1** (the message) and **SCTE 35-2** (XML/binary mappings); the
> binary `splice_info_section` syntax implemented here is unchanged.
## What is SCTE 35?
SCTE 35 is the cueing standard used across the cable / OTT industry to signal
ad-insertion (avail) and content-segmentation opportunities in an MPEG
transport stream. A `splice_info_section` (table_id `0xFC`) carries one splice
**command** (e.g. `splice_insert`, `time_signal`) plus a loop of splice
**descriptors** (e.g. `segmentation_descriptor`), trailed by an MPEG CRC-32.
## Quick start
```rust
use scte35_splice::{SpliceInfoSection, commands::{AnyCommand, TimeSignal}};
use scte35_splice::time::SpliceTime;
use dvb_common::{Parse, Serialize};
// Build a time_signal() section and emit it.
let ts = TimeSignal { 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 parse it straight back (CRC verified on parse).
let parsed = SpliceInfoSection::parse(&bytes).unwrap();
assert!(matches!(parsed.clear.unwrap().command, AnyCommand::TimeSignal(_)));
```
## What's implemented
### splice_info_section
Full §9.6 header: `table_id` (0xFC), `section_length`, `protocol_version`,
`encrypted_packet` flag + `encryption_algorithm` (encrypted region kept raw),
33-bit `pts_adjustment`, 12-bit `tier`, `splice_command_length`,
`splice_command_type`, descriptor_loop_length, CRC-32 (verified on parse).
Decoded accessor: `pts_adjustment_duration()` → `core::time::Duration`.
### Splice commands — 6 (§9.6.1 Table 7)
All 6 are in the `declare_commands!` list in `commands/any.rs`; each has `Parse`
+ `Serialize` impl and round-trip tests. Unknown/reserved command types fall
through to `AnyCommand::Unknown` with raw bytes preserved.
| 0x00 | `splice_null` | Empty; CRC-only keep-alive |
| 0x04 | `splice_schedule` | One or more timed splice events |
| 0x05 | `splice_insert` | Immediate or timed insert with `break_duration` |
| 0x06 | `time_signal` | `splice_time` with optional PTS |
| 0x07 | `bandwidth_reservation` | Empty payload |
| 0xFF | `private_command` | `identifier` (32-bit) + opaque body |
### Splice descriptors — 5 (§10.1 Table 16)
All 5 are in the `declare_splice_descriptors!` list in `descriptors/any.rs`;
unknown tags fall through to `AnySpliceDescriptor::Unknown` with raw body (lossless).
| 0x00 | `avail_descriptor` | `provider_avail_id` |
| 0x01 | `DTMF_descriptor` | preroll + DTMF chars |
| 0x02 | `segmentation_descriptor` | `segmentation_upid`, typed `SegmentationTypeId`, `DeviceRestrictions`, `component_list` |
| 0x03 | `time_descriptor` | TAI seconds + nanoseconds + UTC offset |
| 0x04 | `audio_descriptor` | Multi-component audio coding info |
### Segmentation assignment tables
Typed enums so callers never re-implement the spec lookup tables (§10.3.3.1):
| `DeviceRestrictions` | Table 21 | 4 variants (2-bit field) |
| `SegmentationUpidType` | Table 22 | 18 named variants (0x00–0x11) + `Reserved(u8)` |
| `SegmentationTypeId` | Table 23 | 48 named variants (0x00–0x51) + `Reserved(u8)` |
All three enums round-trip via `from_*` / `to_u8`; unrecognised values are
carried through `Reserved(raw)` without data loss.
### Decoded time accessors
90 kHz `SpliceTime` and `BreakDuration` fields expose `pts()` / `duration()`
accessors returning `core::time::Duration`. The 33-bit `pts_adjustment` field
likewise has a `pts_adjustment_duration()` accessor (carry-ignored wrap per the
spec).
### Dispatch and drift tests
`AnyCommand` and `AnySpliceDescriptor` are each generated from a single
`declare_*!` macro invocation that is the single source of truth for the
dispatcher and a compile-time drift test. Adding a new command or descriptor
requires one line in the macro list.
## dvb-si integration
SCTE 35 sections ride on a PID the PMT labels with a registration descriptor
carrying the `"CUEI"` format_identifier — which [`dvb-si`](../dvb-si/) already
parses. Once you have the `0xFC` section bytes (e.g. from a dvb-si demux),
route them into `SpliceInfoSection::parse`.
## Feature flags
| `std` | **on** | Link the standard library. Without it the crate is `#![no_std]` + `alloc`. |
| `serde` | **on** | Serialize-only (`serde::Serialize`) on all types; no Deserialize. |
## MSRV
Rust **1.81**.
## Spec grounding
The syntax tables and the assignment tables are hand-transcribed in
[`scte35-splice/docs/scte_35.md`](docs/scte_35.md); every module doc cites the SCTE
35 section, table and tag/command_type it implements. SCTE 35 is published by
SCTE at no cost.
## Examples
Run with `cargo run -p scte35-splice --example <name>`:
- **`parse_splice_insert`** — parse a real `splice_info_section` carrying a `splice_insert()`.
- **`round_trip_and_descriptors`** — walk the splice descriptor loop and prove the serializer is byte-exact.
## License
Licensed under either of MIT or Apache-2.0 at your option.