flowly-mp4 0.1.0

MP4 reader and writer library in Rust.
Documentation
use serde::Serialize;
use std::io::Write;

use crate::meta::MetaBox;
use crate::mp4box::*;
use crate::mp4box::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox};

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
pub struct TrakBox {
    pub tkhd: TkhdBox,
    pub mdia: MdiaBox,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub edts: Option<EdtsBox>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub meta: Option<MetaBox>,
}

impl TrakBox {
    pub fn get_type(&self) -> BoxType {
        BoxType::TrakBox
    }

    pub fn get_size(&self) -> u64 {
        let mut size = HEADER_SIZE;
        size += self.tkhd.box_size();
        if let Some(ref edts) = self.edts {
            size += edts.box_size();
        }
        size += self.mdia.box_size();
        size
    }

    pub(crate) fn stsc_index(&self, sample_id: u32) -> Result<usize, Error> {
        if self.mdia.minf.stbl.stsc.entries.is_empty() {
            return Err(Error::InvalidData("no stsc entries"));
        }

        for (i, entry) in self.mdia.minf.stbl.stsc.entries.iter().enumerate() {
            if sample_id < entry.first_sample {
                return if i == 0 {
                    Err(Error::InvalidData("sample not found"))
                } else {
                    Ok(i - 1)
                };
            }
        }

        Ok(self.mdia.minf.stbl.stsc.entries.len() - 1)
    }

    pub(crate) fn chunk_offset(&self, chunk_id: u32) -> Result<u64, Error> {
        if self.mdia.minf.stbl.stco.is_none() && self.mdia.minf.stbl.co64.is_none() {
            return Err(Error::InvalidData("must have either stco or co64 boxes"));
        }

        if let Some(ref stco) = self.mdia.minf.stbl.stco {
            if let Some(offset) = stco.entries.get(chunk_id as usize - 1) {
                return Ok(*offset as u64);
            } else {
                return Err(Error::EntryInStblNotFound(
                    self.tkhd.track_id,
                    BoxType::StcoBox,
                    chunk_id,
                ));
            }
        } else if let Some(ref co64) = self.mdia.minf.stbl.co64 {
            if let Some(offset) = co64.entries.get(chunk_id as usize - 1) {
                return Ok(*offset);
            } else {
                return Err(Error::EntryInStblNotFound(
                    self.tkhd.track_id,
                    BoxType::Co64Box,
                    chunk_id,
                ));
            }
        }

        Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box))
    }

    pub(crate) fn sample_size(&self, sample_id: u32) -> Result<u32, Error> {
        let stsz = &self.mdia.minf.stbl.stsz;

        if stsz.sample_size > 0 {
            return Ok(stsz.sample_size);
        }

        if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
            Ok(*size)
        } else {
            Err(Error::EntryInStblNotFound(
                self.tkhd.track_id,
                BoxType::StszBox,
                sample_id,
            ))
        }
    }

    pub(crate) fn sample_offset(&self, sample_id: u32) -> Result<u64, Error> {
        let stsc_index = self.stsc_index(sample_id)?;

        let stsc = &self.mdia.minf.stbl.stsc;
        let stsc_entry = stsc.entries.get(stsc_index).unwrap();

        let first_chunk = stsc_entry.first_chunk;
        let first_sample = stsc_entry.first_sample;
        let samples_per_chunk = stsc_entry.samples_per_chunk;

        let chunk_id = sample_id
            .checked_sub(first_sample)
            .map(|n| n / samples_per_chunk)
            .and_then(|n| n.checked_add(first_chunk))
            .ok_or(Error::InvalidData(
                "attempt to calculate stsc chunk_id with overflow",
            ))?;

        let chunk_offset = self.chunk_offset(chunk_id)?;

        let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;

        let mut sample_offset = 0;
        for i in first_sample_in_chunk..sample_id {
            sample_offset += self.sample_size(i)?;
        }

        Ok(chunk_offset + sample_offset as u64)
    }

    pub(crate) fn sample_time(&self, sample_id: u32) -> Result<(u64, u32), Error> {
        let stts = &self.mdia.minf.stbl.stts;

        let mut sample_count: u32 = 1;
        let mut elapsed = 0;

        for entry in stts.entries.iter() {
            let new_sample_count =
                sample_count
                    .checked_add(entry.sample_count)
                    .ok_or(Error::InvalidData(
                        "attempt to sum stts entries sample_count with overflow",
                    ))?;

            if sample_id < new_sample_count {
                let start_time =
                    (sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed;
                return Ok((start_time, entry.sample_delta));
            }

            sample_count = new_sample_count;
            elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
        }

        Err(Error::EntryInStblNotFound(
            self.tkhd.track_id,
            BoxType::SttsBox,
            sample_id,
        ))
    }

    pub(crate) fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32), Error> {
        let ctts = self.mdia.minf.stbl.ctts.as_ref().unwrap();
        let mut sample_count: u32 = 1;
        for (i, entry) in ctts.entries.iter().enumerate() {
            let next_sample_count =
                sample_count
                    .checked_add(entry.sample_count)
                    .ok_or(Error::InvalidData(
                        "attempt to sum ctts entries sample_count with overflow",
                    ))?;
            if sample_id < next_sample_count {
                return Ok((i, sample_count));
            }
            sample_count = next_sample_count;
        }

        Err(Error::EntryInStblNotFound(
            self.tkhd.track_id,
            BoxType::CttsBox,
            sample_id,
        ))
    }

    pub fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
        if let Some(ref ctts) = self.mdia.minf.stbl.ctts {
            if let Ok((ctts_index, _)) = self.ctts_index(sample_id) {
                let ctts_entry = ctts.entries.get(ctts_index).unwrap();
                return ctts_entry.sample_offset;
            }
        }

        0
    }

    #[inline]
    pub fn sample_is_sync(&self, sample_id: u32) -> bool {
        if let Some(ref stss) = self.mdia.minf.stbl.stss {
            stss.entries.binary_search(&sample_id).is_ok()
        } else {
            true
        }
    }
}

impl Mp4Box for TrakBox {
    const TYPE: BoxType = BoxType::TrakBox;

    fn box_size(&self) -> u64 {
        self.get_size()
    }

    fn to_json(&self) -> Result<String, Error> {
        Ok(serde_json::to_string(&self).unwrap())
    }

    fn summary(&self) -> Result<String, Error> {
        let s = String::new();
        Ok(s)
    }
}

impl BlockReader for TrakBox {
    fn read_block<'a>(reader: &mut impl Reader<'a>) -> Result<Self, Error> {
        let (tkhd, edts, meta, mdia) = reader.try_find_box4()?;

        if tkhd.is_none() {
            return Err(Error::BoxNotFound(BoxType::TkhdBox));
        }

        if mdia.is_none() {
            return Err(Error::BoxNotFound(BoxType::MdiaBox));
        }

        Ok(TrakBox {
            tkhd: tkhd.unwrap(),
            edts,
            meta,
            mdia: mdia.unwrap(),
        })
    }

    fn size_hint() -> usize {
        TkhdBox::size_hint() + MdiaBox::size_hint()
    }
}

impl<W: Write> WriteBox<&mut W> for TrakBox {
    fn write_box(&self, writer: &mut W) -> Result<u64, Error> {
        let size = self.box_size();
        BoxHeader::new(Self::TYPE, size).write(writer)?;

        self.tkhd.write_box(writer)?;
        if let Some(ref edts) = self.edts {
            edts.write_box(writer)?;
        }
        self.mdia.write_box(writer)?;

        Ok(size)
    }
}