mp4san 0.3.0

An MP4 file sanitizer.
Documentation
#![allow(missing_docs)]

use std::mem::size_of;

use bytes::{Buf, BufMut, Bytes, BytesMut};

use crate::error::Result;

use super::error::{ParseResultExt, WhereEq, WhileParsingField};
use super::mp4box::ParsedBox;
use super::{BoxType, FourCC, Mpeg4IntReaderExt, ParseBox, ParseError};

#[derive(Clone, Debug)]
pub struct FtypBox {
    pub major_brand: FourCC,
    pub minor_version: u32,
    compatible_brands: Bytes,
}

const NAME: BoxType = BoxType::FTYP;

impl FtypBox {
    pub fn new(major_brand: FourCC, minor_version: u32, compatible_brands: impl IntoIterator<Item = FourCC>) -> Self {
        let compatible_brands = compatible_brands.into_iter().flat_map(|fourcc| fourcc.value).collect();
        Self { major_brand, minor_version, compatible_brands }
    }
    pub fn compatible_brands(&self) -> impl Iterator<Item = FourCC> + ExactSizeIterator + '_ {
        self.compatible_brands
            .chunks_exact(4)
            .map(|bytes| FourCC { value: bytes.try_into().unwrap() })
    }
}

impl ParseBox for FtypBox {
    fn parse(reader: &mut BytesMut) -> Result<Self, ParseError> {
        let major_brand = reader.get().while_parsing_field(NAME, "major_brand")?;
        let minor_version = reader.get().while_parsing_field(NAME, "minor_version")?;

        ensure_attach!(
            reader.remaining() % FourCC::size() as usize == 0,
            ParseError::TruncatedBox,
            WhileParsingField(NAME, "compatible_brands"),
            WhereEq("reader.remaining()", reader.remaining()),
        );

        let compatible_brands = reader.copy_to_bytes(reader.remaining());

        Ok(Self { major_brand, minor_version, compatible_brands })
    }

    fn box_type() -> BoxType {
        NAME
    }
}

impl ParsedBox for FtypBox {
    fn encoded_len(&self) -> u64 {
        FourCC::size() + size_of::<u32>() as u64 + self.compatible_brands.len() as u64
    }

    fn put_buf(&self, mut out: &mut dyn BufMut) {
        self.major_brand.put_buf(&mut out);
        out.put_u32(self.minor_version);
        out.put_slice(&self.compatible_brands[..]);
    }
}