Skip to main content

mp4_atom/sample/
mebx.rs

1use std::borrow::Borrow;
2
3use crate::*;
4
5/// One item in a [`MebxSample`]: a value box whose type is a `local_key_id`
6/// declared in the corresponding [`Mebx`] sample entry's `keys` table.
7#[derive(Debug, Clone, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct MebxItem {
10    pub local_key_id: FourCC,
11    pub data: Vec<u8>,
12}
13
14/// The content of a single sample in a `mebx` (multiplexed/"boxed" timed
15/// metadata) track (the [`Mebx`] sample entry): a concatenation of one or
16/// more value boxes, one per declared key present at this sample's time.
17///
18/// Sample content lives inside `mdat` and isn't walked automatically by this
19/// crate -- decode it explicitly via [`MebxSample::decode`]. Unlike
20/// [`EvteSample`](crate::EvteSample), a value box's own type isn't drawn from
21/// a fixed set: it's the arbitrary `local_key_id` the muxer chose per key
22/// (see [`MebxKey`]), so this just preserves each item's raw type and bytes
23/// rather than interpreting them.
24///
25/// See ISO/IEC 14496-12:2026 Section 12.8.3.
26#[derive(Debug, Clone, PartialEq, Eq, Default)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct MebxSample;
29
30impl MebxSample {
31    pub fn decode<B: Buf>(buf: &mut B) -> Result<Vec<MebxItem>> {
32        let mut items = Vec::new();
33        let mut found_null = false;
34
35        while let Some(header) = Header::decode_maybe(buf)? {
36            let size = header.size.unwrap_or(buf.remaining());
37            if size > buf.remaining() {
38                return Err(Error::OutOfBounds);
39            }
40            let body = buf.slice(size);
41
42            if u32::from(header.kind) == 0 {
43                found_null = true;
44            } else {
45                items.push(MebxItem {
46                    local_key_id: header.kind,
47                    data: body.to_vec(),
48                });
49            }
50
51            buf.advance(size);
52        }
53
54        if buf.has_remaining() {
55            Err(Error::ShortRead)
56        } else if items.is_empty() && !found_null {
57            Err(Error::UnexpectedEof)
58        } else {
59            Ok(items)
60        }
61    }
62
63    pub fn encode<B: BufMut>(
64        items: impl IntoIterator<Item = impl Borrow<MebxItem>>,
65        buf: &mut B,
66    ) -> Result<()> {
67        let mut wrote_atom = false;
68
69        for item in items {
70            let item = item.borrow();
71            buf.encode_atom(item.local_key_id, |buf| {
72                buf.append_slice(&item.data);
73                Ok(())
74            })?;
75            wrote_atom = true;
76        }
77
78        if wrote_atom {
79            Ok(())
80        } else {
81            buf.encode_atom(0u32, |_| Ok(()))
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_mebx_sample_empty_roundtrip() {
92        let sample = vec![];
93
94        let mut buf = Vec::new();
95        MebxSample::encode(&sample, &mut buf).unwrap();
96        assert_eq!(buf, b"\0\0\0\x08\0\0\0\0");
97
98        let decoded = MebxSample::decode(&mut buf.as_slice()).expect("failed to decode sample");
99        assert_eq!(decoded, sample);
100    }
101
102    #[test]
103    fn test_mebx_sample_roundtrip() {
104        let sample = vec![
105            MebxItem {
106                local_key_id: FourCC::new(b"test"),
107                data: b"example".to_vec(),
108            },
109            MebxItem {
110                local_key_id: FourCC::new(b"othr"),
111                data: b"something".to_vec(),
112            },
113        ];
114
115        let mut buf = Vec::new();
116        MebxSample::encode(&sample, &mut buf).unwrap();
117
118        let decoded = MebxSample::decode(&mut buf.as_slice()).expect("failed to decode sample");
119        assert_eq!(decoded, sample);
120    }
121
122    #[test]
123    fn test_mebx_sample_ignore_null_items() {
124        // `local_key_id` 0 signals "no metadata of this kind at this time"
125        // and may be used as padding; its content is ignored, not rejected.
126        let mut buf = Vec::new();
127        buf.encode_atom(0u32, |buf| [1, 2].encode(buf)).unwrap();
128        buf.encode_atom(b"test", |buf| [3, 4].encode(buf)).unwrap();
129        buf.encode_atom(0u32, |_| Ok(())).unwrap();
130
131        let decoded = MebxSample::decode(&mut buf.as_slice()).expect("failed to decode sample");
132        assert_eq!(
133            decoded,
134            [MebxItem {
135                local_key_id: FourCC::new(b"test"),
136                data: vec![3, 4],
137            }]
138        );
139    }
140
141    #[test]
142    fn test_mebx_sample_empty_is_error() {
143        let err = MebxSample::decode(&mut [].as_slice()).unwrap_err();
144        assert!(matches!(err, Error::UnexpectedEof));
145    }
146
147    #[test]
148    fn test_mebx_sample_short_read() {
149        let err = MebxSample::decode(&mut [1, 2].as_slice()).unwrap_err();
150        assert!(matches!(err, Error::ShortRead));
151    }
152}