isobmff/
lib.rs

1//! Implementation of the ISO Base Media File Format (ISOBMFF) defined by ISO/IEC 14496-12.
2//!
3//! ## Example
4//!
5//! TODO
6//!
7//! ## Notes
8//!
9//! This implementation does not preserve the order of boxes when remuxing files and individual boxes.
10//! Instead it uses the recommended box order as defined in ISO/IEC 14496-12 - 6.3.4.
11#![cfg_attr(feature = "docs", doc = "\n\nSee the [changelog][changelog] for a full release history.")]
12#![cfg_attr(feature = "docs", doc = "## Feature flags")]
13#![cfg_attr(feature = "docs", doc = document_features::document_features!())]
14//! ## License
15//!
16//! This project is licensed under the [MIT](./LICENSE.MIT) or [Apache-2.0](./LICENSE.Apache-2.0) license.
17//! You can choose between one of them if you use this work.
18//!
19//! `SPDX-License-Identifier: MIT OR Apache-2.0`
20#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
21#![cfg_attr(docsrs, feature(doc_auto_cfg))]
22#![deny(missing_docs)]
23#![deny(unsafe_code)]
24#![deny(unreachable_pub)]
25#![deny(clippy::mod_module_files)]
26
27use std::fmt::Debug;
28use std::io;
29
30use scuffle_bytes_util::BytesCow;
31use scuffle_bytes_util::zero_copy::{Deserialize, DeserializeSeed, Serialize};
32
33pub mod boxes;
34mod common_types;
35mod conformance_tests;
36mod file;
37mod header;
38mod sized;
39mod utils;
40
41pub use common_types::*;
42pub use file::*;
43pub use header::*;
44pub use isobmff_derive::IsoBox;
45pub use sized::*;
46
47#[doc(hidden)]
48pub mod reexports {
49    pub use scuffle_bytes_util;
50}
51
52/// Changelogs generated by [scuffle_changelog]
53#[cfg(feature = "docs")]
54#[scuffle_changelog::changelog]
55pub mod changelog {}
56
57/// This trait should be implemented by all box types.
58pub trait IsoBox: IsoSized {
59    /// The box type of this box.
60    const TYPE: BoxType;
61
62    /// This function calculates the header size, adds it to the given payload size and return the result.
63    ///
64    /// This can be used as a helper function when implementing the [`IsoSized`] trait.
65    fn add_header_size(payload_size: usize) -> usize {
66        let mut box_size = payload_size;
67        box_size += 4 + 4; // size + type
68        if let BoxType::Uuid(_) = Self::TYPE {
69            box_size += 16; // usertype
70        }
71
72        // If the size does not fit in a u32 we use a long size.
73        if box_size > u32::MAX as usize {
74            box_size += 8; // large size
75        }
76
77        box_size
78    }
79
80    /// Constructs a [`BoxHeader`] for this box.
81    fn box_header(&self) -> BoxHeader {
82        BoxHeader {
83            size: self.size().into(),
84            box_type: Self::TYPE,
85        }
86    }
87
88    /// Serializes the box header returned by [`IsoBox::box_header`] to the given writer.
89    fn serialize_box_header<W>(&self, writer: W) -> std::io::Result<()>
90    where
91        W: std::io::Write,
92    {
93        self.box_header().serialize(writer)
94    }
95}
96
97/// Any unknown box.
98///
99/// Can be used whenever the type is not known.
100///
101/// Use [`UnknownBox::try_from_box`] to create an [`UnknownBox`] from a known box type and
102/// [`UnknownBox::deserialize_as_box`] to deserialize it into a specific box type.
103#[derive(PartialEq, Eq)]
104pub struct UnknownBox<'a> {
105    /// The header of the box.
106    pub header: BoxHeader,
107    /// The payload data of the box.
108    pub data: BytesCow<'a>,
109}
110
111impl Debug for UnknownBox<'_> {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        f.debug_struct("UnknownBox")
114            .field("header", &self.header)
115            .field("data.len", &self.data.len())
116            .finish()
117    }
118}
119
120impl<'a> Deserialize<'a> for UnknownBox<'a> {
121    fn deserialize<R>(mut reader: R) -> std::io::Result<Self>
122    where
123        R: scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
124    {
125        let header = BoxHeader::deserialize(&mut reader)?;
126        Self::deserialize_seed(&mut reader, header)
127    }
128}
129
130impl<'a> DeserializeSeed<'a, BoxHeader> for UnknownBox<'a> {
131    fn deserialize_seed<R>(mut reader: R, seed: BoxHeader) -> std::io::Result<Self>
132    where
133        R: scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
134    {
135        Ok(Self {
136            header: seed,
137            data: reader.try_read_to_end()?,
138        })
139    }
140}
141
142impl Serialize for UnknownBox<'_> {
143    fn serialize<W>(&self, mut writer: W) -> std::io::Result<()>
144    where
145        W: std::io::Write,
146    {
147        self.header.serialize(&mut writer)?;
148        self.data.serialize(&mut writer)?;
149        Ok(())
150    }
151}
152
153impl<'a> UnknownBox<'a> {
154    /// Creates an [`UnknownBox`] from a known box type.
155    pub fn try_from_box(box_: impl IsoBox + Serialize) -> Result<Self, io::Error> {
156        #[derive(Debug)]
157        struct SkipWriter<W> {
158            writer: W,
159            skip_size: usize,
160        }
161
162        impl<W> io::Write for SkipWriter<W>
163        where
164            W: io::Write,
165        {
166            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
167                // Calculate how many bytes are left to skip
168                let skip = self.skip_size.min(buf.len());
169
170                // Write the data and skip the specified number of bytes
171                // n is the number of bytes that were actually written considering the skip
172                let n = self.writer.write(&buf[skip..])? + skip;
173                // Update the counter
174                self.skip_size = self.skip_size.saturating_sub(n);
175                Ok(n)
176            }
177
178            fn flush(&mut self) -> io::Result<()> {
179                self.writer.flush()
180            }
181        }
182
183        let header = box_.box_header();
184
185        let mut data = if let Some(size) = header.payload_size() {
186            Vec::with_capacity(size)
187        } else {
188            Vec::new()
189        };
190        box_.serialize(SkipWriter {
191            writer: &mut data,
192            skip_size: header.size(),
193        })?;
194
195        data.shrink_to_fit();
196
197        Ok(Self {
198            header,
199            data: data.into(),
200        })
201    }
202
203    /// Deserializes the box as a specific type.
204    pub fn deserialize_as<T, S>(self) -> std::io::Result<T>
205    where
206        T: DeserializeSeed<'a, S>,
207        S: DeserializeSeed<'a, BoxHeader>,
208    {
209        let mut reader = scuffle_bytes_util::zero_copy::BytesBuf::from(self.data.into_bytes());
210        let seed = S::deserialize_seed(&mut reader, self.header)?;
211        T::deserialize_seed(&mut reader, seed)
212    }
213
214    /// Deserializes the box as a specific type, which implements [`IsoBox`].
215    pub fn deserialize_as_box<B>(self) -> std::io::Result<B>
216    where
217        B: IsoBox + Deserialize<'a>,
218    {
219        if self.header.box_type != B::TYPE {
220            return Err(std::io::Error::new(
221                std::io::ErrorKind::InvalidData,
222                format!("Box type mismatch: expected {:?}, found {:?}", B::TYPE, self.header.box_type),
223            ));
224        }
225
226        let reader = scuffle_bytes_util::zero_copy::BytesBuf::from(self.data.into_bytes());
227        B::deserialize(reader)
228    }
229}
230
231impl IsoSized for UnknownBox<'_> {
232    fn size(&self) -> usize {
233        self.header.size() + self.data.size()
234    }
235}