Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/
mebx.rs

1use crate::*;
2
3/// Metadata Key Declaration Box ('keyd').
4///
5/// Holds the key namespace and key value of that namespace for the given
6/// values.
7///
8/// See ISO/IEC 14496-12:2026 Section 12.8.4.4.
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct Keyd {
12    pub key_namespace: FourCC,
13    pub key_value: Vec<u8>,
14}
15
16impl Atom for Keyd {
17    const KIND: FourCC = FourCC::new(b"keyd");
18
19    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
20        let key_namespace = FourCC::decode(buf)?;
21        let key_value = Vec::decode(buf)?;
22        Ok(Self {
23            key_namespace,
24            key_value,
25        })
26    }
27
28    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
29        self.key_namespace.encode(buf)?;
30        self.key_value.encode(buf)
31    }
32}
33
34/// The value type declared by a [`Dtyp`] atom.
35#[derive(Debug, Clone, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub enum DataType {
38    /// `datatype_namespace` `0`: a well-known type code shared with the
39    /// iTunes `data` atom (e.g. `1` = UTF-8, `21` = BE signed integer, `23` =
40    /// BE float32) -- see the [well-known types](https://developer.apple.com/documentation/quicktime-file-format/well-known_types) table.
41    WellKnown(u32),
42    /// `datatype_namespace` `1`: a namespace-specific type name (a
43    /// case-sensitive reverse-DNS string, e.g. for a custom/structured value
44    /// type not covered by a well-known type).
45    Custom(String),
46    /// A `datatype_namespace` this crate doesn't interpret (reserved for a
47    /// future revision of the spec, or a foreign metadata standard's own
48    /// numbering/naming scheme); the raw bytes are preserved as-is.
49    Unknown(u32, Vec<u8>),
50}
51
52/// Metadata Datatype Definition Atom ('dtyp').
53///
54/// Declares the value type of a metadata key. Optional -- a key with no
55/// `dtyp` has an implicit/unspecified type.
56///
57/// Apple/QuickTime-specific: not part of ISO/IEC 14496-12's own
58/// `MetadataKeyBox` (which instead only defines `keyd`/`loca`/`setu`), but a
59/// commonly-written extension child under the same box.
60///
61/// See Apple's [Metadata datatype definition atom](https://developer.apple.com/documentation/quicktime-file-format/metadata_datatype_definition_atom).
62#[derive(Debug, Clone, PartialEq, Eq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub struct Dtyp {
65    pub data_type: DataType,
66}
67
68impl Atom for Dtyp {
69    const KIND: FourCC = FourCC::new(b"dtyp");
70
71    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
72        let namespace = u32::decode(buf)?;
73        let data_type = match namespace {
74            0 => DataType::WellKnown(u32::decode(buf)?),
75            // A case-sensitive UTF-8 string without a null terminator,
76            // filling the rest of the atom.
77            1 => {
78                let remaining = buf.remaining();
79                let bytes = buf.slice(remaining).to_vec();
80                buf.advance(remaining);
81                DataType::Custom(
82                    String::from_utf8(bytes)
83                        .map_err(|err| Error::InvalidString(err.to_string()))?,
84                )
85            }
86            _ => {
87                let remaining = buf.remaining();
88                let raw = buf.slice(remaining).to_vec();
89                buf.advance(remaining);
90                DataType::Unknown(namespace, raw)
91            }
92        };
93        Ok(Self { data_type })
94    }
95
96    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
97        match &self.data_type {
98            DataType::WellKnown(code) => {
99                0u32.encode(buf)?;
100                code.encode(buf)
101            }
102            DataType::Custom(name) => {
103                1u32.encode(buf)?;
104                buf.append_slice(name.as_bytes());
105                Ok(())
106            }
107            DataType::Unknown(namespace, raw) => {
108                namespace.encode(buf)?;
109                buf.append_slice(raw);
110                Ok(())
111            }
112        }
113    }
114}
115
116/// Metadata Locale Box ('loca').
117///
118/// Tags a key declaration as applying only to a specific locale, so a track
119/// can declare multiple locale-specific variants of the same key. Absent
120/// means the key applies to all locales.
121///
122/// See ISO/IEC 14496-12:2026 Section 12.8.4.5.
123#[derive(Debug, Clone, PartialEq, Eq)]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125pub struct Loca {
126    /// A language tag complying with IETF BCP 47 (e.g. `en-US`).
127    pub locale: String,
128}
129
130impl Atom for Loca {
131    const KIND: FourCC = FourCC::new(b"loca");
132
133    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
134        Ok(Self {
135            locale: String::decode(buf)?,
136        })
137    }
138
139    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
140        self.locale.as_str().encode(buf)
141    }
142}
143
144/// Metadata Setup Box ('setu').
145///
146/// Carries setup data needed to interpret values for some `key_namespace`s
147/// (e.g. the boxes that would otherwise appear in the sample entry, for the
148/// case of multiplexed timed metadata). The contents are namespace-defined,
149/// so this crate preserves them as opaque bytes.
150///
151/// See ISO/IEC 14496-12:2026 Section 12.8.4.6.
152#[derive(Debug, Clone, PartialEq, Eq)]
153#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
154pub struct Setu {
155    pub data: Vec<u8>,
156}
157
158impl Atom for Setu {
159    const KIND: FourCC = FourCC::new(b"setu");
160
161    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
162        Ok(Self {
163            data: Vec::decode(buf)?,
164        })
165    }
166
167    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
168        self.data.encode(buf)
169    }
170}
171
172/// A Metadata Key Box: one local-key declaration inside a [`Mebx`] sample
173/// entry's `keys` table (`MetadataKeyTableBox`).
174///
175/// Unlike every other atom in this crate, its own box type isn't a fixed
176/// FourCC -- it's the arbitrary `local_key_id` the muxer chose for this key,
177/// which per-sample metadata items reference by using that same FourCC as
178/// their own box type.
179/// `local_key_id` `0xFFFFFFFF` is reserved and must not appear here.
180///
181/// See ISO/IEC 14496-12:2026 Section 12.8.4.3.
182#[derive(Debug, Clone, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184pub struct MebxKey {
185    pub local_key_id: FourCC,
186    pub keyd: Keyd,
187    pub dtyp: Option<Dtyp>,
188    pub loca: Option<Loca>,
189    pub setu: Option<Setu>,
190}
191
192impl MebxKey {
193    fn decode_maybe<B: Buf>(buf: &mut B) -> Result<Option<Self>> {
194        let Some(header) = Header::decode_maybe(buf)? else {
195            return Ok(None);
196        };
197        if u32::from(header.kind) == 0xFFFF_FFFF {
198            // `local_key_id` 0xFFFFFFFF is reserved and must never appear.
199            return Err(Error::Reserved);
200        }
201        let size = header.size.unwrap_or(buf.remaining());
202        if size > buf.remaining() {
203            return Ok(None);
204        }
205
206        let mut body = buf.slice(size);
207
208        let mut keyd = None;
209        let mut dtyp = None;
210        let mut loca = None;
211        let mut setu = None;
212        while let Some(atom) = Any::decode_maybe(&mut body)? {
213            match atom {
214                Any::Keyd(atom) => keyd = atom.into(),
215                Any::Dtyp(atom) => dtyp = atom.into(),
216                Any::Loca(atom) => loca = atom.into(),
217                Any::Setu(atom) => setu = atom.into(),
218                unknown => crate::decode_unknown(&unknown, header.kind)?,
219            }
220        }
221        buf.advance(size);
222
223        Ok(Some(Self {
224            local_key_id: header.kind,
225            keyd: keyd.ok_or(Error::MissingBox(Keyd::KIND))?,
226            dtyp,
227            loca,
228            setu,
229        }))
230    }
231
232    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
233        buf.encode_atom(self.local_key_id, |buf| {
234            self.keyd.encode(buf)?;
235            self.dtyp.encode(buf)?;
236            self.loca.encode(buf)?;
237            self.setu.encode(buf)
238        })
239    }
240}
241
242// Mebx's `keys` box shares the FourCC 'keys' with the unrelated ISO
243// meta/ilst "Metadata item keys atom" (`crate::meta::Keys`, a FullBox with a
244// fixed `entry_count`-prefixed layout), which IS registered in the global
245// `Any` table. Deliberately use a local constant here rather than that
246// type's `KIND` -- the two boxes are unrelated and must stay decoupled, even
247// though they happen to share the same literal FourCC value.
248const KEYS_KIND: FourCC = FourCC::new(b"keys");
249
250fn decode_keys<B: Buf>(header: &Header, buf: &mut B) -> Result<Vec<MebxKey>> {
251    let size = header.size.unwrap_or(buf.remaining());
252    let mut body = buf.slice(size);
253
254    let mut keys = Vec::new();
255    while let Some(key) = MebxKey::decode_maybe(&mut body)? {
256        keys.push(key);
257    }
258    buf.advance(size);
259    Ok(keys)
260}
261
262fn encode_keys<B: BufMut>(keys: &[MebxKey], buf: &mut B) -> Result<()> {
263    buf.encode_atom(KEYS_KIND, |buf| {
264        for key in keys {
265            key.encode(buf)?;
266        }
267        Ok(())
268    })
269}
270
271/// BoxedMetadataSampleEntry ('mebx'), used for multiplexed/"boxed" timed
272/// metadata tracks (e.g. per-sample GPS or camera metadata with multiple
273/// typed keys sharing a single track).
274///
275/// Unlike [`Mett`]/[`Metx`]/[`Urim`] (single-value metadata streams), each
276/// sample is itself a concatenation of one or more value boxes, one per key
277/// declared in `keys` -- the sample item's own box type is the key's
278/// `local_key_id`.
279///
280/// See ISO/IEC 14496-12:2026 Section 12.8.
281#[derive(Debug, Clone, PartialEq, Eq)]
282#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
283pub struct Mebx {
284    pub metadata: MetaData,
285    pub keys: Vec<MebxKey>,
286    pub btrt: Option<Btrt>,
287}
288
289impl Atom for Mebx {
290    const KIND: FourCC = FourCC::new(b"mebx");
291
292    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
293        let metadata = MetaData::decode(buf)?;
294
295        let mut keys = None;
296        let mut btrt = None;
297        while let Some(header) = Header::decode_maybe(buf)? {
298            let size = header.size.unwrap_or(buf.remaining());
299            if size > buf.remaining() {
300                break;
301            }
302            if header.kind == KEYS_KIND {
303                keys = Some(decode_keys(&header, buf)?);
304                continue;
305            }
306            match Any::decode_atom(&header, buf)? {
307                Any::Btrt(atom) => btrt = Some(atom),
308                unknown => Self::decode_unknown(&unknown)?,
309            }
310        }
311        skip_trailing_padding(buf);
312
313        Ok(Self {
314            metadata,
315            keys: keys.ok_or(Error::MissingBox(KEYS_KIND))?,
316            btrt,
317        })
318    }
319
320    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
321        self.metadata.encode(buf)?;
322        encode_keys(&self.keys, buf)?;
323        self.btrt.encode(buf)?;
324        Ok(())
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    fn sample_keys() -> Vec<MebxKey> {
333        vec![
334            MebxKey {
335                local_key_id: FourCC::new(b"\0\0\0\x01"),
336                keyd: Keyd {
337                    key_namespace: FourCC::new(b"mdta"),
338                    key_value: b"com.apple.quicktime.location.ISO6709".to_vec(),
339                },
340                dtyp: Some(Dtyp {
341                    data_type: DataType::WellKnown(1), // UTF-8
342                }),
343                loca: None,
344                setu: None,
345            },
346            MebxKey {
347                local_key_id: FourCC::new(b"\0\0\0\x02"),
348                keyd: Keyd {
349                    key_namespace: FourCC::new(b"mdta"),
350                    key_value: b"com.example.custom-value".to_vec(),
351                },
352                dtyp: Some(Dtyp {
353                    data_type: DataType::Custom("com.example.custom-type".into()),
354                }),
355                loca: Some(Loca {
356                    locale: "en-US".into(),
357                }),
358                setu: Some(Setu {
359                    data: vec![1, 2, 3, 4],
360                }),
361            },
362        ]
363    }
364
365    #[test]
366    fn test_keys_roundtrip() {
367        let keys = sample_keys();
368
369        let mut buf = Vec::new();
370        encode_keys(&keys, &mut buf).unwrap();
371
372        let mut slice = buf.as_slice();
373        let header = Header::decode(&mut slice).unwrap();
374        let decoded = decode_keys(&header, &mut slice).expect("failed to decode keys");
375        assert_eq!(decoded, keys);
376    }
377
378    #[test]
379    fn test_mebx_roundtrip() {
380        let mebx = Mebx {
381            metadata: MetaData {
382                data_reference_index: 1,
383            },
384            keys: sample_keys(),
385            btrt: Some(Btrt {
386                buffer_size_db: 1,
387                max_bitrate: 2000,
388                avg_bitrate: 400,
389            }),
390        };
391
392        let mut buf = Vec::new();
393        mebx.encode(&mut buf).unwrap();
394
395        let decoded = Mebx::decode(&mut buf.as_slice()).expect("failed to decode mebx");
396        assert_eq!(decoded, mebx);
397    }
398
399    #[test]
400    fn test_mebx_roundtrip_no_btrt() {
401        let mebx = Mebx {
402            metadata: MetaData {
403                data_reference_index: 1,
404            },
405            keys: vec![],
406            btrt: None,
407        };
408
409        let mut buf = Vec::new();
410        mebx.encode(&mut buf).unwrap();
411
412        let decoded = Mebx::decode(&mut buf.as_slice()).expect("failed to decode mebx");
413        assert_eq!(decoded, mebx);
414    }
415
416    #[test]
417    fn test_mebx_missing_keys() {
418        let mut buf = Vec::new();
419        0u32.encode(&mut buf).unwrap(); // reserved
420        0u16.encode(&mut buf).unwrap(); // reserved
421        1u16.encode(&mut buf).unwrap(); // data_reference_index
422
423        let err = Mebx::decode_body(&mut buf.as_slice()).unwrap_err();
424        assert!(matches!(err, Error::MissingBox(kind) if kind == KEYS_KIND));
425    }
426
427    #[test]
428    fn test_mebx_key_rejects_reserved_local_key_id() {
429        // `local_key_id` 0xFFFFFFFF is reserved and must
430        // never appear as a key's own box type.
431        let mut buf = Vec::new();
432        8u32.encode(&mut buf).unwrap(); // size (header only, empty body)
433        0xFFFF_FFFFu32.encode(&mut buf).unwrap(); // local_key_id (reserved)
434
435        let err = MebxKey::decode_maybe(&mut buf.as_slice()).unwrap_err();
436        assert!(matches!(err, Error::Reserved));
437    }
438
439    #[test]
440    fn test_keyd_non_utf8_key_value_roundtrip() {
441        // key_value is namespace-defined raw bytes, e.g. for the 'uiso'
442        // (ISO user-data) namespace it's a 4-byte user-data FourCC, not text.
443        let keyd = Keyd {
444            key_namespace: FourCC::new(b"uiso"),
445            key_value: b"cprt".to_vec(),
446        };
447
448        let mut buf = Vec::new();
449        keyd.encode(&mut buf).unwrap();
450
451        let decoded = Keyd::decode(&mut buf.as_slice()).expect("failed to decode keyd");
452        assert_eq!(decoded, keyd);
453    }
454
455    #[test]
456    fn test_loca_roundtrip() {
457        let loca = Loca {
458            locale: "fr-FR".into(),
459        };
460
461        let mut buf = Vec::new();
462        loca.encode(&mut buf).unwrap();
463
464        let decoded = Loca::decode(&mut buf.as_slice()).expect("failed to decode loca");
465        assert_eq!(decoded, loca);
466    }
467
468    #[test]
469    fn test_dtyp_well_known_roundtrip() {
470        let dtyp = Dtyp {
471            data_type: DataType::WellKnown(23), // BE float32
472        };
473
474        let mut buf = Vec::new();
475        dtyp.encode(&mut buf).unwrap();
476
477        let decoded = Dtyp::decode(&mut buf.as_slice()).expect("failed to decode dtyp");
478        assert_eq!(decoded, dtyp);
479    }
480
481    #[test]
482    fn test_dtyp_custom_roundtrip() {
483        // The custom-namespace `datatype array` has no null terminator; it
484        // fills the rest of the atom.
485        let dtyp = Dtyp {
486            data_type: DataType::Custom("com.example.custom-type".into()),
487        };
488
489        let mut buf = Vec::new();
490        dtyp.encode(&mut buf).unwrap();
491
492        let decoded = Dtyp::decode(&mut buf.as_slice()).expect("failed to decode dtyp");
493        assert_eq!(decoded, dtyp);
494    }
495
496    #[test]
497    fn test_dtyp_unknown_namespace_preserves_bytes() {
498        // A `datatype_namespace` other than 0/1 should still round-trip its
499        // raw bytes unchanged, per spec ("should be ignored" but "some
500        // processing is still possible... such as copying it between tracks").
501        let dtyp = Dtyp {
502            data_type: DataType::Unknown(99, vec![1, 2, 3, 4]),
503        };
504
505        let mut buf = Vec::new();
506        dtyp.encode(&mut buf).unwrap();
507
508        let decoded = Dtyp::decode(&mut buf.as_slice()).expect("failed to decode dtyp");
509        assert_eq!(decoded, dtyp);
510    }
511}