1mod idat;
2mod iinf;
3mod iloc;
4mod ilst;
5mod iprp;
6mod iref;
7mod pitm;
8mod properties;
9
10pub use idat::*;
11pub use iinf::*;
12pub use iloc::*;
13pub use ilst::*;
14pub use iprp::*;
15pub use iref::*;
16pub use pitm::*;
17pub use properties::*;
18
19use crate::*;
20
21#[derive(Debug, Clone, PartialEq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Meta {
29 pub hdlr: Hdlr,
30 pub items: Vec<Any>,
31}
32
33impl Eq for Meta {}
34
35macro_rules! meta_atom {
36 ($($atom:ident),*,) => {
37 pub trait MetaAtom: AnyAtom {}
39
40 $(impl MetaAtom for $atom {})*
41 };
42}
43
44meta_atom! {
45 Pitm,
46 Dinf,
47 Iloc,
48 Iinf,
49 Iprp,
50 Iref,
51 Idat,
52 Ilst,
53}
54
55impl Meta {
57 pub fn get<T: MetaAtom>(&self) -> Option<&T> {
59 self.items.iter().find_map(T::from_any_ref)
60 }
61
62 pub fn get_mut<T: MetaAtom>(&mut self) -> Option<&mut T> {
64 self.items.iter_mut().find_map(T::from_any_mut)
65 }
66
67 pub fn push<T: MetaAtom>(&mut self, atom: T) {
69 self.items.push(atom.into_any());
70 }
71
72 pub fn remove<T: MetaAtom>(&mut self) -> Option<T> {
76 let pos = self.items.iter().position(|a| T::from_any_ref(a).is_some());
77 if let Some(pos) = pos {
78 Some(T::from_any(self.items.remove(pos)).unwrap())
79 } else {
80 None
81 }
82 }
83}
84
85impl Atom for Meta {
86 const KIND: FourCC = FourCC::new(b"meta");
87 fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
88 if buf.remaining() < 8 {
93 return Err(Error::OutOfBounds);
94 }
95
96 if buf.slice(8)[4..8] == *b"hdlr".as_ref() {
97 tracing::trace!("meta box without fullbox header");
99 } else {
100 let _version_and_flags = u32::decode(buf)?; }
103
104 let hdlr = Hdlr::decode(buf)?;
105 let mut items = Vec::new();
106 while let Some(atom) = Any::decode_maybe(buf)? {
107 items.push(atom);
108 }
109 skip_trailing_padding(buf);
110
111 Ok(Self { hdlr, items })
112 }
113
114 fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
115 0u32.encode(buf)?; self.hdlr.encode(buf)?;
117 for atom in &self.items {
118 atom.encode(buf)?;
119 }
120 Ok(())
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_meta_empty() {
130 let expected = Meta {
131 hdlr: Hdlr {
132 handler: b"fake".into(),
133 name: "".into(),
134 },
135 items: Vec::new(),
136 };
137 let mut buf = Vec::new();
138 expected.encode(&mut buf).unwrap();
139
140 let mut buf = buf.as_ref();
141 let output = Meta::decode(&mut buf).unwrap();
142 assert_eq!(output, expected);
143 }
144
145 #[test]
148 fn test_meta_trailing_padding() {
149 let meta = Meta {
150 hdlr: Hdlr {
151 handler: b"mdir".into(),
152 name: "".into(),
153 },
154 items: Vec::new(),
155 };
156 let mut buf = Vec::new();
157 meta.encode(&mut buf).unwrap();
158 buf.extend_from_slice(&[0, 0, 0, 0]);
159 let size = (buf.len() as u32).to_be_bytes();
160 buf[0..4].copy_from_slice(&size);
161
162 let decoded =
163 Meta::decode(&mut buf.as_slice()).expect("trailing padding must be tolerated");
164 assert_eq!(decoded, meta);
165 }
166
167 #[test]
168 fn test_meta_mdir() {
169 let mut expected = Meta {
170 hdlr: Hdlr {
171 handler: b"mdir".into(),
172 name: "".into(),
173 },
174 items: Vec::new(),
175 };
176
177 expected.push(Pitm { item_id: 3 });
178 expected.push(Dinf {
179 dref: Dref {
180 urls: vec![Url {
181 location: "".into(),
182 }],
183 },
184 });
185 expected.push(Iloc {
186 item_locations: vec![ItemLocation {
187 item_id: 3,
188 construction_method: 0,
189 data_reference_index: 0,
190 base_offset: 0,
191 extents: vec![ItemLocationExtent {
192 item_reference_index: 0,
193 offset: 200,
194 length: 100,
195 }],
196 }],
197 });
198 expected.push(Iinf { item_infos: vec![] });
199 expected.push(Iprp {
200 ipco: Ipco { properties: vec![] },
201 ipma: vec![Ipma {
202 item_properties: vec![
203 PropertyAssociations {
204 item_id: 1,
205 associations: vec![
206 PropertyAssociation {
207 essential: true,
208 property_index: 1,
209 },
210 PropertyAssociation {
211 essential: false,
212 property_index: 2,
213 },
214 PropertyAssociation {
215 essential: false,
216 property_index: 3,
217 },
218 PropertyAssociation {
219 essential: false,
220 property_index: 5,
221 },
222 PropertyAssociation {
223 essential: true,
224 property_index: 4,
225 },
226 ],
227 },
228 PropertyAssociations {
229 item_id: 2,
230 associations: vec![
231 PropertyAssociation {
232 essential: true,
233 property_index: 6,
234 },
235 PropertyAssociation {
236 essential: false,
237 property_index: 3,
238 },
239 PropertyAssociation {
240 essential: false,
241 property_index: 7,
242 },
243 PropertyAssociation {
244 essential: true,
245 property_index: 8,
246 },
247 PropertyAssociation {
248 essential: true,
249 property_index: 4,
250 },
251 ],
252 },
253 ],
254 }],
255 });
256 expected.push(Iref {
257 references: vec![Reference {
258 reference_type: b"cdsc".into(),
259 from_item_id: 2,
260 to_item_ids: vec![1, 3],
261 }],
262 });
263 expected.push(Idat {
264 data: vec![0x01, 0xFF, 0xFE, 0x03],
265 });
266 expected.push(Ilst::default());
267
268 let mut buf = Vec::new();
269 expected.encode(&mut buf).unwrap();
270
271 let mut buf = buf.as_ref();
272 let output = Meta::decode(&mut buf).unwrap();
273 assert_eq!(output, expected);
274 }
275
276 #[test]
277 fn test_meta_apple_quicktime() {
278 let mut buf = Vec::new();
284
285 buf.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); buf.extend_from_slice(b"meta");
288
289 let hdlr = Hdlr {
291 handler: b"mdir".into(),
292 name: "Apple".into(),
293 };
294
295 let mut hdlr_buf = Vec::new();
297 hdlr.encode(&mut hdlr_buf).unwrap();
298 buf.extend_from_slice(&hdlr_buf);
299
300 let ilst = Ilst::default();
302 let mut ilst_buf = Vec::new();
303 ilst.encode(&mut ilst_buf).unwrap();
304 buf.extend_from_slice(&ilst_buf);
305
306 let size = buf.len() as u32;
308 buf[0..4].copy_from_slice(&size.to_be_bytes());
309
310 let mut cursor = std::io::Cursor::new(&buf[8..]);
312
313 let decoded = Meta::decode_body(&mut cursor).expect("failed to decode Apple meta box");
315
316 assert_eq!(decoded.hdlr.handler, FourCC::new(b"mdir"));
318 assert_eq!(decoded.hdlr.name, "Apple");
319 assert_eq!(decoded.items.len(), 1);
320 assert!(decoded.get::<Ilst>().is_some());
321 }
322
323 #[test]
324 fn test_meta_apple_with_ilst() {
325 let mut buf = Vec::new();
327
328 buf.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); buf.extend_from_slice(b"meta");
331
332 let hdlr = Hdlr {
334 handler: b"mdir".into(),
335 name: "".into(),
336 };
337 let mut hdlr_buf = Vec::new();
338 hdlr.encode(&mut hdlr_buf).unwrap();
339 buf.extend_from_slice(&hdlr_buf);
340
341 let ilst = Ilst {
343 name: Some(Name("Test Song".into())),
344 year: Some(Year("2025".into())),
345 ..Default::default()
346 };
347
348 let mut ilst_buf = Vec::new();
349 ilst.encode(&mut ilst_buf).unwrap();
350 buf.extend_from_slice(&ilst_buf);
351
352 let size = buf.len() as u32;
354 buf[0..4].copy_from_slice(&size.to_be_bytes());
355
356 let mut cursor = std::io::Cursor::new(&buf[8..]);
358 let decoded =
359 Meta::decode_body(&mut cursor).expect("failed to decode Apple meta with ilst");
360
361 assert_eq!(decoded.hdlr.handler, FourCC::new(b"mdir"));
363 let decoded_ilst = decoded.get::<Ilst>().expect("ilst not found");
364 assert_eq!(decoded_ilst.name.as_ref().unwrap().0, "Test Song");
365 assert_eq!(decoded_ilst.year.as_ref().unwrap().0, "2025");
366 }
367
368 #[test]
369 fn test_meta_iso_vs_apple_roundtrip() {
370 let meta = Meta {
374 hdlr: Hdlr {
375 handler: b"mdir".into(),
376 name: "Handler".into(),
377 },
378 items: vec![],
379 };
380
381 let mut encoded = Vec::new();
383 meta.encode(&mut encoded).unwrap();
384
385 let mut cursor = std::io::Cursor::new(&encoded);
387 let decoded = Meta::decode(&mut cursor).expect("failed to decode ISO format");
388 assert_eq!(decoded, meta);
389
390 let mut apple_format = Vec::new();
392 apple_format.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); apple_format.extend_from_slice(b"meta");
394
395 let mut hdlr_buf = Vec::new();
396 meta.hdlr.encode(&mut hdlr_buf).unwrap();
397 apple_format.extend_from_slice(&hdlr_buf);
398
399 let size = apple_format.len() as u32;
400 apple_format[0..4].copy_from_slice(&size.to_be_bytes());
401
402 let mut cursor = std::io::Cursor::new(&apple_format);
404 let decoded_apple = Meta::decode(&mut cursor).expect("failed to decode Apple format");
405 assert_eq!(decoded_apple, meta);
406 }
407}