Skip to main content

pito_codecs/mp4/
reader.rs

1use crate::media::{Codec, Error, Packet, Result};
2use crate::mp4::boxes::{Cur, FourCc, boxes, find, parse_box_header};
3use crate::mp4::entry::{CodecConfig, SampleEntry, parse_sample_entry};
4use crate::mp4::source::ByteSource;
5use crate::mp4::{too_large, truncated};
6use std::collections::HashMap;
7use std::fs::File;
8use std::num::NonZeroU64;
9use std::path::Path;
10
11const MAX_SAMPLES: usize = 1 << 26;
12const MAX_EMPTY_SAMPLES: u64 = 1 << 16;
13const MAX_HEADER_BOX: u64 = 1 << 30;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
16pub struct Sample {
17    pub offset: u64,
18    pub size: u32,
19    pub dts: i64,
20    pub pts: i64,
21    pub duration: u32,
22    pub keyframe: bool,
23    pub entry: u32,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct Edit {
28    pub segment_duration: u64,
29    pub media_time: i64,
30    pub media_rate: i32,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum TrackKind {
35    Video,
36    Audio,
37    Subtitle,
38    Other,
39}
40
41#[derive(Clone, Debug, PartialEq)]
42pub struct Track {
43    pub id: u32,
44    pub kind: TrackKind,
45    pub handler: FourCc,
46    pub name: String,
47    pub timescale: u32,
48    pub duration: u64,
49    pub language: String,
50    pub width: u32,
51    pub height: u32,
52    pub enabled: bool,
53    pub matrix: [i32; 9],
54    pub entries: Vec<SampleEntry>,
55    pub samples: Vec<Sample>,
56    pub edits: Vec<Edit>,
57    pub edit_offset: i64,
58    sync_index: Vec<u32>,
59    pts_order: Vec<u32>,
60}
61
62impl Track {
63    pub fn entry(&self) -> Option<&SampleEntry> {
64        self.entries.first()
65    }
66
67    pub fn config(&self) -> Option<&CodecConfig> {
68        self.entry().map(|e| &e.codec)
69    }
70
71    pub fn codec(&self) -> Option<Codec> {
72        match self.config()? {
73            CodecConfig::Avc(_) => Some(Codec::H264),
74            CodecConfig::Hevc(_) => Some(Codec::Hevc),
75            CodecConfig::Vp9(_) => Some(Codec::Vp9),
76            CodecConfig::Aac(_) => Some(Codec::Aac),
77            _ => None,
78        }
79    }
80
81    pub fn extradata(&self) -> Option<Vec<u8>> {
82        self.config()?.extradata()
83    }
84
85    pub fn rotation(&self) -> u16 {
86        let [a, b, _, c, d, _, _, _, _] = self.matrix;
87        match (a.signum(), b.signum(), c.signum(), d.signum()) {
88            (0, 1, -1, 0) => 90,
89            (-1, 0, 0, -1) => 180,
90            (0, -1, 1, 0) => 270,
91            _ => 0,
92        }
93    }
94
95    pub fn channels(&self) -> u32 {
96        match self.config() {
97            Some(CodecConfig::Aac(a)) if a.channels() > 0 => a.channels(),
98            _ => self.entry().map_or(0, |e| {
99                e.ac3_channels()
100                    .or(e.audio.as_ref().map(|a| a.channels))
101                    .unwrap_or(0)
102            }),
103        }
104    }
105
106    pub fn sample_rate(&self) -> u32 {
107        match self.config() {
108            Some(CodecConfig::Aac(a)) if a.sample_rate > 0 => a.sample_rate,
109            _ => self
110                .entry()
111                .and_then(|e| e.audio.as_ref())
112                .map_or(0, |a| a.sample_rate.round() as u32),
113        }
114    }
115
116    pub fn seconds(&self, ticks: i64) -> f64 {
117        if self.timescale == 0 {
118            0.0
119        } else {
120            ticks as f64 / self.timescale as f64
121        }
122    }
123
124    pub fn ticks(&self, seconds: f64) -> i64 {
125        (seconds * self.timescale as f64).round() as i64
126    }
127
128    pub fn duration_seconds(&self) -> f64 {
129        let edited: u64 = self
130            .edits
131            .iter()
132            .filter(|e| e.media_time >= 0)
133            .fold(0u64, |a, e| a.saturating_add(e.segment_duration));
134        let media = self.seconds(self.duration.min(i64::MAX as u64) as i64);
135        if edited == 0 {
136            media
137        } else {
138            media.min(self.seconds(self.media_span()))
139        }
140    }
141
142    fn media_span(&self) -> i64 {
143        let end = self
144            .samples
145            .iter()
146            .map(|s| s.pts.saturating_add(s.duration as i64))
147            .max()
148            .unwrap_or(0);
149        end.saturating_add(self.edit_offset).max(0)
150    }
151
152    pub fn bitrate(&self) -> u32 {
153        let declared = match self.config() {
154            Some(CodecConfig::Aac(a)) if a.avg_bitrate > 0 => a.avg_bitrate,
155            _ => self
156                .entry()
157                .and_then(|e| e.bitrate)
158                .map_or(0, |b| b.avg_bitrate),
159        };
160        if declared > 0 {
161            return declared;
162        }
163        let bytes: u64 = self.samples.iter().map(|s| s.size as u64).sum();
164        let ticks: u64 = self.samples.iter().map(|s| s.duration as u64).sum();
165        if ticks == 0 || self.timescale == 0 {
166            return 0;
167        }
168        (bytes as u128 * 8 * self.timescale as u128 / ticks as u128).min(u32::MAX as u128) as u32
169    }
170
171    pub fn fps(&self) -> f64 {
172        let ticks: u64 = self.samples.iter().map(|s| s.duration as u64).sum();
173        if ticks == 0 || self.timescale == 0 {
174            0.0
175        } else {
176            self.samples.len() as f64 * self.timescale as f64 / ticks as f64
177        }
178    }
179
180    pub fn presentation(&self, index: usize) -> Option<i64> {
181        self.samples
182            .get(index)
183            .map(|s| s.pts.saturating_add(self.edit_offset))
184    }
185
186    pub fn sample_at(&self, presentation: i64) -> Option<usize> {
187        let pts = presentation.saturating_sub(self.edit_offset);
188        if self.pts_order.is_empty() {
189            return None;
190        }
191        let k = self
192            .pts_order
193            .partition_point(|&i| self.samples[i as usize].pts <= pts);
194        Some(self.pts_order[k.saturating_sub(1)] as usize)
195    }
196
197    pub fn keyframe_before(&self, index: usize) -> usize {
198        let k = self.sync_index.partition_point(|&i| i as usize <= index);
199        if k == 0 {
200            0
201        } else {
202            self.sync_index[k - 1] as usize
203        }
204    }
205
206    pub fn keyframes(&self) -> &[u32] {
207        &self.sync_index
208    }
209
210    fn finish_indexes(&mut self) {
211        self.sync_index = self
212            .samples
213            .iter()
214            .enumerate()
215            .filter(|(_, s)| s.keyframe)
216            .map(|(i, _)| i as u32)
217            .collect();
218        let mut order: Vec<u32> = (0..self.samples.len() as u32).collect();
219        order.sort_by_key(|&i| (self.samples[i as usize].pts, i));
220        self.pts_order = order;
221    }
222}
223
224pub struct Reader<S: ByteSource = File> {
225    source: S,
226    len: u64,
227    pub brand: FourCc,
228    pub quicktime: bool,
229    pub timescale: u32,
230    pub duration: u64,
231    pub tracks: Vec<Track>,
232    pub fragmented: bool,
233}
234
235impl Reader<File> {
236    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
237        let path = path.as_ref();
238        File::open(path)
239            .map_err(Error::from)
240            .and_then(Self::new)
241            .map_err(|e| e.in_file(path))
242    }
243}
244
245impl<S: ByteSource> Reader<S> {
246    pub fn new(source: S) -> Result<Self> {
247        open(source)
248    }
249
250    pub fn source(&self) -> &S {
251        &self.source
252    }
253
254    pub fn duration_seconds(&self) -> f64 {
255        let movie = if self.timescale == 0 {
256            0.0
257        } else {
258            self.duration as f64 / self.timescale as f64
259        };
260        let tracks = self
261            .tracks
262            .iter()
263            .map(Track::duration_seconds)
264            .fold(0.0, f64::max);
265        if movie > 0.0 { movie } else { tracks }
266    }
267
268    pub fn video(&self) -> Option<usize> {
269        self.tracks.iter().position(|t| t.kind == TrackKind::Video)
270    }
271
272    pub fn audio(&self) -> Vec<usize> {
273        self.tracks
274            .iter()
275            .enumerate()
276            .filter(|(_, t)| t.kind == TrackKind::Audio)
277            .map(|(i, _)| i)
278            .collect()
279    }
280
281    pub fn read(&self, track: usize, index: usize) -> Result<Vec<u8>> {
282        let mut data = Vec::new();
283        self.read_into(track, index, &mut data)?;
284        Ok(data)
285    }
286
287    pub fn read_into(&self, track: usize, index: usize, data: &mut Vec<u8>) -> Result<()> {
288        let t = self
289            .tracks
290            .get(track)
291            .ok_or_else(|| Error::Invalid(format!("no track {track}")))?;
292        let s = t
293            .samples
294            .get(index)
295            .ok_or_else(|| Error::Invalid(format!("no sample {index} in track {track}")))?;
296        read_range(&self.source, self.len, s.offset, s.size as u64, data)
297    }
298
299    pub fn read_span(&self, offset: u64, data: &mut Vec<u8>) -> Result<()> {
300        read_range(&self.source, self.len, offset, data.len() as u64, data)
301    }
302
303    pub fn packet(&self, track: usize, index: usize) -> Result<Packet> {
304        let data = self.read(track, index)?;
305        let (t, s) = self
306            .tracks
307            .get(track)
308            .and_then(|t| Some((t, *t.samples.get(index)?)))
309            .ok_or_else(|| Error::Invalid(format!("no sample {index} in track {track}")))?;
310        Ok(Packet {
311            data,
312            pts: s.pts.saturating_add(t.edit_offset),
313            dts: s.dts.saturating_add(t.edit_offset),
314            keyframe: s.keyframe,
315        })
316    }
317}
318
319fn times(c: &mut Cur, v: u8) -> Result<(u32, u64)> {
320    c.skip(if v == 1 { 16 } else { 8 })?;
321    let timescale = c.u32()?;
322    let duration = if v == 1 { c.u64()? } else { c.u32()? as u64 };
323    Ok((timescale, duration))
324}
325
326fn read_range<S: ByteSource + ?Sized>(
327    src: &S,
328    len: u64,
329    offset: u64,
330    size: u64,
331    data: &mut Vec<u8>,
332) -> Result<()> {
333    let end = offset
334        .checked_add(size)
335        .ok_or_else(|| truncated("sample"))?;
336    if end > len {
337        return Err(truncated("sample data"));
338    }
339    data.resize(size as usize, 0);
340    src.read_at(offset, data)?;
341    Ok(())
342}
343
344fn parse_name(udta: &[u8]) -> Option<String> {
345    for b in boxes(udta).map_while(|b| b.ok()) {
346        let text = if b.kind == b"name" {
347            b.payload
348        } else if b.kind == [0xA9, b'n', b'a', b'm'] && b.payload.len() >= 4 {
349            let n = u16::from_be_bytes([b.payload[0], b.payload[1]]) as usize;
350            b.payload.get(4..4 + n).unwrap_or(&[])
351        } else {
352            continue;
353        };
354        let text = text.split(|&c| c == 0).next().unwrap_or(&[]);
355        if !text.is_empty() {
356            return Some(String::from_utf8_lossy(text).into_owned());
357        }
358    }
359    None
360}
361
362fn open<S: ByteSource>(src: S) -> Result<Reader<S>> {
363    let len = src.len();
364    let mut offset = 0u64;
365    let mut ftyp: Option<Vec<u8>> = None;
366    let mut moov: Option<(u64, Vec<u8>)> = None;
367    let mut moofs: Vec<(u64, Vec<u8>)> = Vec::new();
368    while len.saturating_sub(offset) >= 8 {
369        let n = (len - offset).min(32) as usize;
370        let mut hb = [0u8; 32];
371        src.read_at(offset, &mut hb[..n])?;
372        let h = parse_box_header(&hb[..n])?;
373        let size = h.size.unwrap_or(len - offset);
374        let end = offset.saturating_add(size).min(len);
375        let body_len = end.saturating_sub(offset + h.header_len);
376        let body = |max: u64| -> Result<Vec<u8>> {
377            if body_len > max {
378                return Err(too_large("header box"));
379            }
380            let mut data = Vec::new();
381            read_range(&src, len, offset + h.header_len, body_len, &mut data)?;
382            Ok(data)
383        };
384        match &h.kind.0 {
385            b"ftyp" if ftyp.is_none() => ftyp = Some(body(1 << 16)?),
386            b"moov" if moov.is_none() => moov = Some((offset, body(MAX_HEADER_BOX)?)),
387            b"moof" => moofs.push((offset, body(MAX_HEADER_BOX)?)),
388            _ => {}
389        }
390        if h.size.is_none() {
391            break;
392        }
393        offset = offset.saturating_add(size);
394    }
395    let (_, moov) = moov.ok_or_else(|| Error::Invalid("no moov box".into()))?;
396    let mut file = Reader {
397        source: src,
398        len,
399        brand: FourCc::default(),
400        quicktime: true,
401        timescale: 0,
402        duration: 0,
403        tracks: Vec::new(),
404        fragmented: false,
405    };
406    if let Some(f) = &ftyp {
407        let mut c = Cur::new(f, "ftyp");
408        file.brand = c.fourcc()?;
409        file.quicktime = file.brand == b"qt  ";
410    }
411    let mut trex: HashMap<u32, Trex> = HashMap::new();
412    let mut trak_payloads = Vec::new();
413    for b in boxes(&moov) {
414        let b = b?;
415        match &b.kind.0 {
416            b"cmov" => return Err(Error::Unsupported("compressed movie header (cmov)".into())),
417            b"mvhd" => {
418                let mut c = Cur::new(b.payload, "mvhd");
419                let (v, _) = c.full_header()?;
420                (file.timescale, file.duration) = times(&mut c, v)?;
421            }
422            b"trak" => trak_payloads.push(b.payload),
423            b"mvex" => {
424                file.fragmented = true;
425                for m in boxes(b.payload).map_while(|b| b.ok()) {
426                    if m.kind == b"trex" {
427                        let mut c = Cur::new(m.payload, "trex");
428                        c.full_header()?;
429                        let id = c.u32()?;
430                        trex.insert(
431                            id,
432                            Trex {
433                                description_index: c.u32()?,
434                                duration: c.u32()?,
435                                size: c.u32()?,
436                                flags: c.u32()?,
437                            },
438                        );
439                    }
440                }
441            }
442            _ => {}
443        }
444    }
445    let mut room = len;
446    for p in trak_payloads {
447        let t = parse_trak(p, file.timescale, file.quicktime, len, &mut room)?;
448        file.tracks.push(t);
449    }
450    let mut next_dts: Vec<i64> = file
451        .tracks
452        .iter()
453        .map(|t| {
454            t.samples
455                .last()
456                .map(|s| s.dts.saturating_add(s.duration as i64))
457                .unwrap_or(0)
458        })
459        .collect();
460    for (moof_off, moof) in &moofs {
461        parse_moof(*moof_off, moof, &mut file.tracks, &trex, &mut next_dts, len)?;
462    }
463    for t in &mut file.tracks {
464        if file.fragmented && !moofs.is_empty() {
465            let d: u64 = t.samples.iter().map(|s| s.duration as u64).sum();
466            t.duration = t.duration.max(d);
467            if t.timescale > 0 && file.timescale > 0 {
468                let md = (t.duration as u128 * file.timescale as u128 / t.timescale as u128) as u64;
469                file.duration = file.duration.max(md);
470            }
471        }
472        t.finish_indexes();
473    }
474    Ok(file)
475}
476
477#[derive(Clone, Copy, Debug, Default)]
478struct Trex {
479    description_index: u32,
480    duration: u32,
481    size: u32,
482    flags: u32,
483}
484
485fn parse_language(code: u16) -> String {
486    if code < 0x400 {
487        return if code == 0 {
488            "eng".into()
489        } else {
490            "und".into()
491        };
492    }
493    let c = |s: u16| (((code >> s) & 0x1F) as u8 + 0x60) as char;
494    let s: String = [c(10), c(5), c(0)].into_iter().collect();
495    if s.chars().all(|ch| ch.is_ascii_lowercase()) {
496        s
497    } else {
498        "und".into()
499    }
500}
501
502fn rescale(v: i128, from: u32, to: u32) -> i64 {
503    if from == 0 {
504        return 0;
505    }
506    (v * to as i128 / from as i128).clamp(i64::MIN as i128, i64::MAX as i128) as i64
507}
508
509fn parse_trak(
510    p: &[u8],
511    movie_timescale: u32,
512    quicktime: bool,
513    file_len: u64,
514    room: &mut u64,
515) -> Result<Track> {
516    let mut t = Track {
517        id: 0,
518        kind: TrackKind::Other,
519        handler: FourCc::default(),
520        name: String::new(),
521        timescale: 0,
522        duration: 0,
523        language: "und".into(),
524        width: 0,
525        height: 0,
526        enabled: true,
527        matrix: [0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x4000_0000],
528        entries: Vec::new(),
529        samples: Vec::new(),
530        edits: Vec::new(),
531        edit_offset: 0,
532        sync_index: Vec::new(),
533        pts_order: Vec::new(),
534    };
535    let mut mdia = None;
536    let mut name = None;
537    for b in boxes(p) {
538        let b = b?;
539        match &b.kind.0 {
540            b"tkhd" => {
541                let mut c = Cur::new(b.payload, "tkhd");
542                let (v, flags) = c.full_header()?;
543                t.enabled = flags & 1 != 0;
544                c.skip(if v == 1 { 16 } else { 8 })?;
545                t.id = c.u32()?;
546                c.skip(if v == 1 { 28 } else { 24 })?;
547                for m in t.matrix.iter_mut() {
548                    *m = c.i32()?;
549                }
550                t.width = c.u32()? >> 16;
551                t.height = c.u32()? >> 16;
552            }
553            b"edts" => {
554                if let Some(e) = find(b.payload, b"elst") {
555                    let mut c = Cur::new(e, "elst");
556                    let (v, _) = c.full_header()?;
557                    let n = c.count(if v == 1 { 20 } else { 12 })?;
558                    for _ in 0..n {
559                        let (segment_duration, media_time) = if v == 1 {
560                            (c.u64()?, c.i64()?)
561                        } else {
562                            (c.u32()? as u64, c.i32()? as i64)
563                        };
564                        t.edits.push(Edit {
565                            segment_duration,
566                            media_time,
567                            media_rate: c.i32()?,
568                        });
569                    }
570                }
571            }
572            b"udta" => {
573                if let Some(n) = parse_name(b.payload) {
574                    name = Some(n);
575                }
576            }
577            b"mdia" => mdia = Some(b.payload),
578            _ => {}
579        }
580    }
581    let mdia = mdia.ok_or_else(|| Error::Invalid("trak without mdia".into()))?;
582    let mut minf = None;
583    for b in boxes(mdia) {
584        let b = b?;
585        match &b.kind.0 {
586            b"mdhd" => {
587                let mut c = Cur::new(b.payload, "mdhd");
588                let (v, _) = c.full_header()?;
589                (t.timescale, t.duration) = times(&mut c, v)?;
590                t.language = parse_language(c.u16()?);
591            }
592            b"hdlr" => {
593                let mut c = Cur::new(b.payload, "hdlr");
594                c.full_header()?;
595                let _pre = c.u32()?;
596                t.handler = c.fourcc()?;
597                c.skip(12)?;
598                let name = c.rest();
599                let s = if !name.is_empty() && name[0] as usize == name.len() - 1 && name.len() > 1
600                {
601                    &name[1..]
602                } else {
603                    name.split(|&b| b == 0).next().unwrap_or(&[])
604                };
605                t.name = String::from_utf8_lossy(s).into_owned();
606            }
607            b"minf" => minf = Some(b.payload),
608            _ => {}
609        }
610    }
611    t.kind = match &t.handler.0 {
612        b"vide" => TrackKind::Video,
613        b"soun" => TrackKind::Audio,
614        b"text" | b"sbtl" | b"subt" | b"clcp" => TrackKind::Subtitle,
615        _ => TrackKind::Other,
616    };
617    let mut empty = 0i128;
618    for e in &t.edits {
619        if e.media_time == -1 {
620            empty += e.segment_duration as i128;
621        } else {
622            t.edit_offset =
623                rescale(empty, movie_timescale, t.timescale).saturating_sub(e.media_time);
624            break;
625        }
626    }
627    if let Some(stbl) = minf.and_then(|m| find(m, b"stbl")) {
628        parse_stbl(stbl, &mut t, quicktime, file_len, room)?;
629    }
630    if let Some(n) = name {
631        t.name = n;
632    }
633    Ok(t)
634}
635
636struct SttsCursor<'a> {
637    runs: &'a [(u32, u32)],
638    run: usize,
639    used: u32,
640}
641
642impl SttsCursor<'_> {
643    fn take(&mut self, mut n: u64) -> u64 {
644        let mut total = 0u64;
645        while n > 0 && self.run < self.runs.len() {
646            let (count, delta) = self.runs[self.run];
647            let avail = (count - self.used) as u64;
648            let k = avail.min(n);
649            total = total.saturating_add(k.saturating_mul(delta as u64));
650            n -= k;
651            self.used += k as u32;
652            if self.used >= count {
653                self.run += 1;
654                self.used = 0;
655            }
656        }
657        total
658    }
659    fn next_value(&mut self) -> u32 {
660        while self.run < self.runs.len() && self.runs[self.run].0 == 0 {
661            self.run += 1;
662        }
663        let Some(&(_, d)) = self.runs.get(self.run) else {
664            return 0;
665        };
666        self.take(1);
667        d
668    }
669}
670
671fn chunk_capacity(stsc: &[(u32, u32, u32)], chunks: usize) -> u64 {
672    let end = chunks as u64 + 1;
673    stsc.iter()
674        .enumerate()
675        .map(|(i, &(first, spc, _))| {
676            let next = stsc.get(i + 1).map_or(end, |n| (n.0 as u64).min(end));
677            next.saturating_sub(first as u64).saturating_mul(spc as u64)
678        })
679        .fold(0u64, u64::saturating_add)
680}
681
682fn parse_stbl(
683    stbl: &[u8],
684    t: &mut Track,
685    is_qt: bool,
686    file_len: u64,
687    room: &mut u64,
688) -> Result<()> {
689    let mut stts: Vec<(u32, u32)> = Vec::new();
690    let mut ctts: Vec<(u32, u32)> = Vec::new();
691    let mut stss: Option<Vec<u32>> = None;
692    let mut stsc: Vec<(u32, u32, u32)> = Vec::new();
693    let mut const_size = 0u32;
694    let mut sample_count = 0usize;
695    let mut sizes: Vec<u32> = Vec::new();
696    let mut chunks: Vec<u64> = Vec::new();
697    for b in boxes(stbl) {
698        let b = b?;
699        let mut c = Cur::new(b.payload, "stbl child");
700        match &b.kind.0 {
701            b"stsd" => {
702                c.full_header()?;
703                let n = c.count(8)?;
704                for e in boxes(c.rest()).take(n) {
705                    let e = e?;
706                    t.entries
707                        .push(parse_sample_entry(e.kind, e.payload, t.handler, is_qt)?);
708                }
709            }
710            b"stts" => {
711                c.full_header()?;
712                let n = c.count(8)?;
713                stts = (0..n)
714                    .map(|_| Ok((c.u32()?, c.u32()?)))
715                    .collect::<Result<_>>()?;
716            }
717            b"ctts" => {
718                c.full_header()?;
719                let n = c.count(8)?;
720                ctts = (0..n)
721                    .map(|_| Ok((c.u32()?, c.u32()?)))
722                    .collect::<Result<_>>()?;
723            }
724            b"stss" => {
725                c.full_header()?;
726                let n = c.count(4)?;
727                let mut v: Vec<u32> = (0..n).map(|_| c.u32()).collect::<Result<_>>()?;
728                v.sort_unstable();
729                stss = Some(v);
730            }
731            b"stsc" => {
732                c.full_header()?;
733                let n = c.count(12)?;
734                stsc = (0..n)
735                    .map(|_| Ok((c.u32()?, c.u32()?, c.u32()?)))
736                    .collect::<Result<_>>()?;
737            }
738            b"stsz" => {
739                c.full_header()?;
740                const_size = c.u32()?;
741                let n = c.u32()? as usize;
742                match NonZeroU64::new(const_size as u64) {
743                    None => {
744                        c.check_count(n, 4)?;
745                        sizes = (0..n).map(|_| c.u32()).collect::<Result<_>>()?;
746                        sample_count = n;
747                    }
748                    Some(size) => {
749                        sample_count =
750                            usize::try_from((n as u64).min(file_len / size)).unwrap_or(usize::MAX);
751                    }
752                }
753            }
754            b"stz2" => {
755                c.full_header()?;
756                let field = c.u32()? & 0xFF;
757                let n = c.u32()? as usize;
758                let bits = match field {
759                    4 | 8 | 16 => field as usize,
760                    _ => return Err(Error::Invalid(format!("stz2 field size {field}"))),
761                };
762                if n.checked_mul(bits)
763                    .is_none_or(|b| b.div_ceil(8) > c.remaining())
764                {
765                    return Err(truncated("stz2"));
766                }
767                let d = c.rest();
768                sizes = (0..n)
769                    .map(|i| match bits {
770                        4 => ((d[i / 2] >> if i % 2 == 0 { 4 } else { 0 }) & 0xF) as u32,
771                        8 => d[i] as u32,
772                        _ => u16::from_be_bytes([d[2 * i], d[2 * i + 1]]) as u32,
773                    })
774                    .collect();
775                sample_count = n;
776            }
777            b"stco" => {
778                c.full_header()?;
779                let n = c.count(4)?;
780                chunks = (0..n)
781                    .map(|_| c.u32().map(|v| v as u64))
782                    .collect::<Result<_>>()?;
783            }
784            b"co64" => {
785                c.full_header()?;
786                let n = c.count(8)?;
787                chunks = (0..n).map(|_| c.u64()).collect::<Result<_>>()?;
788            }
789            _ => {}
790        }
791    }
792    if sample_count == 0 || chunks.is_empty() || stsc.is_empty() {
793        return Ok(());
794    }
795    if stsc[0].0 == 0 || stsc.windows(2).any(|w| w[1].0 <= w[0].0) {
796        return Err(Error::Invalid(
797            "stsc first_chunk values are not increasing from 1".into(),
798        ));
799    }
800    let pcm = match t.entries.first().map(|e| &e.codec) {
801        Some(CodecConfig::Pcm(p)) if const_size != 0 => Some(*p),
802        _ => None,
803    };
804    let mut stts_c = SttsCursor {
805        runs: &stts,
806        run: 0,
807        used: 0,
808    };
809    let mut ctts_c = SttsCursor {
810        runs: &ctts,
811        run: 0,
812        used: 0,
813    };
814    let mut samples = Vec::new();
815    let mut sidx = 0usize;
816    let mut dts = 0i64;
817    let mut stsc_i = 0usize;
818    if let Some(p) = pcm {
819        let a = t.entry().and_then(|e| e.audio.clone()).unwrap_or_default();
820        let frame_bytes = |frames: u64| -> u64 {
821            if a.qt_version == 1 && a.bytes_per_frame > 0 && a.samples_per_packet > 0 {
822                frames / a.samples_per_packet as u64 * a.bytes_per_frame as u64
823            } else if const_size > 1 {
824                frames * const_size as u64
825            } else {
826                frames * p.bytes_per_frame() as u64
827            }
828        };
829        if chunks.len() > MAX_SAMPLES {
830            return Err(too_large("chunk count"));
831        }
832        samples.reserve(chunks.len());
833        for (ci, &off) in chunks.iter().enumerate() {
834            while stsc_i + 1 < stsc.len() && stsc[stsc_i + 1].0 as usize <= ci + 1 {
835                stsc_i += 1;
836            }
837            let spc = (stsc[stsc_i].1 as usize).min(sample_count - sidx);
838            if spc == 0 {
839                continue;
840            }
841            let dur = stts_c.take(spc as u64);
842            let size = frame_bytes(spc as u64).min(u32::MAX as u64) as u32;
843            samples.push(Sample {
844                offset: off,
845                size,
846                dts,
847                pts: dts,
848                duration: dur.min(u32::MAX as u64) as u32,
849                keyframe: true,
850                entry: stsc[stsc_i].2.saturating_sub(1),
851            });
852            dts = dts.saturating_add(dur.min(i64::MAX as u64) as i64);
853            sidx += spc;
854            if sidx >= sample_count {
855                break;
856            }
857        }
858    } else {
859        let capacity = chunk_capacity(&stsc, chunks.len());
860        let mut sample_count = sample_count.min(usize::try_from(capacity).unwrap_or(usize::MAX));
861        if let Some(size) = NonZeroU64::new(const_size as u64) {
862            let fit = usize::try_from(*room / size).unwrap_or(usize::MAX);
863            sample_count = sample_count.min(fit);
864            *room = room.saturating_sub((sample_count as u64).saturating_mul(size.get()));
865        }
866        if sample_count > MAX_SAMPLES {
867            return Err(too_large("sample count"));
868        }
869        samples.reserve(sample_count);
870        let mut stss_i = 0usize;
871        'chunks: for (ci, &off) in chunks.iter().enumerate() {
872            while stsc_i + 1 < stsc.len() && stsc[stsc_i + 1].0 as usize <= ci + 1 {
873                stsc_i += 1;
874            }
875            let (_, spc, sdi) = stsc[stsc_i];
876            let mut pos = off;
877            for _ in 0..spc {
878                if sidx >= sample_count {
879                    break 'chunks;
880                }
881                let size = if const_size != 0 {
882                    const_size
883                } else {
884                    *sizes.get(sidx).ok_or_else(|| truncated("stsz"))?
885                };
886                let duration = stts_c.next_value();
887                let cts = ctts_c.next_value() as i32 as i64;
888                let keyframe = match &stss {
889                    None => true,
890                    Some(v) => {
891                        while stss_i < v.len() && (v[stss_i] as usize) < sidx + 1 {
892                            stss_i += 1;
893                        }
894                        stss_i < v.len() && v[stss_i] as usize == sidx + 1
895                    }
896                };
897                samples.push(Sample {
898                    offset: pos,
899                    size,
900                    dts,
901                    pts: dts.saturating_add(cts),
902                    duration,
903                    keyframe,
904                    entry: sdi.saturating_sub(1),
905                });
906                pos = pos.saturating_add(size as u64);
907                dts = dts.saturating_add(duration as i64);
908                sidx += 1;
909            }
910        }
911    }
912    t.samples = samples;
913    Ok(())
914}
915
916fn parse_moof(
917    moof_off: u64,
918    moof: &[u8],
919    tracks: &mut [Track],
920    trex: &HashMap<u32, Trex>,
921    next_dts: &mut [i64],
922    file_len: u64,
923) -> Result<()> {
924    let mut prev_end: Option<u64> = None;
925    for b in boxes(moof) {
926        let b = b?;
927        if b.kind != b"traf" {
928            continue;
929        }
930        let mut tfhd = None;
931        let mut tfdt = None;
932        let mut truns = Vec::new();
933        for c in boxes(b.payload) {
934            let c = c?;
935            match &c.kind.0 {
936                b"tfhd" => tfhd = Some(c.payload),
937                b"tfdt" => tfdt = Some(c.payload),
938                b"trun" => truns.push(c.payload),
939                _ => {}
940            }
941        }
942        let tfhd = tfhd.ok_or_else(|| Error::Invalid("traf without tfhd".into()))?;
943        let mut c = Cur::new(tfhd, "tfhd");
944        let (_, fl) = c.full_header()?;
945        let id = c.u32()?;
946        let Some(ti) = tracks.iter().position(|t| t.id == id) else {
947            continue;
948        };
949        let d = trex.get(&id).copied().unwrap_or_default();
950        let base = if fl & 1 != 0 {
951            c.u64()?
952        } else if fl & 0x20000 != 0 {
953            moof_off
954        } else {
955            prev_end.unwrap_or(moof_off)
956        };
957        let sdi = if fl & 2 != 0 {
958            c.u32()?
959        } else {
960            d.description_index
961        };
962        let def_dur = if fl & 8 != 0 { c.u32()? } else { d.duration };
963        let def_size = if fl & 0x10 != 0 { c.u32()? } else { d.size };
964        let def_flags = if fl & 0x20 != 0 { c.u32()? } else { d.flags };
965        if let Some(p) = tfdt {
966            let mut c = Cur::new(p, "tfdt");
967            let (v, _) = c.full_header()?;
968            let t = if v == 1 { c.u64()? } else { c.u32()? as u64 };
969            next_dts[ti] = t.min(i64::MAX as u64) as i64;
970        }
971        let mut data_pos = base;
972        for tr in truns {
973            let mut c = Cur::new(tr, "trun");
974            let (v, tf) = c.full_header()?;
975            let per = [0x100u32, 0x200, 0x400, 0x800]
976                .iter()
977                .filter(|&&f| tf & f != 0)
978                .count()
979                * 4;
980            let n = c.u32()? as usize;
981            let total = (tracks[ti].samples.len() as u64).saturating_add(n as u64);
982            if per > 0 {
983                c.check_count(n, per)?;
984            } else if def_size == 0 {
985                if total > (file_len / 64).max(MAX_EMPTY_SAMPLES) {
986                    return Err(too_large("trun sample count"));
987                }
988            } else if total.saturating_mul(def_size as u64) > file_len {
989                return Err(too_large("trun sample count"));
990            }
991            if tracks[ti].samples.len() + n > MAX_SAMPLES {
992                return Err(too_large("sample count"));
993            }
994            if tf & 1 != 0 {
995                data_pos = base.wrapping_add_signed(c.i32()? as i64);
996            }
997            let first_flags = if tf & 4 != 0 { Some(c.u32()?) } else { None };
998            let t = &mut tracks[ti];
999            t.samples.reserve(n);
1000            for i in 0..n {
1001                let dur = if tf & 0x100 != 0 { c.u32()? } else { def_dur };
1002                let size = if tf & 0x200 != 0 { c.u32()? } else { def_size };
1003                let flags = if tf & 0x400 != 0 { c.u32()? } else { def_flags };
1004                let flags = if i == 0 {
1005                    first_flags.unwrap_or(flags)
1006                } else {
1007                    flags
1008                };
1009                let cto = if tf & 0x800 != 0 {
1010                    let raw = c.u32()?;
1011                    if v == 0 {
1012                        raw as i64
1013                    } else {
1014                        raw as i32 as i64
1015                    }
1016                } else {
1017                    0
1018                };
1019                let dts = next_dts[ti];
1020                t.samples.push(Sample {
1021                    offset: data_pos,
1022                    size,
1023                    dts,
1024                    pts: dts.saturating_add(cto),
1025                    duration: dur,
1026                    keyframe: flags & 0x1_0000 == 0,
1027                    entry: sdi.saturating_sub(1),
1028                });
1029                data_pos = data_pos.saturating_add(size as u64);
1030                next_dts[ti] = dts.saturating_add(dur as i64);
1031            }
1032        }
1033        prev_end = Some(data_pos);
1034    }
1035    Ok(())
1036}