Skip to main content

re_mp4/mp4box/
emsg.rs

1use std::ffi::CStr;
2use std::io::{Read, Seek};
3
4use byteorder::{BigEndian, ReadBytesExt as _};
5use serde::Serialize;
6
7use crate::mp4box::{
8    box_start, read_box_header_ext, skip_bytes_to, BoxType, Error, Mp4Box, ReadBox, Result,
9    HEADER_EXT_SIZE, HEADER_SIZE,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
13pub struct EmsgBox {
14    pub version: u8,
15    pub flags: u32,
16    pub timescale: u32,
17    pub presentation_time: Option<u64>,
18    pub presentation_time_delta: Option<u32>,
19    pub event_duration: u32,
20    pub id: u32,
21    pub scheme_id_uri: String,
22    pub value: String,
23    pub message_data: Vec<u8>,
24}
25
26impl EmsgBox {
27    fn size_without_message(version: u8, scheme_id_uri: &str, value: &str) -> u64 {
28        HEADER_SIZE + HEADER_EXT_SIZE +
29            4 + // id
30            Self::time_size(version) +
31            (scheme_id_uri.len() + 1) as u64 +
32            (value.len() as u64 + 1)
33    }
34
35    fn time_size(version: u8) -> u64 {
36        match version {
37            0 => 12,
38            1 => 16,
39            _ => panic!("version must be 0 or 1"),
40        }
41    }
42}
43
44impl Mp4Box for EmsgBox {
45    fn box_type(&self) -> BoxType {
46        BoxType::EmsgBox
47    }
48
49    fn box_size(&self) -> u64 {
50        Self::size_without_message(self.version, &self.scheme_id_uri, &self.value)
51            + self.message_data.len() as u64
52    }
53
54    fn to_json(&self) -> Result<String> {
55        Ok(serde_json::to_string(&self).expect("Failed to convert to JSON"))
56    }
57
58    fn summary(&self) -> Result<String> {
59        let s = format!("id={} value={}", self.id, self.value);
60        Ok(s)
61    }
62}
63
64impl<R: Read + Seek> ReadBox<&mut R> for EmsgBox {
65    fn read_box(reader: &mut R, size: u64) -> Result<Self> {
66        let start = box_start(reader)?;
67        let (version, flags) = read_box_header_ext(reader)?;
68
69        let (
70            timescale,
71            presentation_time,
72            presentation_time_delta,
73            event_duration,
74            id,
75            scheme_id_uri,
76            value,
77        ) = match version {
78            0 => {
79                let scheme_id_uri = read_null_terminated_utf8_string(reader)?;
80                let value = read_null_terminated_utf8_string(reader)?;
81                (
82                    reader.read_u32::<BigEndian>()?,
83                    None,
84                    Some(reader.read_u32::<BigEndian>()?),
85                    reader.read_u32::<BigEndian>()?,
86                    reader.read_u32::<BigEndian>()?,
87                    scheme_id_uri,
88                    value,
89                )
90            }
91            1 => (
92                reader.read_u32::<BigEndian>()?,
93                Some(reader.read_u64::<BigEndian>()?),
94                None,
95                reader.read_u32::<BigEndian>()?,
96                reader.read_u32::<BigEndian>()?,
97                read_null_terminated_utf8_string(reader)?,
98                read_null_terminated_utf8_string(reader)?,
99            ),
100            _ => return Err(Error::InvalidData("version must be 0 or 1")),
101        };
102
103        let message_size = size - Self::size_without_message(version, &scheme_id_uri, &value);
104        let mut message_data = Vec::with_capacity(message_size as usize);
105        for _ in 0..message_size {
106            message_data.push(reader.read_u8()?);
107        }
108
109        skip_bytes_to(reader, start + size)?;
110
111        Ok(Self {
112            version,
113            flags,
114            timescale,
115            presentation_time,
116            presentation_time_delta,
117            event_duration,
118            id,
119            scheme_id_uri,
120            value,
121            message_data,
122        })
123    }
124}
125
126fn read_null_terminated_utf8_string<R: Read + Seek>(reader: &mut R) -> Result<String> {
127    let mut bytes = Vec::new();
128    loop {
129        let byte = reader.read_u8()?;
130        bytes.push(byte);
131        if byte == 0 {
132            break;
133        }
134    }
135    #[expect(unsafe_code)]
136    // SAFETY: we ensure there is exactly one nul-byte at the end of the slice
137    if let Ok(str) = unsafe { CStr::from_bytes_with_nul_unchecked(&bytes) }.to_str() {
138        Ok(str.to_owned())
139    } else {
140        Err(Error::InvalidData("invalid utf8"))
141    }
142}