Skip to main content

mp4_atom/moof/traf/
trun.rs

1use crate::*;
2
3ext! {
4    name: Trun,
5    versions: [0, 1],
6    flags: {
7        data_offset = 0,
8        first_sample_flags = 2,
9        sample_duration = 8,
10        sample_size = 9,
11        sample_flags = 10,
12        sample_cts = 11,
13    }
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Default)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct Trun {
19    pub data_offset: Option<i32>,
20    pub samples: TrunSamples,
21}
22
23/// The samples of a trun box.
24///
25/// A run without any per-sample field is stored as a count, so decoding never
26/// allocates in proportion to an untrusted `sample_count`.
27/// Use [TrunSamples::iter] to visit every sample's [TrunEntry] in either form.
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum TrunSamples {
31    /// Every sample inherits its duration, size, flags, and composition offset
32    /// from tfhd/trex, except that the first sample may override its flags.
33    Inherited {
34        count: u32,
35        first_flags: Option<u32>,
36    },
37
38    /// Per-sample entries, used when the run carries at least one per-sample field.
39    Explicit(Vec<TrunEntry>),
40}
41
42impl TrunSamples {
43    /// The number of samples in the run.
44    pub fn len(&self) -> usize {
45        match self {
46            Self::Inherited { count, .. } => *count as usize,
47            Self::Explicit(entries) => entries.len(),
48        }
49    }
50
51    pub fn is_empty(&self) -> bool {
52        self.len() == 0
53    }
54
55    /// The entry of the sample at `index`, or `None` if it is out of range.
56    pub fn get(&self, index: usize) -> Option<TrunEntry> {
57        match self {
58            Self::Inherited { count, first_flags } => {
59                (index < *count as usize).then(|| TrunEntry {
60                    flags: if index == 0 { *first_flags } else { None },
61                    ..Default::default()
62                })
63            }
64            Self::Explicit(entries) => entries.get(index).copied(),
65        }
66    }
67
68    /// Each sample's entry in order, without allocating.
69    pub fn iter(&self) -> TrunSamplesIter<'_> {
70        match self {
71            Self::Inherited { count, first_flags } => TrunSamplesIter::Inherited {
72                remaining: *count,
73                first_flags: *first_flags,
74            },
75            Self::Explicit(entries) => TrunSamplesIter::Explicit(entries.iter()),
76        }
77    }
78}
79
80impl Default for TrunSamples {
81    fn default() -> Self {
82        Self::Inherited {
83            count: 0,
84            first_flags: None,
85        }
86    }
87}
88
89impl From<Vec<TrunEntry>> for TrunSamples {
90    fn from(entries: Vec<TrunEntry>) -> Self {
91        Self::Explicit(entries)
92    }
93}
94
95impl<'a> IntoIterator for &'a TrunSamples {
96    type Item = TrunEntry;
97    type IntoIter = TrunSamplesIter<'a>;
98
99    fn into_iter(self) -> Self::IntoIter {
100        self.iter()
101    }
102}
103
104/// An iterator over the entries of [TrunSamples].
105#[derive(Debug, Clone)]
106pub enum TrunSamplesIter<'a> {
107    Inherited {
108        remaining: u32,
109        first_flags: Option<u32>,
110    },
111    Explicit(std::slice::Iter<'a, TrunEntry>),
112}
113
114impl Iterator for TrunSamplesIter<'_> {
115    type Item = TrunEntry;
116
117    fn next(&mut self) -> Option<TrunEntry> {
118        match self {
119            Self::Inherited {
120                remaining,
121                first_flags,
122            } => {
123                *remaining = remaining.checked_sub(1)?;
124                Some(TrunEntry {
125                    flags: first_flags.take(),
126                    ..Default::default()
127                })
128            }
129            Self::Explicit(entries) => entries.next().copied(),
130        }
131    }
132
133    fn nth(&mut self, n: usize) -> Option<TrunEntry> {
134        match self {
135            Self::Inherited {
136                remaining,
137                first_flags,
138            } => {
139                // Every sample after the first is identical, so skip without stepping through them.
140                if n > 0 {
141                    *first_flags = None;
142                    *remaining = remaining.saturating_sub(n.try_into().unwrap_or(u32::MAX));
143                }
144            }
145            Self::Explicit(entries) => return entries.nth(n).copied(),
146        }
147        self.next()
148    }
149
150    fn size_hint(&self) -> (usize, Option<usize>) {
151        let len = match self {
152            Self::Inherited { remaining, .. } => *remaining as usize,
153            Self::Explicit(entries) => entries.len(),
154        };
155        (len, Some(len))
156    }
157}
158
159impl ExactSizeIterator for TrunSamplesIter<'_> {}
160
161impl Default for TrunSamplesIter<'_> {
162    /// An empty iterator.
163    fn default() -> Self {
164        Self::Inherited {
165            remaining: 0,
166            first_flags: None,
167        }
168    }
169}
170
171/// A single sample entry in a trun box.
172///
173/// `None` fields mean the value was not present in the per-sample trun data.
174/// After decode, callers should resolve `None` against tfhd defaults
175/// (`default_sample_duration`, `default_sample_size`, `default_sample_flags`)
176/// before using the values. The encoder backfills unresolved `None` with `0`
177/// as a last resort to avoid silently dropping fields that other entries set.
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180pub struct TrunEntry {
181    pub duration: Option<u32>,
182    pub size: Option<u32>,
183    pub flags: Option<u32>,
184    /// The composition offset: unsigned in a version 0 box, signed in version 1.
185    pub cts: Option<i64>,
186}
187
188impl AtomExt for Trun {
189    const KIND_EXT: FourCC = FourCC::new(b"trun");
190
191    type Ext = TrunExt;
192
193    fn decode_body_ext<B: Buf>(buf: &mut B, ext: TrunExt) -> Result<Self> {
194        let sample_count = u32::decode(buf)?;
195        let data_offset = match ext.data_offset {
196            true => i32::decode(buf)?.into(),
197            false => None,
198        };
199
200        let mut first_sample_flags = match ext.first_sample_flags {
201            true => u32::decode(buf)?.into(),
202            false => None,
203        };
204
205        let fields = [
206            ext.sample_duration,
207            ext.sample_size,
208            ext.sample_flags,
209            ext.sample_cts,
210        ]
211        .into_iter()
212        .filter(|&field| field)
213        .count();
214
215        // A run without per-sample fields has zero bytes per sample, so store it as a count
216        // instead of allocating from the untrusted sample_count.
217        if fields == 0 {
218            return Ok(Trun {
219                data_offset,
220                samples: TrunSamples::Inherited {
221                    count: sample_count,
222                    first_flags: first_sample_flags,
223                },
224            });
225        }
226
227        // Otherwise every sample takes at least 4 bytes, so the remaining bytes bound the allocation.
228        let capacity = (sample_count as usize).min(buf.remaining() / (4 * fields));
229        let mut entries = Vec::with_capacity(capacity);
230
231        for _ in 0..sample_count {
232            let duration = match ext.sample_duration {
233                true => u32::decode(buf)?.into(),
234                false => None,
235            };
236            let size = match ext.sample_size {
237                true => u32::decode(buf)?.into(),
238                false => None,
239            };
240            let sample_flags = match first_sample_flags.take() {
241                Some(flags) => Some(flags),
242                None => match ext.sample_flags {
243                    true => u32::decode(buf)?.into(),
244                    false => None,
245                },
246            };
247            let cts = match (ext.sample_cts, ext.version) {
248                (false, _) => None,
249                (true, TrunVersion::V0) => Some(u32::decode(buf)?.into()),
250                (true, TrunVersion::V1) => Some(i32::decode(buf)?.into()),
251            };
252
253            entries.push(TrunEntry {
254                duration,
255                size,
256                flags: sample_flags,
257                cts,
258            });
259        }
260
261        Ok(Trun {
262            data_offset,
263            samples: TrunSamples::Explicit(entries),
264        })
265    }
266
267    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<TrunExt> {
268        let entries = match &self.samples {
269            TrunSamples::Inherited { count, first_flags } => {
270                let ext = TrunExt {
271                    version: TrunVersion::V1,
272                    data_offset: self.data_offset.is_some(),
273                    first_sample_flags: first_flags.is_some(),
274                    ..Default::default()
275                };
276
277                count.encode(buf)?;
278                self.data_offset.encode(buf)?;
279                first_flags.encode(buf)?;
280
281                return Ok(ext);
282            }
283            TrunSamples::Explicit(entries) => entries,
284        };
285
286        let any_flags = entries.iter().any(|s| s.flags.is_some());
287        let first_only_flags = any_flags
288            && entries.first().is_some_and(|s| s.flags.is_some())
289            && entries.iter().skip(1).all(|s| s.flags.is_none());
290
291        // Use per-sample flags when any entry has flags and it's not the first-only pattern.
292        // None entries are backfilled with 0 to avoid silently dropping flags.
293        let sample_flags = any_flags && !first_only_flags;
294
295        // Keep version 1's signed offsets unless one only fits version 0's unsigned range.
296        let mut cts = entries.iter().filter_map(|s| s.cts);
297        let version = if cts.clone().all(|cts| i32::try_from(cts).is_ok()) {
298            TrunVersion::V1
299        } else if cts.all(|cts| u32::try_from(cts).is_ok()) {
300            TrunVersion::V0
301        } else {
302            return Err(Error::InvalidCombination(
303                "trun composition offsets fit neither i32 nor u32",
304            ));
305        };
306
307        let ext = TrunExt {
308            version,
309            data_offset: self.data_offset.is_some(),
310            first_sample_flags: first_only_flags,
311
312            // Use the field if any entry has it set. None entries are backfilled
313            // with 0 during encoding to avoid silently dropping fields on
314            // decode→encode roundtrips (entries that inherited defaults from tfhd
315            // have None after decode).
316            sample_duration: entries.iter().any(|s| s.duration.is_some()),
317            sample_size: entries.iter().any(|s| s.size.is_some()),
318            sample_flags,
319            sample_cts: entries.iter().any(|s| s.cts.is_some()),
320        };
321
322        (entries.len() as u32).encode(buf)?;
323
324        self.data_offset.encode(buf)?;
325        if ext.first_sample_flags {
326            entries[0].flags.unwrap().encode(buf)?;
327        }
328
329        for entry in entries {
330            if ext.sample_duration {
331                Some(Some(entry.duration.unwrap_or(0))).encode(buf)?;
332            }
333            if ext.sample_size {
334                Some(Some(entry.size.unwrap_or(0))).encode(buf)?;
335            }
336            if ext.sample_flags {
337                Some(Some(entry.flags.unwrap_or(0))).encode(buf)?;
338            }
339            if ext.sample_cts {
340                // Range-checked against the version above.
341                let cts = entry.cts.unwrap_or(0);
342                match version {
343                    TrunVersion::V0 => (cts as u32).encode(buf)?,
344                    TrunVersion::V1 => (cts as i32).encode(buf)?,
345                }
346            }
347        }
348
349        Ok(ext)
350    }
351}
352
353#[cfg(test)]
354mod test {
355    use super::*;
356
357    /// Verify that first_sample_flags survives encode→decode roundtrip.
358    ///
359    /// ffmpeg commonly writes trun boxes where only the first entry has flags
360    /// (via first_sample_flags) and the rest inherit default_sample_flags from
361    /// tfhd. After decode, entry[0].flags = Some(keyframe), entries[1..N].flags = None.
362    /// The encoder must preserve this by emitting first_sample_flags.
363    #[test]
364    fn first_sample_flags_roundtrip() {
365        let trun = Trun {
366            data_offset: Some(100),
367            samples: TrunSamples::Explicit(vec![
368                TrunEntry {
369                    duration: Some(512),
370                    size: Some(1000),
371                    flags: Some(0x02000000), // keyframe (sample_depends_on=2)
372                    cts: None,
373                },
374                TrunEntry {
375                    duration: Some(512),
376                    size: Some(200),
377                    flags: None, // inherits default_sample_flags from tfhd
378                    cts: None,
379                },
380                TrunEntry {
381                    duration: Some(512),
382                    size: Some(200),
383                    flags: None,
384                    cts: None,
385                },
386            ]),
387        };
388
389        let mut buf = Vec::new();
390        trun.encode(&mut buf).expect("encode");
391
392        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
393        assert_eq!(decoded.data_offset, Some(100));
394        let decoded: Vec<_> = decoded.samples.iter().collect();
395
396        // entry[0] must have the keyframe flags from first_sample_flags
397        assert_eq!(decoded[0].flags, Some(0x02000000));
398        // entries[1..N] must have None (they use default_sample_flags from tfhd)
399        assert_eq!(decoded[1].flags, None);
400        assert_eq!(decoded[2].flags, None);
401        assert_eq!(decoded.len(), 3);
402    }
403
404    /// When multiple entries have explicit flags (not just the first),
405    /// the encoder must use per-sample flags, not first_sample_flags.
406    #[test]
407    fn mixed_flags_uses_per_sample() {
408        let trun = Trun {
409            data_offset: Some(100),
410            samples: TrunSamples::Explicit(vec![
411                TrunEntry {
412                    duration: Some(512),
413                    size: Some(1000),
414                    flags: Some(0x02000000), // keyframe
415                    cts: None,
416                },
417                TrunEntry {
418                    duration: Some(512),
419                    size: Some(200),
420                    flags: Some(0x01010000), // non-keyframe (explicit)
421                    cts: None,
422                },
423                TrunEntry {
424                    duration: Some(512),
425                    size: Some(200),
426                    flags: None, // no flags
427                    cts: None,
428                },
429            ]),
430        };
431
432        let mut buf = Vec::new();
433        trun.encode(&mut buf).expect("encode");
434
435        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
436        let decoded: Vec<_> = decoded.samples.iter().collect();
437
438        // Mixed Some/None: encoder backfills None with 0 and emits per-sample flags.
439        assert_eq!(decoded[0].flags, Some(0x02000000));
440        assert_eq!(decoded[1].flags, Some(0x01010000));
441        assert_eq!(decoded[2].flags, Some(0)); // was None, backfilled to 0
442    }
443
444    /// When all entries have explicit flags, per-sample flags are used.
445    #[test]
446    fn all_flags_roundtrip() {
447        let trun = Trun {
448            data_offset: Some(100),
449            samples: TrunSamples::Explicit(vec![
450                TrunEntry {
451                    duration: Some(512),
452                    size: Some(1000),
453                    flags: Some(0x02000000),
454                    cts: None,
455                },
456                TrunEntry {
457                    duration: Some(512),
458                    size: Some(200),
459                    flags: Some(0x01010000),
460                    cts: None,
461                },
462            ]),
463        };
464
465        let mut buf = Vec::new();
466        trun.encode(&mut buf).expect("encode");
467
468        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
469        let decoded: Vec<_> = decoded.samples.iter().collect();
470
471        assert_eq!(decoded[0].flags, Some(0x02000000));
472        assert_eq!(decoded[1].flags, Some(0x01010000));
473    }
474
475    /// Entries with None duration (inherited from tfhd default_sample_duration)
476    /// must not cause the duration field to be dropped entirely on re-encode.
477    #[test]
478    fn duration_backfill_roundtrip() {
479        let trun = Trun {
480            data_offset: Some(100),
481            samples: TrunSamples::Explicit(vec![
482                TrunEntry {
483                    duration: Some(512),
484                    size: Some(1000),
485                    flags: Some(0x02000000),
486                    cts: None,
487                },
488                TrunEntry {
489                    duration: None, // inherited from tfhd
490                    size: Some(200),
491                    flags: None,
492                    cts: None,
493                },
494            ]),
495        };
496
497        let mut buf = Vec::new();
498        trun.encode(&mut buf).expect("encode");
499
500        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
501        let decoded: Vec<_> = decoded.samples.iter().collect();
502
503        assert_eq!(decoded[0].duration, Some(512));
504        // None backfilled to 0 during encode
505        assert_eq!(decoded[1].duration, Some(0));
506    }
507
508    /// Entries with None size (inherited from tfhd default_sample_size)
509    /// must not cause the size field to be dropped entirely on re-encode.
510    #[test]
511    fn size_backfill_roundtrip() {
512        let trun = Trun {
513            data_offset: Some(100),
514            samples: TrunSamples::Explicit(vec![
515                TrunEntry {
516                    duration: Some(512),
517                    size: Some(1000),
518                    flags: None,
519                    cts: None,
520                },
521                TrunEntry {
522                    duration: Some(512),
523                    size: None, // inherited from tfhd
524                    flags: None,
525                    cts: None,
526                },
527            ]),
528        };
529
530        let mut buf = Vec::new();
531        trun.encode(&mut buf).expect("encode");
532
533        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
534        let decoded: Vec<_> = decoded.samples.iter().collect();
535
536        assert_eq!(decoded[0].size, Some(1000));
537        // None backfilled to 0 during encode
538        assert_eq!(decoded[1].size, Some(0));
539    }
540
541    /// When all entries have None for a field, the flag is not set and
542    /// the field is omitted entirely (no unnecessary bytes written).
543    #[test]
544    fn all_none_fields_omitted() {
545        let trun = Trun {
546            data_offset: Some(100),
547            samples: TrunSamples::Explicit(vec![
548                TrunEntry {
549                    duration: None,
550                    size: None,
551                    flags: None,
552                    cts: None,
553                },
554                TrunEntry {
555                    duration: None,
556                    size: None,
557                    flags: None,
558                    cts: None,
559                },
560            ]),
561        };
562
563        let mut buf = Vec::new();
564        trun.encode(&mut buf).expect("encode");
565
566        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
567        let decoded: Vec<_> = decoded.samples.iter().collect();
568
569        assert_eq!(decoded[0].duration, None);
570        assert_eq!(decoded[0].size, None);
571        assert_eq!(decoded[0].flags, None);
572        assert_eq!(decoded[0].cts, None);
573    }
574
575    /// A run without per-sample fields decodes as a count, not one entry per sample.
576    #[test]
577    fn inherited_roundtrip() {
578        for first_flags in [None, Some(0x02000000)] {
579            let trun = Trun {
580                data_offset: Some(100),
581                samples: TrunSamples::Inherited {
582                    count: u32::MAX,
583                    first_flags,
584                },
585            };
586
587            let mut buf = Vec::new();
588            trun.encode(&mut buf).expect("encode");
589
590            // size, fourcc, version/flags, sample_count, data_offset, and the optional first flags.
591            let expected = 20 + first_flags.map_or(0, |_| 4);
592            assert_eq!(buf.len(), expected);
593
594            let decoded = Trun::decode(&mut &buf[..]).expect("decode");
595            assert_eq!(decoded, trun);
596            assert_eq!(decoded.samples.len(), u32::MAX as usize);
597
598            let mut entries = decoded.samples.iter();
599            assert_eq!(entries.len(), u32::MAX as usize);
600            assert_eq!(
601                entries.next(),
602                Some(TrunEntry {
603                    flags: first_flags,
604                    ..Default::default()
605                })
606            );
607            assert_eq!(entries.next(), Some(TrunEntry::default()));
608            assert_eq!(entries.len(), u32::MAX as usize - 2);
609
610            // Skipping into the run takes constant time.
611            assert_eq!(
612                entries.nth(u32::MAX as usize - 3),
613                Some(TrunEntry::default())
614            );
615            assert_eq!(entries.next(), None);
616
617            let last = u32::MAX as usize - 1;
618            assert_eq!(decoded.samples.get(0), decoded.samples.iter().next());
619            assert_eq!(decoded.samples.get(last), Some(TrunEntry::default()));
620            assert_eq!(decoded.samples.get(last + 1), None);
621        }
622    }
623
624    /// Explicit entries without any field encode as an inherited run.
625    #[test]
626    fn explicit_without_fields_decodes_inherited() {
627        let trun = Trun {
628            data_offset: None,
629            samples: TrunSamples::Explicit(vec![
630                TrunEntry {
631                    flags: Some(0x02000000),
632                    ..Default::default()
633                },
634                TrunEntry::default(),
635            ]),
636        };
637
638        let mut buf = Vec::new();
639        trun.encode(&mut buf).expect("encode");
640
641        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
642        assert_eq!(
643            decoded.samples,
644            TrunSamples::Inherited {
645                count: 2,
646                first_flags: Some(0x02000000),
647            }
648        );
649        assert!(decoded.samples.iter().eq(trun.samples.iter()));
650    }
651
652    /// `get` and `nth` agree with stepping through the run in both forms.
653    #[test]
654    fn random_access() {
655        let inherited = TrunSamples::Inherited {
656            count: 3,
657            first_flags: Some(0x02000000),
658        };
659        let explicit = TrunSamples::Explicit(vec![
660            TrunEntry {
661                size: Some(1),
662                ..Default::default()
663            },
664            TrunEntry {
665                size: Some(2),
666                ..Default::default()
667            },
668        ]);
669
670        for samples in [inherited, explicit] {
671            let all: Vec<_> = samples.iter().collect();
672            for n in 0..=all.len() {
673                assert_eq!(samples.get(n), all.get(n).copied());
674                assert_eq!(samples.iter().nth(n), all.get(n).copied());
675            }
676        }
677    }
678
679    fn cts_entries(cts: &[i64]) -> Trun {
680        Trun {
681            data_offset: None,
682            samples: cts
683                .iter()
684                .map(|&cts| TrunEntry {
685                    cts: Some(cts),
686                    ..Default::default()
687                })
688                .collect::<Vec<_>>()
689                .into(),
690        }
691    }
692
693    /// Composition offsets pick the version that represents them: signed (1) by default,
694    /// unsigned (0) only when an offset exceeds i32.
695    #[test]
696    fn cts_version_roundtrip() {
697        for (cts, version) in [
698            (vec![0, 1024, -512], 1),
699            (vec![0, 1 << 31, u32::MAX as i64], 0),
700        ] {
701            let trun = cts_entries(&cts);
702            let mut buf = Vec::new();
703            trun.encode(&mut buf).expect("encode");
704            assert_eq!(buf[8], version);
705
706            let decoded = Trun::decode(&mut &buf[..]).expect("decode");
707            assert!(decoded.samples.iter().map(|e| e.cts.unwrap()).eq(cts));
708        }
709    }
710
711    /// A version 0 offset above i32::MAX decodes as unsigned, not negative.
712    #[test]
713    fn cts_version_0_is_unsigned() {
714        let mut buf = Vec::new();
715        cts_entries(&[-1]).encode(&mut buf).expect("encode");
716        assert_eq!(buf[8], 1);
717        buf[8] = 0;
718
719        let decoded = Trun::decode(&mut &buf[..]).expect("decode");
720        assert_eq!(decoded.samples.get(0).unwrap().cts, Some(u32::MAX as i64));
721    }
722
723    #[test]
724    fn cts_out_of_range_is_refused() {
725        for cts in [vec![-1, 1 << 31], vec![1 << 32], vec![i32::MIN as i64 - 1]] {
726            assert!(matches!(
727                cts_entries(&cts).encode(&mut Vec::new()),
728                Err(Error::InvalidCombination(_))
729            ));
730        }
731    }
732
733    /// A sample_count larger than the per-sample data fails without reserving for it.
734    #[test]
735    fn explicit_truncated() {
736        let mut buf = Vec::new();
737        Trun {
738            data_offset: None,
739            samples: TrunSamples::Explicit(vec![TrunEntry {
740                size: Some(1),
741                ..Default::default()
742            }]),
743        }
744        .encode(&mut buf)
745        .expect("encode");
746
747        // Claim u32::MAX samples while carrying one.
748        buf[12..16].copy_from_slice(&u32::MAX.to_be_bytes());
749        assert!(Trun::decode(&mut &buf[..]).is_err());
750    }
751}