Skip to main content

oximedia_codec/container/
isobmff.rs

1//! ISOBMFF (ISO Base Media File Format / ISO 14496-12) box primitives.
2//!
3//! Provides low-level utilities for reading and writing ISOBMFF boxes used by
4//! container formats such as JXL (ISO/IEC 18181-2), HEIF, AVIF, and MP4.
5
6use std::io::{self, Read};
7
8/// Write an ISOBMFF box with a 4-byte type code.
9///
10/// Uses a 32-bit size field when the total box fits in `u32::MAX` bytes, and
11/// falls back to the extended 64-bit `largesize` form for very large payloads.
12///
13/// # Box layout (standard)
14/// ```text
15/// [4 bytes: size] [4 bytes: fourcc] [payload]
16/// ```
17///
18/// # Box layout (extended)
19/// ```text
20/// [4 bytes: 0x00000001] [4 bytes: fourcc] [8 bytes: largesize] [payload]
21/// ```
22pub fn make_box(fourcc: [u8; 4], payload: &[u8]) -> Vec<u8> {
23    let payload_len = payload.len();
24    if let Ok(size) = u32::try_from(payload_len + 8) {
25        let mut out = Vec::with_capacity(8 + payload_len);
26        out.extend_from_slice(&size.to_be_bytes());
27        out.extend_from_slice(&fourcc);
28        out.extend_from_slice(payload);
29        out
30    } else {
31        // Extended size box: size=1 signals that a 64-bit largesize follows.
32        let size64 = (payload_len as u64) + 16; // 8 header + 8 largesize
33        let mut out = Vec::with_capacity(16 + payload_len);
34        out.extend_from_slice(&1u32.to_be_bytes()); // size = 1 → largesize
35        out.extend_from_slice(&fourcc);
36        out.extend_from_slice(&size64.to_be_bytes());
37        out.extend_from_slice(payload);
38        out
39    }
40}
41
42/// Write an ISOBMFF FullBox (version + 24-bit flags precede the payload).
43///
44/// # Box layout
45/// ```text
46/// [make_box header] [1 byte: version] [3 bytes: flags] [payload]
47/// ```
48pub fn make_full_box(fourcc: [u8; 4], version: u8, flags: u32, payload: &[u8]) -> Vec<u8> {
49    let mut full_payload = Vec::with_capacity(4 + payload.len());
50    full_payload.push(version);
51    let flags_be = flags.to_be_bytes();
52    full_payload.extend_from_slice(&flags_be[1..4]); // 24-bit flags (big-endian, skip MSB)
53    full_payload.extend_from_slice(payload);
54    make_box(fourcc, &full_payload)
55}
56
57/// An iterator over ISOBMFF boxes in a `Read` stream.
58///
59/// Each successful item is `([u8; 4], Vec<u8>)` where the first element is the
60/// 4-byte box type code and the second is the box payload (excluding the
61/// 8-byte / 16-byte header).
62pub struct BoxIter<R: Read> {
63    reader: R,
64    done: bool,
65}
66
67impl<R: Read> BoxIter<R> {
68    /// Create a new `BoxIter` wrapping `reader`.
69    pub fn new(reader: R) -> Self {
70        Self {
71            reader,
72            done: false,
73        }
74    }
75}
76
77impl<R: Read> Iterator for BoxIter<R> {
78    type Item = io::Result<([u8; 4], Vec<u8>)>;
79
80    fn next(&mut self) -> Option<Self::Item> {
81        if self.done {
82            return None;
83        }
84
85        // Read 8-byte box header (size + fourcc).
86        let mut header = [0u8; 8];
87        match self.reader.read_exact(&mut header) {
88            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
89                self.done = true;
90                return None;
91            }
92            Err(e) => {
93                self.done = true;
94                return Some(Err(e));
95            }
96            Ok(()) => {}
97        }
98
99        // Parse size field and fourcc — use copy_from_slice to avoid unwrap().
100        let mut size_bytes = [0u8; 4];
101        size_bytes.copy_from_slice(&header[0..4]);
102        let size_field = u32::from_be_bytes(size_bytes);
103
104        let mut fourcc = [0u8; 4];
105        fourcc.copy_from_slice(&header[4..8]);
106
107        let payload_len: usize = match size_field {
108            0 => {
109                // Box extends to EOF — read the remainder.
110                let mut payload = Vec::new();
111                if let Err(e) = self.reader.read_to_end(&mut payload) {
112                    self.done = true;
113                    return Some(Err(e));
114                }
115                self.done = true;
116                return Some(Ok((fourcc, payload)));
117            }
118            1 => {
119                // Extended size: next 8 bytes hold the full 64-bit box size.
120                let mut ls = [0u8; 8];
121                if let Err(e) = self.reader.read_exact(&mut ls) {
122                    self.done = true;
123                    return Some(Err(e));
124                }
125                let total = u64::from_be_bytes(ls);
126                // Payload = total_size − 8 (header) − 8 (largesize field)
127                (total as usize).saturating_sub(16)
128            }
129            n => {
130                // Standard 32-bit size: payload is size − 8 (header).
131                (n as usize).saturating_sub(8)
132            }
133        };
134
135        let mut payload = vec![0u8; payload_len];
136        if let Err(e) = self.reader.read_exact(&mut payload) {
137            self.done = true;
138            return Some(Err(e));
139        }
140        Some(Ok((fourcc, payload)))
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use std::io::Cursor;
148
149    #[test]
150    fn make_box_32bit_layout() {
151        let payload = b"hello";
152        let boxed = make_box(*b"test", payload);
153        // Total = 8 header + 5 payload = 13
154        assert_eq!(boxed.len(), 13);
155        let mut size_buf = [0u8; 4];
156        size_buf.copy_from_slice(&boxed[0..4]);
157        assert_eq!(u32::from_be_bytes(size_buf), 13);
158        assert_eq!(&boxed[4..8], b"test");
159        assert_eq!(&boxed[8..], b"hello");
160    }
161
162    #[test]
163    fn make_full_box_layout() {
164        let payload = b"data";
165        let boxed = make_full_box(*b"mdhd", 0, 0x000001, payload);
166        // Outer size = 8 + 1 (version) + 3 (flags) + 4 (payload) = 16
167        assert_eq!(boxed.len(), 16);
168        // Version byte
169        assert_eq!(boxed[8], 0);
170        // 24-bit flags = 0x000001
171        assert_eq!(&boxed[9..12], &[0x00, 0x00, 0x01]);
172    }
173
174    #[test]
175    fn box_iter_single_box() {
176        let boxed = make_box(*b"foo1", b"abcd");
177        let parsed: Vec<_> = BoxIter::new(Cursor::new(boxed))
178            .collect::<Result<Vec<_>, _>>()
179            .expect("parse ok");
180        assert_eq!(parsed.len(), 1);
181        assert_eq!(&parsed[0].0, b"foo1");
182        assert_eq!(&parsed[0].1, b"abcd");
183    }
184
185    #[test]
186    fn box_iter_multiple_boxes() {
187        let box1 = make_box(*b"foo1", b"aaaa");
188        let box2 = make_box(*b"foo2", b"bbbb");
189        let mut combined = box1;
190        combined.extend(box2);
191        let parsed: Vec<_> = BoxIter::new(Cursor::new(combined))
192            .collect::<Result<Vec<_>, _>>()
193            .expect("parse ok");
194        assert_eq!(parsed.len(), 2);
195        assert_eq!(&parsed[0].0, b"foo1");
196        assert_eq!(&parsed[1].0, b"foo2");
197        assert_eq!(&parsed[0].1, b"aaaa");
198        assert_eq!(&parsed[1].1, b"bbbb");
199    }
200
201    #[test]
202    fn box_iter_empty_payload() {
203        let boxed = make_box(*b"free", b"");
204        let parsed: Vec<_> = BoxIter::new(Cursor::new(boxed))
205            .collect::<Result<Vec<_>, _>>()
206            .expect("parse ok");
207        assert_eq!(parsed.len(), 1);
208        assert!(parsed[0].1.is_empty());
209    }
210}