Skip to main content

mp4_track/
reader.rs

1use crate::parse::{
2    children, one, validate_tree, validate_typed_atom, BoxHeader, Cursor, FTYP, MDAT, MOOF, MOOV,
3};
4use crate::*;
5use std::collections::BTreeMap;
6use std::io::{Read, Seek, SeekFrom};
7use std::time::Duration;
8
9const MVHD: FourCc = FourCc(*b"mvhd");
10const TRAK: FourCc = FourCc(*b"trak");
11const TKHD: FourCc = FourCc(*b"tkhd");
12const MDIA: FourCc = FourCc(*b"mdia");
13const MDHD: FourCc = FourCc(*b"mdhd");
14const HDLR: FourCc = FourCc(*b"hdlr");
15const MINF: FourCc = FourCc(*b"minf");
16const STBL: FourCc = FourCc(*b"stbl");
17const STSD: FourCc = FourCc(*b"stsd");
18const STTS: FourCc = FourCc(*b"stts");
19const CTTS: FourCc = FourCc(*b"ctts");
20const STSS: FourCc = FourCc(*b"stss");
21const STSC: FourCc = FourCc(*b"stsc");
22const STSZ: FourCc = FourCc(*b"stsz");
23const STZ2: FourCc = FourCc(*b"stz2");
24const STCO: FourCc = FourCc(*b"stco");
25const CO64: FourCc = FourCc(*b"co64");
26const EDTS: FourCc = FourCc(*b"edts");
27const ELST: FourCc = FourCc(*b"elst");
28const MVEX: FourCc = FourCc(*b"mvex");
29const TREX: FourCc = FourCc(*b"trex");
30const EMSG: FourCc = FourCc(*b"emsg");
31const UDTA: FourCc = FourCc(*b"udta");
32const DINF: FourCc = FourCc(*b"dinf");
33
34/// A bounded reader over track-level MP4 data.
35pub struct Mp4Reader<R: Read + Seek> {
36    reader: R,
37    file_len: u64,
38    brands: Brands,
39    movie_timescale: u32,
40    movie_duration: u64,
41    tracks: Vec<Track>,
42    samples: Vec<Vec<SampleInfo>>,
43    fragments: Vec<Fragment>,
44    has_mvex: bool,
45    events: Vec<Event>,
46    metadata: Metadata,
47    warnings: Vec<Warning>,
48    limits: Limits,
49}
50
51impl<R: Read + Seek> Mp4Reader<R> {
52    /// Opens a stream with default resource limits.
53    pub fn open(reader: R) -> Result<Self, Error> {
54        Self::open_with(reader, Limits::default())
55    }
56
57    /// Opens a stream with caller-supplied resource limits.
58    pub fn open_with(mut reader: R, limits: Limits) -> Result<Self, Error> {
59        let file_len = reader.seek(SeekFrom::End(0))?;
60        let mut at = 0u64;
61        let mut brands = Brands::default();
62        let mut movie: Option<ParsedMovie> = None;
63        let mut moofs = Vec::new();
64        let mut events = Vec::new();
65        let mut warnings = Vec::new();
66
67        while at < file_len {
68            let remaining = file_len.saturating_sub(at);
69            if remaining < 8 {
70                if movie.is_some() {
71                    warnings.push(Warning::TrailingBytes {
72                        at,
73                        count: remaining,
74                    });
75                    break;
76                }
77                return Err(Error::Truncated { at, needed: 8 });
78            }
79            let step = read_top_header(&mut reader, at, file_len).and_then(|header| {
80                let end = header
81                    .start
82                    .checked_add(header.size)
83                    .ok_or(Error::BoxExceedsFile {
84                        kind: header.kind,
85                        at,
86                        declared_end: u64::MAX,
87                        file_len,
88                    })?;
89                Ok((header, end))
90            });
91            let (header, mut end) = match step {
92                Ok(value) => value,
93                Err(Error::Io(error)) => return Err(Error::Io(error)),
94                // Bytes after a complete movie box that do not form a
95                // readable top-level box are trailing data, not an error.
96                Err(_) if movie.is_some() => {
97                    warnings.push(Warning::TrailingBytes {
98                        at,
99                        count: remaining,
100                    });
101                    break;
102                }
103                Err(error) => return Err(error),
104            };
105            if end > file_len {
106                if header.kind == MDAT && movie.is_some() {
107                    end = file_len;
108                } else if movie.is_some() {
109                    warnings.push(Warning::TrailingBytes {
110                        at,
111                        count: remaining,
112                    });
113                    break;
114                } else {
115                    return Err(Error::BoxExceedsFile {
116                        kind: header.kind,
117                        at,
118                        declared_end: end,
119                        file_len,
120                    });
121                }
122            }
123            let body_len =
124                header
125                    .size
126                    .checked_sub(header.header_len)
127                    .ok_or(Error::BoxSizeTooSmall {
128                        kind: header.kind,
129                        size: header.size,
130                        at,
131                    })?;
132            match header.kind {
133                FTYP => {
134                    let body = read_body(&mut reader, &header, body_len, u64::from(u32::MAX))?;
135                    brands = parse_ftyp(&body)?;
136                    validate_typed_atom(FTYP, &body)?;
137                }
138                MOOV => {
139                    if movie.is_some() {
140                        return Err(Error::DuplicateBox {
141                            kind: MOOV,
142                            parent: FourCc(*b"root"),
143                        });
144                    }
145                    if header.size > limits.max_moov_bytes {
146                        return Err(Error::LimitExceeded {
147                            limit: "max_moov_bytes",
148                            value: header.size,
149                            max: limits.max_moov_bytes,
150                        });
151                    }
152                    let body = read_body(&mut reader, &header, body_len, limits.max_moov_bytes)?;
153                    let body_at =
154                        header
155                            .start
156                            .checked_add(header.header_len)
157                            .ok_or(Error::Malformed {
158                                kind: MOOV,
159                                reason: "movie body offset overflow",
160                            })?;
161                    validate_tree(&body, MOOV, body_at, limits)?;
162                    let parsed = parse_movie(&body, body_at, limits, &mut warnings)?;
163                    validate_typed_atom(MOOV, &body)?;
164                    movie = Some(parsed);
165                }
166                MOOF => {
167                    let count = u32::try_from(moofs.len()).unwrap_or(u32::MAX);
168                    if count >= limits.max_fragments {
169                        return Err(Error::LimitExceeded {
170                            limit: "max_fragments",
171                            value: u64::from(count).saturating_add(1),
172                            max: u64::from(limits.max_fragments),
173                        });
174                    }
175                    if header.size > limits.max_moof_bytes {
176                        return Err(Error::LimitExceeded {
177                            limit: "max_moof_bytes",
178                            value: header.size,
179                            max: limits.max_moof_bytes,
180                        });
181                    }
182                    let body = read_body(&mut reader, &header, body_len, limits.max_moof_bytes)?;
183                    let body_at =
184                        header
185                            .start
186                            .checked_add(header.header_len)
187                            .ok_or(Error::Malformed {
188                                kind: MOOF,
189                                reason: "fragment body offset overflow",
190                            })?;
191                    validate_tree(&body, MOOF, body_at, limits)?;
192                    moofs.push((header.start, body_at, body));
193                }
194                EMSG => {
195                    let body = read_body(&mut reader, &header, body_len, limits.max_moof_bytes)?;
196                    let event = parse_event(&body)?;
197                    validate_typed_atom(EMSG, &body)?;
198                    events.push(event);
199                }
200                kind if [
201                    MDAT,
202                    FourCc(*b"free"),
203                    FourCc(*b"skip"),
204                    FourCc(*b"sidx"),
205                    FourCc(*b"styp"),
206                    FourCc(*b"mfra"),
207                    FourCc(*b"uuid"),
208                ]
209                .contains(&kind) => {}
210                kind => warnings.push(Warning::UnknownBox { kind, parent: None }),
211            }
212            at = end;
213            if header.size_zero {
214                break;
215            }
216        }
217
218        let mut parsed = movie.ok_or(Error::MissingBox {
219            kind: MOOV,
220            parent: None,
221        })?;
222        let mut fragments = Vec::new();
223        let mut running_dts: BTreeMap<TrackId, u64> = BTreeMap::new();
224        for (fragment_index, (moof_at, body_at, body)) in moofs.iter().enumerate() {
225            let mut fragment = parse_fragment(
226                body,
227                *body_at,
228                *moof_at,
229                u32::try_from(fragment_index).map_err(|_| Error::LimitExceeded {
230                    limit: "max_fragments",
231                    value: u64::try_from(fragment_index).unwrap_or(u64::MAX),
232                    max: u64::from(limits.max_fragments),
233                })?,
234                &parsed.trex,
235                limits,
236                &mut running_dts,
237                &mut warnings,
238            )?;
239            validate_typed_atom(MOOF, body)?;
240            for run in &mut fragment.runs {
241                let position = parsed
242                    .tracks
243                    .iter()
244                    .position(|track| track.id == run.track)
245                    .ok_or(Error::NoSuchTrack(run.track))?;
246                let list = parsed
247                    .samples
248                    .get_mut(position)
249                    .ok_or(Error::NoSuchTrack(run.track))?;
250                let total = list.len().saturating_add(run.samples.len());
251                if u64::try_from(total).unwrap_or(u64::MAX) > u64::from(limits.max_sample_count) {
252                    return Err(Error::LimitExceeded {
253                        limit: "max_sample_count",
254                        value: u64::try_from(total).unwrap_or(u64::MAX),
255                        max: u64::from(limits.max_sample_count),
256                    });
257                }
258                let base_index = u32::try_from(list.len()).map_err(|_| Error::LimitExceeded {
259                    limit: "sample_count",
260                    value: u64::try_from(list.len()).unwrap_or(u64::MAX),
261                    max: u64::from(u32::MAX),
262                })?;
263                for (offset, sample) in run.samples.iter_mut().enumerate() {
264                    sample.index = base_index
265                        .checked_add(u32::try_from(offset).map_err(|_| Error::LimitExceeded {
266                            limit: "sample_count",
267                            value: u64::try_from(offset).unwrap_or(u64::MAX),
268                            max: u64::from(u32::MAX),
269                        })?)
270                        .ok_or(Error::LimitExceeded {
271                            limit: "sample_count",
272                            value: u64::from(u32::MAX).saturating_add(1),
273                            max: u64::from(u32::MAX),
274                        })?;
275                }
276                list.extend_from_slice(&run.samples);
277            }
278            fragments.push(fragment);
279        }
280        Ok(Self {
281            reader,
282            file_len,
283            brands,
284            movie_timescale: parsed.movie_timescale,
285            movie_duration: parsed.movie_duration,
286            tracks: parsed.tracks,
287            samples: parsed.samples,
288            fragments,
289            has_mvex: parsed.mvex_present,
290            events,
291            metadata: parsed.metadata,
292            warnings,
293            limits,
294        })
295    }
296
297    /// Returns file-type brands.
298    pub fn brands(&self) -> &Brands {
299        &self.brands
300    }
301
302    /// Returns the movie timescale.
303    pub fn movie_timescale(&self) -> u32 {
304        self.movie_timescale
305    }
306
307    /// Returns the movie duration in movie timescale units.
308    pub fn movie_duration(&self) -> u64 {
309        self.movie_duration
310    }
311
312    /// Returns the movie duration as a standard duration.
313    pub fn duration(&self) -> Duration {
314        let scale = u64::from(self.movie_timescale);
315        let seconds = self.movie_duration.checked_div(scale).unwrap_or_default();
316        let remainder = self.movie_duration.checked_rem(scale).unwrap_or_default();
317        let nanos = remainder
318            .saturating_mul(1_000_000_000)
319            .checked_div(scale)
320            .unwrap_or_default();
321        Duration::new(seconds, u32::try_from(nanos).unwrap_or(999_999_999))
322    }
323
324    /// Reports whether the movie declares or contains fragments.
325    pub fn is_fragmented(&self) -> bool {
326        self.has_mvex
327            || !self.fragments.is_empty()
328            || self
329                .tracks
330                .iter()
331                .any(|track| track.chunk_offsets == ChunkOffsetKind::None)
332    }
333
334    /// Returns tracks in movie order.
335    pub fn tracks(&self) -> &[Track] {
336        &self.tracks
337    }
338
339    /// Finds a track by identifier.
340    pub fn track(&self, id: TrackId) -> Option<&Track> {
341        self.tracks.iter().find(|track| track.id == id)
342    }
343
344    /// Iterates sample metadata without performing I/O.
345    pub fn samples(&self, id: TrackId) -> Result<SampleIter<'_>, Error> {
346        let position = self.track_position(id)?;
347        let list = self.samples.get(position).ok_or(Error::NoSuchTrack(id))?;
348        Ok(SampleIter { inner: list.iter() })
349    }
350
351    /// Returns metadata for one sample.
352    pub fn sample_info(&self, id: TrackId, index: u32) -> Result<SampleInfo, Error> {
353        let position = self.track_position(id)?;
354        self.samples
355            .get(position)
356            .and_then(|list| list.get(usize::try_from(index).ok()?))
357            .copied()
358            .ok_or(Error::NoSuchSample { track: id, index })
359    }
360
361    /// Reads one sample into a new allocation.
362    pub fn read_sample(&mut self, id: TrackId, index: u32) -> Result<Sample, Error> {
363        let info = self.sample_info(id, index)?;
364        let mut data = Vec::new();
365        self.read_sample_data(id, info, &mut data)?;
366        Ok(Sample { info, data })
367    }
368
369    /// Reads one sample into a reusable buffer.
370    pub fn read_sample_into(
371        &mut self,
372        id: TrackId,
373        index: u32,
374        buf: &mut Vec<u8>,
375    ) -> Result<SampleInfo, Error> {
376        let info = self.sample_info(id, index)?;
377        self.read_sample_data(id, info, buf)?;
378        Ok(info)
379    }
380
381    /// Returns parsed fragments.
382    pub fn fragments(&self) -> &[Fragment] {
383        &self.fragments
384    }
385
386    /// Returns top-level event messages.
387    pub fn events(&self) -> &[Event] {
388        &self.events
389    }
390
391    /// Returns user metadata.
392    pub fn metadata(&self) -> &Metadata {
393        &self.metadata
394    }
395
396    /// Returns tolerated parsing warnings.
397    pub fn warnings(&self) -> &[Warning] {
398        &self.warnings
399    }
400
401    /// Returns the wrapped stream.
402    pub fn into_inner(self) -> R {
403        self.reader
404    }
405
406    fn track_position(&self, id: TrackId) -> Result<usize, Error> {
407        self.tracks
408            .iter()
409            .position(|track| track.id == id)
410            .ok_or(Error::NoSuchTrack(id))
411    }
412
413    fn read_sample_data(
414        &mut self,
415        id: TrackId,
416        info: SampleInfo,
417        buf: &mut Vec<u8>,
418    ) -> Result<(), Error> {
419        let end = info
420            .offset
421            .checked_add(u64::from(info.size))
422            .ok_or(Error::SampleOutOfFile {
423                track: id,
424                index: info.index,
425                offset: info.offset,
426                size: info.size,
427                file_len: self.file_len,
428            })?;
429        if end > self.file_len {
430            return Err(Error::SampleOutOfFile {
431                track: id,
432                index: info.index,
433                offset: info.offset,
434                size: info.size,
435                file_len: self.file_len,
436            });
437        }
438        if info.size > self.limits.max_sample_bytes {
439            return Err(Error::LimitExceeded {
440                limit: "max_sample_bytes",
441                value: u64::from(info.size),
442                max: u64::from(self.limits.max_sample_bytes),
443            });
444        }
445        let len = usize::try_from(info.size).map_err(|_| Error::LimitExceeded {
446            limit: "max_sample_bytes",
447            value: u64::from(info.size),
448            max: u64::from(self.limits.max_sample_bytes),
449        })?;
450        buf.clear();
451        buf.resize(len, 0);
452        self.reader.seek(SeekFrom::Start(info.offset))?;
453        self.reader.read_exact(buf)?;
454        Ok(())
455    }
456}
457
458/// An exact, double-ended iterator over sample metadata.
459pub struct SampleIter<'a> {
460    inner: std::slice::Iter<'a, SampleInfo>,
461}
462
463impl Iterator for SampleIter<'_> {
464    type Item = SampleInfo;
465
466    fn next(&mut self) -> Option<Self::Item> {
467        self.inner.next().copied()
468    }
469
470    fn size_hint(&self) -> (usize, Option<usize>) {
471        self.inner.size_hint()
472    }
473}
474
475impl DoubleEndedIterator for SampleIter<'_> {
476    fn next_back(&mut self) -> Option<Self::Item> {
477        self.inner.next_back().copied()
478    }
479}
480
481impl ExactSizeIterator for SampleIter<'_> {}
482impl std::iter::FusedIterator for SampleIter<'_> {}
483
484struct TopHeader {
485    kind: FourCc,
486    start: u64,
487    size: u64,
488    header_len: u64,
489    size_zero: bool,
490}
491
492fn read_top_header<R: Read + Seek>(
493    reader: &mut R,
494    at: u64,
495    file_len: u64,
496) -> Result<TopHeader, Error> {
497    reader.seek(SeekFrom::Start(at))?;
498    let mut fixed = [0u8; 8];
499    reader.read_exact(&mut fixed).map_err(|error| {
500        if error.kind() == std::io::ErrorKind::UnexpectedEof {
501            Error::Truncated { at, needed: 8 }
502        } else {
503            Error::Io(error)
504        }
505    })?;
506    let size32 = u32::from_be_bytes(
507        fixed
508            .get(..4)
509            .and_then(|bytes| bytes.try_into().ok())
510            .unwrap_or([0; 4]),
511    );
512    let kind = FourCc(
513        fixed
514            .get(4..8)
515            .and_then(|bytes| bytes.try_into().ok())
516            .unwrap_or(*b"    "),
517    );
518    let (size, mut header_len, size_zero) = match size32 {
519        0 => (file_len.saturating_sub(at), 8u64, true),
520        1 => {
521            let mut large = [0u8; 8];
522            reader.read_exact(&mut large).map_err(|error| {
523                if error.kind() == std::io::ErrorKind::UnexpectedEof {
524                    Error::Truncated { at, needed: 16 }
525                } else {
526                    Error::Io(error)
527                }
528            })?;
529            (u64::from_be_bytes(large), 16u64, false)
530        }
531        value => (u64::from(value), 8u64, false),
532    };
533    if kind == FourCc(*b"uuid") {
534        header_len = header_len
535            .checked_add(16)
536            .ok_or(Error::BoxSizeTooSmall { kind, size, at })?;
537    }
538    if size < header_len {
539        return Err(Error::BoxSizeTooSmall { kind, size, at });
540    }
541    Ok(TopHeader {
542        kind,
543        start: at,
544        size,
545        header_len,
546        size_zero,
547    })
548}
549
550fn read_body<R: Read + Seek>(
551    reader: &mut R,
552    header: &TopHeader,
553    body_len: u64,
554    max: u64,
555) -> Result<Vec<u8>, Error> {
556    if body_len > max {
557        return Err(Error::LimitExceeded {
558            limit: "box_body_bytes",
559            value: body_len,
560            max,
561        });
562    }
563    let len = usize::try_from(body_len).map_err(|_| Error::LimitExceeded {
564        limit: "box_body_bytes",
565        value: body_len,
566        max,
567    })?;
568    let body_at = header
569        .start
570        .checked_add(header.header_len)
571        .ok_or(Error::Malformed {
572            kind: header.kind,
573            reason: "body offset overflow",
574        })?;
575    reader.seek(SeekFrom::Start(body_at))?;
576    let mut body = vec![0u8; len];
577    reader.read_exact(&mut body).map_err(|error| {
578        if error.kind() == std::io::ErrorKind::UnexpectedEof {
579            Error::Truncated {
580                at: body_at,
581                needed: body_len,
582            }
583        } else {
584            Error::Io(error)
585        }
586    })?;
587    Ok(body)
588}
589
590fn parse_ftyp(body: &[u8]) -> Result<Brands, Error> {
591    let mut cursor = Cursor::new(body, FTYP);
592    let major = cursor.fourcc()?;
593    let minor_version = cursor.u32()?;
594    if cursor.remaining() % 4 != 0 {
595        return Err(Error::Malformed {
596            kind: FTYP,
597            reason: "compatible brands are not four-byte aligned",
598        });
599    }
600    let mut compatible = Vec::with_capacity(cursor.remaining() / 4);
601    while cursor.remaining() != 0 {
602        compatible.push(cursor.fourcc()?);
603    }
604    Ok(Brands {
605        major,
606        minor_version,
607        compatible,
608    })
609}
610
611struct ParsedMovie {
612    movie_timescale: u32,
613    movie_duration: u64,
614    tracks: Vec<Track>,
615    samples: Vec<Vec<SampleInfo>>,
616    trex: BTreeMap<TrackId, Trex>,
617    mvex_present: bool,
618    metadata: Metadata,
619}
620
621fn warn_unknown_children(
622    boxes: &[(BoxHeader, &[u8])],
623    parent: FourCc,
624    known: &[FourCc],
625    warnings: &mut Vec<Warning>,
626) {
627    for (header, _) in boxes {
628        if known.contains(&header.kind)
629            || header.kind == FourCc(*b"free")
630            || header.kind == FourCc(*b"skip")
631        {
632            continue;
633        }
634        warnings.push(Warning::UnknownBox {
635            kind: header.kind,
636            parent: Some(parent),
637        });
638    }
639}
640
641#[derive(Clone, Copy)]
642struct Trex {
643    description_index: u32,
644    duration: u32,
645    size: u32,
646    flags: u32,
647}
648
649fn parse_movie(
650    body: &[u8],
651    absolute_base: u64,
652    limits: Limits,
653    warnings: &mut Vec<Warning>,
654) -> Result<ParsedMovie, Error> {
655    let boxes = children(body, MOOV, absolute_base)?;
656    warn_unknown_children(&boxes, MOOV, &[MVHD, TRAK, MVEX, UDTA], warnings);
657    let mvhd = one(&boxes, MVHD, MOOV, true)?.ok_or(Error::MissingBox {
658        kind: MVHD,
659        parent: Some(MOOV),
660    })?;
661    let (movie_timescale, movie_duration) = parse_mvhd(mvhd)?;
662    if movie_timescale == 0 {
663        return Err(Error::InvalidTimescale { track: None });
664    }
665    let mut tracks = Vec::new();
666    let mut samples = Vec::new();
667    for (_, trak) in boxes.iter().filter(|(header, _)| header.kind == TRAK) {
668        let (track, track_samples) = parse_track(trak, absolute_base, limits, warnings)?;
669        if tracks
670            .iter()
671            .any(|existing: &Track| existing.id == track.id)
672        {
673            return Err(Error::Malformed {
674                kind: TKHD,
675                reason: "duplicate track id",
676            });
677        }
678        tracks.push(track);
679        samples.push(track_samples);
680    }
681    let mut trex = BTreeMap::new();
682    let mvex_box = one(&boxes, MVEX, MOOV, false)?;
683    let mvex_present = mvex_box.is_some();
684    if let Some(mvex) = mvex_box {
685        for (_, body) in children(mvex, MVEX, absolute_base)?
686            .iter()
687            .filter(|(header, _)| header.kind == TREX)
688        {
689            let (id, value) = parse_trex(body)?;
690            if trex.insert(id, value).is_some() {
691                return Err(Error::DuplicateBox {
692                    kind: TREX,
693                    parent: MVEX,
694                });
695            }
696        }
697    }
698    let metadata = parse_metadata(&boxes, absolute_base)?;
699    Ok(ParsedMovie {
700        movie_timescale,
701        movie_duration,
702        tracks,
703        samples,
704        trex,
705        mvex_present,
706        metadata,
707    })
708}
709
710fn parse_mvhd(body: &[u8]) -> Result<(u32, u64), Error> {
711    let mut cursor = Cursor::new(body, MVHD);
712    let (version, _) = cursor.full_box()?;
713    match version {
714        0 => {
715            cursor.skip(8)?;
716            Ok((cursor.u32()?, u64::from(cursor.u32()?)))
717        }
718        1 => {
719            cursor.skip(16)?;
720            Ok((cursor.u32()?, cursor.u64()?))
721        }
722        _ => Err(Error::Malformed {
723            kind: MVHD,
724            reason: "unsupported version",
725        }),
726    }
727}
728
729fn parse_track(
730    body: &[u8],
731    absolute_base: u64,
732    limits: Limits,
733    warnings: &mut Vec<Warning>,
734) -> Result<(Track, Vec<SampleInfo>), Error> {
735    let boxes = children(body, TRAK, absolute_base)?;
736    warn_unknown_children(&boxes, TRAK, &[TKHD, EDTS, MDIA, UDTA], warnings);
737    let tkhd_body = one(&boxes, TKHD, TRAK, true)?.ok_or(Error::MissingBox {
738        kind: TKHD,
739        parent: Some(TRAK),
740    })?;
741    let tkhd = parse_tkhd(tkhd_body)?;
742    let mdia = one(&boxes, MDIA, TRAK, true)?.ok_or(Error::MissingBox {
743        kind: MDIA,
744        parent: Some(TRAK),
745    })?;
746    let mdia_boxes = children(mdia, MDIA, absolute_base)?;
747    warn_unknown_children(&mdia_boxes, MDIA, &[MDHD, HDLR, MINF], warnings);
748    let mdhd_body = one(&mdia_boxes, MDHD, MDIA, true)?.ok_or(Error::MissingBox {
749        kind: MDHD,
750        parent: Some(MDIA),
751    })?;
752    let mdhd = parse_mdhd(mdhd_body, tkhd.id)?;
753    let hdlr_body = one(&mdia_boxes, HDLR, MDIA, true)?.ok_or(Error::MissingBox {
754        kind: HDLR,
755        parent: Some(MDIA),
756    })?;
757    let (handler, handler_name) = parse_hdlr(hdlr_body)?;
758    let minf = one(&mdia_boxes, MINF, MDIA, true)?.ok_or(Error::MissingBox {
759        kind: MINF,
760        parent: Some(MDIA),
761    })?;
762    let minf_boxes = children(minf, MINF, absolute_base)?;
763    warn_unknown_children(
764        &minf_boxes,
765        MINF,
766        &[
767            FourCc(*b"vmhd"),
768            FourCc(*b"smhd"),
769            FourCc(*b"nmhd"),
770            FourCc(*b"sthd"),
771            FourCc(*b"hmhd"),
772            FourCc(*b"gmhd"),
773            DINF,
774            STBL,
775        ],
776        warnings,
777    );
778    let stbl = one(&minf_boxes, STBL, MINF, true)?.ok_or(Error::MissingBox {
779        kind: STBL,
780        parent: Some(MINF),
781    })?;
782    let edit_list = if let Some(edts) = one(&boxes, EDTS, TRAK, false)? {
783        let edts_boxes = children(edts, EDTS, absolute_base)?;
784        warn_unknown_children(&edts_boxes, EDTS, &[ELST], warnings);
785        match one(&edts_boxes, ELST, EDTS, false)? {
786            Some(elst) => parse_elst(elst)?,
787            None => Vec::new(),
788        }
789    } else {
790        Vec::new()
791    };
792    let parsed = parse_sample_table(
793        stbl,
794        absolute_base,
795        tkhd.id,
796        mdhd.timescale,
797        limits,
798        warnings,
799    )?;
800    let kind = if handler == FourCc(*b"vide") {
801        TrackKind::Video
802    } else if handler == FourCc(*b"soun") {
803        TrackKind::Audio
804    } else if handler == FourCc(*b"subt") {
805        TrackKind::Subtitle
806    } else {
807        TrackKind::Other(handler)
808    };
809    let duration = parsed.samples.iter().try_fold(0u64, |sum, sample| {
810        sum.checked_add(u64::from(sample.duration))
811            .ok_or(Error::InvalidSampleTable {
812                track: tkhd.id,
813                reason: "sample duration sum overflow",
814            })
815    })?;
816    Ok((
817        Track {
818            id: tkhd.id,
819            kind,
820            handler_name,
821            timescale: mdhd.timescale,
822            duration,
823            language: mdhd.language,
824            enabled: tkhd.enabled,
825            width: tkhd.width,
826            height: tkhd.height,
827            codec: parsed.codec,
828            edit_list,
829            sample_count: u32::try_from(parsed.samples.len()).map_err(|_| {
830                Error::InvalidSampleTable {
831                    track: tkhd.id,
832                    reason: "sample count exceeds u32",
833                }
834            })?,
835            chunk_offsets: parsed.chunk_kind,
836            sync_sample_count: parsed.sync_count,
837        },
838        parsed.samples,
839    ))
840}
841
842struct TkhdFields {
843    id: TrackId,
844    enabled: bool,
845    width: f64,
846    height: f64,
847}
848
849fn parse_tkhd(body: &[u8]) -> Result<TkhdFields, Error> {
850    let mut cursor = Cursor::new(body, TKHD);
851    let (version, flags) = cursor.full_box()?;
852    let id = match version {
853        0 => {
854            cursor.skip(8)?;
855            let id = cursor.u32()?;
856            cursor.skip(4)?;
857            cursor.skip(4)?;
858            id
859        }
860        1 => {
861            cursor.skip(16)?;
862            let id = cursor.u32()?;
863            cursor.skip(4)?;
864            cursor.skip(8)?;
865            id
866        }
867        _ => {
868            return Err(Error::Malformed {
869                kind: TKHD,
870                reason: "unsupported version",
871            })
872        }
873    };
874    cursor.skip(8 + 2 + 2 + 2 + 2 + 36)?;
875    let width = f64::from(cursor.u32()?) / 65_536.0;
876    let height = f64::from(cursor.u32()?) / 65_536.0;
877    Ok(TkhdFields {
878        id: TrackId(id),
879        enabled: flags & 1 != 0,
880        width,
881        height,
882    })
883}
884
885struct MdhdFields {
886    timescale: u32,
887    language: String,
888}
889
890fn parse_mdhd(body: &[u8], track: TrackId) -> Result<MdhdFields, Error> {
891    let mut cursor = Cursor::new(body, MDHD);
892    let (version, _) = cursor.full_box()?;
893    let timescale = match version {
894        0 => {
895            cursor.skip(8)?;
896            let value = cursor.u32()?;
897            cursor.skip(4)?;
898            value
899        }
900        1 => {
901            cursor.skip(16)?;
902            let value = cursor.u32()?;
903            cursor.skip(8)?;
904            value
905        }
906        _ => {
907            return Err(Error::Malformed {
908                kind: MDHD,
909                reason: "unsupported version",
910            })
911        }
912    };
913    if timescale == 0 {
914        return Err(Error::InvalidTimescale { track: Some(track) });
915    }
916    let packed = cursor.u16()?;
917    let mut language = String::with_capacity(3);
918    for shift in [10u16, 5, 0] {
919        let code = u8::try_from((packed >> shift) & 0x1f).unwrap_or_default();
920        language.push(char::from(code.saturating_add(0x60)));
921    }
922    Ok(MdhdFields {
923        timescale,
924        language,
925    })
926}
927
928fn parse_hdlr(body: &[u8]) -> Result<(FourCc, String), Error> {
929    let mut cursor = Cursor::new(body, HDLR);
930    cursor.full_box()?;
931    cursor.skip(4)?;
932    let handler = cursor.fourcc()?;
933    cursor.skip(12)?;
934    let raw = cursor.rest();
935    let name_bytes = raw.strip_suffix(&[0]).unwrap_or(raw);
936    let name = String::from_utf8(name_bytes.to_vec()).map_err(|_| Error::Malformed {
937        kind: HDLR,
938        reason: "handler name is not UTF-8",
939    })?;
940    Ok((handler, name))
941}
942
943fn parse_elst(body: &[u8]) -> Result<Vec<Edit>, Error> {
944    let mut cursor = Cursor::new(body, ELST);
945    let (version, _) = cursor.full_box()?;
946    let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
947        kind: ELST,
948        reason: "entry count is too large",
949    })?;
950    let width = if version == 1 {
951        20usize
952    } else if version == 0 {
953        12usize
954    } else {
955        return Err(Error::Malformed {
956            kind: ELST,
957            reason: "unsupported version",
958        });
959    };
960    if count > cursor.remaining().checked_div(width).unwrap_or_default() {
961        return Err(Error::Malformed {
962            kind: ELST,
963            reason: "entry count exceeds box body",
964        });
965    }
966    let mut edits = Vec::with_capacity(count);
967    for _ in 0..count {
968        let (segment_duration, media_time) = if version == 1 {
969            (cursor.u64()?, cursor.i64()?)
970        } else {
971            (u64::from(cursor.u32()?), i64::from(cursor.i32()?))
972        };
973        let rate_integer = cursor.i16()?;
974        let rate_fraction = cursor.u16()?;
975        let media_rate = f32::from(rate_integer) + f32::from(rate_fraction) / 65_536.0;
976        let media_time = if media_time == -1 {
977            None
978        } else {
979            Some(u64::try_from(media_time).map_err(|_| Error::Malformed {
980                kind: ELST,
981                reason: "negative media time",
982            })?)
983        };
984        edits.push(Edit {
985            segment_duration,
986            media_time,
987            media_rate,
988        });
989    }
990    Ok(edits)
991}
992
993struct ParsedTable {
994    codec: Codec,
995    samples: Vec<SampleInfo>,
996    chunk_kind: ChunkOffsetKind,
997    sync_count: Option<u32>,
998}
999
1000#[derive(Clone, Copy)]
1001struct StscEntry {
1002    first_chunk: u32,
1003    samples_per_chunk: u32,
1004    description_index: u32,
1005}
1006
1007fn parse_sample_table(
1008    body: &[u8],
1009    absolute_base: u64,
1010    track: TrackId,
1011    timescale: u32,
1012    limits: Limits,
1013    warnings: &mut Vec<Warning>,
1014) -> Result<ParsedTable, Error> {
1015    let boxes = children(body, STBL, absolute_base)?;
1016    warn_unknown_children(
1017        &boxes,
1018        STBL,
1019        &[STSD, STTS, CTTS, STSS, STSC, STSZ, STZ2, STCO, CO64],
1020        warnings,
1021    );
1022    if boxes.iter().any(|(header, _)| header.kind == STZ2) {
1023        return Err(Error::Unsupported("compact sample sizes"));
1024    }
1025    let stsd = one(&boxes, STSD, STBL, true)?.ok_or(Error::MissingBox {
1026        kind: STSD,
1027        parent: Some(STBL),
1028    })?;
1029    let codecs = parse_stsd(stsd, track, timescale)?;
1030    if codecs.is_empty() {
1031        return Err(Error::InvalidSampleTable {
1032            track,
1033            reason: "sample description table is empty",
1034        });
1035    }
1036    let codec = codecs.first().cloned().ok_or(Error::InvalidSampleTable {
1037        track,
1038        reason: "sample description table is empty",
1039    })?;
1040    let stts = one(&boxes, STTS, STBL, true)?.ok_or(Error::MissingBox {
1041        kind: STTS,
1042        parent: Some(STBL),
1043    })?;
1044    let timings = parse_stts(stts)?;
1045    let sizes = parse_stsz(
1046        one(&boxes, STSZ, STBL, true)?.ok_or(Error::MissingBox {
1047            kind: STSZ,
1048            parent: Some(STBL),
1049        })?,
1050        limits,
1051    )?;
1052    let ctts = match one(&boxes, CTTS, STBL, false)? {
1053        Some(value) => Some(parse_ctts(value)?),
1054        None => None,
1055    };
1056    let stsc = parse_stsc(one(&boxes, STSC, STBL, true)?.ok_or(Error::MissingBox {
1057        kind: STSC,
1058        parent: Some(STBL),
1059    })?)?;
1060    let stco = one(&boxes, STCO, STBL, false)?;
1061    let co64 = one(&boxes, CO64, STBL, false)?;
1062    if stco.is_some() && co64.is_some() {
1063        return Err(Error::InvalidSampleTable {
1064            track,
1065            reason: "both stco and co64 are present",
1066        });
1067    }
1068    let (offsets, mut chunk_kind) = match (stco, co64) {
1069        (Some(value), None) => (parse_offsets(value, STCO)?, ChunkOffsetKind::Stco),
1070        (None, Some(value)) => (parse_offsets(value, CO64)?, ChunkOffsetKind::Co64),
1071        (None, None) if sizes.is_empty() => (Vec::new(), ChunkOffsetKind::None),
1072        _ => {
1073            return Err(Error::MissingBox {
1074                kind: STCO,
1075                parent: Some(STBL),
1076            })
1077        }
1078    };
1079    if sizes.is_empty() && offsets.is_empty() {
1080        chunk_kind = ChunkOffsetKind::None;
1081    }
1082    let sync = match one(&boxes, STSS, STBL, false)? {
1083        Some(value) => Some(parse_stss(value, sizes.len(), track)?),
1084        None => None,
1085    };
1086    if u64::try_from(sizes.len()).unwrap_or(u64::MAX) > u64::from(limits.max_sample_count) {
1087        return Err(Error::LimitExceeded {
1088            limit: "max_sample_count",
1089            value: u64::try_from(sizes.len()).unwrap_or(u64::MAX),
1090            max: u64::from(limits.max_sample_count),
1091        });
1092    }
1093    let samples = expand_samples(
1094        track,
1095        &sizes,
1096        &timings,
1097        ctts.as_deref(),
1098        &stsc,
1099        &offsets,
1100        sync.as_deref(),
1101        codecs.len(),
1102    )?;
1103    Ok(ParsedTable {
1104        codec,
1105        samples,
1106        chunk_kind,
1107        sync_count: sync
1108            .as_ref()
1109            .map(|items| u32::try_from(items.len()).unwrap_or(u32::MAX)),
1110    })
1111}
1112
1113fn parse_stsd(body: &[u8], track: TrackId, timescale: u32) -> Result<Vec<Codec>, Error> {
1114    let mut cursor = Cursor::new(body, STSD);
1115    let (version, _) = cursor.full_box()?;
1116    if version != 0 {
1117        return Err(Error::Malformed {
1118            kind: STSD,
1119            reason: "unsupported version",
1120        });
1121    }
1122    let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
1123        kind: STSD,
1124        reason: "entry count is too large",
1125    })?;
1126    if count > cursor.remaining() / 8 {
1127        return Err(Error::Malformed {
1128            kind: STSD,
1129            reason: "entry count exceeds box body",
1130        });
1131    }
1132    let mut codecs = Vec::with_capacity(count);
1133    for _ in 0..count {
1134        let size = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
1135            kind: STSD,
1136            reason: "sample entry is too large",
1137        })?;
1138        let entry = cursor.fourcc()?;
1139        if size < 8 {
1140            return Err(Error::Malformed {
1141                kind: entry,
1142                reason: "sample entry size is smaller than its header",
1143            });
1144        }
1145        let body_len = size.checked_sub(8).ok_or(Error::Malformed {
1146            kind: entry,
1147            reason: "sample entry size underflow",
1148        })?;
1149        let raw = cursor.take(body_len)?.to_vec();
1150        codecs.push(parse_codec(entry, &raw, timescale)?);
1151    }
1152    if cursor.remaining() != 0 {
1153        return Err(Error::InvalidSampleTable {
1154            track,
1155            reason: "trailing bytes in sample description table",
1156        });
1157    }
1158    Ok(codecs)
1159}
1160
1161fn sidecar_boxes(boxes: &[(BoxHeader, &[u8])]) -> Vec<SidecarBox> {
1162    boxes
1163        .iter()
1164        .filter(|(header, _)| {
1165            [
1166                FourCc(*b"pasp"),
1167                FourCc(*b"btrt"),
1168                FourCc(*b"fiel"),
1169                FourCc(*b"colr"),
1170            ]
1171            .contains(&header.kind)
1172        })
1173        .map(|(header, body)| SidecarBox {
1174            kind: header.kind,
1175            payload: body.to_vec(),
1176        })
1177        .collect()
1178}
1179
1180fn parse_codec(entry: FourCc, raw: &[u8], timescale: u32) -> Result<Codec, Error> {
1181    if entry == FourCc(*b"avc1") || entry == FourCc(*b"avc3") {
1182        let children_bytes = raw.get(78..).ok_or(Error::Malformed {
1183            kind: entry,
1184            reason: "visual sample entry is truncated",
1185        })?;
1186        let boxes = children(children_bytes, entry, 0)?;
1187        let payload = one(&boxes, FourCc(*b"avcC"), entry, true)?.ok_or(Error::MissingBox {
1188            kind: FourCc(*b"avcC"),
1189            parent: Some(entry),
1190        })?;
1191        let mut config = parse_avcc(payload)?;
1192        config.sidecars = sidecar_boxes(&boxes);
1193        Ok(Codec::H264 { entry, config })
1194    } else if entry == FourCc(*b"hvc1") || entry == FourCc(*b"hev1") {
1195        let children_bytes = raw.get(78..).ok_or(Error::Malformed {
1196            kind: entry,
1197            reason: "visual sample entry is truncated",
1198        })?;
1199        let boxes = children(children_bytes, entry, 0)?;
1200        let payload = one(&boxes, FourCc(*b"hvcC"), entry, true)?.ok_or(Error::MissingBox {
1201            kind: FourCc(*b"hvcC"),
1202            parent: Some(entry),
1203        })?;
1204        let mut config = parse_hvcc(payload)?;
1205        config.sidecars = sidecar_boxes(&boxes);
1206        Ok(Codec::H265 { entry, config })
1207    } else if entry == FourCc(*b"mp4a") {
1208        let version = raw
1209            .get(8..10)
1210            .and_then(|value| value.try_into().ok())
1211            .map(u16::from_be_bytes)
1212            .ok_or(Error::Malformed {
1213                kind: entry,
1214                reason: "audio sample entry is truncated",
1215            })?;
1216        let fixed = match version {
1217            0 => 28usize,
1218            1 => 44usize,
1219            _ => return Err(Error::Unsupported("QuickTime audio sample entry version")),
1220        };
1221        let channels = raw
1222            .get(16..18)
1223            .and_then(|value| value.try_into().ok())
1224            .map(u16::from_be_bytes)
1225            .ok_or(Error::Malformed {
1226                kind: entry,
1227                reason: "audio channel count is missing",
1228            })?;
1229        let sample_rate_fixed = raw
1230            .get(24..28)
1231            .and_then(|value| value.try_into().ok())
1232            .map(u32::from_be_bytes)
1233            .ok_or(Error::Malformed {
1234                kind: entry,
1235                reason: "audio sample rate is missing",
1236            })?;
1237        let child_bytes = raw.get(fixed..).ok_or(Error::Malformed {
1238            kind: entry,
1239            reason: "audio sample entry is truncated",
1240        })?;
1241        let boxes = children(child_bytes, entry, 0)?;
1242        let esds = one(&boxes, FourCc(*b"esds"), entry, true)?.ok_or(Error::MissingBox {
1243            kind: FourCc(*b"esds"),
1244            parent: Some(entry),
1245        })?;
1246        let (object_type, audio_specific_config) = parse_esds(esds)?;
1247        // A rate above 65535 does not fit the entry's 16.16 field and lives
1248        // in the media timescale instead, leaving the field zero.
1249        let sample_rate = if sample_rate_fixed >> 16 == 0 {
1250            timescale
1251        } else {
1252            sample_rate_fixed >> 16
1253        };
1254        Ok(Codec::Aac {
1255            config: AacConfig {
1256                raw_esds: esds.to_vec(),
1257                object_type,
1258                audio_specific_config,
1259                channels,
1260                sample_rate,
1261                sidecars: sidecar_boxes(&boxes),
1262            },
1263        })
1264    } else if entry == FourCc(*b"Opus") {
1265        let child_bytes = raw.get(28..).ok_or(Error::Malformed {
1266            kind: entry,
1267            reason: "audio sample entry is truncated",
1268        })?;
1269        let boxes = children(child_bytes, entry, 0)?;
1270        let dops = one(&boxes, FourCc(*b"dOps"), entry, true)?.ok_or(Error::MissingBox {
1271            kind: FourCc(*b"dOps"),
1272            parent: Some(entry),
1273        })?;
1274        let mut cursor = Cursor::new(dops, FourCc(*b"dOps"));
1275        let version = cursor.u8()?;
1276        if version != 0 {
1277            return Err(Error::Malformed {
1278                kind: FourCc(*b"dOps"),
1279                reason: "unsupported version",
1280            });
1281        }
1282        let channels = cursor.u8()?;
1283        let pre_skip = cursor.u16()?;
1284        let input_sample_rate = cursor.u32()?;
1285        let output_gain = cursor.i16()?;
1286        let mapping_family = cursor.u8()?;
1287        Ok(Codec::Opus {
1288            config: OpusConfig {
1289                raw: dops.to_vec(),
1290                channels,
1291                pre_skip,
1292                input_sample_rate,
1293                output_gain,
1294                mapping_family,
1295                sidecars: sidecar_boxes(&boxes),
1296            },
1297        })
1298    } else {
1299        Ok(Codec::Other {
1300            entry,
1301            raw: raw.to_vec(),
1302        })
1303    }
1304}
1305
1306fn parse_avcc(data: &[u8]) -> Result<AvcConfig, Error> {
1307    let mut cursor = Cursor::new(data, FourCc(*b"avcC"));
1308    let version = cursor.u8()?;
1309    if version != 1 {
1310        return Err(Error::Malformed {
1311            kind: FourCc(*b"avcC"),
1312            reason: "unsupported version",
1313        });
1314    }
1315    let profile = cursor.u8()?;
1316    let profile_compat = cursor.u8()?;
1317    let level = cursor.u8()?;
1318    let nal_length_size = (cursor.u8()? & 3).saturating_add(1);
1319    let sps_count = usize::from(cursor.u8()? & 0x1f);
1320    let mut sps = Vec::with_capacity(sps_count);
1321    for _ in 0..sps_count {
1322        let len = usize::from(cursor.u16()?);
1323        sps.push(cursor.take(len)?.to_vec());
1324    }
1325    let pps_count = usize::from(cursor.u8()?);
1326    let mut pps = Vec::with_capacity(pps_count);
1327    for _ in 0..pps_count {
1328        let len = usize::from(cursor.u16()?);
1329        pps.push(cursor.take(len)?.to_vec());
1330    }
1331    Ok(AvcConfig {
1332        raw: data.to_vec(),
1333        profile,
1334        profile_compat,
1335        level,
1336        nal_length_size,
1337        sps,
1338        pps,
1339        sidecars: Vec::new(),
1340    })
1341}
1342
1343fn parse_hvcc(data: &[u8]) -> Result<HevcConfig, Error> {
1344    let mut cursor = Cursor::new(data, FourCc(*b"hvcC"));
1345    if cursor.u8()? != 1 {
1346        return Err(Error::Malformed {
1347            kind: FourCc(*b"hvcC"),
1348            reason: "unsupported version",
1349        });
1350    }
1351    cursor.skip(20)?;
1352    let nal_length_size = (cursor.u8()? & 3).saturating_add(1);
1353    let array_count = usize::from(cursor.u8()?);
1354    let mut arrays = Vec::with_capacity(array_count);
1355    for _ in 0..array_count {
1356        let nal_type = cursor.u8()? & 0x3f;
1357        let count = usize::from(cursor.u16()?);
1358        if count > cursor.remaining() / 2 {
1359            return Err(Error::Malformed {
1360                kind: FourCc(*b"hvcC"),
1361                reason: "NAL count exceeds configuration payload",
1362            });
1363        }
1364        let mut nals = Vec::with_capacity(count);
1365        for _ in 0..count {
1366            let len = usize::from(cursor.u16()?);
1367            nals.push(cursor.take(len)?.to_vec());
1368        }
1369        arrays.push(NalArray { nal_type, nals });
1370    }
1371    Ok(HevcConfig {
1372        raw: data.to_vec(),
1373        nal_length_size,
1374        arrays,
1375        sidecars: Vec::new(),
1376    })
1377}
1378
1379fn descriptor_len(data: &[u8], start: usize) -> Option<(usize, usize)> {
1380    let mut value = 0usize;
1381    let mut used = 0usize;
1382    for byte in data.get(start..)?.iter().take(4) {
1383        value = value
1384            .checked_shl(7)?
1385            .checked_add(usize::from(byte & 0x7f))?;
1386        used = used.checked_add(1)?;
1387        if byte & 0x80 == 0 {
1388            return Some((value, used));
1389        }
1390    }
1391    None
1392}
1393
1394fn read_descriptor(data: &[u8], at: usize) -> Result<(u8, &[u8], usize), Error> {
1395    let malformed = |reason: &'static str| Error::Malformed {
1396        kind: FourCc(*b"esds"),
1397        reason,
1398    };
1399    let tag = *data.get(at).ok_or(malformed("descriptor tag is missing"))?;
1400    let len_at = at
1401        .checked_add(1)
1402        .ok_or(malformed("descriptor offset overflow"))?;
1403    let (len, width) =
1404        descriptor_len(data, len_at).ok_or(malformed("descriptor length is invalid"))?;
1405    let body_at = len_at
1406        .checked_add(width)
1407        .ok_or(malformed("descriptor offset overflow"))?;
1408    let end = body_at
1409        .checked_add(len)
1410        .ok_or(malformed("descriptor size overflow"))?;
1411    let body = data
1412        .get(body_at..end)
1413        .ok_or(malformed("descriptor exceeds its container"))?;
1414    Ok((tag, body, end))
1415}
1416
1417fn parse_esds(data: &[u8]) -> Result<(u8, Vec<u8>), Error> {
1418    let malformed = |reason: &'static str| Error::Malformed {
1419        kind: FourCc(*b"esds"),
1420        reason,
1421    };
1422    let payload = data.get(4..).ok_or(malformed("missing full-box fields"))?;
1423    let (es_tag, es_body, _) = read_descriptor(payload, 0)?;
1424    if es_tag != 0x03 {
1425        return Err(malformed("elementary stream descriptor is missing"));
1426    }
1427    let mut header = Cursor::new(es_body, FourCc(*b"esds"));
1428    header.skip(2)?;
1429    let stream_flags = header.u8()?;
1430    if stream_flags & 0x80 != 0 {
1431        header.skip(2)?;
1432    }
1433    if stream_flags & 0x40 != 0 {
1434        let url_length = usize::from(header.u8()?);
1435        header.skip(url_length)?;
1436    }
1437    if stream_flags & 0x20 != 0 {
1438        header.skip(2)?;
1439    }
1440    let mut at = header.position();
1441    let mut decoder_config = None;
1442    while at < es_body.len() {
1443        let (tag, body, next) = read_descriptor(es_body, at)?;
1444        if tag == 0x04 {
1445            decoder_config = Some(body);
1446            break;
1447        }
1448        at = next;
1449    }
1450    let decoder_config =
1451        decoder_config.ok_or(malformed("decoder configuration descriptor is missing"))?;
1452    let object_type = *decoder_config
1453        .first()
1454        .ok_or(malformed("decoder configuration descriptor is empty"))?;
1455    // The object type, stream type, buffer size, and both bitrate fields
1456    // occupy 13 bytes before any nested descriptor.
1457    let mut at = 13usize;
1458    let mut specific_info = None;
1459    while at < decoder_config.len() {
1460        let (tag, body, next) = read_descriptor(decoder_config, at)?;
1461        if tag == 0x05 {
1462            specific_info = Some(body.to_vec());
1463            break;
1464        }
1465        at = next;
1466    }
1467    Ok((
1468        object_type,
1469        specific_info.ok_or(malformed("audio specific configuration is missing"))?,
1470    ))
1471}
1472
1473fn parse_counted_pairs(body: &[u8], kind: FourCc, signed: bool) -> Result<Vec<(u32, i64)>, Error> {
1474    let mut cursor = Cursor::new(body, kind);
1475    let (version, _) = cursor.full_box()?;
1476    if (!signed && version != 0) || (signed && version > 1) {
1477        return Err(Error::Malformed {
1478            kind,
1479            reason: "unsupported version",
1480        });
1481    }
1482    let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
1483        kind,
1484        reason: "entry count is too large",
1485    })?;
1486    if count > cursor.remaining() / 8 {
1487        return Err(Error::Malformed {
1488            kind,
1489            reason: "entry count exceeds box body",
1490        });
1491    }
1492    let mut entries = Vec::with_capacity(count);
1493    for _ in 0..count {
1494        let sample_count = cursor.u32()?;
1495        let value = if signed && version == 1 {
1496            i64::from(cursor.i32()?)
1497        } else {
1498            i64::from(cursor.u32()?)
1499        };
1500        entries.push((sample_count, value));
1501    }
1502    Ok(entries)
1503}
1504
1505fn parse_stts(body: &[u8]) -> Result<Vec<(u32, u32)>, Error> {
1506    parse_counted_pairs(body, STTS, false)?
1507        .into_iter()
1508        .map(|(count, delta)| {
1509            Ok((
1510                count,
1511                u32::try_from(delta).map_err(|_| Error::Malformed {
1512                    kind: STTS,
1513                    reason: "sample delta exceeds u32",
1514                })?,
1515            ))
1516        })
1517        .collect()
1518}
1519
1520fn parse_ctts(body: &[u8]) -> Result<Vec<(u32, i64)>, Error> {
1521    parse_counted_pairs(body, CTTS, true)
1522}
1523
1524fn parse_stsz(body: &[u8], limits: Limits) -> Result<Vec<u32>, Error> {
1525    let mut cursor = Cursor::new(body, STSZ);
1526    let (version, _) = cursor.full_box()?;
1527    if version != 0 {
1528        return Err(Error::Malformed {
1529            kind: STSZ,
1530            reason: "unsupported version",
1531        });
1532    }
1533    let fixed = cursor.u32()?;
1534    let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
1535        kind: STSZ,
1536        reason: "sample count is too large",
1537    })?;
1538    if fixed == 0 {
1539        if count > cursor.remaining() / 4 {
1540            return Err(Error::Malformed {
1541                kind: STSZ,
1542                reason: "sample count exceeds box body",
1543            });
1544        }
1545        check_sample_count(count, limits)?;
1546        let mut sizes = Vec::with_capacity(count);
1547        for _ in 0..count {
1548            sizes.push(cursor.u32()?);
1549        }
1550        Ok(sizes)
1551    } else {
1552        check_sample_count(count, limits)?;
1553        Ok(vec![fixed; count])
1554    }
1555}
1556
1557fn check_sample_count(count: usize, limits: Limits) -> Result<(), Error> {
1558    if u64::try_from(count).unwrap_or(u64::MAX) > u64::from(limits.max_sample_count) {
1559        return Err(Error::LimitExceeded {
1560            limit: "max_sample_count",
1561            value: u64::try_from(count).unwrap_or(u64::MAX),
1562            max: u64::from(limits.max_sample_count),
1563        });
1564    }
1565    Ok(())
1566}
1567
1568fn parse_stsc(body: &[u8]) -> Result<Vec<StscEntry>, Error> {
1569    let mut cursor = Cursor::new(body, STSC);
1570    let (version, _) = cursor.full_box()?;
1571    if version != 0 {
1572        return Err(Error::Malformed {
1573            kind: STSC,
1574            reason: "unsupported version",
1575        });
1576    }
1577    let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
1578        kind: STSC,
1579        reason: "entry count is too large",
1580    })?;
1581    if count > cursor.remaining() / 12 {
1582        return Err(Error::Malformed {
1583            kind: STSC,
1584            reason: "entry count exceeds box body",
1585        });
1586    }
1587    let mut entries = Vec::with_capacity(count);
1588    for _ in 0..count {
1589        entries.push(StscEntry {
1590            first_chunk: cursor.u32()?,
1591            samples_per_chunk: cursor.u32()?,
1592            description_index: cursor.u32()?,
1593        });
1594    }
1595    Ok(entries)
1596}
1597
1598fn parse_offsets(body: &[u8], kind: FourCc) -> Result<Vec<u64>, Error> {
1599    let mut cursor = Cursor::new(body, kind);
1600    let (version, _) = cursor.full_box()?;
1601    if version != 0 {
1602        return Err(Error::Malformed {
1603            kind,
1604            reason: "unsupported version",
1605        });
1606    }
1607    let width = if kind == STCO { 4usize } else { 8usize };
1608    let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
1609        kind,
1610        reason: "entry count is too large",
1611    })?;
1612    if count > cursor.remaining().checked_div(width).unwrap_or_default() {
1613        return Err(Error::Malformed {
1614            kind,
1615            reason: "entry count exceeds box body",
1616        });
1617    }
1618    let mut values = Vec::with_capacity(count);
1619    for _ in 0..count {
1620        values.push(if kind == STCO {
1621            u64::from(cursor.u32()?)
1622        } else {
1623            cursor.u64()?
1624        });
1625    }
1626    Ok(values)
1627}
1628
1629fn parse_stss(body: &[u8], sample_count: usize, track: TrackId) -> Result<Vec<u32>, Error> {
1630    let mut cursor = Cursor::new(body, STSS);
1631    let (version, _) = cursor.full_box()?;
1632    if version != 0 {
1633        return Err(Error::Malformed {
1634            kind: STSS,
1635            reason: "unsupported version",
1636        });
1637    }
1638    let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
1639        kind: STSS,
1640        reason: "entry count is too large",
1641    })?;
1642    if count > cursor.remaining() / 4 {
1643        return Err(Error::Malformed {
1644            kind: STSS,
1645            reason: "entry count exceeds box body",
1646        });
1647    }
1648    let max = u32::try_from(sample_count).unwrap_or(u32::MAX);
1649    let mut previous = 0u32;
1650    let mut values = Vec::with_capacity(count);
1651    for _ in 0..count {
1652        let value = cursor.u32()?;
1653        if value == 0 || value > max || value <= previous {
1654            return Err(Error::InvalidSampleTable {
1655                track,
1656                reason: "sync samples are not ascending within range",
1657            });
1658        }
1659        previous = value;
1660        values.push(value);
1661    }
1662    Ok(values)
1663}
1664
1665#[allow(clippy::too_many_arguments)]
1666fn expand_samples(
1667    track: TrackId,
1668    sizes: &[u32],
1669    timings: &[(u32, u32)],
1670    compositions: Option<&[(u32, i64)]>,
1671    mappings: &[StscEntry],
1672    chunks: &[u64],
1673    sync: Option<&[u32]>,
1674    description_count: usize,
1675) -> Result<Vec<SampleInfo>, Error> {
1676    let sample_count = sizes.len();
1677    let timing_count = timings.iter().try_fold(0u64, |sum, (count, _)| {
1678        sum.checked_add(u64::from(*count))
1679            .ok_or(Error::InvalidSampleTable {
1680                track,
1681                reason: "time table count overflow",
1682            })
1683    })?;
1684    if timing_count != u64::try_from(sample_count).unwrap_or(u64::MAX) {
1685        return Err(Error::InvalidSampleTable {
1686            track,
1687            reason: "time table count differs from sample count",
1688        });
1689    }
1690    if let Some(values) = compositions {
1691        let count = values.iter().try_fold(0u64, |sum, (entry_count, _)| {
1692            sum.checked_add(u64::from(*entry_count))
1693                .ok_or(Error::InvalidSampleTable {
1694                    track,
1695                    reason: "composition table count overflow",
1696                })
1697        })?;
1698        if count != u64::try_from(sample_count).unwrap_or(u64::MAX) {
1699            return Err(Error::InvalidSampleTable {
1700                track,
1701                reason: "composition table count differs from sample count",
1702            });
1703        }
1704    }
1705    if sample_count == 0 && chunks.is_empty() {
1706        return Ok(Vec::new());
1707    }
1708    if mappings.is_empty() || mappings.first().map(|entry| entry.first_chunk) != Some(1) {
1709        return Err(Error::InvalidSampleTable {
1710            track,
1711            reason: "sample-to-chunk table must start at chunk one",
1712        });
1713    }
1714    for pair in mappings.windows(2) {
1715        let first = pair.first().copied().ok_or(Error::InvalidSampleTable {
1716            track,
1717            reason: "sample-to-chunk entry is missing",
1718        })?;
1719        let second = pair.get(1).copied().ok_or(Error::InvalidSampleTable {
1720            track,
1721            reason: "sample-to-chunk entry is missing",
1722        })?;
1723        if second.first_chunk <= first.first_chunk {
1724            return Err(Error::InvalidSampleTable {
1725                track,
1726                reason: "sample-to-chunk starts are not strictly increasing",
1727            });
1728        }
1729    }
1730    let mut durations = Vec::with_capacity(sample_count);
1731    let mut dts_values = Vec::with_capacity(sample_count);
1732    let mut dts = 0u64;
1733    for (count, delta) in timings {
1734        for _ in 0..*count {
1735            dts_values.push(dts);
1736            durations.push(*delta);
1737            dts = dts
1738                .checked_add(u64::from(*delta))
1739                .ok_or(Error::InvalidSampleTable {
1740                    track,
1741                    reason: "decode time overflow",
1742                })?;
1743        }
1744    }
1745    let mut cts_values = Vec::with_capacity(sample_count);
1746    if let Some(values) = compositions {
1747        for (count, offset) in values {
1748            for _ in 0..*count {
1749                cts_values.push(*offset);
1750            }
1751        }
1752    } else {
1753        cts_values.resize(sample_count, 0);
1754    }
1755    let mut samples = Vec::with_capacity(sample_count);
1756    let mut sample_index = 0usize;
1757    let mut mapping_index = 0usize;
1758    for (chunk_index, chunk_offset) in chunks.iter().copied().enumerate() {
1759        let chunk_number = u32::try_from(chunk_index)
1760            .unwrap_or(u32::MAX)
1761            .saturating_add(1);
1762        while let Some(next) = mappings.get(mapping_index.saturating_add(1)) {
1763            if next.first_chunk > chunk_number {
1764                break;
1765            }
1766            mapping_index = mapping_index.saturating_add(1);
1767        }
1768        let mapping = mappings
1769            .get(mapping_index)
1770            .copied()
1771            .ok_or(Error::InvalidSampleTable {
1772                track,
1773                reason: "sample-to-chunk mapping is missing",
1774            })?;
1775        if mapping.samples_per_chunk == 0 {
1776            return Err(Error::InvalidSampleTable {
1777                track,
1778                reason: "samples per chunk is zero",
1779            });
1780        }
1781        if mapping.description_index == 0
1782            || usize::try_from(mapping.description_index).unwrap_or(usize::MAX) > description_count
1783        {
1784            return Err(Error::InvalidSampleTable {
1785                track,
1786                reason: "sample description index is out of range",
1787            });
1788        }
1789        let mut within = 0u64;
1790        for _ in 0..mapping.samples_per_chunk {
1791            let size = sizes
1792                .get(sample_index)
1793                .copied()
1794                .ok_or(Error::InvalidSampleTable {
1795                    track,
1796                    reason: "chunk mapping produces too many samples",
1797                })?;
1798            let offset = chunk_offset
1799                .checked_add(within)
1800                .ok_or(Error::InvalidSampleTable {
1801                    track,
1802                    reason: "sample offset overflow",
1803                })?;
1804            within = within
1805                .checked_add(u64::from(size))
1806                .ok_or(Error::InvalidSampleTable {
1807                    track,
1808                    reason: "sample offset overflow",
1809                })?;
1810            let dts = dts_values
1811                .get(sample_index)
1812                .copied()
1813                .ok_or(Error::InvalidSampleTable {
1814                    track,
1815                    reason: "decode time is missing",
1816                })?;
1817            let cts_offset =
1818                cts_values
1819                    .get(sample_index)
1820                    .copied()
1821                    .ok_or(Error::InvalidSampleTable {
1822                        track,
1823                        reason: "composition offset is missing",
1824                    })?;
1825            let pts = i64::try_from(dts)
1826                .ok()
1827                .and_then(|value| value.checked_add(cts_offset))
1828                .ok_or(Error::InvalidSampleTable {
1829                    track,
1830                    reason: "presentation time overflow",
1831                })?;
1832            let number = u32::try_from(sample_index)
1833                .unwrap_or(u32::MAX)
1834                .saturating_add(1);
1835            let is_sync = sync
1836                .map(|values| values.binary_search(&number).is_ok())
1837                .unwrap_or(true);
1838            samples.push(SampleInfo {
1839                index: u32::try_from(sample_index).map_err(|_| Error::InvalidSampleTable {
1840                    track,
1841                    reason: "sample index exceeds u32",
1842                })?,
1843                offset,
1844                size,
1845                dts,
1846                cts_offset,
1847                pts,
1848                duration: durations.get(sample_index).copied().ok_or(
1849                    Error::InvalidSampleTable {
1850                        track,
1851                        reason: "sample duration is missing",
1852                    },
1853                )?,
1854                is_sync,
1855                chunk: u32::try_from(chunk_index).map_err(|_| Error::InvalidSampleTable {
1856                    track,
1857                    reason: "chunk index exceeds u32",
1858                })?,
1859                description_index: mapping.description_index,
1860            });
1861            sample_index = sample_index
1862                .checked_add(1)
1863                .ok_or(Error::InvalidSampleTable {
1864                    track,
1865                    reason: "sample index overflow",
1866                })?;
1867        }
1868    }
1869    if sample_index != sample_count {
1870        return Err(Error::InvalidSampleTable {
1871            track,
1872            reason: "chunk mapping does not produce the sample count",
1873        });
1874    }
1875    Ok(samples)
1876}
1877
1878fn parse_trex(body: &[u8]) -> Result<(TrackId, Trex), Error> {
1879    let mut cursor = Cursor::new(body, TREX);
1880    let (version, _) = cursor.full_box()?;
1881    if version != 0 {
1882        return Err(Error::Malformed {
1883            kind: TREX,
1884            reason: "unsupported version",
1885        });
1886    }
1887    let track = TrackId(cursor.u32()?);
1888    Ok((
1889        track,
1890        Trex {
1891            description_index: cursor.u32()?,
1892            duration: cursor.u32()?,
1893            size: cursor.u32()?,
1894            flags: cursor.u32()?,
1895        },
1896    ))
1897}
1898
1899#[derive(Clone, Copy)]
1900struct Tfhd {
1901    track: TrackId,
1902    base_data_offset: Option<u64>,
1903    description_index: Option<u32>,
1904    duration: Option<u32>,
1905    size: Option<u32>,
1906    flags: Option<u32>,
1907    default_base_is_moof: bool,
1908}
1909
1910fn parse_tfhd(body: &[u8]) -> Result<Tfhd, Error> {
1911    let kind = FourCc(*b"tfhd");
1912    let mut cursor = Cursor::new(body, kind);
1913    let (version, flags) = cursor.full_box()?;
1914    if version != 0 {
1915        return Err(Error::Malformed {
1916            kind,
1917            reason: "unsupported version",
1918        });
1919    }
1920    let track = TrackId(cursor.u32()?);
1921    Ok(Tfhd {
1922        track,
1923        base_data_offset: if flags & 0x1 != 0 {
1924            Some(cursor.u64()?)
1925        } else {
1926            None
1927        },
1928        description_index: if flags & 0x2 != 0 {
1929            Some(cursor.u32()?)
1930        } else {
1931            None
1932        },
1933        duration: if flags & 0x8 != 0 {
1934            Some(cursor.u32()?)
1935        } else {
1936            None
1937        },
1938        size: if flags & 0x10 != 0 {
1939            Some(cursor.u32()?)
1940        } else {
1941            None
1942        },
1943        flags: if flags & 0x20 != 0 {
1944            Some(cursor.u32()?)
1945        } else {
1946            None
1947        },
1948        default_base_is_moof: flags & 0x020000 != 0,
1949    })
1950}
1951
1952#[allow(clippy::too_many_arguments)]
1953fn parse_fragment(
1954    body: &[u8],
1955    absolute_base: u64,
1956    moof_offset: u64,
1957    fragment_index: u32,
1958    defaults: &BTreeMap<TrackId, Trex>,
1959    limits: Limits,
1960    running_dts: &mut BTreeMap<TrackId, u64>,
1961    warnings: &mut Vec<Warning>,
1962) -> Result<Fragment, Error> {
1963    let boxes = children(body, MOOF, absolute_base)?;
1964    warn_unknown_children(
1965        &boxes,
1966        MOOF,
1967        &[FourCc(*b"mfhd"), FourCc(*b"traf")],
1968        warnings,
1969    );
1970    let mfhd_kind = FourCc(*b"mfhd");
1971    let mfhd = one(&boxes, mfhd_kind, MOOF, true)?.ok_or(Error::MissingBox {
1972        kind: mfhd_kind,
1973        parent: Some(MOOF),
1974    })?;
1975    let mut mfhd_cursor = Cursor::new(mfhd, mfhd_kind);
1976    let (version, _) = mfhd_cursor.full_box()?;
1977    if version != 0 {
1978        return Err(Error::Malformed {
1979            kind: mfhd_kind,
1980            reason: "unsupported version",
1981        });
1982    }
1983    let sequence_number = mfhd_cursor.u32()?;
1984    let traf_kind = FourCc(*b"traf");
1985    let mut runs = Vec::new();
1986    for (_, traf) in boxes.iter().filter(|(header, _)| header.kind == traf_kind) {
1987        let traf_boxes = children(traf, traf_kind, absolute_base)?;
1988        warn_unknown_children(
1989            &traf_boxes,
1990            traf_kind,
1991            &[FourCc(*b"tfhd"), FourCc(*b"tfdt"), FourCc(*b"trun")],
1992            warnings,
1993        );
1994        let tfhd_kind = FourCc(*b"tfhd");
1995        let tfhd = parse_tfhd(one(&traf_boxes, tfhd_kind, traf_kind, true)?.ok_or(
1996            Error::MissingBox {
1997                kind: tfhd_kind,
1998                parent: Some(traf_kind),
1999            },
2000        )?)?;
2001        let trex = defaults.get(&tfhd.track).copied().ok_or(Error::Malformed {
2002            kind: tfhd_kind,
2003            reason: "track has no fragment defaults",
2004        })?;
2005        let base = if let Some(value) = tfhd.base_data_offset {
2006            value
2007        } else if tfhd.default_base_is_moof {
2008            moof_offset
2009        } else {
2010            return Err(Error::Unsupported("implicit fragment base offsets"));
2011        };
2012        let tfdt_kind = FourCc(*b"tfdt");
2013        let mut dts = if let Some(tfdt) = one(&traf_boxes, tfdt_kind, traf_kind, false)? {
2014            let mut cursor = Cursor::new(tfdt, tfdt_kind);
2015            let (version, _) = cursor.full_box()?;
2016            match version {
2017                0 => u64::from(cursor.u32()?),
2018                1 => cursor.u64()?,
2019                _ => {
2020                    return Err(Error::Malformed {
2021                        kind: tfdt_kind,
2022                        reason: "unsupported version",
2023                    })
2024                }
2025            }
2026        } else {
2027            running_dts.get(&tfhd.track).copied().unwrap_or(0)
2028        };
2029        let trun_kind = FourCc(*b"trun");
2030        let mut prior_run_end: Option<u64> = None;
2031        for (_, trun) in traf_boxes
2032            .iter()
2033            .filter(|(header, _)| header.kind == trun_kind)
2034        {
2035            let mut cursor = Cursor::new(trun, trun_kind);
2036            let (version, flags) = cursor.full_box()?;
2037            if version > 1 {
2038                return Err(Error::Malformed {
2039                    kind: trun_kind,
2040                    reason: "unsupported version",
2041                });
2042            }
2043            let count = usize::try_from(cursor.u32()?).map_err(|_| Error::Malformed {
2044                kind: trun_kind,
2045                reason: "sample count is too large",
2046            })?;
2047            let fields = ((flags & 0x100 != 0) as usize)
2048                .saturating_add((flags & 0x200 != 0) as usize)
2049                .saturating_add((flags & 0x400 != 0) as usize)
2050                .saturating_add((flags & 0x800 != 0) as usize);
2051            let minimum = count
2052                .checked_mul(fields.saturating_mul(4))
2053                .ok_or(Error::Malformed {
2054                    kind: trun_kind,
2055                    reason: "sample field size overflow",
2056                })?;
2057            if minimum > cursor.remaining() {
2058                return Err(Error::Malformed {
2059                    kind: trun_kind,
2060                    reason: "sample count exceeds box body",
2061                });
2062            }
2063            check_sample_count(count, limits)?;
2064            let data_offset = if flags & 0x1 != 0 {
2065                Some(cursor.i32()?)
2066            } else {
2067                None
2068            };
2069            let first_flags = if flags & 0x4 != 0 {
2070                Some(cursor.u32()?)
2071            } else {
2072                None
2073            };
2074            let run_start = if let Some(offset) = data_offset {
2075                if offset >= 0 {
2076                    base.checked_add(u64::try_from(offset).unwrap_or_default())
2077                } else {
2078                    base.checked_sub(u64::from(offset.unsigned_abs()))
2079                }
2080                .ok_or(Error::Malformed {
2081                    kind: trun_kind,
2082                    reason: "data offset overflow",
2083                })?
2084            } else {
2085                prior_run_end.unwrap_or(base)
2086            };
2087            let mut offset = run_start;
2088            let mut samples = Vec::with_capacity(count);
2089            for index in 0..count {
2090                let duration = if flags & 0x100 != 0 {
2091                    cursor.u32()?
2092                } else {
2093                    tfhd.duration.unwrap_or(trex.duration)
2094                };
2095                let size = if flags & 0x200 != 0 {
2096                    cursor.u32()?
2097                } else {
2098                    tfhd.size.unwrap_or(trex.size)
2099                };
2100                let sample_flags = if flags & 0x400 != 0 {
2101                    cursor.u32()?
2102                } else if index == 0 {
2103                    first_flags.unwrap_or(tfhd.flags.unwrap_or(trex.flags))
2104                } else {
2105                    tfhd.flags.unwrap_or(trex.flags)
2106                };
2107                let cts_offset = if flags & 0x800 != 0 {
2108                    if version == 1 {
2109                        i64::from(cursor.i32()?)
2110                    } else {
2111                        i64::from(cursor.u32()?)
2112                    }
2113                } else {
2114                    0
2115                };
2116                let pts = i64::try_from(dts)
2117                    .ok()
2118                    .and_then(|value| value.checked_add(cts_offset))
2119                    .ok_or(Error::Malformed {
2120                        kind: trun_kind,
2121                        reason: "presentation time overflow",
2122                    })?;
2123                samples.push(SampleInfo {
2124                    index: u32::try_from(index).map_err(|_| Error::Malformed {
2125                        kind: trun_kind,
2126                        reason: "sample index exceeds u32",
2127                    })?,
2128                    offset,
2129                    size,
2130                    dts,
2131                    cts_offset,
2132                    pts,
2133                    duration,
2134                    is_sync: sample_flags & 0x1_0000 == 0,
2135                    chunk: fragment_index,
2136                    description_index: tfhd.description_index.unwrap_or(trex.description_index),
2137                });
2138                offset = offset
2139                    .checked_add(u64::from(size))
2140                    .ok_or(Error::Malformed {
2141                        kind: trun_kind,
2142                        reason: "sample offset overflow",
2143                    })?;
2144                dts = dts
2145                    .checked_add(u64::from(duration))
2146                    .ok_or(Error::Malformed {
2147                        kind: trun_kind,
2148                        reason: "decode time overflow",
2149                    })?;
2150            }
2151            prior_run_end = Some(offset);
2152            runs.push(TrackRun {
2153                track: tfhd.track,
2154                samples,
2155            });
2156        }
2157        running_dts.insert(tfhd.track, dts);
2158    }
2159    Ok(Fragment {
2160        sequence_number,
2161        moof_offset,
2162        runs,
2163    })
2164}
2165
2166fn take_c_string(data: &[u8], position: &mut usize, kind: FourCc) -> Result<String, Error> {
2167    let tail = data.get(*position..).ok_or(Error::Malformed {
2168        kind,
2169        reason: "string offset is outside box",
2170    })?;
2171    let end = tail
2172        .iter()
2173        .position(|byte| *byte == 0)
2174        .ok_or(Error::Malformed {
2175            kind,
2176            reason: "unterminated string",
2177        })?;
2178    let bytes = tail.get(..end).ok_or(Error::Malformed {
2179        kind,
2180        reason: "string is outside box",
2181    })?;
2182    *position = position
2183        .checked_add(end.saturating_add(1))
2184        .ok_or(Error::Malformed {
2185            kind,
2186            reason: "string offset overflow",
2187        })?;
2188    String::from_utf8(bytes.to_vec()).map_err(|_| Error::Malformed {
2189        kind,
2190        reason: "string is not UTF-8",
2191    })
2192}
2193
2194fn parse_event(body: &[u8]) -> Result<Event, Error> {
2195    let mut cursor = Cursor::new(body, EMSG);
2196    let (version, _) = cursor.full_box()?;
2197    match version {
2198        0 => {
2199            let mut position = cursor.position();
2200            let scheme_id_uri = take_c_string(body, &mut position, EMSG)?;
2201            let value = take_c_string(body, &mut position, EMSG)?;
2202            let mut rest = Cursor::new(
2203                body.get(position..).ok_or(Error::Malformed {
2204                    kind: EMSG,
2205                    reason: "event fields are truncated",
2206                })?,
2207                EMSG,
2208            );
2209            let timescale = rest.u32()?;
2210            let presentation_time = u64::from(rest.u32()?);
2211            let event_duration = rest.u32()?;
2212            let id = rest.u32()?;
2213            Ok(Event {
2214                scheme_id_uri,
2215                value,
2216                timescale,
2217                presentation_time,
2218                event_duration,
2219                id,
2220                message_data: rest.rest().to_vec(),
2221            })
2222        }
2223        1 => {
2224            let timescale = cursor.u32()?;
2225            let presentation_time = cursor.u64()?;
2226            let event_duration = cursor.u32()?;
2227            let id = cursor.u32()?;
2228            let mut position = cursor.position();
2229            let scheme_id_uri = take_c_string(body, &mut position, EMSG)?;
2230            let value = take_c_string(body, &mut position, EMSG)?;
2231            let message_data = body.get(position..).unwrap_or_default().to_vec();
2232            Ok(Event {
2233                scheme_id_uri,
2234                value,
2235                timescale,
2236                presentation_time,
2237                event_duration,
2238                id,
2239                message_data,
2240            })
2241        }
2242        _ => Err(Error::Malformed {
2243            kind: EMSG,
2244            reason: "unsupported version",
2245        }),
2246    }
2247}
2248
2249fn parse_metadata(boxes: &[(BoxHeader, &[u8])], absolute_base: u64) -> Result<Metadata, Error> {
2250    let udta_kind = FourCc(*b"udta");
2251    let meta_kind = FourCc(*b"meta");
2252    let ilst_kind = FourCc(*b"ilst");
2253    let Some(udta) = one(boxes, udta_kind, MOOV, false)? else {
2254        return Ok(Metadata::default());
2255    };
2256    let udta_boxes = children(udta, udta_kind, absolute_base)?;
2257    let Some(meta) = one(&udta_boxes, meta_kind, udta_kind, false)? else {
2258        return Ok(Metadata::default());
2259    };
2260    let nested = meta.get(4..).ok_or(Error::Malformed {
2261        kind: meta_kind,
2262        reason: "missing full-box fields",
2263    })?;
2264    let meta_boxes = children(nested, meta_kind, absolute_base)?;
2265    let Some(ilst) = one(&meta_boxes, ilst_kind, meta_kind, false)? else {
2266        return Ok(Metadata::default());
2267    };
2268    let mut metadata = Metadata::default();
2269    for (item_header, item_body) in children(ilst, ilst_kind, absolute_base)? {
2270        let item_boxes = children(item_body, item_header.kind, absolute_base)?;
2271        let data_kind = FourCc(*b"data");
2272        let Some(data) = one(&item_boxes, data_kind, item_header.kind, false)? else {
2273            continue;
2274        };
2275        let payload = data.get(8..).ok_or(Error::Malformed {
2276            kind: data_kind,
2277            reason: "metadata value is truncated",
2278        })?;
2279        let key = match item_header.kind.0 {
2280            [0xa9, b'n', b'a', b'm'] => MetaKey::Title,
2281            [0xa9, b'A', b'R', b'T'] => MetaKey::Artist,
2282            [0xa9, b'a', b'l', b'b'] => MetaKey::Album,
2283            [b'a', b'A', b'R', b'T'] => MetaKey::AlbumArtist,
2284            [0xa9, b'w', b'r', b't'] => MetaKey::Composer,
2285            [0xa9, b'g', b'e', b'n'] => MetaKey::Genre,
2286            [0xa9, b'c', b'm', b't'] => MetaKey::Comment,
2287            [0xa9, b'd', b'a', b'y'] => MetaKey::Date,
2288            _ => MetaKey::Other(item_header.kind),
2289        };
2290        let value = match String::from_utf8(payload.to_vec()) {
2291            Ok(text) => MetaValue::Text(text),
2292            Err(_) => MetaValue::Binary(payload.to_vec()),
2293        };
2294        metadata.entries.push((key, value));
2295    }
2296    Ok(metadata)
2297}