Skip to main content

mp4_emsg/
lib.rs

1//! `mp4-emsg` — ISO BMFF / DASH Event Message Box (`emsg`): inband DASH/CMAF
2//! timed events (SCTE 35 splice signalling, ID3 metadata, ad/tracking triggers).
3//!
4//! The `emsg` box delivers sparse, timed application events alongside the media
5//! in a DASH/CMAF segment. This crate implements:
6//!
7//! - [`EmsgBox`] — the `'emsg'` ISOBMFF `FullBox` (`size` / `'emsg'` /
8//!   `version` / `flags`) plus both version bodies: the two null-terminated
9//!   UTF-8 strings (`scheme_id_uri`, `value`), the integer fields (`timescale`,
10//!   `event_duration`, `id`), the version-discriminated presentation-time
11//!   field, and the opaque `message_data[]`. `size` and `version` are
12//!   **recomputed/derived on serialize** from the typed fields (no raw
13//!   passthrough).
14//! - [`PresentationTime`] — the version-discriminated timing field:
15//!   `presentation_time_delta` (u32, version 0, segment-relative) vs
16//!   `presentation_time` (u64, version 1, representation-relative). Selecting a
17//!   variant *is* selecting the box version.
18//! - [`EmsgVersion`] — the `version` byte (0 / 1) with its spec label.
19//! - [`EmsgBox::is_scte35`] — recognises the SCTE 35 scheme
20//!   ([`SCTE35_SCHEME_PREFIX`], `urn:scte:scte35…`), in which case
21//!   `message_data` carries a SCTE 35 `splice_info_section`.
22//!
23//! Note the **v0/v1 field ordering differs**: version 0 places the two strings
24//! *first* (before the integer fields); version 1 places the integer fields
25//! first and the strings last. Both orderings are parsed and serialized.
26//!
27//! # ⚠ Source footing — softer than the fully-free crates
28//!
29//! The `emsg` **field semantics and types** (`scheme_id_uri`, `value`,
30//! `timescale`, `presentation_time`/`presentation_time_delta`,
31//! `event_duration`, `id`, `message_data`) are render-verified from a **free**
32//! source: **DASH-IF IOP Part 10 (Events and Timed Metadata) V5.0.0, §6.1 +
33//! Table 6-2** (transcribed in `mp4-emsg/docs/emsg.md`).
34//!
35//! However, the **normative ISOBMFF box syntax** —
36//!
37//! ```text
38//! aligned(8) class EventMessageBox extends FullBox('emsg', version, flags = 0)
39//! ```
40//!
41//! — i.e. the exact byte-level field *ordering*, the `version`-gated branch
42//! (`presentation_time_delta` vs `presentation_time`), and the
43//! null-terminated-string layout, lives in **ISO/IEC 23009-1 §5.10.3.3**, which
44//! is **paid and NOT vendored** in this repo. DASH-IF Part 10 references it but
45//! does not reproduce it. The box layout here is therefore implemented from the
46//! **well-known public `emsg` structure** (widely reproduced in MPEG-DASH /
47//! CMAF) combined with the free DASH-IF Part 10 semantics, with **ISO/IEC
48//! 23009-1 §5.10.3.3 cited as the formal (paid) normative source**. This is
49//! **softer footing** than the fully-free crates in this workspace — flagged
50//! here per project policy. The v0/v1 ordering in particular is the part most
51//! reliant on the non-vendored ISO source.
52//!
53//! Likewise, the SCTE 35 scheme-URI strings and the `message_data` binding are
54//! defined by **SCTE 214-1 / ANSI/SCTE 35** (not vendored); [`is_scte35`] only
55//! recognises the well-known `urn:scte:scte35…` URI prefix.
56//!
57//! [`is_scte35`]: EmsgBox::is_scte35
58//!
59//! `#![no_std]` + `alloc`; depends only on `dvb-common`.
60//!
61//! # Examples
62//!
63//! Build a version 0 (segment-relative) `emsg` from typed fields and round-trip
64//! it:
65//!
66//! ```
67//! use mp4_emsg::{EmsgBox, PresentationTime};
68//!
69//! let scte35 = [0xFCu8, 0x30, 0x11]; // start of a splice_info_section
70//! let b = EmsgBox {
71//!     scheme_id_uri: "urn:scte:scte35:2013:bin",
72//!     value: "",
73//!     timescale: 90_000,
74//!     presentation_time: PresentationTime::Delta(0),
75//!     event_duration: 0xFFFF_FFFF,
76//!     id: 1,
77//!     message_data: &scte35,
78//! };
79//! assert!(b.is_scte35());
80//! let bytes = b.to_vec().unwrap();
81//! assert_eq!(EmsgBox::parse(&bytes).unwrap(), b);
82//! ```
83#![no_std]
84#![cfg_attr(docsrs, feature(doc_cfg))]
85#![warn(missing_docs)]
86// Runnable examples, embedded so they render on docs.rs and stay in sync with
87// the actual `examples/*.rs` files (shown, not compiled).
88#![doc = "\n## Runnable examples\n"]
89#![doc = "Run with `cargo run -p mp4-emsg --example <name>`.\n"]
90#![doc = "\n### `build_emsg`\n\n```rust,ignore"]
91#![doc = include_str!("../examples/build_emsg.rs")]
92#![doc = "```\n\n### `parse_emsg`\n\n```rust,ignore"]
93#![doc = include_str!("../examples/parse_emsg.rs")]
94#![doc = "```"]
95
96extern crate alloc;
97
98mod emsg;
99mod error;
100mod version;
101
102pub use emsg::{
103    EmsgBox, PresentationTime, EMSG_BOX_TYPE, EMSG_FLAGS, FULLBOX_HEADER_LEN, SCTE35_SCHEME_PREFIX,
104    STRING_TERMINATOR,
105};
106pub use error::{Error, Result};
107pub use version::{EmsgVersion, VERSION_0, VERSION_1};