Skip to main content

mp4/mp4box/
meta.rs

1use std::io::{Read, Seek};
2
3use serde::Serialize;
4
5use crate::mp4box::hdlr::HdlrBox;
6use crate::mp4box::ilst::IlstBox;
7use crate::mp4box::*;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
10#[serde(tag = "hdlr")]
11#[serde(rename_all = "lowercase")]
12pub enum MetaBox {
13    Mdir {
14        #[serde(skip_serializing_if = "Option::is_none")]
15        ilst: Option<IlstBox>,
16    },
17
18    #[serde(skip)]
19    Unknown {
20        #[serde(skip)]
21        hdlr: HdlrBox,
22
23        #[serde(skip)]
24        data: Vec<(BoxType, Vec<u8>)>,
25    },
26}
27
28const MDIR: FourCC = FourCC { value: *b"mdir" };
29
30impl MetaBox {
31    pub fn get_type(&self) -> BoxType {
32        BoxType::MetaBox
33    }
34
35    pub fn get_size(&self) -> u64 {
36        let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
37        match self {
38            Self::Mdir { ilst } => {
39                size += HdlrBox::default().box_size();
40                if let Some(ilst) = ilst {
41                    size += ilst.box_size();
42                }
43            }
44            Self::Unknown { hdlr, data } => {
45                size += hdlr.box_size()
46                    + data
47                        .iter()
48                        .map(|(_, data)| data.len() as u64 + HEADER_SIZE)
49                        .sum::<u64>()
50            }
51        }
52        size
53    }
54}
55
56impl Mp4Box for MetaBox {
57    fn box_type(&self) -> BoxType {
58        self.get_type()
59    }
60
61    fn box_size(&self) -> u64 {
62        self.get_size()
63    }
64
65    fn to_json(&self) -> Result<String> {
66        Ok(serde_json::to_string(&self).unwrap())
67    }
68
69    fn summary(&self) -> Result<String> {
70        let s = match self {
71            Self::Mdir { .. } => "hdlr=ilst".to_string(),
72            Self::Unknown { hdlr, data } => {
73                format!("hdlr={} data_len={}", hdlr.handler_type, data.len())
74            }
75        };
76        Ok(s)
77    }
78}
79
80impl Default for MetaBox {
81    fn default() -> Self {
82        Self::Unknown {
83            hdlr: Default::default(),
84            data: Default::default(),
85        }
86    }
87}
88
89impl<R: Read + Seek> ReadBox<&mut R> for MetaBox {
90    fn read_box(reader: &mut R, size: u64) -> Result<Self> {
91        let start = box_start(reader)?;
92
93        let extended_header = reader.read_u32::<BigEndian>()?;
94        if extended_header != 0 {
95            // ISO mp4 requires this header (version & flags) to be 0. Some
96            // files skip the extended header and directly start the hdlr box.
97            let possible_hdlr = BoxType::from(reader.read_u32::<BigEndian>()?);
98            if possible_hdlr == BoxType::HdlrBox {
99                // This file skipped the extended header! Go back to start.
100                reader.seek(SeekFrom::Current(-8))?;
101            } else {
102                // Looks like we actually have a bad version number or flags.
103                let v = (extended_header >> 24) as u8;
104                return Err(Error::UnsupportedBoxVersion(BoxType::MetaBox, v));
105            }
106        }
107
108        let mut current = reader.stream_position()?;
109        let end = start + size;
110
111        let content_start = current;
112
113        // find the hdlr box
114        let mut hdlr = None;
115        while current < end {
116            // Get box header.
117            let header = BoxHeader::read(reader)?;
118            let BoxHeader { name, size: s } = header;
119
120            match name {
121                BoxType::HdlrBox => {
122                    hdlr = Some(HdlrBox::read_box(reader, s)?);
123                }
124                _ => {
125                    // XXX warn!()
126                    skip_box(reader, s)?;
127                }
128            }
129
130            current = reader.stream_position()?;
131        }
132
133        let Some(hdlr) = hdlr else {
134            return Err(Error::BoxNotFound(BoxType::HdlrBox));
135        };
136
137        // rewind and handle the other boxes
138        reader.seek(SeekFrom::Start(content_start))?;
139        current = reader.stream_position()?;
140
141        let mut ilst = None;
142
143        match hdlr.handler_type {
144            MDIR => {
145                while current < end {
146                    // Get box header.
147                    let header = BoxHeader::read(reader)?;
148                    let BoxHeader { name, size: s } = header;
149
150                    match name {
151                        BoxType::IlstBox => {
152                            ilst = Some(IlstBox::read_box(reader, s)?);
153                        }
154                        _ => {
155                            // XXX warn!()
156                            skip_box(reader, s)?;
157                        }
158                    }
159
160                    current = reader.stream_position()?;
161                }
162
163                Ok(MetaBox::Mdir { ilst })
164            }
165            _ => {
166                let mut data = Vec::new();
167
168                while current < end {
169                    // Get box header.
170                    let header = BoxHeader::read(reader)?;
171                    let BoxHeader { name, size: s } = header;
172
173                    match name {
174                        BoxType::HdlrBox => {
175                            skip_box(reader, s)?;
176                        }
177                        _ => {
178                            let mut box_data = vec![0; (s - HEADER_SIZE) as usize];
179                            reader.read_exact(&mut box_data)?;
180
181                            data.push((name, box_data));
182                        }
183                    }
184
185                    current = reader.stream_position()?;
186                }
187
188                Ok(MetaBox::Unknown { hdlr, data })
189            }
190        }
191    }
192}
193
194impl<W: Write> WriteBox<&mut W> for MetaBox {
195    fn write_box(&self, writer: &mut W) -> Result<u64> {
196        let size = self.box_size();
197        BoxHeader::new(self.box_type(), size).write(writer)?;
198
199        write_box_header_ext(writer, 0, 0)?;
200
201        let hdlr = match self {
202            Self::Mdir { .. } => HdlrBox {
203                handler_type: MDIR,
204                ..Default::default()
205            },
206            Self::Unknown { hdlr, .. } => hdlr.clone(),
207        };
208        hdlr.write_box(writer)?;
209
210        match self {
211            Self::Mdir { ilst } => {
212                if let Some(ilst) = ilst {
213                    ilst.write_box(writer)?;
214                }
215            }
216            Self::Unknown { data, .. } => {
217                for (box_type, data) in data {
218                    BoxHeader::new(*box_type, data.len() as u64 + HEADER_SIZE).write(writer)?;
219                    writer.write_all(data)?;
220                }
221            }
222        }
223        Ok(size)
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::mp4box::BoxHeader;
231    use std::io::Cursor;
232
233    #[test]
234    fn test_meta_mdir_empty() {
235        let src_box = MetaBox::Mdir { ilst: None };
236
237        let mut buf = Vec::new();
238        src_box.write_box(&mut buf).unwrap();
239        assert_eq!(buf.len(), src_box.box_size() as usize);
240
241        let mut reader = Cursor::new(&buf);
242        let header = BoxHeader::read(&mut reader).unwrap();
243        assert_eq!(header.name, BoxType::MetaBox);
244        assert_eq!(header.size, src_box.box_size());
245
246        let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
247        assert_eq!(dst_box, src_box);
248    }
249
250    #[test]
251    fn test_meta_mdir() {
252        let src_box = MetaBox::Mdir {
253            ilst: Some(IlstBox::default()),
254        };
255
256        let mut buf = Vec::new();
257        src_box.write_box(&mut buf).unwrap();
258        assert_eq!(buf.len(), src_box.box_size() as usize);
259
260        let mut reader = Cursor::new(&buf);
261        let header = BoxHeader::read(&mut reader).unwrap();
262        assert_eq!(header.name, BoxType::MetaBox);
263        assert_eq!(header.size, src_box.box_size());
264
265        let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
266        assert_eq!(dst_box, src_box);
267    }
268
269    #[test]
270    fn test_meta_hdrl_non_first() {
271        let data = b"\x00\x00\x00\x7fmeta\x00\x00\x00\x00\x00\x00\x00Qilst\x00\x00\x00I\xa9too\x00\x00\x00Adata\x00\x00\x00\x01\x00\x00\x00\x00TMPGEnc Video Mastering Works 7 Version 7.0.15.17\x00\x00\x00\"hdlr\x00\x00\x00\x00\x00\x00\x00\x00mdirappl\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
272        let mut reader = Cursor::new(data);
273        let header = BoxHeader::read(&mut reader).unwrap();
274        assert_eq!(header.name, BoxType::MetaBox);
275
276        let meta_box = MetaBox::read_box(&mut reader, header.size).unwrap();
277
278        // this contains \xa9too box in the ilst
279        // it designates the tool that created the file, but is not yet supported by this crate
280        assert_eq!(
281            meta_box,
282            MetaBox::Mdir {
283                ilst: Some(IlstBox::default())
284            }
285        );
286    }
287
288    #[test]
289    fn test_meta_unknown() {
290        let src_hdlr = HdlrBox {
291            handler_type: FourCC::from(*b"test"),
292            ..Default::default()
293        };
294        let src_data = (BoxType::UnknownBox(0x42494241), b"123".to_vec());
295        let src_box = MetaBox::Unknown {
296            hdlr: src_hdlr,
297            data: vec![src_data],
298        };
299
300        let mut buf = Vec::new();
301        src_box.write_box(&mut buf).unwrap();
302        assert_eq!(buf.len(), src_box.box_size() as usize);
303
304        let mut reader = Cursor::new(&buf);
305        let header = BoxHeader::read(&mut reader).unwrap();
306        assert_eq!(header.name, BoxType::MetaBox);
307        assert_eq!(header.size, src_box.box_size());
308
309        let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
310        assert_eq!(dst_box, src_box);
311    }
312}