memedb_core/formats/
isobmff.rs

1//! # ISO Base Media File Format
2//!
3//! ISOBMFF data is organized in boxes. Each box is structured as follows:
4//!
5//! - 4 byte big-endian number describing the length of the data within.
6//! - 4 byte identifier for the box type.
7//! - if the size is 1, then the size is actually stored in the next 8 bytes.
8//! - if the size is 0, then the box lasts until the end of the file.
9//! - if the type is `uuid`, then the box type is actually stored in the next 12 bytes.
10//! - The box data itself, which may consist of other boxes.
11//!
12//! An ISOBMFF file consists of a series of boxes, the first of which must be of the type `ftyp`.
13//!
14//! MemeDB stores its tags in a `uuid` box with the UUID `12EBC64DEA6247A08E92B9FB3B518C28`. The
15//! box is placed at the end of the file since boxes can reference data via byte offset.
16//!
17//! ## Relevant Links
18//!
19//! - [Wikipedia article for ISOBMFF](https://en.wikipedia.org/wiki/ISO_base_media_file_format)
20//! - [ISO/IEC 14496-12 standard](https://www.iso.org/standard/83102.html)
21
22pub(crate) const MAGIC: &[u8] = b"ftyp";
23pub(crate) const OFFSET: usize = 4;
24
25use crate::{
26    utils::{decode_tags, encode_tags, or_eof, passthrough, read_stack, skip},
27    Error,
28};
29use std::io::{Read, Seek, Write};
30
31const MEMEDB_UUID: [u8; 16] = *b"\x12\xeb\xc6\x4d\xea\x62\x47\xa0\x8e\x92\xb9\xfb\x3b\x51\x8c\x28";
32
33#[derive(Debug)]
34enum Size {
35    Short(u32),
36    Long(u64),
37}
38
39#[derive(Debug)]
40enum Type {
41    Short([u8; 4]),
42    Long([u8; 16]),
43}
44
45#[derive(Debug)]
46struct Box {
47    size: Size,
48    r#type: Type,
49}
50
51impl Box {
52    fn new(r#type: Type, data_size: u64) -> Self {
53        let type_size = match r#type {
54            Type::Short(_) => 4,
55            Type::Long(_) => 4 + 16,
56        };
57        let total_size = 4 + type_size + data_size;
58        let size = if total_size > u32::MAX.into() {
59            Size::Long(total_size + 8)
60        } else {
61            Size::Short(total_size as u32)
62        };
63        Self { size, r#type }
64    }
65
66    fn read(src: &mut impl Read) -> Result<Box, std::io::Error> {
67        let short_size = u32::from_be_bytes(read_stack::<4>(src)?);
68        let short_type = read_stack::<4>(src)?;
69        let r#box = Box {
70            size: match short_size {
71                1 => Size::Long(u64::from_be_bytes(read_stack::<8>(src)?)),
72                _ => Size::Short(short_size),
73            },
74            r#type: match &short_type {
75                b"uuid" => Type::Long(read_stack::<16>(src)?),
76                _ => Type::Short(short_type),
77            },
78        };
79        Ok(r#box)
80    }
81
82    fn write(&self, dest: &mut impl Write) -> Result<(), std::io::Error> {
83        match self.size {
84            Size::Short(s) => dest.write_all(&s.to_be_bytes())?,
85            Size::Long(_) => dest.write_all(&[0, 0, 0, 1])?,
86        }
87        match self.r#type {
88            Type::Short(t) => dest.write_all(&t)?,
89            Type::Long(_) => dest.write_all(b"uuid")?,
90        };
91        if let Size::Long(s) = self.size {
92            dest.write_all(&s.to_be_bytes())?;
93        }
94        if let Type::Long(t) = self.r#type {
95            dest.write_all(&t)?;
96        }
97        Ok(())
98    }
99
100    fn data_size(&self) -> u64 {
101        let type_size = match self.r#type {
102            Type::Short(_) => 4,
103            Type::Long(_) => 20,
104        };
105        // Prevents panic when box size is impossibly small, will instead silently pass through.
106        match self.size {
107            Size::Short(s) => (s as u64).saturating_sub(4 + type_size),
108            Size::Long(s) => s.saturating_sub(12 + type_size),
109        }
110    }
111}
112
113/// Given a `src`, return the tags contained inside.
114pub fn read_tags(src: &mut (impl Read + Seek)) -> Result<Vec<String>, Error> {
115    while let Some(r#box) = or_eof(Box::read(src))? {
116        if let Size::Short(0) = r#box.size {
117            return Ok(Vec::new());
118        }
119        if let Type::Long(MEMEDB_UUID) = r#box.r#type {
120            return decode_tags(src);
121        }
122        let size = r#box.data_size();
123        // We passthrough instead of skip to get number of bytes read
124        if passthrough(src, &mut std::io::sink(), size)? != size {
125            Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof))?;
126        };
127    }
128    Ok(Vec::new())
129}
130
131/// Read data from `src`, set the provided `tags`, and write to `dest`.
132///
133/// This function will remove any tags that previously existed in `src`.
134pub fn write_tags(
135    src: &mut (impl Read + Seek),
136    dest: &mut impl Write,
137    tags: impl IntoIterator<Item = impl AsRef<str>>,
138) -> Result<(), Error> {
139    while let Some(r#box) = or_eof(Box::read(src))? {
140        if let Size::Short(0) = r#box.size {
141            let pos = src.stream_position()?;
142            let len = src.seek(std::io::SeekFrom::End(0))?;
143            if pos != len {
144                src.seek(std::io::SeekFrom::Start(pos))?;
145            }
146            Box::new(r#box.r#type, len - pos).write(dest)?;
147            std::io::copy(src, dest)?;
148            break;
149        }
150        if let Type::Long(MEMEDB_UUID) = r#box.r#type {
151            skip(src, r#box.data_size() as i64)?;
152        } else {
153            r#box.write(dest)?;
154            passthrough(src, dest, r#box.data_size())?;
155        };
156    }
157
158    let mut tag_bytes = Vec::new();
159    encode_tags(tags, &mut tag_bytes)?;
160    let r#box = Box::new(Type::Long(MEMEDB_UUID), tag_bytes.len() as u64);
161    r#box.write(dest)?;
162    dest.write_all(&tag_bytes)?;
163    Ok(())
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use std::io::Cursor;
170
171    const ZERO_BOX: &[&[u8]] = &[&0u32.to_be_bytes(), &[0; 8]];
172    const SIZED_BOX: &[&[u8]] = &[&12u32.to_be_bytes(), &[0; 8]];
173    const TAGS: &[&[u8]] = &[&26u32.to_be_bytes(), b"uuid", &MEMEDB_UUID, &[0x80, 0x00]];
174
175    #[test]
176    fn size_zero_box() {
177        let src = &ZERO_BOX.concat();
178        assert_eq!(read_tags(&mut Cursor::new(src)).unwrap(), Vec::<String>::new());
179        let mut dest = Vec::new();
180        write_tags(&mut Cursor::new(src), &mut dest, vec![""]).unwrap();
181        let expected = &[SIZED_BOX.concat(), TAGS.concat()].concat();
182        assert_eq!(&dest, expected);
183    }
184}
185
186crate::utils::standard_tests!("mp4");