https://github.com/rafaelcaricio/scte35
Parse and encoding of data using the SCTE-35 standard.
https://github.com/rafaelcaricio/scte35
advertisement monetization scte-35 scte-marker streaming-video video-processing
Last synced: about 2 months ago
JSON representation
Parse and encoding of data using the SCTE-35 standard.
- Host: GitHub
- URL: https://github.com/rafaelcaricio/scte35
- Owner: rafaelcaricio
- License: mit
- Created: 2022-04-23T22:05:51.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2025-06-11T20:55:02.000Z (4 months ago)
- Last Synced: 2025-08-17T19:42:37.827Z (about 2 months ago)
- Topics: advertisement, monetization, scte-35, scte-marker, streaming-video, video-processing
- Language: Rust
- Homepage: https://docs.rs/scte35/latest/scte35/
- Size: 356 KB
- Stars: 4
- Watchers: 3
- Forks: 1
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# SCTE-35 Library
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.
## Features
- **Builder Pattern API** - Type-safe builder pattern for creating SCTE-35 messages from scratch with validation
- **Serde support** - Serialize/deserialize SCTE-35 messages to/from JSON and other formats (enabled by default)
- **CRC validation** - Built-in CRC-32 validation using MPEG-2 algorithm (enabled by default)
- **Human-readable UPID parsing** - Full support for 18 standard UPID types with intelligent formatting
- **Human-readable segmentation types** - Complete set of 48 standard segmentation types with descriptive names
- **Segmentation descriptor parsing** - Complete parsing of segmentation descriptors including UPID data
- **Minimal dependencies** - Only the `crc` crate for validation (optional) and `serde` for serialization (optional)
- **Full SCTE-35 parsing** - Supports all major SCTE-35 command types
- **Bit-level precision** - Accurate parsing of bit-packed SCTE-35 messages
- **Optional CLI tool** - Command-line interface for parsing base64-encoded messages with text and JSON output formats
- **Type-safe** - Strongly typed representations of all SCTE-35 structures
- **Data integrity** - Detects corrupted or tampered SCTE-35 messages## Installation
### With All Features (Default)
Add this to your `Cargo.toml`:
```toml
[dependencies]
scte35 = "0.2.0"
```This includes both CRC validation and serde support.
### Without Serde Support
If you don't need JSON serialization:
```toml
[dependencies]
scte35 = { version = "0.2.0", default-features = false, features = ["crc-validation"] }
```### Minimal (No CRC or Serde)
For a minimal library without CRC validation or serde:
```toml
[dependencies]
scte35 = { version = "0.2.0", default-features = false }
```### With CLI Tool (Automatically includes CRC validation)
To include the command-line tool, enable the `cli` feature:
```toml
[dependencies]
scte35 = { version = "0.2.0", features = ["cli"] }
```Or install the CLI tool directly:
```bash
cargo install scte35 --features cli
```**Note**: The CLI feature automatically enables CRC validation to provide complete message diagnostics.
## Usage
### Library Usage
```rust
use scte35::{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 scte35::parse(&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),
}
```### CRC Validation
By default, the library validates CRC-32 checksums in SCTE-35 messages to ensure data integrity:
```rust
use scte35::parse_splice_info_section;// 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");
#[cfg(feature = "crc-validation")]
{
use scte35::CrcValidatable;
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);
}
}
}#[cfg(feature = "crc-validation")]
{
use scte35::{validate_scte35_crc, CrcValidatable};
// 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),
}
}
}
```### Duration Conversion
SCTE-35 time values are represented as 90kHz clock ticks. This library provides convenient conversion to Rust's `std::time::Duration`:
```rust
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);
```### Serde Support (JSON Serialization)
The library includes built-in serde support for serializing/deserializing SCTE-35 messages:
```rust
use scte35::parse_splice_info_section;// 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) {
#[cfg(feature = "serde")]
{
use serde_json;
// 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:
- **Binary data as base64**: All raw bytes (private commands, UPID data, alignment bits) are encoded as base64 strings
- **Human-readable enums**: Segmentation types and UPID types include both numeric values and descriptions
- **Time duration info**: PTS times and durations include both raw ticks and human-readable formats
- **Computed fields**: Segmentation descriptors include parsed UPID strings when availableExample JSON output:
```json
{
"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
}
```### Segmentation Types
The library provides human-readable segmentation types that correspond to the numeric IDs in SCTE-35 messages:
```rust
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());
}
}
}
```#### Supported Segmentation Types
The library supports all standard SCTE-35 segmentation types including:
**Program Boundaries:**
- Program Start/End
- Program Early Termination
- Program Breakaway/Resumption
- Program Runover (Planned/Unplanned)
- Program Overlap Start
- Program Blackout Override
- Program Join**Content Segments:**
- Chapter Start/End
- Break Start/End
- Content Identification**Advertisement Opportunities:**
- Provider/Distributor Advertisement Start/End
- Provider/Distributor Placement Opportunity Start/End
- Provider/Distributor Overlay Placement Opportunity Start/End
- Provider/Distributor Promo Start/End
- Provider/Distributor Ad Block Start/End**Special Events:**
- Unscheduled Event Start/End
- Alternate Content Opportunity Start/End
- Network Start/End### Builder Pattern API (Creating SCTE-35 Messages)
The library includes a comprehensive builder pattern API for creating SCTE-35 messages from scratch with type safety and validation:
```rust
# 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(())
# }
```#### Creating Time Signals with Segmentation Descriptors
```rust
# 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(())
# }
```#### Component-Level Splice Operations
```rust
# 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(())
# }
```#### Advanced Segmentation with Delivery Restrictions
```rust
# 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(())
# }
```#### Comprehensive UPID Support
The builder API supports all SCTE-35 UPID types with validation:
```rust
# 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(())
# }
```#### Error Handling and Validation
The builder API provides comprehensive validation with clear error messages:
```rust
# 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(())
# }
```#### Builder Features
- **Type Safety**: Compile-time prevention of invalid message states
- **Validation**: Runtime validation with descriptive error messages
- **Ergonomic API**: Fluent interface with sensible defaults
- **Spec Compliance**: Automatic handling of reserved fields and constraints
- **Complete Coverage**: Builders for all major SCTE-35 structuresSee the [builder_demo example](examples/builder_demo.rs) for more comprehensive usage examples.
### CLI Usage
When built with the `cli` feature, you can parse base64-encoded SCTE-35 messages with multiple output formats:
```bash
# 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:
```text
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)
```#### CLI Output Formats
The CLI supports two output formats:
- **Text format** (default): Human-readable format with detailed field descriptions
- **JSON format**: Structured JSON output for programmatic useJSON output includes the complete parsed structure with CRC validation results:
```bash
cargo run --features cli -- -o json "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo="
``````json
{
"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
}
}
```## Supported SCTE-35 Commands
- **SpliceNull** - Null command
- **SpliceSchedule** - Scheduled splice events
- **SpliceInsert** - Immediate or scheduled ad insertion points
- **TimeSignal** - Time synchronization signals
- **BandwidthReservation** - Bandwidth allocation commands
- **PrivateCommand** - Private/custom commands## CRC Validation
By default, the library validates CRC-32 checksums in SCTE-35 messages to ensure data integrity. This feature can be disabled if needed:
### With CRC Validation (Default)
```toml
[dependencies]
scte35 = "0.2.0"
```### Without CRC Validation (Library only)
```toml
[dependencies]
scte35 = { version = "0.2.0", default-features = false }
```### With CLI Tool (Automatically includes CRC validation)
```toml
[dependencies]
scte35 = { version = "0.2.0", features = ["cli"] }
```**Note**: The CLI feature automatically enables CRC validation to provide complete message diagnostics.
### Benefits of CRC Validation
1. **Data Integrity**: Ensures SCTE-35 messages haven't been corrupted during transmission
2. **Security**: Helps detect tampered messages
3. **Debugging**: Identifies parsing issues vs. data corruption
4. **Standards Compliance**: Follows SCTE-35 specification requirements
5. **Flexibility**: Optional feature allows users to choose performance vs. validation trade-offs## API Documentation
Full API documentation is available at [docs.rs](https://docs.rs/scte35) or can be generated locally:
```bash
cargo doc --no-deps --open
```### Main Functions
#### `parse(buffer: &[u8]) -> Result`
Convenient alias for parsing SCTE-35 messages. This is the recommended function for most use cases, providing a clean and ergonomic API.
```rust
# use scte35;
# fn main() -> Result<(), Box> {
# #[cfg(feature = "cli")]
# {
# use data_encoding::BASE64;
# let base64_message = "/DAWAAAAAAAAAP/wBQb+Qjo1vQAAuwxz9A==";
# let buffer = BASE64.decode(base64_message.as_bytes())?;
# let section = scte35::parse(&buffer)?;
# }
#
# // For doctests without the cli feature, use a direct byte array
# let buffer = vec![
# 0xfc, 0x30, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0,
# 0x05, 0x06, 0xfe, 0x42, 0x3a, 0x35, 0xbd, 0x00, 0x00, 0xbb, 0x0c, 0x73,
# 0xf4
# ];
let section = scte35::parse(&buffer)?;
# Ok(())
# }
```#### `parse_splice_info_section(buffer: &[u8]) -> Result`
Parses a complete SCTE-35 splice information section from a byte buffer. Automatically validates CRC-32 when the `crc-validation` feature is enabled. The original function behind `parse`.
#### `validate_scte35_crc(buffer: &[u8]) -> Result`
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.
### Data Structures
#### `SpliceInfoSection`
The top-level structure containing all SCTE-35 message fields:
- `table_id`: Table identifier (should be 0xFC for SCTE-35)
- `section_length`: Length of the section
- `protocol_version`: SCTE-35 protocol version
- `splice_command_type`: Type of splice command
- `splice_command`: The actual command data (enum)
- `descriptor_loop_length`: Length of descriptors
- `splice_descriptors`: List of splice descriptors
- `crc_32`: CRC32 checksum#### `SpliceCommand`
An enum representing different SCTE-35 command types:
- `SpliceNull`
- `SpliceSchedule(SpliceSchedule)`
- `SpliceInsert(SpliceInsert)`
- `TimeSignal(TimeSignal)`
- `BandwidthReservation(BandwidthReservation)`
- `PrivateCommand(PrivateCommand)`
- `Unknown`## Building from Source
### Build Library Only
```bash
git clone https://github.com/yourusername/scte35
cd scte35
cargo build --release
```### Build with CLI Tool
```bash
cargo build --release --features cli
```### Run Tests
```bash
cargo test
```## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## References
- [SCTE-35 2023r1 Specification](https://www.scte.org/standards/)
- [Digital Program Insertion Cueing Message](https://en.wikipedia.org/wiki/SCTE-35)