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        let start = buf.len();
234        0u32.encode(buf)?; // size placeholder
235        self.local_key_id.encode(buf)?;
236        self.keyd.encode(buf)?;
237        self.dtyp.encode(buf)?;
238        self.loca.encode(buf)?;
239        self.setu.encode(buf)?;
240
241        let size: u32 = (buf.len() - start)
242            .try_into()
243            .map_err(|_| Error::TooLarge(self.local_key_id))?;
244        buf.set_slice(start, &size.to_be_bytes());
245        Ok(())
246    }
247}
248
249// Mebx's `keys` box shares the FourCC 'keys' with the unrelated ISO
250// meta/ilst "Metadata item keys atom" (`crate::meta::Keys`, a FullBox with a
251// fixed `entry_count`-prefixed layout), which IS registered in the global
252// `Any` table. Deliberately use a local constant here rather than that
253// type's `KIND` -- the two boxes are unrelated and must stay decoupled, even
254// though they happen to share the same literal FourCC value.
255const KEYS_KIND: FourCC = FourCC::new(b"keys");
256
257fn decode_keys<B: Buf>(header: &Header, buf: &mut B) -> Result<Vec<MebxKey>> {
258    let size = header.size.unwrap_or(buf.remaining());
259    let mut body = buf.slice(size);
260
261    let mut keys = Vec::new();
262    while let Some(key) = MebxKey::decode_maybe(&mut body)? {
263        keys.push(key);
264    }
265    buf.advance(size);
266    Ok(keys)
267}
268
269fn encode_keys<B: BufMut>(keys: &[MebxKey], buf: &mut B) -> Result<()> {
270    let start = buf.len();
271    0u32.encode(buf)?; // size placeholder
272    KEYS_KIND.encode(buf)?;
273    for key in keys {
274        key.encode(buf)?;
275    }
276
277    let size: u32 = (buf.len() - start)
278        .try_into()
279        .map_err(|_| Error::TooLarge(KEYS_KIND))?;
280    buf.set_slice(start, &size.to_be_bytes());
281    Ok(())
282}
283
284/// BoxedMetadataSampleEntry ('mebx'), used for multiplexed/"boxed" timed
285/// metadata tracks (e.g. per-sample GPS or camera metadata with multiple
286/// typed keys sharing a single track).
287///
288/// Unlike [`Mett`]/[`Metx`]/[`Urim`] (single-value metadata streams), each
289/// sample is itself a concatenation of one or more value boxes, one per key
290/// declared in `keys` -- the sample item's own box type is the key's
291/// `local_key_id`.
292///
293/// See ISO/IEC 14496-12:2026 Section 12.8.
294#[derive(Debug, Clone, PartialEq, Eq)]
295#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
296pub struct Mebx {
297    pub metadata: MetaData,
298    pub keys: Vec<MebxKey>,
299    pub btrt: Option<Btrt>,
300}
301
302impl Atom for Mebx {
303    const KIND: FourCC = FourCC::new(b"mebx");
304
305    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
306        let metadata = MetaData::decode(buf)?;
307
308        let mut keys = None;
309        let mut btrt = None;
310        while let Some(header) = Header::decode_maybe(buf)? {
311            let size = header.size.unwrap_or(buf.remaining());
312            if size > buf.remaining() {
313                break;
314            }
315            if header.kind == KEYS_KIND {
316                keys = Some(decode_keys(&header, buf)?);
317                continue;
318            }
319            match Any::decode_atom(&header, buf)? {
320                Any::Btrt(atom) => btrt = Some(atom),
321                unknown => Self::decode_unknown(&unknown)?,
322            }
323        }
324        skip_trailing_padding(buf);
325
326        Ok(Self {
327            metadata,
328            keys: keys.ok_or(Error::MissingBox(KEYS_KIND))?,
329            btrt,
330        })
331    }
332
333    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
334        self.metadata.encode(buf)?;
335        encode_keys(&self.keys, buf)?;
336        self.btrt.encode(buf)?;
337        Ok(())
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    fn sample_keys() -> Vec<MebxKey> {
346        vec![
347            MebxKey {
348                local_key_id: FourCC::new(b"\0\0\0\x01"),
349                keyd: Keyd {
350                    key_namespace: FourCC::new(b"mdta"),
351                    key_value: b"com.apple.quicktime.location.ISO6709".to_vec(),
352                },
353                dtyp: Some(Dtyp {
354                    data_type: DataType::WellKnown(1), // UTF-8
355                }),
356                loca: None,
357                setu: None,
358            },
359            MebxKey {
360                local_key_id: FourCC::new(b"\0\0\0\x02"),
361                keyd: Keyd {
362                    key_namespace: FourCC::new(b"mdta"),
363                    key_value: b"com.example.custom-value".to_vec(),
364                },
365                dtyp: Some(Dtyp {
366                    data_type: DataType::Custom("com.example.custom-type".into()),
367                }),
368                loca: Some(Loca {
369                    locale: "en-US".into(),
370                }),
371                setu: Some(Setu {
372                    data: vec![1, 2, 3, 4],
373                }),
374            },
375        ]
376    }
377
378    #[test]
379    fn test_keys_roundtrip() {
380        let keys = sample_keys();
381
382        let mut buf = Vec::new();
383        encode_keys(&keys, &mut buf).unwrap();
384
385        let mut slice = buf.as_slice();
386        let header = Header::decode(&mut slice).unwrap();
387        let decoded = decode_keys(&header, &mut slice).expect("failed to decode keys");
388        assert_eq!(decoded, keys);
389    }
390
391    #[test]
392    fn test_mebx_roundtrip() {
393        let mebx = Mebx {
394            metadata: MetaData {
395                data_reference_index: 1,
396            },
397            keys: sample_keys(),
398            btrt: Some(Btrt {
399                buffer_size_db: 1,
400                max_bitrate: 2000,
401                avg_bitrate: 400,
402            }),
403        };
404
405        let mut buf = Vec::new();
406        mebx.encode(&mut buf).unwrap();
407
408        let decoded = Mebx::decode(&mut buf.as_slice()).expect("failed to decode mebx");
409        assert_eq!(decoded, mebx);
410    }
411
412    #[test]
413    fn test_mebx_roundtrip_no_btrt() {
414        let mebx = Mebx {
415            metadata: MetaData {
416                data_reference_index: 1,
417            },
418            keys: vec![],
419            btrt: None,
420        };
421
422        let mut buf = Vec::new();
423        mebx.encode(&mut buf).unwrap();
424
425        let decoded = Mebx::decode(&mut buf.as_slice()).expect("failed to decode mebx");
426        assert_eq!(decoded, mebx);
427    }
428
429    #[test]
430    fn test_mebx_missing_keys() {
431        let mut buf = Vec::new();
432        0u32.encode(&mut buf).unwrap(); // reserved
433        0u16.encode(&mut buf).unwrap(); // reserved
434        1u16.encode(&mut buf).unwrap(); // data_reference_index
435
436        let err = Mebx::decode_body(&mut buf.as_slice()).unwrap_err();
437        assert!(matches!(err, Error::MissingBox(kind) if kind == KEYS_KIND));
438    }
439
440    #[test]
441    fn test_mebx_key_rejects_reserved_local_key_id() {
442        // `local_key_id` 0xFFFFFFFF is reserved and must
443        // never appear as a key's own box type.
444        let mut buf = Vec::new();
445        8u32.encode(&mut buf).unwrap(); // size (header only, empty body)
446        0xFFFF_FFFFu32.encode(&mut buf).unwrap(); // local_key_id (reserved)
447
448        let err = MebxKey::decode_maybe(&mut buf.as_slice()).unwrap_err();
449        assert!(matches!(err, Error::Reserved));
450    }
451
452    #[test]
453    fn test_keyd_non_utf8_key_value_roundtrip() {
454        // key_value is namespace-defined raw bytes, e.g. for the 'uiso'
455        // (ISO user-data) namespace it's a 4-byte user-data FourCC, not text.
456        let keyd = Keyd {
457            key_namespace: FourCC::new(b"uiso"),
458            key_value: b"cprt".to_vec(),
459        };
460
461        let mut buf = Vec::new();
462        keyd.encode(&mut buf).unwrap();
463
464        let decoded = Keyd::decode(&mut buf.as_slice()).expect("failed to decode keyd");
465        assert_eq!(decoded, keyd);
466    }
467
468    #[test]
469    fn test_loca_roundtrip() {
470        let loca = Loca {
471            locale: "fr-FR".into(),
472        };
473
474        let mut buf = Vec::new();
475        loca.encode(&mut buf).unwrap();
476
477        let decoded = Loca::decode(&mut buf.as_slice()).expect("failed to decode loca");
478        assert_eq!(decoded, loca);
479    }
480
481    #[test]
482    fn test_dtyp_well_known_roundtrip() {
483        let dtyp = Dtyp {
484            data_type: DataType::WellKnown(23), // BE float32
485        };
486
487        let mut buf = Vec::new();
488        dtyp.encode(&mut buf).unwrap();
489
490        let decoded = Dtyp::decode(&mut buf.as_slice()).expect("failed to decode dtyp");
491        assert_eq!(decoded, dtyp);
492    }
493
494    #[test]
495    fn test_dtyp_custom_roundtrip() {
496        // The custom-namespace `datatype array` has no null terminator; it
497        // fills the rest of the atom.
498        let dtyp = Dtyp {
499            data_type: DataType::Custom("com.example.custom-type".into()),
500        };
501
502        let mut buf = Vec::new();
503        dtyp.encode(&mut buf).unwrap();
504
505        let decoded = Dtyp::decode(&mut buf.as_slice()).expect("failed to decode dtyp");
506        assert_eq!(decoded, dtyp);
507    }
508
509    #[test]
510    fn test_dtyp_unknown_namespace_preserves_bytes() {
511        // A `datatype_namespace` other than 0/1 should still round-trip its
512        // raw bytes unchanged, per spec ("should be ignored" but "some
513        // processing is still possible... such as copying it between tracks").
514        let dtyp = Dtyp {
515            data_type: DataType::Unknown(99, vec![1, 2, 3, 4]),
516        };
517
518        let mut buf = Vec::new();
519        dtyp.encode(&mut buf).unwrap();
520
521        let decoded = Dtyp::decode(&mut buf.as_slice()).expect("failed to decode dtyp");
522        assert_eq!(decoded, dtyp);
523    }
524}