mp4-track 0.1.0

Track-level MP4 reader and progressive writer with bounded parsing
Documentation
use crate::{FourCc, TrackId};
use std::fmt;

/// Errors produced by parsing, sample reads, and writing.
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
    /// An input or output operation failed.
    Io(std::io::Error),
    /// A header or fixed field ended early.
    Truncated {
        /// Absolute byte offset.
        at: u64,
        /// Required byte count.
        needed: u64,
    },
    /// A box size is smaller than its header.
    BoxSizeTooSmall {
        /// Box code.
        kind: FourCc,
        /// Declared size.
        size: u64,
        /// Absolute box offset.
        at: u64,
    },
    /// A top-level box extends beyond the stream.
    BoxExceedsFile {
        /// Box code.
        kind: FourCc,
        /// Absolute box offset.
        at: u64,
        /// Declared box end.
        declared_end: u64,
        /// Stream length.
        file_len: u64,
    },
    /// A child box extends beyond its parent.
    ChildExceedsParent {
        /// Parent box code.
        parent: FourCc,
        /// Child box code.
        child: FourCc,
        /// Absolute child offset.
        at: u64,
    },
    /// A nested box uses a zero size.
    ZeroSizeNested {
        /// Box code.
        kind: FourCc,
        /// Absolute box offset.
        at: u64,
    },
    /// Structural nesting exceeds the configured limit.
    NestingTooDeep {
        /// Observed depth.
        depth: u8,
        /// Absolute box offset.
        at: u64,
    },
    /// A required box is missing.
    MissingBox {
        /// Missing box code.
        kind: FourCc,
        /// Expected parent.
        parent: Option<FourCc>,
    },
    /// A box that must be unique appears more than once.
    DuplicateBox {
        /// Repeated box code.
        kind: FourCc,
        /// Enclosing box code.
        parent: FourCc,
    },
    /// A known box has invalid fields.
    Malformed {
        /// Box code.
        kind: FourCc,
        /// Stable failure description.
        reason: &'static str,
    },
    /// A sample table is inconsistent.
    InvalidSampleTable {
        /// Affected track.
        track: TrackId,
        /// Stable failure description.
        reason: &'static str,
    },
    /// A movie or media timescale is zero.
    InvalidTimescale {
        /// Affected track, or `None` for the movie.
        track: Option<TrackId>,
    },
    /// A sample points beyond the stream.
    SampleOutOfFile {
        /// Affected track.
        track: TrackId,
        /// Zero-based sample index.
        index: u32,
        /// Absolute sample offset.
        offset: u64,
        /// Sample size.
        size: u32,
        /// Stream length.
        file_len: u64,
    },
    /// A configured resource limit was exceeded.
    LimitExceeded {
        /// Limit name.
        limit: &'static str,
        /// Observed value.
        value: u64,
        /// Configured maximum.
        max: u64,
    },
    /// The file uses an unsupported feature.
    Unsupported(&'static str),
    /// No track has the requested identifier.
    NoSuchTrack(TrackId),
    /// The requested sample index is absent.
    NoSuchSample {
        /// Requested track.
        track: TrackId,
        /// Zero-based sample index.
        index: u32,
    },
    /// The box-layer dependency rejected data.
    Atom(mp4_atom::Error),
    /// A writer method was called in an invalid state.
    WriterState(&'static str),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Io(error) => write!(f, "I/O error: {error}"),
            Self::Truncated { at, needed } => write!(f, "truncated data at {at}, need {needed} bytes"),
            Self::BoxSizeTooSmall { kind, size, at } => write!(f, "box {kind} at {at} has invalid size {size}"),
            Self::BoxExceedsFile { kind, at, declared_end, file_len } => {
                write!(f, "box {kind} at {at} ends at {declared_end}, past file length {file_len}")
            }
            Self::ChildExceedsParent { parent, child, at } => write!(f, "box {child} at {at} extends beyond parent {parent}"),
            Self::ZeroSizeNested { kind, at } => write!(f, "nested box {kind} at {at} has size zero"),
            Self::NestingTooDeep { depth, at } => write!(f, "box nesting depth {depth} exceeds the limit at {at}"),
            Self::MissingBox { kind, parent: Some(parent) } => write!(f, "missing {kind} box in {parent}"),
            Self::MissingBox { kind, parent: None } => write!(f, "missing top-level {kind} box"),
            Self::DuplicateBox { kind, parent } => write!(f, "duplicate {kind} box in {parent}"),
            Self::Malformed { kind, reason } => write!(f, "malformed {kind} box: {reason}"),
            Self::InvalidSampleTable { track, reason } => write!(f, "invalid sample table for track {}: {reason}", track.0),
            Self::InvalidTimescale { track: Some(track) } => write!(f, "track {} has a zero timescale", track.0),
            Self::InvalidTimescale { track: None } => f.write_str("movie has a zero timescale"),
            Self::SampleOutOfFile { track, index, offset, size, file_len } => write!(
                f,
                "sample {index} from track {} at {offset} with size {size} exceeds file length {file_len}",
                track.0
            ),
            Self::LimitExceeded { limit, value, max } => write!(f, "{limit} value {value} exceeds limit {max}"),
            Self::Unsupported(feature) => write!(f, "unsupported MP4 feature: {feature}"),
            Self::NoSuchTrack(track) => write!(f, "track {} does not exist", track.0),
            Self::NoSuchSample { track, index } => write!(f, "sample {index} does not exist in track {}", track.0),
            Self::Atom(error) => write!(f, "atom decode error: {error}"),
            Self::WriterState(reason) => write!(f, "invalid writer state: {reason}"),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Io(error) => Some(error),
            Self::Atom(error) => Some(error),
            _ => None,
        }
    }
}

impl From<std::io::Error> for Error {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<mp4_atom::Error> for Error {
    fn from(value: mp4_atom::Error) -> Self {
        Self::Atom(value)
    }
}