| Crates.io | scte35 |
| lib.rs | scte35 |
| version | 0.2.0 |
| created_at | 2022-04-23 22:10:22.66072+00 |
| updated_at | 2025-06-09 18:50:22.013447+00 |
| description | A Rust library for creating and parsing SCTE-35 (Society of Cable Telecommunications Engineers) messages with zero-dependency. |
| homepage | https://docs.rs/scte35 |
| repository | https://github.com/rafaelcaricio/scte35 |
| max_upload_size | |
| id | 572841 |
| size | 511,264 |
A Rust library for creating and parsing SCTE-35 (Society of Cable Telecommunications Engineers) messages with built-in CRC validation. SCTE-35 is a standard for inserting cue messages into video streams, commonly used for ad insertion points in broadcast television.
crc crate for validation (optional) and serde for serialization (optional)Add this to your Cargo.toml:
[dependencies]
scte35 = "0.2.0"
This includes both CRC validation and serde support.
If you don't need JSON serialization:
[dependencies]
scte35 = { version = "0.2.0", default-features = false, features = ["crc-validation"] }
For a minimal library without CRC validation or serde:
[dependencies]
scte35 = { version = "0.2.0", default-features = false }
To include the command-line tool, enable the cli feature:
[dependencies]
scte35 = { version = "0.2.0", features = ["cli"] }
Or install the CLI tool directly:
cargo install scte35 --features cli
Note: The CLI feature automatically enables CRC validation to provide complete message diagnostics.
use scte35::{parse_splice_info_section, SpliceCommand, SpliceDescriptor};
use std::time::Duration;
// Your SCTE-35 message as bytes (example message)
let scte35_bytes = vec![
0xFC, 0x30, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x05, 0x06, 0xFE,
0x42, 0x3A, 0x35, 0xBD, 0x00, 0x00, 0xBB, 0x0C, 0x73, 0xF4
];
match parse_splice_info_section(&scte35_bytes) {
Ok(section) => {
println!("Table ID: {}", section.table_id);
println!("Command Type: {}", section.splice_command_type);
match section.splice_command {
SpliceCommand::SpliceInsert(insert) => {
println!("Splice Event ID: 0x{:08x}", insert.splice_event_id);
// Convert break duration to std::time::Duration
if let Some(break_duration) = &insert.break_duration {
let duration: Duration = break_duration.into();
println!("Break Duration: {:?}", duration);
println!("Break Duration: {:.3} seconds", duration.as_secs_f64());
}
// Convert splice time to Duration
if let Some(duration) = insert.splice_time.as_ref()
.and_then(|st| st.to_duration()) {
println!("Splice Time: {:?}", duration);
}
}
SpliceCommand::TimeSignal(signal) => {
if let Some(duration) = signal.splice_time.to_duration() {
println!("Time Signal: {:?}", duration);
}
}
_ => println!("Other command type"),
}
// Parse segmentation descriptors with UPID information
for descriptor in §ion.splice_descriptors {
if let SpliceDescriptor::Segmentation(seg_desc) = descriptor {
println!("Segmentation Event ID: 0x{:08x}", seg_desc.segmentation_event_id);
println!("UPID Type: {}", seg_desc.upid_type_description());
println!("Segmentation Type: {}", seg_desc.segmentation_type_description());
if let Some(upid_str) = seg_desc.upid_as_string() {
println!("UPID: {}", upid_str);
}
}
}
}
Err(e) => eprintln!("Error parsing SCTE-35: {}", e),
}
By default, the library validates CRC-32 checksums in SCTE-35 messages to ensure data integrity:
use scte35::{parse_splice_info_section, validate_scte35_crc, CrcValidatable};
// Example SCTE-35 message bytes
let scte35_bytes = vec![
0xFC, 0x30, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x05, 0x06, 0xFE,
0x42, 0x3A, 0x35, 0xBD, 0x00, 0x00, 0xBB, 0x0C, 0x73, 0xF4
];
// Parse with automatic CRC validation (default behavior)
match parse_splice_info_section(&scte35_bytes) {
Ok(section) => {
println!("Valid SCTE-35 message parsed successfully");
println!("CRC-32: 0x{:08X}", section.get_crc());
}
Err(e) => {
if e.to_string().contains("CRC validation failed") {
eprintln!("Message corrupted or tampered: {}", e);
} else {
eprintln!("Parse error: {}", e);
}
}
}
// Validate CRC independently
match validate_scte35_crc(&scte35_bytes) {
Ok(true) => println!("CRC validation passed"),
Ok(false) => println!("CRC validation failed or not available"),
Err(e) => eprintln!("Validation error: {}", e),
}
// Validate using the parsed section
if let Ok(section) = parse_splice_info_section(&scte35_bytes) {
match section.validate_crc(&scte35_bytes) {
Ok(true) => println!("Message integrity verified"),
Ok(false) => println!("Message integrity check failed"),
Err(e) => eprintln!("Validation error: {}", e),
}
}
SCTE-35 time values are represented as 90kHz clock ticks. This library provides convenient conversion to Rust's std::time::Duration:
use scte35::BreakDuration;
use std::time::Duration;
// Create a break duration of 30 seconds (30 * 90000 ticks)
let break_duration = BreakDuration {
auto_return: 1,
reserved: 0,
duration: 2_700_000,
};
// Convert using Into trait
let duration: Duration = (&break_duration).into();
assert_eq!(duration.as_secs(), 30);
// Or use the method directly
let duration2 = break_duration.to_duration();
assert_eq!(duration2.as_secs(), 30);
The library includes built-in serde support for serializing/deserializing SCTE-35 messages:
use scte35::parse_splice_info_section;
use serde_json;
// Parse SCTE-35 message
let scte35_bytes = vec![
0xFC, 0x30, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x05, 0x06, 0xFE,
0x42, 0x3A, 0x35, 0xBD, 0x00, 0x00, 0xBB, 0x0C, 0x73, 0xF4
];
if let Ok(section) = parse_splice_info_section(&scte35_bytes) {
// Serialize to JSON
let json = serde_json::to_string_pretty(§ion).unwrap();
println!("{}", json);
// Deserialize from JSON
let deserialized: scte35::SpliceInfoSection =
serde_json::from_str(&json).unwrap();
assert_eq!(section, deserialized);
}
The serde implementation includes:
Example JSON output:
{
"table_id": 252,
"section_syntax_indicator": 0,
"private_indicator": 0,
"section_length": 22,
"protocol_version": 0,
"encrypted_packet": 0,
"encryption_algorithm": 0,
"pts_adjustment": 0,
"cw_index": 255,
"tier": 4095,
"splice_command_length": 5,
"splice_command_type": 6,
"splice_command": {
"type": "TimeSignal",
"splice_time": {
"time_specified_flag": 1,
"pts_time": 900000,
"duration_info": {
"ticks": 900000,
"seconds": 10.0,
"human_readable": "10.0s"
}
}
},
"descriptor_loop_length": 0,
"splice_descriptors": [],
"alignment_stuffing_bits": "",
"e_crc_32": null,
"crc_32": 0
}
The library provides human-readable segmentation types that correspond to the numeric IDs in SCTE-35 messages:
use scte35::{SegmentationType, parse_splice_info_section, SpliceDescriptor};
// Work with segmentation types directly
let seg_type = SegmentationType::ProviderAdvertisementStart;
println!("Type: {} (ID: 0x{:02X})", seg_type, seg_type.id());
// Convert from numeric ID (useful when parsing)
let seg_type = SegmentationType::from_id(0x30);
assert_eq!(seg_type, SegmentationType::ProviderAdvertisementStart);
assert_eq!(seg_type.to_string(), "Provider Advertisement Start");
// Example: Parse a message and check segmentation descriptors
let scte35_bytes = vec![
0xFC, 0x30, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x05, 0x06, 0xFE,
0x42, 0x3A, 0x35, 0xBD, 0x00, 0x00, 0xBB, 0x0C, 0x73, 0xF4
];
if let Ok(section) = parse_splice_info_section(&scte35_bytes) {
// Segmentation descriptors automatically populate both fields
// The numeric ID and human-readable type are always consistent
for descriptor in §ion.splice_descriptors {
if let SpliceDescriptor::Segmentation(seg_desc) = descriptor {
println!("Segmentation Type ID: 0x{:02X}", seg_desc.segmentation_type_id);
println!("Segmentation Type: {:?}", seg_desc.segmentation_type);
println!("Description: {}", seg_desc.segmentation_type_description());
}
}
}
The library supports all standard SCTE-35 segmentation types including:
Program Boundaries:
Content Segments:
Advertisement Opportunities:
Special Events:
The library includes a comprehensive builder pattern API for creating SCTE-35 messages from scratch with type safety and validation:
# use scte35::builders::*;
# use scte35::types::SegmentationType;
# use std::time::Duration;
# fn main() -> BuilderResult<()> {
// Example 1: Creating a 30-second ad break starting at 20 seconds
let splice_insert = SpliceInsertBuilder::new(12345)
.at_pts(Duration::from_secs(20))?
.duration(Duration::from_secs(30))
.unique_program_id(0x1234)
.avail(1, 4) // First of 4 avails
.build()?;
let section = SpliceInfoSectionBuilder::new()
.pts_adjustment(0)
.splice_insert(splice_insert)
.build()?;
println!("Created SCTE-35 message with {} byte payload", section.section_length);
# Ok(())
# }
# use scte35::builders::*;
# use scte35::types::SegmentationType;
# use std::time::Duration;
# fn example() -> BuilderResult<()> {
// Example 2: Program start boundary with UPID
let segmentation = SegmentationDescriptorBuilder::new(
5678,
SegmentationType::ProgramStart
)
.upid(Upid::AdId("ABC123456789".to_string()))?
.duration(Duration::from_secs(1800))? // 30-minute program
.build()?;
let section = SpliceInfoSectionBuilder::new()
.time_signal(TimeSignalBuilder::new().immediate().build()?)
.add_segmentation_descriptor(segmentation)
.build()?;
# Ok(())
# }
# use scte35::builders::*;
# use std::time::Duration;
# fn example() -> BuilderResult<()> {
// Example 3: Component-level splice for specific audio/video streams
let splice_insert = SpliceInsertBuilder::new(3333)
.component_splice(vec![
(0x01, Some(Duration::from_secs(10))), // Video component
(0x02, Some(Duration::from_secs(10))), // Audio component 1
(0x03, Some(Duration::from_secs(10))), // Audio component 2
])?
.duration(Duration::from_secs(15))
.build()?;
# Ok(())
# }
# use scte35::builders::*;
# use scte35::types::SegmentationType;
# fn example() -> BuilderResult<()> {
// Example 4: Complex segmentation with delivery restrictions
let restrictions = DeliveryRestrictions {
web_delivery_allowed: false,
no_regional_blackout: false,
archive_allowed: true,
device_restrictions: DeviceRestrictions::RestrictGroup1,
};
let uuid_bytes = [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0];
let segmentation = SegmentationDescriptorBuilder::new(
7777,
SegmentationType::DistributorAdvertisementStart
)
.delivery_restrictions(restrictions)
.upid(Upid::Uuid(uuid_bytes))?
.segment(2, 6) // 2nd of 6 segments
.build()?;
# Ok(())
# }
The builder API supports all SCTE-35 UPID types with validation:
# use scte35::builders::*;
# fn example() -> BuilderResult<()> {
# let mut builder = SegmentationDescriptorBuilder::new(1, scte35::types::SegmentationType::ProgramStart);
// Ad ID (12 ASCII characters)
builder = builder.upid(Upid::AdId("ABC123456789".to_string()))?;
// UUID (16 bytes)
let uuid_bytes = [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0];
builder = builder.upid(Upid::Uuid(uuid_bytes))?;
// URI (variable length)
builder = builder.upid(Upid::Uri("https://example.com/content/123".to_string()))?;
// ISAN (12 bytes)
let isan_bytes = [0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23];
builder = builder.upid(Upid::Isan(isan_bytes))?;
// And many more: UMID, EIDR, TID, AiringID, etc.
# Ok(())
# }
The builder API provides comprehensive validation with clear error messages:
# use scte35::builders::*;
# use scte35::types::SegmentationType;
# use std::time::Duration;
# fn example() -> BuilderResult<()> {
// Invalid UPID length
let result = SegmentationDescriptorBuilder::new(1234, SegmentationType::ProgramStart)
.upid(Upid::AdId("TOO_SHORT".to_string()));
match result {
Err(BuilderError::InvalidUpidLength { expected, actual }) => {
println!("UPID validation failed: expected {} chars, got {}", expected, actual);
}
_ => {}
}
// Duration too large for 33-bit PTS
let result = SpliceInsertBuilder::new(1234)
.at_pts(Duration::from_secs(u64::MAX / 90_000 + 1))?
.build();
match result {
Err(BuilderError::DurationTooLarge { field, duration }) => {
println!("Duration {} is too large for field {}", duration.as_secs(), field);
}
_ => {}
}
# Ok(())
# }
See the builder_demo example for more comprehensive usage examples.
When built with the cli feature, you can parse base64-encoded SCTE-35 messages with multiple output formats:
# Text output (default)
cargo run --features cli -- "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo="
# JSON output
cargo run --features cli -- -o json "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo="
# Or with long flag
cargo run --features cli -- --output json "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo="
# Get help
cargo run --features cli -- --help
Example output with splice insert command and break duration:
Successfully parsed SpliceInfoSection:
Table ID: 252
Section Length: 47
Protocol Version: 0
Splice Command Type: 5
Splice Command Length: 20
Splice Command: SpliceInsert
Splice Event ID: 0x4800008f
Splice Event Cancel: 0
Out of Network: 1
Program Splice Flag: 1
Duration Flag: 1
Splice Immediate Flag: 0
Splice Time PTS: 0x07369c02e
Splice Time: 21514.559089 seconds
Break Duration:
Auto Return: 1
Duration: 0x00052ccf5 (60.293567 seconds)
Unique Program ID: 0
Avail Num: 0
Avails Expected: 0
Descriptor Loop Length: 10
Number of Descriptors: 1
Unknown Descriptor:
Tag: 0x00
Length: 8
Content: "CUEI 5"
CRC-32: 0x62DBA30A ✓ (Valid)
The CLI supports two output formats:
JSON output includes the complete parsed structure with CRC validation results:
cargo run --features cli -- -o json "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo="
{
"status": "success",
"data": {
"table_id": 252,
"section_length": 47,
"protocol_version": 0,
"splice_command_type": 5,
"splice_command_length": 20,
"splice_command": {
"type": "SpliceInsert",
"splice_event_id": 1207959695,
"splice_event_cancel_indicator": 0,
"out_of_network_indicator": 1,
"program_splice_flag": 1,
"duration_flag": 1,
"splice_immediate_flag": 0,
"splice_time": {
"time_specified_flag": 1,
"pts_time": 1936310318,
"duration_info": {
"ticks": 1936310318,
"seconds": 21514.559088888887,
"human_readable": "5h 58m 34.6s"
}
},
"break_duration": {
"auto_return": 1,
"duration": 5426421,
"duration_info": {
"ticks": 5426421,
"seconds": 60.29356666666666,
"human_readable": "1m 0.3s"
}
},
"unique_program_id": 0,
"avail_num": 0,
"avails_expected": 0
},
"descriptor_loop_length": 10,
"splice_descriptors": [
{
"descriptor_type": "Unknown",
"tag": 0,
"length": 8,
"data": "Q1VFSQAAATU="
}
],
"crc_32": 1658561290
},
"crc_validation": {
"valid": true,
"error": null
}
}
By default, the library validates CRC-32 checksums in SCTE-35 messages to ensure data integrity. This feature can be disabled if needed:
[dependencies]
scte35 = "0.2.0"
[dependencies]
scte35 = { version = "0.2.0", default-features = false }
[dependencies]
scte35 = { version = "0.2.0", features = ["cli"] }
Note: The CLI feature automatically enables CRC validation to provide complete message diagnostics.
Full API documentation is available at docs.rs or can be generated locally:
cargo doc --no-deps --open
parse_splice_info_section(buffer: &[u8]) -> Result<SpliceInfoSection, io::Error>Parses a complete SCTE-35 splice information section from a byte buffer. Automatically validates CRC-32 when the crc-validation feature is enabled.
validate_scte35_crc(buffer: &[u8]) -> Result<bool, io::Error>Validates the CRC-32 checksum of an SCTE-35 message independently. Returns Ok(true) if valid, Ok(false) if invalid or CRC validation is disabled.
SpliceInfoSectionThe top-level structure containing all SCTE-35 message fields:
table_id: Table identifier (should be 0xFC for SCTE-35)section_length: Length of the sectionprotocol_version: SCTE-35 protocol versionsplice_command_type: Type of splice commandsplice_command: The actual command data (enum)descriptor_loop_length: Length of descriptorssplice_descriptors: List of splice descriptorscrc_32: CRC32 checksumSpliceCommandAn enum representing different SCTE-35 command types:
SpliceNullSpliceSchedule(SpliceSchedule)SpliceInsert(SpliceInsert)TimeSignal(TimeSignal)BandwidthReservation(BandwidthReservation)PrivateCommand(PrivateCommand)Unknowngit clone https://github.com/yourusername/scte35
cd scte35
cargo build --release
cargo build --release --features cli
cargo test
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.