mp4ameta 0.13.0

A library for reading and writing iTunes style MPEG-4 audio metadata.
Documentation
use super::*;

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Stbl {
    pub state: State,
    pub stsd: Option<Stsd>,
    pub stts: Option<Stts>,
    pub stsc: Option<Stsc>,
    pub stsz: Option<Stsz>,
    pub stco: Option<Stco>,
    pub co64: Option<Co64>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Table<T> {
    Shallow { pos: u64, num_entries: u32 },
    Full(Vec<T>),
}

impl<T> Default for Table<T> {
    fn default() -> Self {
        Self::Full(Vec::new())
    }
}

impl<T> Table<T> {
    pub fn len(&self) -> usize {
        match self {
            Table::Shallow { num_entries, .. } => *num_entries as usize,
            Table::Full(items) => items.len(),
        }
    }
}

impl<T: ReadItem> Table<T> {
    pub fn get_or_read<'a>(
        &'a self,
        reader: &mut (impl Read + Seek),
    ) -> Result<Cow<'a, [T]>, crate::Error> {
        match self {
            &Table::Shallow { pos, num_entries } => {
                reader.seek(SeekFrom::Start(pos))?;
                let items = Self::read_items(reader, num_entries)?;
                Ok(Cow::Owned(items))
            }
            Table::Full(items) => Ok(Cow::Borrowed(items)),
        }
    }

    pub fn read_items(reader: &mut impl Read, num_entries: u32) -> Result<Vec<T>, crate::Error> {
        let mut items = Vec::with_capacity(num_entries as usize);
        for _ in 0..num_entries {
            items.push(T::read_item(reader)?);
        }
        Ok(items)
    }
}

pub trait ReadItem: Sized + Clone {
    fn read_item(reader: &mut impl Read) -> std::io::Result<Self>;
}

impl ReadItem for u32 {
    fn read_item(reader: &mut impl Read) -> std::io::Result<Self> {
        reader.read_be_u32()
    }
}

impl ReadItem for u64 {
    fn read_item(reader: &mut impl Read) -> std::io::Result<Self> {
        reader.read_be_u64()
    }
}

impl ReadItem for SttsItem {
    fn read_item(reader: &mut impl Read) -> std::io::Result<Self> {
        Ok(SttsItem {
            sample_count: reader.read_be_u32()?,
            sample_duration: reader.read_be_u32()?,
        })
    }
}

impl ReadItem for StscItem {
    fn read_item(reader: &mut impl Read) -> std::io::Result<Self> {
        Ok(StscItem {
            first_chunk: reader.read_be_u32()?,
            samples_per_chunk: reader.read_be_u32()?,
            sample_description_id: reader.read_be_u32()?,
        })
    }
}

impl Atom for Stbl {
    const FOURCC: Fourcc = SAMPLE_TABLE;
}

impl ParseAtom for Stbl {
    fn parse_atom(
        reader: &mut (impl Read + Seek),
        cfg: &ParseConfig<'_>,
        size: Size,
    ) -> crate::Result<Self> {
        let bounds = find_bounds(reader, size)?;
        let mut stbl = Self {
            state: State::Existing(bounds),
            ..Default::default()
        };
        let mut parsed_bytes = 0;

        while parsed_bytes < size.content_len() {
            let remaining_bytes = size.content_len() - parsed_bytes;
            let head = head::parse(reader, remaining_bytes)?;

            match head.fourcc() {
                SAMPLE_TABLE_SAMPLE_DESCRIPTION if cfg.write || cfg.cfg.read_audio_info => {
                    stbl.stsd = Some(Stsd::parse(reader, cfg, head.size())?)
                }
                SAMPLE_TABLE_TIME_TO_SAMPLE if cfg.cfg.read_chapter_track => {
                    stbl.stts = Some(Stts::parse(reader, cfg, head.size())?)
                }
                SAMPLE_TABLE_SAMPLE_TO_CHUNK if cfg.cfg.read_chapter_track => {
                    stbl.stsc = Some(Stsc::parse(reader, cfg, head.size())?)
                }
                SAMPLE_TABLE_SAMPLE_SIZE if cfg.cfg.read_chapter_track => {
                    stbl.stsz = Some(Stsz::parse(reader, cfg, head.size())?)
                }
                SAMPLE_TABLE_CHUNK_OFFSET if cfg.write || cfg.cfg.read_chapter_track => {
                    stbl.stco = Some(Stco::parse(reader, cfg, head.size())?)
                }
                SAMPLE_TABLE_CHUNK_OFFSET_64 if cfg.write || cfg.cfg.read_chapter_track => {
                    stbl.co64 = Some(Co64::parse(reader, cfg, head.size())?)
                }
                _ => reader.skip(head.content_len() as i64)?,
            }

            parsed_bytes += head.len();
        }

        Ok(stbl)
    }
}

impl AtomSize for Stbl {
    fn size(&self) -> Size {
        let content_len = self.stsd.len_or_zero()
            + self.stts.len_or_zero()
            + self.stsc.len_or_zero()
            + self.stsz.len_or_zero()
            + self.stco.len_or_zero()
            + self.co64.len_or_zero();
        Size::from(content_len)
    }
}

impl WriteAtom for Stbl {
    fn write_atom(&self, writer: &mut impl Write, changes: &[Change<'_>]) -> crate::Result<()> {
        self.write_head(writer)?;
        if let Some(a) = &self.stsd {
            a.write(writer, changes)?;
        }
        if let Some(a) = &self.stts {
            a.write(writer, changes)?;
        }
        if let Some(a) = &self.stsc {
            a.write(writer, changes)?;
        }
        if let Some(a) = &self.stsz {
            a.write(writer, changes)?;
        }
        if let Some(a) = &self.stco {
            a.write(writer, changes)?;
        }
        if let Some(a) = &self.co64 {
            a.write(writer, changes)?;
        }
        Ok(())
    }
}

impl SimpleCollectChanges for Stbl {
    fn state(&self) -> &State {
        &self.state
    }

    fn existing<'a>(
        &'a self,
        level: u8,
        bounds: &'a AtomBounds,
        changes: &mut Vec<Change<'a>>,
    ) -> i64 {
        self.stsd.collect_changes(bounds.end(), level, changes)
            + self.stts.collect_changes(bounds.end(), level, changes)
            + self.stsc.collect_changes(bounds.end(), level, changes)
            + self.stsz.collect_changes(bounds.end(), level, changes)
            + self.stco.collect_changes(bounds.end(), level, changes)
            + self.co64.collect_changes(bounds.end(), level, changes)
    }

    fn atom_ref(&self) -> AtomRef<'_> {
        AtomRef::Stbl(self)
    }
}