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 urls: vec![Url {
184 location: "".into(),
185 }],
186 },
187 });
188 expected.push(Iloc {
189 item_locations: vec![ItemLocation {
190 item_id: 3,
191 construction_method: 0,
192 data_reference_index: 0,
193 base_offset: 0,
194 extents: vec![ItemLocationExtent {
195 item_reference_index: 0,
196 offset: 200,
197 length: 100,
198 }],
199 }],
200 });
201 expected.push(Iinf { item_infos: vec![] });
202 expected.push(Iprp {
203 ipco: Ipco { properties: vec![] },
204 ipma: vec![Ipma {
205 item_properties: vec![
206 PropertyAssociations {
207 item_id: 1,
208 associations: vec![
209 PropertyAssociation {
210 essential: true,
211 property_index: 1,
212 },
213 PropertyAssociation {
214 essential: false,
215 property_index: 2,
216 },
217 PropertyAssociation {
218 essential: false,
219 property_index: 3,
220 },
221 PropertyAssociation {
222 essential: false,
223 property_index: 5,
224 },
225 PropertyAssociation {
226 essential: true,
227 property_index: 4,
228 },
229 ],
230 },
231 PropertyAssociations {
232 item_id: 2,
233 associations: vec![
234 PropertyAssociation {
235 essential: true,
236 property_index: 6,
237 },
238 PropertyAssociation {
239 essential: false,
240 property_index: 3,
241 },
242 PropertyAssociation {
243 essential: false,
244 property_index: 7,
245 },
246 PropertyAssociation {
247 essential: true,
248 property_index: 8,
249 },
250 PropertyAssociation {
251 essential: true,
252 property_index: 4,
253 },
254 ],
255 },
256 ],
257 }],
258 });
259 expected.push(Iref {
260 references: vec![Reference {
261 reference_type: b"cdsc".into(),
262 from_item_id: 2,
263 to_item_ids: vec![1, 3],
264 }],
265 });
266 expected.push(Idat {
267 data: vec![0x01, 0xFF, 0xFE, 0x03],
268 });
269 expected.push(Ilst::default());
270
271 let mut buf = Vec::new();
272 expected.encode(&mut buf).unwrap();
273
274 let mut buf = buf.as_ref();
275 let output = Meta::decode(&mut buf).unwrap();
276 assert_eq!(output, expected);
277 }
278
279 #[test]
280 fn test_meta_apple_quicktime() {
281 let mut buf = Vec::new();
287
288 buf.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); buf.extend_from_slice(b"meta");
291
292 let hdlr = Hdlr {
294 handler: b"mdir".into(),
295 name: "Apple".into(),
296 };
297
298 let mut hdlr_buf = Vec::new();
300 hdlr.encode(&mut hdlr_buf).unwrap();
301 buf.extend_from_slice(&hdlr_buf);
302
303 let ilst = Ilst::default();
305 let mut ilst_buf = Vec::new();
306 ilst.encode(&mut ilst_buf).unwrap();
307 buf.extend_from_slice(&ilst_buf);
308
309 let size = buf.len() as u32;
311 buf[0..4].copy_from_slice(&size.to_be_bytes());
312
313 let mut cursor = std::io::Cursor::new(&buf[8..]);
315
316 let decoded = Meta::decode_body(&mut cursor).expect("failed to decode Apple meta box");
318
319 assert_eq!(decoded.hdlr.handler, FourCC::new(b"mdir"));
321 assert_eq!(decoded.hdlr.name, "Apple");
322 assert_eq!(decoded.items.len(), 1);
323 assert!(decoded.get::<Ilst>().is_some());
324 }
325
326 #[test]
327 fn test_meta_apple_with_ilst() {
328 let mut buf = Vec::new();
330
331 buf.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); buf.extend_from_slice(b"meta");
334
335 let hdlr = Hdlr {
337 handler: b"mdir".into(),
338 name: "".into(),
339 };
340 let mut hdlr_buf = Vec::new();
341 hdlr.encode(&mut hdlr_buf).unwrap();
342 buf.extend_from_slice(&hdlr_buf);
343
344 let ilst = Ilst {
346 name: Some(Name("Test Song".into())),
347 year: Some(Year("2025".into())),
348 ..Default::default()
349 };
350
351 let mut ilst_buf = Vec::new();
352 ilst.encode(&mut ilst_buf).unwrap();
353 buf.extend_from_slice(&ilst_buf);
354
355 let size = buf.len() as u32;
357 buf[0..4].copy_from_slice(&size.to_be_bytes());
358
359 let mut cursor = std::io::Cursor::new(&buf[8..]);
361 let decoded =
362 Meta::decode_body(&mut cursor).expect("failed to decode Apple meta with ilst");
363
364 assert_eq!(decoded.hdlr.handler, FourCC::new(b"mdir"));
366 let decoded_ilst = decoded.get::<Ilst>().expect("ilst not found");
367 assert_eq!(decoded_ilst.name.as_ref().unwrap().0, "Test Song");
368 assert_eq!(decoded_ilst.year.as_ref().unwrap().0, "2025");
369 }
370
371 #[test]
372 fn test_meta_iso_vs_apple_roundtrip() {
373 let meta = Meta {
377 hdlr: Hdlr {
378 handler: b"mdir".into(),
379 name: "Handler".into(),
380 },
381 items: vec![],
382 };
383
384 let mut encoded = Vec::new();
386 meta.encode(&mut encoded).unwrap();
387
388 let mut cursor = std::io::Cursor::new(&encoded);
390 let decoded = Meta::decode(&mut cursor).expect("failed to decode ISO format");
391 assert_eq!(decoded, meta);
392
393 let mut apple_format = Vec::new();
395 apple_format.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); apple_format.extend_from_slice(b"meta");
397
398 let mut hdlr_buf = Vec::new();
399 meta.hdlr.encode(&mut hdlr_buf).unwrap();
400 apple_format.extend_from_slice(&hdlr_buf);
401
402 let size = apple_format.len() as u32;
403 apple_format[0..4].copy_from_slice(&size.to_be_bytes());
404
405 let mut cursor = std::io::Cursor::new(&apple_format);
407 let decoded_apple = Meta::decode(&mut cursor).expect("failed to decode Apple format");
408 assert_eq!(decoded_apple, meta);
409 }
410}