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