timed-metadata 0.1.0

Convert DPI / timed-metadata signalling between SCTE-35, HLS EXT-X-DATERANGE and DASH emsg. no_std.
Documentation

timed-metadata

crates.io docs.rs License

Convert DPI / timed-metadata signalling between SCTE-35, HLS EXT-X-DATERANGE, and DASH emsg — losslessly. no_std, independently versioned.

Install

[dependencies]
timed-metadata = "0.1"

What is timed-metadata?

Ad insertion and content-segmentation signalling flows through three formats across the OTT delivery chain:

Format Standard Carrier
SCTE-35 splice_info_section ANSI/SCTE 35 2023r1 MPEG-2 TS
HLS EXT-X-DATERANGE RFC 8216 / draft-pantos-hls-rfc8216bis §4.4.5.1 HLS playlist
DASH emsg SCTE 214-3; scheme urn:scte:scte35:2013:bin MP4/CMAF segment

timed-metadata translates between all three. Conversions are lossless: the original splice_info_section bytes travel verbatim as the SCTE35-OUT hex in DATERANGE or as the message_data payload in emsg. No re-encoding, no interpretation loss.

The three edges

Edge 1 — SCTE-35 → HLS EXT-X-DATERANGE

use timed_metadata::{TimeAnchor, Timeline};

// Wall-clock anchor: PTS 0 == 2024-01-15T12:00:00 UTC.
let anchor = TimeAnchor { pts_90k: 0, utc_epoch_ms: 1_705_320_000_000 };
let mut timeline = Timeline::with_anchor(anchor);

// Real Unified Streaming splice ID 2002 (24-second break).
let splice_hex = "FC302100000000000000FFF01005000007D27FEF7F7E0020F580C0000000000088B9661D";
let raw: Vec<u8> = (0..splice_hex.len()).step_by(2)
    .map(|i| u8::from_str_radix(&splice_hex[i..i+2], 16).unwrap())
    .collect();

let event = timeline.push_scte35(&raw).unwrap();
let daterange = timeline.to_daterange(&event).unwrap();
println!("{}", daterange.to_tag_line());
// #EXT-X-DATERANGE:ID="2002",START-DATE="2024-01-15T12:00:00.000Z",
//   PLANNED-DURATION=24,SCTE35-OUT=0xFC302100...

Edge 2 — SCTE-35 → DASH emsg

use mp4_emsg::PresentationTime;
use timed_metadata::{convert::EmsgConfig, TimeAnchor, Timeline};

let anchor = TimeAnchor { pts_90k: 0, utc_epoch_ms: 1_705_320_000_000 };
let mut timeline = Timeline::with_anchor(anchor);
let event = timeline.push_scte35(&raw).unwrap();

let cfg = EmsgConfig {
    timescale: 90_000,
    presentation: PresentationTime::Delta(0),
    event_duration: 2_160_000, // 24 s * 90000
    value: "34".to_string(),
    id: event.id.unwrap_or(0),
};
let emsg_bytes = timeline.to_emsg(&event, &cfg).unwrap();
// emsg_bytes is an ISO BMFF 'emsg' box ready to inject into a CMAF segment.

Edge 3 — DASH emsg → SCTE-35

use timed_metadata::convert::emsg_to_scte35;

let splice_raw: Vec<u8> = emsg_to_scte35(&emsg_bytes).unwrap();
// splice_raw is the verbatim splice_info_section; feed to scte35_splice::SpliceInfoSection.

Timeline session

[Timeline] is a stateful conversion session. It holds a [TimeAnchor] (a PTS↔UTC mapping) and unrolls 33-bit PTS wrap so timestamps stay monotonically increasing across a long stream.

  • [Timeline::push_scte35] — parse a section, unroll PTS, return a [TimedEvent].
  • [Timeline::to_daterange] — convert to [DateRange] (requires an anchor).
  • [Timeline::to_emsg] — serialize the event as an emsg box.

The pure conversion functions live in [convert] for use without a session.

Features

Feature Default Description
std yes Enable std in all dependencies.
serde yes Serialize/Deserialize for all public types.
chrono yes chrono dependency (future wall-clock helpers).

no_std + alloc when built with default-features = false. All conversions are available in no_std mode.

Spec citations

  • ANSI/SCTE 35 2023r1splice_info_section wire format.
  • RFC 8216 / draft-pantos-hls-rfc8216bis §4.4.5.1EXT-X-DATERANGE.
  • SCTE 214-3 — SCTE-35 binary carriage in DASH emsg; scheme urn:scte:scte35:2013:bin.
  • ISO/IEC 23009-1 §5.10.3.3 — ISOBMFF Event Message Box (emsg).

License

Licensed under either of MIT or Apache-2.0 at your option.