Skip to main content

timed_metadata/convert/
emsg.rs

1//! SCTE-35 ↔ DASH `emsg` conversion (scheme `urn:scte:scte35:2013:bin`).
2//!
3//! Carriage is **ANSI/SCTE 214-3 §8.3.3** for the inband `emsg` form used
4//! here. (The MPD `EventStream` form, `urn:scte:scte35:2014:xml+bin`, is
5//! SCTE 214-1 §6.8.4 — a different part for a different carriage.)
6//!
7//! #951 recorded this crate and `mp4-emsg` citing contradictory parts. This
8//! crate was right. Settled from DASH-IF IOP v4.3 §5.4.3 p.163, which is
9//! freely published and states the split explicitly, so no paywalled SCTE
10//! document was needed after all.
11use crate::error::{Error, Result};
12use alloc::{string::String, vec::Vec};
13use mp4_emsg::{EmsgBox, PresentationTime};
14
15/// The SCTE-35 binary carriage scheme for inband DASH `emsg`
16/// (ANSI/SCTE 214-3 §8.3.3).
17pub const SCTE35_SCHEME: &str = "urn:scte:scte35:2013:bin";
18
19/// Parameters for emitting a SCTE-35-carrying `emsg`.
20#[derive(Debug, Clone)]
21pub struct EmsgConfig {
22    /// `timescale` (ticks/second) for the emsg time fields.
23    pub timescale: u32,
24    /// `presentation_time_delta` (v0) or `presentation_time` (v1).
25    pub presentation: PresentationTime,
26    /// `event_duration` in `timescale` units (0 if unknown).
27    pub event_duration: u32,
28    /// `value` string (often the segmentation type id, as text).
29    pub value: String,
30    /// `id` — unique event identifier (u32).
31    pub id: u32,
32}
33
34/// Wrap a verbatim `splice_info_section` as a SCTE-35 `emsg` box (serialized bytes).
35pub fn scte35_to_emsg(splice_raw: &[u8], cfg: &EmsgConfig) -> Result<Vec<u8>> {
36    let boxx = EmsgBox {
37        scheme_id_uri: SCTE35_SCHEME,
38        value: &cfg.value,
39        timescale: cfg.timescale,
40        presentation_time: cfg.presentation,
41        event_duration: cfg.event_duration,
42        id: cfg.id,
43        message_data: splice_raw,
44    };
45    Ok(boxx.to_vec()?)
46}
47
48/// Extract the verbatim `splice_info_section` from a SCTE-35 `emsg` box.
49pub fn emsg_to_scte35(emsg_bytes: &[u8]) -> Result<Vec<u8>> {
50    let boxx = EmsgBox::parse(emsg_bytes)?;
51    if !boxx.is_scte35() {
52        return Err(Error::UnsupportedScheme {
53            scheme: String::from(boxx.scheme_id_uri),
54        });
55    }
56    Ok(boxx.message_data.to_vec())
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use alloc::string::ToString;
63
64    fn splice_2002() -> alloc::vec::Vec<u8> {
65        let hex = "FC302100000000000000FFF01005000007D27FEF7F7E0020F580C0000000000088B9661D";
66        (0..hex.len())
67            .step_by(2)
68            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
69            .collect()
70    }
71
72    #[test]
73    fn scte35_to_emsg_embeds_splice_verbatim_then_round_trips() {
74        let splice = splice_2002();
75        let cfg = EmsgConfig {
76            timescale: 90_000,
77            presentation: PresentationTime::Delta(0),
78            event_duration: 2_160_000,
79            value: "1".to_string(),
80            id: 1,
81        };
82        let emsg = scte35_to_emsg(&splice, &cfg).unwrap();
83        // message_data must equal the splice verbatim:
84        let extracted = emsg_to_scte35(&emsg).unwrap();
85        assert_eq!(extracted, splice);
86    }
87}