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