use crate::parse::{
children, one, validate_tree, validate_typed_atom, BoxHeader, Cursor, FTYP, MDAT, MOOF, MOOV,
};
use crate::*;
use std::collections::BTreeMap;
use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
const MVHD: FourCc = FourCc(*b"mvhd");
const TRAK: FourCc = FourCc(*b"trak");
const TKHD: FourCc = FourCc(*b"tkhd");
const MDIA: FourCc = FourCc(*b"mdia");
const MDHD: FourCc = FourCc(*b"mdhd");
const HDLR: FourCc = FourCc(*b"hdlr");
const MINF: FourCc = FourCc(*b"minf");
const STBL: FourCc = FourCc(*b"stbl");
const STSD: FourCc = FourCc(*b"stsd");
const STTS: FourCc = FourCc(*b"stts");
const CTTS: FourCc = FourCc(*b"ctts");
const STSS: FourCc = FourCc(*b"stss");
const STSC: FourCc = FourCc(*b"stsc");
const STSZ: FourCc = FourCc(*b"stsz");
const STZ2: FourCc = FourCc(*b"stz2");
const STCO: FourCc = FourCc(*b"stco");
const CO64: FourCc = FourCc(*b"co64");
const EDTS: FourCc = FourCc(*b"edts");
const ELST: FourCc = FourCc(*b"elst");
const MVEX: FourCc = FourCc(*b"mvex");
const TREX: FourCc = FourCc(*b"trex");
const EMSG: FourCc = FourCc(*b"emsg");
const UDTA: FourCc = FourCc(*b"udta");
const DINF: FourCc = FourCc(*b"dinf");
pub struct Mp4Reader<R: Read + Seek> {
reader: R,
file_len: u64,
brands: Brands,
movie_timescale: u32,
movie_duration: u64,
tracks: Vec<Track>,
samples: Vec<Vec<SampleInfo>>,
fragments: Vec<Fragment>,
has_mvex: bool,
events: Vec<Event>,
metadata: Metadata,
warnings: Vec<Warning>,
limits: Limits,
}
impl<R: Read + Seek> Mp4Reader<R> {
pub fn open(reader: R) -> Result<Self, Error> {
Self::open_with(reader, Limits::default())
}
pub fn open_with(mut reader: R, limits: Limits) -> Result<Self, Error> {
let file_len = reader.seek(SeekFrom::End(0))?;
let mut at = 0u64;
let mut brands = Brands::default();
let mut movie: Option<ParsedMovie> = None;
let mut moofs = Vec::new();
let mut events = Vec::new();
let mut warnings = Vec::new();
while at < file_len {
let remaining = file_len.saturating_sub(at);
if remaining < 8 {
if movie.is_some() {
warnings.push(Warning::TrailingBytes {
at,
count: remaining,
});
break;
}
return Err(Error::Truncated { at, needed: 8 });
}
let step = read_top_header(&mut reader, at, file_len).and_then(|header| {
let end = header
.start
.checked_add(header.size)
.ok_or(Error::BoxExceedsFile {
kind: header.kind,
at,
declared_end: u64::MAX,
file_len,
})?;
Ok((header, end))
});
let (header, mut end) = match step {
Ok(value) => value,
Err(Error::Io(error)) => return Err(Error::Io(error)),
Err(_) if movie.is_some() => {
warnings.push(Warning::TrailingBytes {
at,
count: remaining,
});
break;
}
Err(error) => return Err(error),
};
if end > file_len {
if header.kind == MDAT && movie.is_some() {
end = file_len;
} else if movie.is_some() {
warnings.push(Warning::TrailingBytes {
at,
count: remaining,
});
break;
} else {
return Err(Error::BoxExceedsFile {
kind: header.kind,
at,
declared_end: end,
file_len,
});
}
}
let body_len =
header
.size
.checked_sub(header.header_len)
.ok_or(Error::BoxSizeTooSmall {
kind: header.kind,
size: header.size,
at,
})?;
match header.kind {
FTYP => {
let body = read_body(&mut reader, &header, body_len, u64::from(u32::MAX))?;
brands = parse_ftyp(&body)?;
validate_typed_atom(FTYP, &body)?;
}
MOOV => {
if movie.is_some() {
return Err(Error::DuplicateBox {
kind: MOOV,
parent: FourCc(*b"root"),
});
}
if header.size > limits.max_moov_bytes {
return Err(Error::LimitExceeded {
limit: "max_moov_bytes",
value: header.size,
max: limits.max_moov_bytes,
});
}
let body = read_body(&mut reader, &header, body_len, limits.max_moov_bytes)?;
let body_at =
header
.start
.checked_add(header.header_len)
.ok_or(Error::Malformed {
kind: MOOV,
reason: "movie body offset overflow",
})?;
validate_tree(&body, MOOV, body_at, limits)?;
let parsed = parse_movie(&body, body_at, limits, &mut warnings)?;
validate_typed_atom(MOOV, &body)?;
movie = Some(parsed);
}
MOOF => {
let count = u32::try_from(moofs.len()).unwrap_or(u32::MAX);
if count >= limits.max_fragments {
return Err(Error::LimitExceeded {
limit: "max_fragments",
value: u64::from(count).saturating_add(1),
max: u64::from(limits.max_fragments),
});
}
if header.size > limits.max_moof_bytes {
return Err(Error::LimitExceeded {
limit: "max_moof_bytes",
value: header.size,
max: limits.max_moof_bytes,
});
}
let body = read_body(&mut reader, &header, body_len, limits.max_moof_bytes)?;
let body_at =
header
.start
.checked_add(header.header_len)
.ok_or(Error::Malformed {
kind: MOOF,
reason: "fragment body offset overflow",
})?;
validate_tree(&body, MOOF, body_at, limits)?;
moofs.push((header.start, body_at, body));
}
EMSG => {
let body = read_body(&mut reader, &header, body_len, limits.max_moof_bytes)?;
let event = parse_event(&body)?;
validate_typed_atom(EMSG, &body)?;
events.push(event);
}
kind if [
MDAT,
FourCc(*b"free"),
FourCc(*b"skip"),
FourCc(*b"sidx"),
FourCc(*b"styp"),
FourCc(*b"mfra"),
FourCc(*b"uuid"),
]
.contains(&kind) => {}
kind => warnings.push(Warning::UnknownBox { kind, parent: None }),
}
at = end;
if header.size_zero {
break;
}
}
let mut parsed = movie.ok_or(Error::MissingBox {
kind: MOOV,
parent: None,
})?;
let mut fragments = Vec::new();
let mut running_dts: BTreeMap<TrackId, u64> = BTreeMap::new();
for (fragment_index, (moof_at, body_at, body)) in moofs.iter().enumerate() {
let mut fragment = parse_fragment(
body,
*body_at,
*moof_at,
u32::try_from(fragment_index).map_err(|_| Error::LimitExceeded {
limit: "max_fragments",
value: u64::try_from(fragment_index).unwrap_or(u64::MAX),
max: u64::from(limits.max_fragments),
})?,
&parsed.trex,
limits,
&mut running_dts,
&mut warnings,
)?;
validate_typed_atom(MOOF, body)?;
for run in &mut fragment.runs {
let position = parsed
.tracks
.iter()
.position(|track| track.id == run.track)
.ok_or(Error::NoSuchTrack(run.track))?;
let list = parsed
.samples
.get_mut(position)
.ok_or(Error::NoSuchTrack(run.track))?;
let total = list.len().saturating_add(run.samples.len());
if u64::try_from(total).unwrap_or(u64::MAX) > u64::from(limits.max_sample_count) {
return Err(Error::LimitExceeded {
limit: "max_sample_count",
value: u64::try_from(total).unwrap_or(u64::MAX),
max: u64::from(limits.max_sample_count),
});
}
let base_index = u32::try_from(list.len()).map_err(|_| Error::LimitExceeded {
limit: "sample_count",
value: u64::try_from(list.len()).unwrap_or(u64::MAX),
max: u64::from(u32::MAX),
})?;
for (offset, sample) in run.samples.iter_mut().enumerate() {
sample.index = base_index
.checked_add(u32::try_from(offset).map_err(|_| Error::LimitExceeded {
limit: "sample_count",
value: u64::try_from(offset).unwrap_or(u64::MAX),
max: u64::from(u32::MAX),
})?)
.ok_or(Error::LimitExceeded {
limit: "sample_count",
value: u64::from(u32::MAX).saturating_add(1),
max: u64::from(u32::MAX),
})?;
}
list.extend_from_slice(&run.samples);
}
fragments.push(fragment);
}
Ok(Self {
reader,
file_len,
brands,
movie_timescale: parsed.movie_timescale,
movie_duration: parsed.movie_duration,
tracks: parsed.tracks,
samples: parsed.samples,
fragments,
has_mvex: parsed.mvex_present,
events,
metadata: parsed.metadata,
warnings,
limits,
})
}
pub fn brands(&self) -> &Brands {
&self.brands
}
pub fn movie_timescale(&self) -> u32 {
self.movie_timescale
}
pub fn movie_duration(&self) -> u64 {
self.movie_duration
}
pub fn duration(&self) -> Duration {
let scale = u64::from(self.movie_timescale);
let seconds = self.movie_duration.checked_div(scale).unwrap_or_default();
let remainder = self.movie_duration.checked_rem(scale).unwrap_or_default();
let nanos = remainder
.saturating_mul(1_000_000_000)
.checked_div(scale)
.unwrap_or_default();
Duration::new(seconds, u32::try_from(nanos).unwrap_or(999_999_999))
}
pub fn is_fragmented(&self) -> bool {
self.has_mvex
|| !self.fragments.is_empty()
|| self
.tracks
.iter()
.any(|track| track.chunk_offsets == ChunkOffsetKind::None)
}
pub fn tracks(&self) -> &[Track] {
&self.tracks
}
pub fn track(&self, id: TrackId) -> Option<&Track> {
self.tracks.iter().find(|track| track.id == id)
}
pub fn samples(&self, id: TrackId) -> Result<SampleIter<'_>, Error> {
let position = self.track_position(id)?;
let list = self.samples.get(position).ok_or(Error::NoSuchTrack(id))?;
Ok(SampleIter { inner: list.iter() })
}
pub fn sample_info(&self, id: TrackId, index: u32) -> Result<SampleInfo, Error> {
let position = self.track_position(id)?;
self.samples
.get(position)
.and_then(|list| list.get(usize::try_from(index).ok()?))
.copied()
.ok_or(Error::NoSuchSample { track: id, index })
}
pub fn read_sample(&mut self, id: TrackId, index: u32) -> Result<Sample, Error> {
let info = self.sample_info(id, index)?;
let mut data = Vec::new();
self.read_sample_data(id, info, &mut data)?;
Ok(Sample { info, data })
}
pub fn read_sample_into(
&mut self,
id: TrackId,
index: u32,
buf: &mut Vec<u8>,
) -> Result<SampleInfo, Error> {
let info = self.sample_info(id, index)?;
self.read_sample_data(id, info, buf)?;
Ok(info)
}
pub fn fragments(&self) -> &[Fragment] {
&self.fragments
}
pub fn events(&self) -> &[Event] {
&self.events
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn warnings(&self) -> &[Warning] {
&self.warnings
}
pub fn into_inner(self) -> R {
self.reader
}
fn track_position(&self, id: TrackId) -> Result<usize, Error> {
self.tracks
.iter()
.position(|track| track.id == id)
.ok_or(Error::NoSuchTrack(id))
}
fn read_sample_data(
&mut self,
id: TrackId,
info: SampleInfo,
buf: &mut Vec<u8>,
) -> Result<(), Error> {
let end = info
.offset
.checked_add(u64::from(info.size))
.ok_or(Error::SampleOutOfFile {
track: id,
index: info.index,
offset: info.offset,
size: info.size,
file_len: self.file_len,
})?;
if end > self.file_len {
return Err(Error::SampleOutOfFile {
track: id,
index: info.index,
offset: info.offset,
size: info.size,
file_len: self.file_len,
});
}
if info.size > self.limits.max_sample_bytes {
return Err(Error::LimitExceeded {
limit: "max_sample_bytes",
value: u64::from(info.size),
max: u64::from(self.limits.max_sample_bytes),
});
}
let len = usize::try_from(info.size).map_err(|_| Error::LimitExceeded {
limit: "max_sample_bytes",
value: u64::from(info.size),
max: u64::from(self.limits.max_sample_bytes),
})?;
buf.clear();
buf.resize(len, 0);
self.reader.seek(SeekFrom::Start(info.offset))?;
self.reader.read_exact(buf)?;
Ok(())
}
}
pub struct SampleIter<'a> {
inner: std::slice::Iter<'a, SampleInfo>,
}
impl Iterator for SampleIter<'_> {
type Item = SampleInfo;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().copied()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl DoubleEndedIterator for SampleIter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().copied()
}
}
impl ExactSizeIterator for SampleIter<'_> {}
impl std::iter::FusedIterator for SampleIter<'_> {}
struct TopHeader {
kind: FourCc,
start: u64,
size: u64,
header_len: u64,
size_zero: bool,
}
fn read_top_header<R: Read + Seek>(
reader: &mut R,
at: u64,
file_len: u64,
) -> Result<TopHeader, Error> {
reader.seek(SeekFrom::Start(at))?;
let mut fixed = [0u8; 8];
reader.read_exact(&mut fixed).map_err(|error| {
if error.kind() == std::io::ErrorKind::UnexpectedEof {
Error::Truncated { at, needed: 8 }
} else {
Error::Io(error)
}
})?;
let size32 = u32::from_be_bytes(
fixed
.get(..4)
.and_then(|bytes| bytes.try_into().ok())
.unwrap_or([0; 4]),
);
let kind = FourCc(
fixed
.get(4..8)
.and_then(|bytes| bytes.try_into().ok())
.unwrap_or(*b" "),
);
let (size, mut header_len, size_zero) = match size32 {
0 => (file_len.saturating_sub(at), 8u64, true),
1 => {
let mut large = [0u8; 8];
reader.read_exact(&mut large).map_err(|error| {
if error.kind() == std::io::ErrorKind::UnexpectedEof {
Error::Truncated { at, needed: 16 }
} else {
Error::Io(error)
}
})?;
(u64::from_be_bytes(large), 16u64, false)
}
value => (u64::from(value), 8u64, false),
};
if kind == FourCc(*b"uuid") {
header_len = header_len
.checked_add(16)
.ok_or(Error::BoxSizeTooSmall { kind, size, at })?;
}
if size < header_len {
return Err(Error::BoxSizeTooSmall { kind, size, at });
}
Ok(TopHeader {
kind,
start: at,
size,
header_len,
size_zero,
})
}
fn read_body<R: Read + Seek>(
reader: &mut R,
header: &TopHeader,
body_len: u64,
max: u64,
) -> Result<Vec<u8>, Error> {
if body_len > max {
return Err(Error::LimitExceeded {
limit: "box_body_bytes",
value: body_len,
max,
});
}
let len = usize::try_from(body_len).map_err(|_| Error::LimitExceeded {
limit: "box_body_bytes",
value: body_len,
max,
})?;
let body_at = header
.start
.checked_add(header.header_len)
.ok_or(Error::Malformed {
kind: header.kind,
reason: "body offset overflow",
})?;
reader.seek(SeekFrom::Start(body_at))?;
let mut body = vec![0u8; len];
reader.read_exact(&mut body).map_err(|error| {
if error.kind() == std::io::ErrorKind::UnexpectedEof {
Error::Truncated {
at: body_at,
needed: body_len,
}
} else {
Error::Io(error)
}
})?;
Ok(body)
}
fn parse_ftyp(body: &[u8]) -> Result<Brands, Error> {
let mut cursor = Cursor::new(body, FTYP);
let major = cursor.fourcc()?;
let minor_version = cursor.u32()?;
if cursor.remaining() % 4 != 0 {
return Err(Error::Malformed {
kind: FTYP,
reason: "compatible brands are not four-byte aligned",
});
}
let mut compatible = Vec::with_capacity(cursor.remaining() / 4);
while cursor.remaining() != 0 {
compatible.push(cursor.fourcc()?);
}
Ok(Brands {
major,
minor_version,
compatible,
})
}
struct ParsedMovie {
movie_timescale: u32,
movie_duration: u64,
tracks: Vec<Track>,
samples: Vec<Vec<SampleInfo>>,
trex: BTreeMap<TrackId, Trex>,
mvex_present: bool,
metadata: Metadata,
}
fn warn_unknown_children(
boxes: &[(BoxHeader, &[u8])],
parent: FourCc,
known: &[FourCc],
warnings: &mut Vec<Warning>,
) {
for (header, _) in boxes {
if known.contains(&header.kind)
|| header.kind == FourCc(*b"free")
|| header.kind == FourCc(*b"skip")
{
continue;
}
warnings.push(Warning::UnknownBox {
kind: header.kind,
parent: Some(parent),
});
}
}
#[derive(Clone, Copy)]
struct Trex {
description_index: u32,
duration: u32,
size: u32,
flags: u32,
}
fn parse_movie(
body: &[u8],
absolute_base: u64,
limits: Limits,
warnings: &mut Vec<Warning>,
) -> Result<ParsedMovie, Error> {
let boxes = children(body, MOOV, absolute_base)?;
warn_unknown_children(&boxes, MOOV, &[MVHD, TRAK, MVEX, UDTA], warnings);
let mvhd = one(&boxes, MVHD, MOOV, true)?.ok_or(Error::MissingBox {
kind: MVHD,
parent: Some(MOOV),
})?;
let (movie_timescale, movie_duration) = parse_mvhd(mvhd)?;
if movie_timescale == 0 {
return Err(Error::InvalidTimescale { track: None });
}
let mut tracks = Vec::new();
let mut samples = Vec::new();
for (_, trak) in boxes.iter().filter(|(header, _)| header.kind == TRAK) {
let (track, track_samples) = parse_track(trak, absolute_base, limits, warnings)?;
if tracks
.iter()
.any(|existing: &Track| existing.id == track.id)
{
return Err(Error::Malformed {
kind: TKHD,
reason: "duplicate track id",
});
}
tracks.push(track);
samples.push(track_samples);
}
let mut trex = BTreeMap::new();
let mvex_box = one(&boxes, MVEX, MOOV, false)?;
let mvex_present = mvex_box.is_some();
if let Some(mvex) = mvex_box {
for (_, body) in children(mvex, MVEX, absolute_base)?
.iter()
.filter(|(header, _)| header.kind == TREX)
{
let (id, value) = parse_trex(body)?;
if trex.insert(id, value).is_some() {
return Err(Error::DuplicateBox {
kind: TREX,
parent: MVEX,
});
}
}
}
let metadata = parse_metadata(&boxes, absolute_base)?;
Ok(ParsedMovie {
movie_timescale,
movie_duration,
tracks,
samples,
trex,
mvex_present,
metadata,
})
}
fn parse_mvhd(body: &[u8]) -> Result<(u32, u64), Error> {
let mut cursor = Cursor::new(body, MVHD);
let (version, _) = cursor.full_box()?;
match version {
0 => {
cursor.skip(8)?;
Ok((cursor.u32()?, u64::from(cursor.u32()?)))
}
1 => {
cursor.skip(16)?;
Ok((cursor.u32()?, cursor.u64()?))
}
_ => Err(Error::Malformed {
kind: MVHD,
reason: "unsupported version",
}),
}
}
fn parse_track(
body: &[u8],
absolute_base: u64,
limits: Limits,
warnings: &mut Vec<Warning>,
) -> Result<(Track, Vec<SampleInfo>), Error> {
let boxes = children(body, TRAK, absolute_base)?;
warn_unknown_children(&boxes, TRAK, &[TKHD, EDTS, MDIA, UDTA], warnings);
let tkhd_body = one(&boxes, TKHD, TRAK, true)?.ok_or(Error::MissingBox {
kind: TKHD,
parent: Some(TRAK),
})?;
let tkhd = parse_tkhd(tkhd_body)?;
let mdia = one(&boxes, MDIA, TRAK, true)?.ok_or(Error::MissingBox {
kind: MDIA,
parent: Some(TRAK),
})?;
let mdia_boxes = children(mdia, MDIA, absolute_base)?;
warn_unknown_children(&mdia_boxes, MDIA, &[MDHD, HDLR, MINF], warnings);
let mdhd_body = one(&mdia_boxes, MDHD, MDIA, true)?.ok_or(Error::MissingBox {
kind: MDHD,
parent: Some(MDIA),
})?;
let mdhd = parse_mdhd(mdhd_body, tkhd.id)?;
let hdlr_body = one(&mdia_boxes, HDLR, MDIA, true)?.ok_or(Error::MissingBox {
kind: HDLR,
parent: Some(MDIA),
})?;
let (handler, handler_name) = parse_hdlr(hdlr_body)?;
let minf = one(&mdia_boxes, MINF, MDIA, true)?.ok_or(Error::MissingBox {
kind: MINF,
parent: Some(MDIA),
})?;
let minf_boxes = children(minf, MINF, absolute_base)?;
warn_unknown_children(
&minf_boxes,
MINF,
&[
FourCc(*b"vmhd"),
FourCc(*b"smhd"),
FourCc(*b"nmhd"),
FourCc(*b"sthd"),
FourCc(*b"hmhd"),
FourCc(*b"gmhd"),
DINF,
STBL,
],
warnings,
);
let stbl = one(&minf_boxes, STBL, MINF, true)?.ok_or(Error::MissingBox {
kind: STBL,
parent: Some(MINF),
})?;
let edit_list = if let Some(edts) = one(&boxes, EDTS, TRAK, false)? {
let edts_boxes = children(edts, EDTS, absolute_base)?;
warn_unknown_children(&edts_boxes, EDTS, &[ELST], warnings);
match one(&edts_boxes, ELST, EDTS, false)? {
Some(elst) => parse_elst(elst)?,
None => Vec::new(),
}
} else {
Vec::new()
};
let parsed = parse_sample_table(
stbl,
absolute_base,
tkhd.id,
mdhd.timescale,
limits,
warnings,
)?;
let kind = if handler == FourCc(*b"vide") {
TrackKind::Video
} else if handler == FourCc(*b"soun") {
TrackKind::Audio
} else if handler == FourCc(*b"subt") {
TrackKind::Subtitle
} else {
TrackKind::Other(handler)
};
let duration = parsed.samples.iter().try_fold(0u64, |sum, sample| {
sum.checked_add(u64::from(sample.duration))
.ok_or(Error::InvalidSampleTable {
track: tkhd.id,
reason: "sample duration sum overflow",
})
})?;
Ok((
Track {
id: tkhd.id,
kind,
handler_name,
timescale: mdhd.timescale,
duration,
language: mdhd.language,
enabled: tkhd.enabled,
width: tkhd.width,
height: tkhd.height,
codec: parsed.codec,
edit_list,
sample_count: u32::try_from(parsed.samples.len()).map_err(|_| {
Error::InvalidSampleTable {
track: tkhd.id,
reason: "sample count exceeds u32",
}
})?,
chunk_offsets: parsed.chunk_kind,
sync_sample_count: parsed.sync_count,
},
parsed.samples,
))
}
struct TkhdFields {
id: TrackId,
enabled: bool,
width: f64,
height: f64,
}
fn parse_tkhd(body: &[u8]) -> Result<TkhdFields, Error> {
let mut cursor = Cursor::new(body, TKHD);
let (version, flags) = cursor.full_box()?;
let id = match version {
0 => {
cursor.skip(8)?;
let id = cursor.u32()?;
cursor.skip(4)?;
cursor.skip(4)?;
id
}
1 => {
cursor.skip(16)?;
let id = cursor.u32()?;
cursor.skip(4)?;
cursor.skip(8)?;
id
}
_ => {
return Err(Error::Malformed {
kind: TKHD,
reason: "unsupported version",
})
}
};
cursor.skip(8 + 2 + 2 + 2 + 2 + 36)?;
let width = f64::from(cursor.u32()?) / 65_536.0;
let height = f64::from(cursor.u32()?) / 65_536.0;
Ok(TkhdFields {
id: TrackId(id),
enabled: flags & 1 != 0,
width,
height,
})
}
struct MdhdFields {
timescale: u32,
language: String,
}
fn parse_mdhd(body: &[u8], track: TrackId) -> Result<MdhdFields, Error> {
let mut cursor = Cursor::new(body, MDHD);
let (version, _) = cursor.full_box()?;
let timescale = match version {
0 => {
cursor.skip(8)?;
let value = cursor.u32()?;
cursor.skip(4)?;
value
}
1 => {
cursor.skip(16)?;
let value = cursor.u32()?;
cursor.skip(8)?;
value
}
_ => {
return Err(Error::Malformed {
kind: MDHD,
reason: "unsupported version",
})
}
};
if timescale == 0 {
return Err(Error::InvalidTimescale { track: Some(track) });
}
let packed = cursor.u16()?;
let mut language = String::with_capacity(3);
for shift in [10u16, 5, 0] {
let code = u8::try_from((packed >> shift) & 0x1f).unwrap_or_default();
language.push(char::from(code.saturating_add(0x60)));
}
Ok(MdhdFields {
timescale,
language,
})
}
fn parse_hdlr(body: &[u8]) -> Result<(FourCc, String), Error> {
let mut cursor = Cursor::new(body, HDLR);
cursor.full_box()?;
cursor.skip(4)?;
let handler = cursor.fourcc()?;
cursor.skip(12)?;
let raw = cursor.rest();
let name_bytes = raw.strip_suffix(&[0]).unwrap_or(raw);
let name = String::from_utf8(name_bytes.to_vec()).map_err(|_| Error::Malformed {
kind: HDLR,
reason: "handler name is not UTF-8",
})?;
Ok((handler, name))
}
fn parse_elst(body: &[u8]) -> Result<Vec<Edit>, Error> {
let mut cursor = Cursor::new(body, ELST);
let (version, _) = cursor.full_box()?;
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind: ELST,
reason: "entry count is too large",
})?;
let width = if version == 1 {
20usize
} else if version == 0 {
12usize
} else {
return Err(Error::Malformed {
kind: ELST,
reason: "unsupported version",
});
};
if count > cursor.remaining().checked_div(width).unwrap_or_default() {
return Err(Error::Malformed {
kind: ELST,
reason: "entry count exceeds box body",
});
}
let mut edits = Vec::with_capacity(count);
for _ in 0..count {
let (segment_duration, media_time) = if version == 1 {
(cursor.u64()?, cursor.i64()?)
} else {
(u64::from(cursor.u32()?), i64::from(cursor.i32()?))
};
let rate_integer = cursor.i16()?;
let rate_fraction = cursor.u16()?;
let media_rate = f32::from(rate_integer) + f32::from(rate_fraction) / 65_536.0;
let media_time = if media_time == -1 {
None
} else {
Some(u64::try_from(media_time).map_err(|_| Error::Malformed {
kind: ELST,
reason: "negative media time",
})?)
};
edits.push(Edit {
segment_duration,
media_time,
media_rate,
});
}
Ok(edits)
}
struct ParsedTable {
codec: Codec,
samples: Vec<SampleInfo>,
chunk_kind: ChunkOffsetKind,
sync_count: Option<u32>,
}
#[derive(Clone, Copy)]
struct StscEntry {
first_chunk: u32,
samples_per_chunk: u32,
description_index: u32,
}
fn parse_sample_table(
body: &[u8],
absolute_base: u64,
track: TrackId,
timescale: u32,
limits: Limits,
warnings: &mut Vec<Warning>,
) -> Result<ParsedTable, Error> {
let boxes = children(body, STBL, absolute_base)?;
warn_unknown_children(
&boxes,
STBL,
&[STSD, STTS, CTTS, STSS, STSC, STSZ, STZ2, STCO, CO64],
warnings,
);
if boxes.iter().any(|(header, _)| header.kind == STZ2) {
return Err(Error::Unsupported("compact sample sizes"));
}
let stsd = one(&boxes, STSD, STBL, true)?.ok_or(Error::MissingBox {
kind: STSD,
parent: Some(STBL),
})?;
let codecs = parse_stsd(stsd, track, timescale)?;
if codecs.is_empty() {
return Err(Error::InvalidSampleTable {
track,
reason: "sample description table is empty",
});
}
let codec = codecs.first().cloned().ok_or(Error::InvalidSampleTable {
track,
reason: "sample description table is empty",
})?;
let stts = one(&boxes, STTS, STBL, true)?.ok_or(Error::MissingBox {
kind: STTS,
parent: Some(STBL),
})?;
let timings = parse_stts(stts)?;
let sizes = parse_stsz(
one(&boxes, STSZ, STBL, true)?.ok_or(Error::MissingBox {
kind: STSZ,
parent: Some(STBL),
})?,
limits,
)?;
let ctts = match one(&boxes, CTTS, STBL, false)? {
Some(value) => Some(parse_ctts(value)?),
None => None,
};
let stsc = parse_stsc(one(&boxes, STSC, STBL, true)?.ok_or(Error::MissingBox {
kind: STSC,
parent: Some(STBL),
})?)?;
let stco = one(&boxes, STCO, STBL, false)?;
let co64 = one(&boxes, CO64, STBL, false)?;
if stco.is_some() && co64.is_some() {
return Err(Error::InvalidSampleTable {
track,
reason: "both stco and co64 are present",
});
}
let (offsets, mut chunk_kind) = match (stco, co64) {
(Some(value), None) => (parse_offsets(value, STCO)?, ChunkOffsetKind::Stco),
(None, Some(value)) => (parse_offsets(value, CO64)?, ChunkOffsetKind::Co64),
(None, None) if sizes.is_empty() => (Vec::new(), ChunkOffsetKind::None),
_ => {
return Err(Error::MissingBox {
kind: STCO,
parent: Some(STBL),
})
}
};
if sizes.is_empty() && offsets.is_empty() {
chunk_kind = ChunkOffsetKind::None;
}
let sync = match one(&boxes, STSS, STBL, false)? {
Some(value) => Some(parse_stss(value, sizes.len(), track)?),
None => None,
};
if u64::try_from(sizes.len()).unwrap_or(u64::MAX) > u64::from(limits.max_sample_count) {
return Err(Error::LimitExceeded {
limit: "max_sample_count",
value: u64::try_from(sizes.len()).unwrap_or(u64::MAX),
max: u64::from(limits.max_sample_count),
});
}
let samples = expand_samples(
track,
&sizes,
&timings,
ctts.as_deref(),
&stsc,
&offsets,
sync.as_deref(),
codecs.len(),
)?;
Ok(ParsedTable {
codec,
samples,
chunk_kind,
sync_count: sync
.as_ref()
.map(|items| u32::try_from(items.len()).unwrap_or(u32::MAX)),
})
}
fn parse_stsd(body: &[u8], track: TrackId, timescale: u32) -> Result<Vec<Codec>, Error> {
let mut cursor = Cursor::new(body, STSD);
let (version, _) = cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind: STSD,
reason: "unsupported version",
});
}
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind: STSD,
reason: "entry count is too large",
})?;
if count > cursor.remaining() / 8 {
return Err(Error::Malformed {
kind: STSD,
reason: "entry count exceeds box body",
});
}
let mut codecs = Vec::with_capacity(count);
for _ in 0..count {
let size = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind: STSD,
reason: "sample entry is too large",
})?;
let entry = cursor.fourcc()?;
if size < 8 {
return Err(Error::Malformed {
kind: entry,
reason: "sample entry size is smaller than its header",
});
}
let body_len = size.checked_sub(8).ok_or(Error::Malformed {
kind: entry,
reason: "sample entry size underflow",
})?;
let raw = cursor.take(body_len)?.to_vec();
codecs.push(parse_codec(entry, &raw, timescale)?);
}
if cursor.remaining() != 0 {
return Err(Error::InvalidSampleTable {
track,
reason: "trailing bytes in sample description table",
});
}
Ok(codecs)
}
fn sidecar_boxes(boxes: &[(BoxHeader, &[u8])]) -> Vec<SidecarBox> {
boxes
.iter()
.filter(|(header, _)| {
[
FourCc(*b"pasp"),
FourCc(*b"btrt"),
FourCc(*b"fiel"),
FourCc(*b"colr"),
]
.contains(&header.kind)
})
.map(|(header, body)| SidecarBox {
kind: header.kind,
payload: body.to_vec(),
})
.collect()
}
fn parse_codec(entry: FourCc, raw: &[u8], timescale: u32) -> Result<Codec, Error> {
if entry == FourCc(*b"avc1") || entry == FourCc(*b"avc3") {
let children_bytes = raw.get(78..).ok_or(Error::Malformed {
kind: entry,
reason: "visual sample entry is truncated",
})?;
let boxes = children(children_bytes, entry, 0)?;
let payload = one(&boxes, FourCc(*b"avcC"), entry, true)?.ok_or(Error::MissingBox {
kind: FourCc(*b"avcC"),
parent: Some(entry),
})?;
let mut config = parse_avcc(payload)?;
config.sidecars = sidecar_boxes(&boxes);
Ok(Codec::H264 { entry, config })
} else if entry == FourCc(*b"hvc1") || entry == FourCc(*b"hev1") {
let children_bytes = raw.get(78..).ok_or(Error::Malformed {
kind: entry,
reason: "visual sample entry is truncated",
})?;
let boxes = children(children_bytes, entry, 0)?;
let payload = one(&boxes, FourCc(*b"hvcC"), entry, true)?.ok_or(Error::MissingBox {
kind: FourCc(*b"hvcC"),
parent: Some(entry),
})?;
let mut config = parse_hvcc(payload)?;
config.sidecars = sidecar_boxes(&boxes);
Ok(Codec::H265 { entry, config })
} else if entry == FourCc(*b"mp4a") {
let version = raw
.get(8..10)
.and_then(|value| value.try_into().ok())
.map(u16::from_be_bytes)
.ok_or(Error::Malformed {
kind: entry,
reason: "audio sample entry is truncated",
})?;
let fixed = match version {
0 => 28usize,
1 => 44usize,
_ => return Err(Error::Unsupported("QuickTime audio sample entry version")),
};
let channels = raw
.get(16..18)
.and_then(|value| value.try_into().ok())
.map(u16::from_be_bytes)
.ok_or(Error::Malformed {
kind: entry,
reason: "audio channel count is missing",
})?;
let sample_rate_fixed = raw
.get(24..28)
.and_then(|value| value.try_into().ok())
.map(u32::from_be_bytes)
.ok_or(Error::Malformed {
kind: entry,
reason: "audio sample rate is missing",
})?;
let child_bytes = raw.get(fixed..).ok_or(Error::Malformed {
kind: entry,
reason: "audio sample entry is truncated",
})?;
let boxes = children(child_bytes, entry, 0)?;
let esds = one(&boxes, FourCc(*b"esds"), entry, true)?.ok_or(Error::MissingBox {
kind: FourCc(*b"esds"),
parent: Some(entry),
})?;
let (object_type, audio_specific_config) = parse_esds(esds)?;
let sample_rate = if sample_rate_fixed >> 16 == 0 {
timescale
} else {
sample_rate_fixed >> 16
};
Ok(Codec::Aac {
config: AacConfig {
raw_esds: esds.to_vec(),
object_type,
audio_specific_config,
channels,
sample_rate,
sidecars: sidecar_boxes(&boxes),
},
})
} else if entry == FourCc(*b"Opus") {
let child_bytes = raw.get(28..).ok_or(Error::Malformed {
kind: entry,
reason: "audio sample entry is truncated",
})?;
let boxes = children(child_bytes, entry, 0)?;
let dops = one(&boxes, FourCc(*b"dOps"), entry, true)?.ok_or(Error::MissingBox {
kind: FourCc(*b"dOps"),
parent: Some(entry),
})?;
let mut cursor = Cursor::new(dops, FourCc(*b"dOps"));
let version = cursor.u8()?;
if version != 0 {
return Err(Error::Malformed {
kind: FourCc(*b"dOps"),
reason: "unsupported version",
});
}
let channels = cursor.u8()?;
let pre_skip = cursor.u16()?;
let input_sample_rate = cursor.u32()?;
let output_gain = cursor.i16()?;
let mapping_family = cursor.u8()?;
Ok(Codec::Opus {
config: OpusConfig {
raw: dops.to_vec(),
channels,
pre_skip,
input_sample_rate,
output_gain,
mapping_family,
sidecars: sidecar_boxes(&boxes),
},
})
} else {
Ok(Codec::Other {
entry,
raw: raw.to_vec(),
})
}
}
fn parse_avcc(data: &[u8]) -> Result<AvcConfig, Error> {
let mut cursor = Cursor::new(data, FourCc(*b"avcC"));
let version = cursor.u8()?;
if version != 1 {
return Err(Error::Malformed {
kind: FourCc(*b"avcC"),
reason: "unsupported version",
});
}
let profile = cursor.u8()?;
let profile_compat = cursor.u8()?;
let level = cursor.u8()?;
let nal_length_size = (cursor.u8()? & 3).saturating_add(1);
let sps_count = usize::from(cursor.u8()? & 0x1f);
let mut sps = Vec::with_capacity(sps_count);
for _ in 0..sps_count {
let len = usize::from(cursor.u16()?);
sps.push(cursor.take(len)?.to_vec());
}
let pps_count = usize::from(cursor.u8()?);
let mut pps = Vec::with_capacity(pps_count);
for _ in 0..pps_count {
let len = usize::from(cursor.u16()?);
pps.push(cursor.take(len)?.to_vec());
}
Ok(AvcConfig {
raw: data.to_vec(),
profile,
profile_compat,
level,
nal_length_size,
sps,
pps,
sidecars: Vec::new(),
})
}
fn parse_hvcc(data: &[u8]) -> Result<HevcConfig, Error> {
let mut cursor = Cursor::new(data, FourCc(*b"hvcC"));
if cursor.u8()? != 1 {
return Err(Error::Malformed {
kind: FourCc(*b"hvcC"),
reason: "unsupported version",
});
}
cursor.skip(20)?;
let nal_length_size = (cursor.u8()? & 3).saturating_add(1);
let array_count = usize::from(cursor.u8()?);
let mut arrays = Vec::with_capacity(array_count);
for _ in 0..array_count {
let nal_type = cursor.u8()? & 0x3f;
let count = usize::from(cursor.u16()?);
if count > cursor.remaining() / 2 {
return Err(Error::Malformed {
kind: FourCc(*b"hvcC"),
reason: "NAL count exceeds configuration payload",
});
}
let mut nals = Vec::with_capacity(count);
for _ in 0..count {
let len = usize::from(cursor.u16()?);
nals.push(cursor.take(len)?.to_vec());
}
arrays.push(NalArray { nal_type, nals });
}
Ok(HevcConfig {
raw: data.to_vec(),
nal_length_size,
arrays,
sidecars: Vec::new(),
})
}
fn descriptor_len(data: &[u8], start: usize) -> Option<(usize, usize)> {
let mut value = 0usize;
let mut used = 0usize;
for byte in data.get(start..)?.iter().take(4) {
value = value
.checked_shl(7)?
.checked_add(usize::from(byte & 0x7f))?;
used = used.checked_add(1)?;
if byte & 0x80 == 0 {
return Some((value, used));
}
}
None
}
fn read_descriptor(data: &[u8], at: usize) -> Result<(u8, &[u8], usize), Error> {
let malformed = |reason: &'static str| Error::Malformed {
kind: FourCc(*b"esds"),
reason,
};
let tag = *data.get(at).ok_or(malformed("descriptor tag is missing"))?;
let len_at = at
.checked_add(1)
.ok_or(malformed("descriptor offset overflow"))?;
let (len, width) =
descriptor_len(data, len_at).ok_or(malformed("descriptor length is invalid"))?;
let body_at = len_at
.checked_add(width)
.ok_or(malformed("descriptor offset overflow"))?;
let end = body_at
.checked_add(len)
.ok_or(malformed("descriptor size overflow"))?;
let body = data
.get(body_at..end)
.ok_or(malformed("descriptor exceeds its container"))?;
Ok((tag, body, end))
}
fn parse_esds(data: &[u8]) -> Result<(u8, Vec<u8>), Error> {
let malformed = |reason: &'static str| Error::Malformed {
kind: FourCc(*b"esds"),
reason,
};
let payload = data.get(4..).ok_or(malformed("missing full-box fields"))?;
let (es_tag, es_body, _) = read_descriptor(payload, 0)?;
if es_tag != 0x03 {
return Err(malformed("elementary stream descriptor is missing"));
}
let mut header = Cursor::new(es_body, FourCc(*b"esds"));
header.skip(2)?;
let stream_flags = header.u8()?;
if stream_flags & 0x80 != 0 {
header.skip(2)?;
}
if stream_flags & 0x40 != 0 {
let url_length = usize::from(header.u8()?);
header.skip(url_length)?;
}
if stream_flags & 0x20 != 0 {
header.skip(2)?;
}
let mut at = header.position();
let mut decoder_config = None;
while at < es_body.len() {
let (tag, body, next) = read_descriptor(es_body, at)?;
if tag == 0x04 {
decoder_config = Some(body);
break;
}
at = next;
}
let decoder_config =
decoder_config.ok_or(malformed("decoder configuration descriptor is missing"))?;
let object_type = *decoder_config
.first()
.ok_or(malformed("decoder configuration descriptor is empty"))?;
let mut at = 13usize;
let mut specific_info = None;
while at < decoder_config.len() {
let (tag, body, next) = read_descriptor(decoder_config, at)?;
if tag == 0x05 {
specific_info = Some(body.to_vec());
break;
}
at = next;
}
Ok((
object_type,
specific_info.ok_or(malformed("audio specific configuration is missing"))?,
))
}
fn parse_counted_pairs(body: &[u8], kind: FourCc, signed: bool) -> Result<Vec<(u32, i64)>, Error> {
let mut cursor = Cursor::new(body, kind);
let (version, _) = cursor.full_box()?;
if (!signed && version != 0) || (signed && version > 1) {
return Err(Error::Malformed {
kind,
reason: "unsupported version",
});
}
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind,
reason: "entry count is too large",
})?;
if count > cursor.remaining() / 8 {
return Err(Error::Malformed {
kind,
reason: "entry count exceeds box body",
});
}
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
let sample_count = cursor.u32()?;
let value = if signed && version == 1 {
i64::from(cursor.i32()?)
} else {
i64::from(cursor.u32()?)
};
entries.push((sample_count, value));
}
Ok(entries)
}
fn parse_stts(body: &[u8]) -> Result<Vec<(u32, u32)>, Error> {
parse_counted_pairs(body, STTS, false)?
.into_iter()
.map(|(count, delta)| {
Ok((
count,
u32::try_from(delta).map_err(|_| Error::Malformed {
kind: STTS,
reason: "sample delta exceeds u32",
})?,
))
})
.collect()
}
fn parse_ctts(body: &[u8]) -> Result<Vec<(u32, i64)>, Error> {
parse_counted_pairs(body, CTTS, true)
}
fn parse_stsz(body: &[u8], limits: Limits) -> Result<Vec<u32>, Error> {
let mut cursor = Cursor::new(body, STSZ);
let (version, _) = cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind: STSZ,
reason: "unsupported version",
});
}
let fixed = cursor.u32()?;
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind: STSZ,
reason: "sample count is too large",
})?;
if fixed == 0 {
if count > cursor.remaining() / 4 {
return Err(Error::Malformed {
kind: STSZ,
reason: "sample count exceeds box body",
});
}
check_sample_count(count, limits)?;
let mut sizes = Vec::with_capacity(count);
for _ in 0..count {
sizes.push(cursor.u32()?);
}
Ok(sizes)
} else {
check_sample_count(count, limits)?;
Ok(vec![fixed; count])
}
}
fn check_sample_count(count: usize, limits: Limits) -> Result<(), Error> {
if u64::try_from(count).unwrap_or(u64::MAX) > u64::from(limits.max_sample_count) {
return Err(Error::LimitExceeded {
limit: "max_sample_count",
value: u64::try_from(count).unwrap_or(u64::MAX),
max: u64::from(limits.max_sample_count),
});
}
Ok(())
}
fn parse_stsc(body: &[u8]) -> Result<Vec<StscEntry>, Error> {
let mut cursor = Cursor::new(body, STSC);
let (version, _) = cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind: STSC,
reason: "unsupported version",
});
}
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind: STSC,
reason: "entry count is too large",
})?;
if count > cursor.remaining() / 12 {
return Err(Error::Malformed {
kind: STSC,
reason: "entry count exceeds box body",
});
}
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
entries.push(StscEntry {
first_chunk: cursor.u32()?,
samples_per_chunk: cursor.u32()?,
description_index: cursor.u32()?,
});
}
Ok(entries)
}
fn parse_offsets(body: &[u8], kind: FourCc) -> Result<Vec<u64>, Error> {
let mut cursor = Cursor::new(body, kind);
let (version, _) = cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind,
reason: "unsupported version",
});
}
let width = if kind == STCO { 4usize } else { 8usize };
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind,
reason: "entry count is too large",
})?;
if count > cursor.remaining().checked_div(width).unwrap_or_default() {
return Err(Error::Malformed {
kind,
reason: "entry count exceeds box body",
});
}
let mut values = Vec::with_capacity(count);
for _ in 0..count {
values.push(if kind == STCO {
u64::from(cursor.u32()?)
} else {
cursor.u64()?
});
}
Ok(values)
}
fn parse_stss(body: &[u8], sample_count: usize, track: TrackId) -> Result<Vec<u32>, Error> {
let mut cursor = Cursor::new(body, STSS);
let (version, _) = cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind: STSS,
reason: "unsupported version",
});
}
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind: STSS,
reason: "entry count is too large",
})?;
if count > cursor.remaining() / 4 {
return Err(Error::Malformed {
kind: STSS,
reason: "entry count exceeds box body",
});
}
let max = u32::try_from(sample_count).unwrap_or(u32::MAX);
let mut previous = 0u32;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
let value = cursor.u32()?;
if value == 0 || value > max || value <= previous {
return Err(Error::InvalidSampleTable {
track,
reason: "sync samples are not ascending within range",
});
}
previous = value;
values.push(value);
}
Ok(values)
}
#[allow(clippy::too_many_arguments)]
fn expand_samples(
track: TrackId,
sizes: &[u32],
timings: &[(u32, u32)],
compositions: Option<&[(u32, i64)]>,
mappings: &[StscEntry],
chunks: &[u64],
sync: Option<&[u32]>,
description_count: usize,
) -> Result<Vec<SampleInfo>, Error> {
let sample_count = sizes.len();
let timing_count = timings.iter().try_fold(0u64, |sum, (count, _)| {
sum.checked_add(u64::from(*count))
.ok_or(Error::InvalidSampleTable {
track,
reason: "time table count overflow",
})
})?;
if timing_count != u64::try_from(sample_count).unwrap_or(u64::MAX) {
return Err(Error::InvalidSampleTable {
track,
reason: "time table count differs from sample count",
});
}
if let Some(values) = compositions {
let count = values.iter().try_fold(0u64, |sum, (entry_count, _)| {
sum.checked_add(u64::from(*entry_count))
.ok_or(Error::InvalidSampleTable {
track,
reason: "composition table count overflow",
})
})?;
if count != u64::try_from(sample_count).unwrap_or(u64::MAX) {
return Err(Error::InvalidSampleTable {
track,
reason: "composition table count differs from sample count",
});
}
}
if sample_count == 0 && chunks.is_empty() {
return Ok(Vec::new());
}
if mappings.is_empty() || mappings.first().map(|entry| entry.first_chunk) != Some(1) {
return Err(Error::InvalidSampleTable {
track,
reason: "sample-to-chunk table must start at chunk one",
});
}
for pair in mappings.windows(2) {
let first = pair.first().copied().ok_or(Error::InvalidSampleTable {
track,
reason: "sample-to-chunk entry is missing",
})?;
let second = pair.get(1).copied().ok_or(Error::InvalidSampleTable {
track,
reason: "sample-to-chunk entry is missing",
})?;
if second.first_chunk <= first.first_chunk {
return Err(Error::InvalidSampleTable {
track,
reason: "sample-to-chunk starts are not strictly increasing",
});
}
}
let mut durations = Vec::with_capacity(sample_count);
let mut dts_values = Vec::with_capacity(sample_count);
let mut dts = 0u64;
for (count, delta) in timings {
for _ in 0..*count {
dts_values.push(dts);
durations.push(*delta);
dts = dts
.checked_add(u64::from(*delta))
.ok_or(Error::InvalidSampleTable {
track,
reason: "decode time overflow",
})?;
}
}
let mut cts_values = Vec::with_capacity(sample_count);
if let Some(values) = compositions {
for (count, offset) in values {
for _ in 0..*count {
cts_values.push(*offset);
}
}
} else {
cts_values.resize(sample_count, 0);
}
let mut samples = Vec::with_capacity(sample_count);
let mut sample_index = 0usize;
let mut mapping_index = 0usize;
for (chunk_index, chunk_offset) in chunks.iter().copied().enumerate() {
let chunk_number = u32::try_from(chunk_index)
.unwrap_or(u32::MAX)
.saturating_add(1);
while let Some(next) = mappings.get(mapping_index.saturating_add(1)) {
if next.first_chunk > chunk_number {
break;
}
mapping_index = mapping_index.saturating_add(1);
}
let mapping = mappings
.get(mapping_index)
.copied()
.ok_or(Error::InvalidSampleTable {
track,
reason: "sample-to-chunk mapping is missing",
})?;
if mapping.samples_per_chunk == 0 {
return Err(Error::InvalidSampleTable {
track,
reason: "samples per chunk is zero",
});
}
if mapping.description_index == 0
|| usize::try_from(mapping.description_index).unwrap_or(usize::MAX) > description_count
{
return Err(Error::InvalidSampleTable {
track,
reason: "sample description index is out of range",
});
}
let mut within = 0u64;
for _ in 0..mapping.samples_per_chunk {
let size = sizes
.get(sample_index)
.copied()
.ok_or(Error::InvalidSampleTable {
track,
reason: "chunk mapping produces too many samples",
})?;
let offset = chunk_offset
.checked_add(within)
.ok_or(Error::InvalidSampleTable {
track,
reason: "sample offset overflow",
})?;
within = within
.checked_add(u64::from(size))
.ok_or(Error::InvalidSampleTable {
track,
reason: "sample offset overflow",
})?;
let dts = dts_values
.get(sample_index)
.copied()
.ok_or(Error::InvalidSampleTable {
track,
reason: "decode time is missing",
})?;
let cts_offset =
cts_values
.get(sample_index)
.copied()
.ok_or(Error::InvalidSampleTable {
track,
reason: "composition offset is missing",
})?;
let pts = i64::try_from(dts)
.ok()
.and_then(|value| value.checked_add(cts_offset))
.ok_or(Error::InvalidSampleTable {
track,
reason: "presentation time overflow",
})?;
let number = u32::try_from(sample_index)
.unwrap_or(u32::MAX)
.saturating_add(1);
let is_sync = sync
.map(|values| values.binary_search(&number).is_ok())
.unwrap_or(true);
samples.push(SampleInfo {
index: u32::try_from(sample_index).map_err(|_| Error::InvalidSampleTable {
track,
reason: "sample index exceeds u32",
})?,
offset,
size,
dts,
cts_offset,
pts,
duration: durations.get(sample_index).copied().ok_or(
Error::InvalidSampleTable {
track,
reason: "sample duration is missing",
},
)?,
is_sync,
chunk: u32::try_from(chunk_index).map_err(|_| Error::InvalidSampleTable {
track,
reason: "chunk index exceeds u32",
})?,
description_index: mapping.description_index,
});
sample_index = sample_index
.checked_add(1)
.ok_or(Error::InvalidSampleTable {
track,
reason: "sample index overflow",
})?;
}
}
if sample_index != sample_count {
return Err(Error::InvalidSampleTable {
track,
reason: "chunk mapping does not produce the sample count",
});
}
Ok(samples)
}
fn parse_trex(body: &[u8]) -> Result<(TrackId, Trex), Error> {
let mut cursor = Cursor::new(body, TREX);
let (version, _) = cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind: TREX,
reason: "unsupported version",
});
}
let track = TrackId(cursor.u32()?);
Ok((
track,
Trex {
description_index: cursor.u32()?,
duration: cursor.u32()?,
size: cursor.u32()?,
flags: cursor.u32()?,
},
))
}
#[derive(Clone, Copy)]
struct Tfhd {
track: TrackId,
base_data_offset: Option<u64>,
description_index: Option<u32>,
duration: Option<u32>,
size: Option<u32>,
flags: Option<u32>,
default_base_is_moof: bool,
}
fn parse_tfhd(body: &[u8]) -> Result<Tfhd, Error> {
let kind = FourCc(*b"tfhd");
let mut cursor = Cursor::new(body, kind);
let (version, flags) = cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind,
reason: "unsupported version",
});
}
let track = TrackId(cursor.u32()?);
Ok(Tfhd {
track,
base_data_offset: if flags & 0x1 != 0 {
Some(cursor.u64()?)
} else {
None
},
description_index: if flags & 0x2 != 0 {
Some(cursor.u32()?)
} else {
None
},
duration: if flags & 0x8 != 0 {
Some(cursor.u32()?)
} else {
None
},
size: if flags & 0x10 != 0 {
Some(cursor.u32()?)
} else {
None
},
flags: if flags & 0x20 != 0 {
Some(cursor.u32()?)
} else {
None
},
default_base_is_moof: flags & 0x020000 != 0,
})
}
#[allow(clippy::too_many_arguments)]
fn parse_fragment(
body: &[u8],
absolute_base: u64,
moof_offset: u64,
fragment_index: u32,
defaults: &BTreeMap<TrackId, Trex>,
limits: Limits,
running_dts: &mut BTreeMap<TrackId, u64>,
warnings: &mut Vec<Warning>,
) -> Result<Fragment, Error> {
let boxes = children(body, MOOF, absolute_base)?;
warn_unknown_children(
&boxes,
MOOF,
&[FourCc(*b"mfhd"), FourCc(*b"traf")],
warnings,
);
let mfhd_kind = FourCc(*b"mfhd");
let mfhd = one(&boxes, mfhd_kind, MOOF, true)?.ok_or(Error::MissingBox {
kind: mfhd_kind,
parent: Some(MOOF),
})?;
let mut mfhd_cursor = Cursor::new(mfhd, mfhd_kind);
let (version, _) = mfhd_cursor.full_box()?;
if version != 0 {
return Err(Error::Malformed {
kind: mfhd_kind,
reason: "unsupported version",
});
}
let sequence_number = mfhd_cursor.u32()?;
let traf_kind = FourCc(*b"traf");
let mut runs = Vec::new();
for (_, traf) in boxes.iter().filter(|(header, _)| header.kind == traf_kind) {
let traf_boxes = children(traf, traf_kind, absolute_base)?;
warn_unknown_children(
&traf_boxes,
traf_kind,
&[FourCc(*b"tfhd"), FourCc(*b"tfdt"), FourCc(*b"trun")],
warnings,
);
let tfhd_kind = FourCc(*b"tfhd");
let tfhd = parse_tfhd(one(&traf_boxes, tfhd_kind, traf_kind, true)?.ok_or(
Error::MissingBox {
kind: tfhd_kind,
parent: Some(traf_kind),
},
)?)?;
let trex = defaults.get(&tfhd.track).copied().ok_or(Error::Malformed {
kind: tfhd_kind,
reason: "track has no fragment defaults",
})?;
let base = if let Some(value) = tfhd.base_data_offset {
value
} else if tfhd.default_base_is_moof {
moof_offset
} else {
return Err(Error::Unsupported("implicit fragment base offsets"));
};
let tfdt_kind = FourCc(*b"tfdt");
let mut dts = if let Some(tfdt) = one(&traf_boxes, tfdt_kind, traf_kind, false)? {
let mut cursor = Cursor::new(tfdt, tfdt_kind);
let (version, _) = cursor.full_box()?;
match version {
0 => u64::from(cursor.u32()?),
1 => cursor.u64()?,
_ => {
return Err(Error::Malformed {
kind: tfdt_kind,
reason: "unsupported version",
})
}
}
} else {
running_dts.get(&tfhd.track).copied().unwrap_or(0)
};
let trun_kind = FourCc(*b"trun");
let mut prior_run_end: Option<u64> = None;
for (_, trun) in traf_boxes
.iter()
.filter(|(header, _)| header.kind == trun_kind)
{
let mut cursor = Cursor::new(trun, trun_kind);
let (version, flags) = cursor.full_box()?;
if version > 1 {
return Err(Error::Malformed {
kind: trun_kind,
reason: "unsupported version",
});
}
let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
kind: trun_kind,
reason: "sample count is too large",
})?;
let fields = ((flags & 0x100 != 0) as usize)
.saturating_add((flags & 0x200 != 0) as usize)
.saturating_add((flags & 0x400 != 0) as usize)
.saturating_add((flags & 0x800 != 0) as usize);
let minimum = count
.checked_mul(fields.saturating_mul(4))
.ok_or(Error::Malformed {
kind: trun_kind,
reason: "sample field size overflow",
})?;
if minimum > cursor.remaining() {
return Err(Error::Malformed {
kind: trun_kind,
reason: "sample count exceeds box body",
});
}
check_sample_count(count, limits)?;
let data_offset = if flags & 0x1 != 0 {
Some(cursor.i32()?)
} else {
None
};
let first_flags = if flags & 0x4 != 0 {
Some(cursor.u32()?)
} else {
None
};
let run_start = if let Some(offset) = data_offset {
if offset >= 0 {
base.checked_add(u64::try_from(offset).unwrap_or_default())
} else {
base.checked_sub(u64::from(offset.unsigned_abs()))
}
.ok_or(Error::Malformed {
kind: trun_kind,
reason: "data offset overflow",
})?
} else {
prior_run_end.unwrap_or(base)
};
let mut offset = run_start;
let mut samples = Vec::with_capacity(count);
for index in 0..count {
let duration = if flags & 0x100 != 0 {
cursor.u32()?
} else {
tfhd.duration.unwrap_or(trex.duration)
};
let size = if flags & 0x200 != 0 {
cursor.u32()?
} else {
tfhd.size.unwrap_or(trex.size)
};
let sample_flags = if flags & 0x400 != 0 {
cursor.u32()?
} else if index == 0 {
first_flags.unwrap_or(tfhd.flags.unwrap_or(trex.flags))
} else {
tfhd.flags.unwrap_or(trex.flags)
};
let cts_offset = if flags & 0x800 != 0 {
if version == 1 {
i64::from(cursor.i32()?)
} else {
i64::from(cursor.u32()?)
}
} else {
0
};
let pts = i64::try_from(dts)
.ok()
.and_then(|value| value.checked_add(cts_offset))
.ok_or(Error::Malformed {
kind: trun_kind,
reason: "presentation time overflow",
})?;
samples.push(SampleInfo {
index: u32::try_from(index).map_err(|_| Error::Malformed {
kind: trun_kind,
reason: "sample index exceeds u32",
})?,
offset,
size,
dts,
cts_offset,
pts,
duration,
is_sync: sample_flags & 0x1_0000 == 0,
chunk: fragment_index,
description_index: tfhd.description_index.unwrap_or(trex.description_index),
});
offset = offset
.checked_add(u64::from(size))
.ok_or(Error::Malformed {
kind: trun_kind,
reason: "sample offset overflow",
})?;
dts = dts
.checked_add(u64::from(duration))
.ok_or(Error::Malformed {
kind: trun_kind,
reason: "decode time overflow",
})?;
}
prior_run_end = Some(offset);
runs.push(TrackRun {
track: tfhd.track,
samples,
});
}
running_dts.insert(tfhd.track, dts);
}
Ok(Fragment {
sequence_number,
moof_offset,
runs,
})
}
fn take_c_string(data: &[u8], position: &mut usize, kind: FourCc) -> Result<String, Error> {
let tail = data.get(*position..).ok_or(Error::Malformed {
kind,
reason: "string offset is outside box",
})?;
let end = tail
.iter()
.position(|byte| *byte == 0)
.ok_or(Error::Malformed {
kind,
reason: "unterminated string",
})?;
let bytes = tail.get(..end).ok_or(Error::Malformed {
kind,
reason: "string is outside box",
})?;
*position = position
.checked_add(end.saturating_add(1))
.ok_or(Error::Malformed {
kind,
reason: "string offset overflow",
})?;
String::from_utf8(bytes.to_vec()).map_err(|_| Error::Malformed {
kind,
reason: "string is not UTF-8",
})
}
fn parse_event(body: &[u8]) -> Result<Event, Error> {
let mut cursor = Cursor::new(body, EMSG);
let (version, _) = cursor.full_box()?;
match version {
0 => {
let mut position = cursor.position();
let scheme_id_uri = take_c_string(body, &mut position, EMSG)?;
let value = take_c_string(body, &mut position, EMSG)?;
let mut rest = Cursor::new(
body.get(position..).ok_or(Error::Malformed {
kind: EMSG,
reason: "event fields are truncated",
})?,
EMSG,
);
let timescale = rest.u32()?;
let presentation_time = u64::from(rest.u32()?);
let event_duration = rest.u32()?;
let id = rest.u32()?;
Ok(Event {
scheme_id_uri,
value,
timescale,
presentation_time,
event_duration,
id,
message_data: rest.rest().to_vec(),
})
}
1 => {
let timescale = cursor.u32()?;
let presentation_time = cursor.u64()?;
let event_duration = cursor.u32()?;
let id = cursor.u32()?;
let mut position = cursor.position();
let scheme_id_uri = take_c_string(body, &mut position, EMSG)?;
let value = take_c_string(body, &mut position, EMSG)?;
let message_data = body.get(position..).unwrap_or_default().to_vec();
Ok(Event {
scheme_id_uri,
value,
timescale,
presentation_time,
event_duration,
id,
message_data,
})
}
_ => Err(Error::Malformed {
kind: EMSG,
reason: "unsupported version",
}),
}
}
fn parse_metadata(boxes: &[(BoxHeader, &[u8])], absolute_base: u64) -> Result<Metadata, Error> {
let udta_kind = FourCc(*b"udta");
let meta_kind = FourCc(*b"meta");
let ilst_kind = FourCc(*b"ilst");
let Some(udta) = one(boxes, udta_kind, MOOV, false)? else {
return Ok(Metadata::default());
};
let udta_boxes = children(udta, udta_kind, absolute_base)?;
let Some(meta) = one(&udta_boxes, meta_kind, udta_kind, false)? else {
return Ok(Metadata::default());
};
let nested = meta.get(4..).ok_or(Error::Malformed {
kind: meta_kind,
reason: "missing full-box fields",
})?;
let meta_boxes = children(nested, meta_kind, absolute_base)?;
let Some(ilst) = one(&meta_boxes, ilst_kind, meta_kind, false)? else {
return Ok(Metadata::default());
};
let mut metadata = Metadata::default();
for (item_header, item_body) in children(ilst, ilst_kind, absolute_base)? {
let item_boxes = children(item_body, item_header.kind, absolute_base)?;
let data_kind = FourCc(*b"data");
let Some(data) = one(&item_boxes, data_kind, item_header.kind, false)? else {
continue;
};
let payload = data.get(8..).ok_or(Error::Malformed {
kind: data_kind,
reason: "metadata value is truncated",
})?;
let key = match item_header.kind.0 {
[0xa9, b'n', b'a', b'm'] => MetaKey::Title,
[0xa9, b'A', b'R', b'T'] => MetaKey::Artist,
[0xa9, b'a', b'l', b'b'] => MetaKey::Album,
[b'a', b'A', b'R', b'T'] => MetaKey::AlbumArtist,
[0xa9, b'w', b'r', b't'] => MetaKey::Composer,
[0xa9, b'g', b'e', b'n'] => MetaKey::Genre,
[0xa9, b'c', b'm', b't'] => MetaKey::Comment,
[0xa9, b'd', b'a', b'y'] => MetaKey::Date,
_ => MetaKey::Other(item_header.kind),
};
let value = match String::from_utf8(payload.to_vec()) {
Ok(text) => MetaValue::Text(text),
Err(_) => MetaValue::Binary(payload.to_vec()),
};
metadata.entries.push((key, value));
}
Ok(metadata)
}