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