Skip to main content

mp4_atom/moov/udta/
mod.rs

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