Skip to main content

mp4_atom/moov/udta/
mod.rs

1mod chpl;
2mod cprt;
3mod kind;
4mod rtng;
5mod skip;
6
7pub use chpl::*;
8pub use cprt::*;
9pub use kind::*;
10pub use rtng::*;
11pub use skip::*;
12
13use crate::*;
14
15#[derive(Debug, Clone, PartialEq, Eq, Default)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Udta {
18    /// Zero or one, a Nero-style chapter list (not part of the ISO/QuickTime spec).
19    pub chpl: Option<Chpl>,
20    /// Zero or more, one per language (ISO/IEC 14496-12 §8.10.2).
21    pub cprt: Vec<Cprt>,
22    /// Zero or more, one per role/kind label (ISO/IEC 14496-12 §8.10.4).
23    pub kind: Vec<Kind>,
24    /// Zero or one (ISO/IEC 14496-12 §8.11.1).
25    pub meta: Option<Meta>,
26    /// Zero or more, one per language (3GPP TS 26.244 clause 8).
27    pub rtng: Vec<Rtng>,
28}
29
30impl Atom for Udta {
31    const KIND: FourCC = FourCC::new(b"udta");
32
33    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
34        let mut chpl = None;
35        let mut cprt = Vec::new();
36        let mut kind = Vec::new();
37        let mut meta = None;
38        let mut rtng = Vec::new();
39
40        // `udta` is a free-form user-data container. QuickTime writes a
41        // track-name `name` box here, whose fourcc collides with the iTunes
42        // `ilst` Name item in the global atom table, so dispatch children by
43        // header against the udta namespace only — the same shape as
44        // `Ilst::decode_body` — rather than through `Any`. Unknown children go
45        // to `decode_unknown` (a warning by default, an error under strict).
46        while let Some(header) = Header::decode_maybe(buf)? {
47            let size = header.size.unwrap_or(buf.remaining());
48            if size > buf.remaining() {
49                // A child whose declared size exceeds what remains is truncated.
50                return Err(Error::OutOfBounds);
51            }
52            // Multiplicity follows the specs: `cprt` and `kind` are "zero or
53            // more" (ISO/IEC 14496-12 §8.10.2 — one copyright notice per
54            // language — and §8.10.4 — one box per role value), and 3GPP
55            // TS 26.244 clause 8 admits each asset box (`rtng` included) once
56            // per language, so repeats accumulate. `meta` is "zero or one" per
57            // container (ISO/IEC 14496-12 §8.11.1), so a second one is
58            // malformed.
59            match header.kind {
60                Chpl::KIND => chpl.insert_once(Chpl::decode_atom(&header, buf)?)?,
61                Cprt::KIND => cprt.push(Cprt::decode_atom(&header, buf)?),
62                Kind::KIND => kind.push(Kind::decode_atom(&header, buf)?),
63                Meta::KIND => meta.insert_once(Meta::decode_atom(&header, buf)?)?,
64                Rtng::KIND => rtng.push(Rtng::decode_atom(&header, buf)?),
65                // `free`/`skip` are padding boxes; drop them.
66                Free::KIND | Skip::KIND => {
67                    buf.advance(size);
68                }
69                unknown => {
70                    let body = Vec::decode(&mut buf.slice(size))?;
71                    buf.advance(size);
72                    Self::decode_unknown(&Any::Unknown(unknown, body))?;
73                }
74            }
75        }
76        // Drop any sub-header padding remainder after the children.
77        skip_trailing_padding(buf);
78
79        Ok(Udta {
80            chpl,
81            cprt,
82            kind,
83            meta,
84            rtng,
85        })
86    }
87
88    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
89        self.chpl.encode(buf)?;
90        self.cprt.encode(buf)?;
91        self.meta.encode(buf)?;
92        self.kind.encode(buf)?;
93        self.rtng.encode(buf)?;
94        Ok(())
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_udta_empty() {
104        let expected = Udta {
105            chpl: None,
106            cprt: vec![],
107            meta: None,
108            kind: vec![],
109            rtng: vec![],
110        };
111
112        let mut buf = Vec::new();
113        expected.encode(&mut buf).unwrap();
114
115        let mut buf = buf.as_ref();
116        let output = Udta::decode(&mut buf).unwrap();
117        assert_eq!(output, expected);
118    }
119
120    #[test]
121    fn test_udta() {
122        let expected = Udta {
123            chpl: None,
124            cprt: vec![Cprt {
125                language: "und".into(),
126                notice: "MIT or Apache".into(),
127            }],
128            meta: Some(Meta {
129                hdlr: Hdlr {
130                    handler: FourCC::new(b"fake"),
131                    name: "".into(),
132                },
133                items: vec![],
134            }),
135            kind: vec![Kind {
136                scheme_uri: "http://www.w3.org/TR/html5/".into(),
137                value: "".into(),
138            }],
139            rtng: vec![Rtng {
140                entity: b"BBFC".into(),
141                criteria: b"PG13".into(),
142                language: "eng".into(),
143                rating_info: "test info".into(),
144            }],
145        };
146
147        let mut buf = Vec::new();
148        expected.encode(&mut buf).unwrap();
149
150        let mut buf = buf.as_ref();
151        let output = Udta::decode(&mut buf).unwrap();
152        assert_eq!(output, expected);
153    }
154
155    // From MPEG File Format Conformance, isobmff/02_dref_edts_img.mp4
156    const ENCODED_UDTA_WITH_CPRT: &[u8] = &[
157        0x00, 0x00, 0x00, 0x70, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x68, 0x63, 0x70, 0x72,
158        0x74, 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x45, 0x4e, 0x53, 0x54, 0x20, 0x49, 0x73, 0x6f,
159        0x4d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e,
160        0x63, 0x65, 0x20, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x2d, 0x20, 0x45, 0x4e, 0x53, 0x54,
161        0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x2d, 0x20, 0x52, 0x69, 0x67,
162        0x68, 0x74, 0x73, 0x20, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x20, 0x66, 0x6f,
163        0x72, 0x20, 0x49, 0x53, 0x4f, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e,
164        0x63, 0x65, 0x20, 0x75, 0x73, 0x65, 0x00,
165    ];
166
167    #[test]
168    fn test_udta_cprt() {
169        let mut buf = std::io::Cursor::new(ENCODED_UDTA_WITH_CPRT);
170
171        let udta = Udta::decode(&mut buf).expect("failed to decode udta");
172
173        assert_eq!(
174            udta,
175            Udta {
176                cprt: vec![Cprt { language: "und".into(), notice: "ENST IsoMedia Conformance Files - ENST (c) 2006 - Rights released for ISO Conformance use".into() }],
177                ..Default::default()
178            }
179        );
180
181        let mut buf = Vec::new();
182        udta.encode(&mut buf).unwrap();
183
184        assert_eq!(buf, ENCODED_UDTA_WITH_CPRT);
185    }
186
187    // From MPEG File Format Conformance, nalu/hevc/hev1_clg1_header.mp4
188    const ENCODED_UDTA_WITH_KIND: &[u8] = &[
189        0x00, 0x00, 0x00, 0x31, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x29, 0x6b, 0x69, 0x6e,
190        0x64, 0x00, 0x00, 0x00, 0x00, 0x75, 0x72, 0x6e, 0x3a, 0x6d, 0x70, 0x65, 0x67, 0x3a, 0x64,
191        0x61, 0x73, 0x68, 0x3a, 0x72, 0x6f, 0x6c, 0x65, 0x3a, 0x32, 0x30, 0x31, 0x31, 0x00, 0x6d,
192        0x61, 0x69, 0x6e, 0x00,
193    ];
194
195    #[test]
196    fn test_udta_kind() {
197        let mut buf = std::io::Cursor::new(ENCODED_UDTA_WITH_KIND);
198
199        let udta = Udta::decode(&mut buf).expect("failed to decode udta");
200
201        assert_eq!(
202            udta,
203            Udta {
204                kind: vec![Kind {
205                    scheme_uri: "urn:mpeg:dash:role:2011".into(),
206                    value: "main".into()
207                }],
208                ..Default::default()
209            }
210        );
211
212        let mut buf = Vec::new();
213        udta.encode(&mut buf).unwrap();
214
215        assert_eq!(buf, ENCODED_UDTA_WITH_KIND);
216    }
217
218    // A track-level QuickTime `udta` holding an empty (4-null-byte) track-name
219    // `name` box. Its fourcc collides with the iTunes `ilst` Name item in the
220    // global atom table.
221    const ENCODED_UDTA_WITH_QT_NAME: &[u8] = &[
222        0x00, 0x00, 0x00, 0x14, 0x75, 0x64, 0x74, 0x61, // udta, size 20
223        0x00, 0x00, 0x00, 0x0c, 0x6e, 0x61, 0x6d, 0x65, // name, size 12
224        0x00, 0x00, 0x00, 0x00, // body: empty QuickTime track-name string
225    ];
226
227    // A sub-header padding remainder after a `udta`'s children must be skipped
228    // as padding while the real children still decode.
229    #[test]
230    fn test_udta_trailing_padding() {
231        let mut buf = ENCODED_UDTA_WITH_CPRT.to_vec();
232        buf.extend_from_slice(&[0, 0, 0, 0]);
233        let size = (buf.len() as u32).to_be_bytes();
234        buf[0..4].copy_from_slice(&size);
235
236        let udta = Udta::decode(&mut buf.as_slice()).expect("trailing padding must be tolerated");
237        assert_eq!(
238            udta.cprt.len(),
239            1,
240            "the real child still decodes, the padding is dropped"
241        );
242    }
243
244    #[test]
245    fn test_udta_quicktime_name_not_misparsed() {
246        // The udta-namespace dispatch must NOT route `name` to the global
247        // `Any` table's ilst Name item (which would UnderDecode the 4-byte
248        // body). Under `cfg(test)` an unknown child is an error, proving the
249        // atom is treated as unknown-to-udta rather than misparsed as ilst.
250        let mut buf = std::io::Cursor::new(ENCODED_UDTA_WITH_QT_NAME);
251        match Udta::decode(&mut buf) {
252            Err(Error::UnexpectedBox(kind)) => assert_eq!(kind, FourCC::new(b"name")),
253            other => panic!("expected UnexpectedBox(name), got {other:?}"),
254        }
255    }
256
257    // Repeated `cprt`/`kind`/`rtng` children are spec-legal — ISO/IEC 14496-12
258    // declares both CopyrightBox (§8.10.2) and KindBox (§8.10.4) as quantity
259    // "zero or more" (one copyright per language, one kind per role value),
260    // and 3GPP TS 26.244 clause 8 admits "zero or more sub-boxes of each kind,
261    // zero or one for each language" of the asset boxes, `rtng` included.
262    // Repeats must accumulate — not error, not overwrite each other.
263    #[test]
264    fn test_udta_repeated_children() {
265        let expected = Udta {
266            chpl: None,
267            cprt: vec![
268                Cprt {
269                    language: "eng".into(),
270                    notice: "All rights reserved".into(),
271                },
272                Cprt {
273                    language: "fra".into(),
274                    notice: "Tous droits réservés".into(),
275                },
276            ],
277            kind: vec![
278                Kind {
279                    scheme_uri: "urn:mpeg:dash:role:2011".into(),
280                    value: "main".into(),
281                },
282                Kind {
283                    scheme_uri: "urn:mpeg:dash:role:2011".into(),
284                    value: "caption".into(),
285                },
286            ],
287            meta: None,
288            rtng: vec![
289                Rtng {
290                    entity: b"BBFC".into(),
291                    criteria: b"PG13".into(),
292                    language: "eng".into(),
293                    rating_info: "parental guidance".into(),
294                },
295                Rtng {
296                    entity: b"BBFC".into(),
297                    criteria: b"PG13".into(),
298                    language: "fra".into(),
299                    rating_info: "accord parental".into(),
300                },
301            ],
302        };
303
304        let mut buf = Vec::new();
305        expected.encode(&mut buf).unwrap();
306
307        let mut buf = buf.as_ref();
308        let output = Udta::decode(&mut buf).unwrap();
309        assert_eq!(output, expected);
310    }
311
312    // `meta` is quantity "zero or one" per container (ISO/IEC 14496-12
313    // §8.11.1) — unlike the repeatable children above, a second one is
314    // malformed and must be rejected, not silently overwritten.
315    #[test]
316    fn test_udta_duplicate_meta() {
317        let meta = Meta {
318            hdlr: Hdlr {
319                handler: FourCC::new(b"fake"),
320                name: "".into(),
321            },
322            items: vec![],
323        };
324        let mut body = Vec::new();
325        meta.encode(&mut body).unwrap();
326        meta.encode(&mut body).unwrap();
327
328        let mut bytes = Vec::new();
329        bytes.extend_from_slice(&((body.len() + 8) as u32).to_be_bytes());
330        bytes.extend_from_slice(b"udta");
331        bytes.extend_from_slice(&body);
332
333        match Udta::decode(&mut bytes.as_slice()) {
334            Err(Error::DuplicateBox(kind)) => assert_eq!(kind, Meta::KIND),
335            other => panic!("expected DuplicateBox(meta), got {other:?}"),
336        }
337    }
338}