1use bon::{bon, Builder};
2use derive_more::{Deref, DerefMut};
3
4use rangemap::RangeSet;
5use std::{
6 fmt::{self, Debug},
7 ops::{Bound, Range, RangeBounds, Sub},
8};
9
10use crate::{
11 atom::{util::DebugList, FourCC},
12 parser::ParseAtomData,
13 writer::SerializeAtom,
14 ParseError,
15};
16
17pub const STTS: FourCC = FourCC::new(b"stts");
18
19#[derive(Default, Clone, Deref, DerefMut)]
20pub struct TimeToSampleEntries(Vec<TimeToSampleEntry>);
21
22impl From<Vec<TimeToSampleEntry>> for TimeToSampleEntries {
23 fn from(entries: Vec<TimeToSampleEntry>) -> Self {
24 Self::new(entries)
25 }
26}
27
28impl TimeToSampleEntries {
29 pub fn new(inner: Vec<TimeToSampleEntry>) -> Self {
30 Self(inner)
31 }
32
33 pub fn inner(&self) -> &[TimeToSampleEntry] {
34 &self.0
35 }
36}
37
38impl fmt::Debug for TimeToSampleEntries {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 fmt::Debug::fmt(&DebugList::new(self.0.iter(), 10), f)
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Builder)]
46pub struct TimeToSampleEntry {
47 pub sample_count: u32,
49 pub sample_duration: u32,
51}
52
53#[derive(Default, Debug, Clone)]
55pub struct TimeToSampleAtom {
56 pub version: u8,
57 pub flags: [u8; 3],
58 pub entries: TimeToSampleEntries,
59}
60
61#[bon]
62impl TimeToSampleAtom {
63 #[builder]
64 pub fn new(
65 #[builder(default = 0)] version: u8,
66 #[builder(default = [0u8; 3])] flags: [u8; 3],
67 #[builder(with = FromIterator::from_iter)] entries: Vec<TimeToSampleEntry>,
68 ) -> Self {
69 Self {
70 version,
71 flags,
72 entries: entries.into(),
73 }
74 }
75
76 #[cfg(feature = "experimental-trim")]
85 pub(crate) fn trim_duration<R>(&mut self, trim_ranges: &[R]) -> (u64, RangeSet<usize>)
86 where
87 R: RangeBounds<u64> + Debug,
88 {
89 let mut trim_range_index = 0;
90 let mut removed_sample_indices = RangeSet::new();
91 let mut remove_entry_range = RangeSet::new();
92 let mut next_duration_offset = 0u64;
93 let mut next_sample_index = 0usize;
94 let mut total_original_duration = 0u64;
95 let mut total_duration_trimmed = 0u64;
96
97 'entries: for (entry_index, entry) in self.entries.iter_mut().enumerate() {
98 let current_duration_offset = next_duration_offset;
99 let entry_duration = entry.sample_count as u64 * entry.sample_duration as u64;
100 next_duration_offset = current_duration_offset + entry_duration;
101 total_original_duration += entry_duration;
102
103 let current_sample_index = next_sample_index;
104 next_sample_index += entry.sample_count as usize;
105
106 let entry_duration = {
107 let entry_duration_start = current_duration_offset;
108 let entry_duration_end = current_duration_offset
109 + (entry.sample_count as u64 * entry.sample_duration as u64).saturating_sub(1);
110 entry_duration_start..entry_duration_end + 1
111 };
112
113 'trim_range: for (i, trim_range) in trim_ranges.iter().enumerate() {
114 let (trim_duration, entry_trim_duration) =
115 match entry_trim_duration(&entry_duration, trim_range) {
116 Some(m) => m,
117 None => {
118 continue 'trim_range;
120 }
121 };
122
123 debug_assert!(
124 i >= trim_range_index,
125 "invariant: trim ranges must not overlap"
126 );
127 trim_range_index = i;
128
129 if trim_duration.contains(&entry_duration.start)
131 && trim_duration.contains(&(entry_duration.end - 1))
132 {
133 remove_entry_range.insert(entry_index..entry_index + 1);
134 removed_sample_indices.insert(
135 current_sample_index..current_sample_index + entry.sample_count as usize,
136 );
137 total_duration_trimmed += entry_duration.end - entry_duration.start;
138 continue 'entries;
139 }
140
141 if entry.sample_count == 1 {
144 continue 'entries;
147 }
148
149 let sample_duration = entry.sample_duration as u64;
150
151 let trim_sample_start_index = (current_sample_index as u64
152 + (entry_trim_duration.start - entry_duration.start).div_ceil(sample_duration))
153 as usize;
154 let trim_sample_end_index =
155 match (entry_trim_duration.end - entry_duration.start) / sample_duration {
156 0 => trim_sample_start_index,
157 end => current_sample_index + end as usize - 1,
158 };
159
160 assert!(trim_sample_end_index >= trim_sample_start_index);
162
163 let num_samples_to_remove = trim_sample_end_index + 1 - trim_sample_start_index;
164 if num_samples_to_remove == entry.sample_count as usize {
165 if entry.sample_count > 1 {
166 if trim_sample_start_index == 0 {
168 removed_sample_indices
170 .insert(trim_sample_start_index..trim_sample_end_index);
171 } else {
172 removed_sample_indices
174 .insert((trim_sample_start_index + 1)..trim_sample_end_index);
175 }
176 }
177 entry.sample_count = 1;
178 let trimmed_duration = entry_trim_duration.end - entry_trim_duration.start;
179 entry.sample_duration -= trimmed_duration as u32;
180 total_duration_trimmed += trimmed_duration;
181 } else {
182 removed_sample_indices
183 .insert(trim_sample_start_index..(trim_sample_end_index + 1));
184
185 entry.sample_count = entry.sample_count.sub(num_samples_to_remove as u32);
186
187 total_duration_trimmed += ((trim_sample_end_index as u64 + 1)
188 * sample_duration)
189 - (trim_sample_start_index as u64 * sample_duration);
190 }
191 }
192 }
193
194 let mut n_removed = 0;
195 for range in remove_entry_range.into_iter() {
196 let mut range = (range.start - n_removed)..(range.end - n_removed);
197 n_removed += range.len();
198
199 if range.start > 0 {
201 if let Ok([prev_entry, next_entry]) = self
202 .entries
203 .as_mut_slice()
204 .get_disjoint_mut([range.start - 1, range.end])
205 {
206 if prev_entry.sample_duration == next_entry.sample_duration {
207 prev_entry.sample_count += next_entry.sample_count;
208 range.end += 1;
209 }
210 }
211 }
212
213 self.entries.drain(range);
214 }
215
216 (
217 total_original_duration - total_duration_trimmed,
218 removed_sample_indices,
219 )
220 }
221}
222
223#[cfg(feature = "experimental-trim")]
224fn entry_trim_duration<'a, R>(
225 entry_range: &Range<u64>,
226 trim_range: &'a R,
227) -> Option<(&'a R, Range<u64>)>
228where
229 R: RangeBounds<u64>,
230{
231 if trim_range.contains(&entry_range.start) && trim_range.contains(&(entry_range.end - 1)) {
233 return Some((trim_range, entry_range.clone()));
234 }
235
236 let finite_trim_range = convert_range(entry_range, trim_range);
237
238 if finite_trim_range.end <= entry_range.start {
240 return None;
241 }
242
243 if entry_range.contains(&finite_trim_range.start)
245 && finite_trim_range.end > 0
246 && entry_range.contains(&(finite_trim_range.end - 1))
247 {
248 return Some((trim_range, finite_trim_range));
249 }
250
251 if finite_trim_range.start >= entry_range.start && finite_trim_range.start < entry_range.end {
253 return Some((trim_range, finite_trim_range.start..entry_range.end));
254 }
255
256 if trim_range.contains(&entry_range.start)
258 && finite_trim_range.start < entry_range.start
259 && finite_trim_range.end <= entry_range.end
260 {
261 return Some((trim_range, entry_range.start..finite_trim_range.end));
262 }
263
264 None
265}
266
267#[cfg(feature = "experimental-trim")]
268fn convert_range(reference_range: &Range<u64>, range: &impl RangeBounds<u64>) -> Range<u64> {
269 let start = match range.start_bound() {
270 Bound::Included(start) => *start,
271 Bound::Excluded(start) => *start + 1,
272 Bound::Unbounded => 0,
273 };
274 let end = match range.end_bound() {
275 Bound::Included(end) => *end + 1,
276 Bound::Excluded(end) => *end,
277 Bound::Unbounded => reference_range.end,
278 };
279 start..end
280}
281
282impl<S: time_to_sample_atom_builder::State> TimeToSampleAtomBuilder<S> {
283 pub fn entry(
284 self,
285 entry: impl Into<TimeToSampleEntry>,
286 ) -> TimeToSampleAtomBuilder<time_to_sample_atom_builder::SetEntries<S>>
287 where
288 S::Entries: time_to_sample_atom_builder::IsUnset,
289 {
290 self.entries(vec![entry.into()])
291 }
292}
293
294impl From<Vec<TimeToSampleEntry>> for TimeToSampleAtom {
295 fn from(entries: Vec<TimeToSampleEntry>) -> Self {
296 TimeToSampleAtom {
297 version: 0,
298 flags: [0u8; 3],
299 entries: entries.into(),
300 }
301 }
302}
303
304impl ParseAtomData for TimeToSampleAtom {
305 fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
306 crate::atom::util::parser::assert_atom_type!(atom_type, STTS);
307 use crate::atom::util::parser::stream;
308 use winnow::Parser;
309 Ok(parser::parse_stts_data.parse(stream(input))?)
310 }
311}
312
313impl SerializeAtom for TimeToSampleAtom {
314 fn atom_type(&self) -> FourCC {
315 STTS
316 }
317
318 fn into_body_bytes(self) -> Vec<u8> {
319 serializer::serialize_mdhd_atom(self)
320 }
321}
322
323mod serializer {
324 use crate::atom::util::serializer::be_u32;
325
326 use super::TimeToSampleAtom;
327
328 pub fn serialize_mdhd_atom(stts: TimeToSampleAtom) -> Vec<u8> {
329 let mut data = Vec::new();
330
331 data.push(stts.version);
332 data.extend(stts.flags);
333 data.extend(be_u32(
334 u32::try_from(stts.entries.len()).expect("stts entries len must fit in u32"),
335 ));
336
337 for entry in stts.entries.0.into_iter() {
338 data.extend(entry.sample_count.to_be_bytes());
339 data.extend(entry.sample_duration.to_be_bytes());
340 }
341
342 data
343 }
344}
345
346mod parser {
347 use winnow::{
348 binary::{be_u32, length_repeat},
349 combinator::{seq, trace},
350 error::StrContext,
351 ModalResult, Parser,
352 };
353
354 use super::{TimeToSampleAtom, TimeToSampleEntries, TimeToSampleEntry};
355 use crate::atom::util::parser::{flags3, version, Stream};
356
357 pub fn parse_stts_data(input: &mut Stream<'_>) -> ModalResult<TimeToSampleAtom> {
358 trace(
359 "stts",
360 seq!(TimeToSampleAtom {
361 version: version,
362 flags: flags3,
363 entries: length_repeat(be_u32, entry)
364 .map(TimeToSampleEntries)
365 .context(StrContext::Label("entries")),
366 })
367 .context(StrContext::Label("stts")),
368 )
369 .parse_next(input)
370 }
371
372 fn entry(input: &mut Stream<'_>) -> ModalResult<TimeToSampleEntry> {
373 trace(
374 "entry",
375 seq!(TimeToSampleEntry {
376 sample_count: be_u32.context(StrContext::Label("sample_count")),
377 sample_duration: be_u32.context(StrContext::Label("sample_duration")),
378 })
379 .context(StrContext::Label("entry")),
380 )
381 .parse_next(input)
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use crate::atom::test_utils::test_atom_roundtrip;
389
390 #[test]
392 fn test_stts_roundtrip() {
393 test_atom_roundtrip::<TimeToSampleAtom>(STTS);
394 }
395}
396
397#[cfg(feature = "experimental-trim")]
398#[cfg(test)]
399mod trim_tests {
400 use std::ops::Bound;
401
402 use super::*;
403
404 struct TrimDurationTestCase {
405 trim_duration: Vec<(Bound<u64>, Bound<u64>)>,
406 expect_removed_samples: Vec<Range<usize>>,
407 expect_removed_duration: u64,
408 expect_entries: Vec<TimeToSampleEntry>,
409 }
410
411 fn test_trim_duration_stts() -> TimeToSampleAtom {
412 TimeToSampleAtom::builder()
413 .entries(vec![
414 TimeToSampleEntry {
417 sample_count: 1,
418 sample_duration: 100,
419 },
420 TimeToSampleEntry {
423 sample_count: 4,
424 sample_duration: 200,
425 },
426 TimeToSampleEntry {
429 sample_count: 4,
430 sample_duration: 100,
431 },
432 ])
433 .build()
434 }
435
436 fn test_trim_duration<F>(mut stts: TimeToSampleAtom, test_case: F)
437 where
438 F: FnOnce(&TimeToSampleAtom) -> TrimDurationTestCase,
439 {
440 let starting_duration = stts.entries.iter().fold(0, |sum, entry| {
441 sum + (entry.sample_count as u64 * entry.sample_duration as u64)
442 });
443
444 let test_case = test_case(&stts);
445
446 let (actual_remaining_duration, actual_removed_samples) =
447 stts.trim_duration(&test_case.trim_duration);
448
449 assert_eq!(
450 actual_removed_samples,
451 RangeSet::from_iter(test_case.expect_removed_samples.into_iter()),
452 "removed sample indices don't match what's expected"
453 );
454
455 let calculated_remaining_duration = stts.entries.iter().fold(0, |sum, entry| {
456 sum + (entry.sample_count as u64 * entry.sample_duration as u64)
457 });
458 assert_eq!(
459 actual_remaining_duration, calculated_remaining_duration,
460 "remaining duration doesn't add up correctly"
461 );
462
463 let actual_removed_duration = starting_duration.saturating_sub(actual_remaining_duration);
464 assert_eq!(
465 actual_removed_duration, test_case.expect_removed_duration,
466 "removed duration doesn't match what's expected; started with {starting_duration} and ended up with {actual_remaining_duration}"
467 );
468
469 assert_eq!(
470 stts.entries.0, test_case.expect_entries,
471 "time to sample entries don't match what's expected"
472 )
473 }
474
475 macro_rules! test_trim_duration {
476 ($($name:ident $(($stts:expr))? => $test_case:expr,)*) => {
477 $(
478 #[test]
479 fn $name() {
480 let stts = test_trim_duration!(@get_stts $($stts)?);
481 test_trim_duration(stts, $test_case);
482 }
483 )*
484 };
485
486 (@get_stts $stts:expr) => { $stts };
487 (@get_stts) => { test_trim_duration_stts() };
488 }
489
490 test_trim_duration!(
491 trim_unbounded_to_0_excluded_from_start => |stts| TrimDurationTestCase {
492 trim_duration: vec![(Bound::Unbounded, Bound::Excluded(0))],
493 expect_removed_samples: vec![],
494 expect_removed_duration: 0,
495 expect_entries: stts.entries.iter().cloned().collect::<Vec<_>>(),
496 },
497 trim_unbounded_to_0_included_from_start_trim_nothing => |stts| {
498 let expected_entries = stts.entries.0.clone();
500 TrimDurationTestCase {
501 trim_duration: vec![(Bound::Unbounded, Bound::Included(0))],
502 expect_removed_samples: vec![],
503 expect_removed_duration: 0,
504 expect_entries: expected_entries,
505 }
506 },
507 trim_unbounded_to_0_included_from_start_trim_sample ({
508 TimeToSampleAtom::builder().entry(
509 TimeToSampleEntry {
510 sample_count: 100,
511 sample_duration: 1,
512 },
513 ).build()
514 }) => |stts| {
515 let mut expected_entries = stts.entries.0.clone();
517 expected_entries[0].sample_count -= 1;
518 TrimDurationTestCase {
519 trim_duration: vec![(Bound::Unbounded, Bound::Included(0))],
520 expect_removed_samples: vec![0..1],
521 expect_removed_duration: 1,
522 expect_entries: expected_entries,
523 }
524 },
525 trim_first_entry_unbounded_start => |stts| TrimDurationTestCase {
526 trim_duration: vec![(Bound::Unbounded, Bound::Excluded(100))],
527 expect_removed_samples: vec![0..1],
528 expect_removed_duration: 100,
529 expect_entries: stts.entries[1..].to_vec(),
530 },
531 trim_first_entry_included_start => |stts| TrimDurationTestCase {
532 trim_duration: vec![(Bound::Included(0), Bound::Excluded(100))],
533 expect_removed_samples: vec![0..1],
534 expect_removed_duration: 100,
535 expect_entries: stts.entries[1..].to_vec(),
536 },
537 trim_last_sample_unbounded_end => |stts| {
538 let mut expect_entries = stts.entries.clone().0;
539 expect_entries.last_mut().unwrap().sample_count = 3;
540 TrimDurationTestCase {
541 trim_duration: vec![(Bound::Included(1_200), Bound::Unbounded)],
542 expect_removed_duration: 100,
543 expect_removed_samples: vec![8..9],
544 expect_entries,
545 }
546 },
547 trim_last_three_samples_unbounded_end => |stts| {
548 let mut expect_entries = stts.entries.clone().0;
549 expect_entries.last_mut().unwrap().sample_count = 1;
550 TrimDurationTestCase {
551 trim_duration: vec![(Bound::Included(1_000), Bound::Unbounded)],
552 expect_removed_duration: 300,
553 expect_removed_samples: vec![6..9],
554 expect_entries,
555 }
556 },
557 trim_last_sample_included_end => |stts| {
558 let mut expect_entries = stts.entries.clone().0;
559 expect_entries.last_mut().unwrap().sample_count = 3;
560 TrimDurationTestCase {
561 trim_duration: vec![(Bound::Included(1_200), Bound::Included(1_300 - 1))],
562 expect_removed_duration: 100,
563 expect_removed_samples: vec![8..9],
564 expect_entries,
565 }
566 },
567 trim_middle_entry_excluded_end => |_| TrimDurationTestCase {
568 trim_duration: vec![(Bound::Included(100), Bound::Excluded(900))],
569 expect_removed_duration: 800,
570 expect_removed_samples: vec![1..5],
571 expect_entries: vec![
572 TimeToSampleEntry {
573 sample_count: 5,
574 sample_duration: 100,
575 },
576 ],
577 },
578 trim_middle_entry_excluded_start => |_| TrimDurationTestCase {
579 trim_duration: vec![(Bound::Excluded(99), Bound::Excluded(900))],
580 expect_removed_duration: 800,
581 expect_removed_samples: vec![1..5],
582 expect_entries: vec![
583 TimeToSampleEntry {
584 sample_count: 5,
585 sample_duration: 100,
586 },
587 ],
588 },
589 trim_middle_entry_excluded_start_included_end => |_| TrimDurationTestCase {
590 trim_duration: vec![(Bound::Excluded(99), Bound::Included(899))],
591 expect_removed_duration: 800,
592 expect_removed_samples: vec![1..5],
593 expect_entries: vec![
594 TimeToSampleEntry {
595 sample_count: 5,
596 sample_duration: 100,
597 },
598 ],
599 },
600 trim_middle_samples => |stts| TrimDurationTestCase {
601 trim_duration: vec![(Bound::Included(300), Bound::Excluded(700))],
607 expect_removed_duration: 400,
608 expect_removed_samples: vec![2..4],
609 expect_entries: vec![
610 stts.entries[0].clone(),
611 TimeToSampleEntry {
612 sample_count: 2,
613 sample_duration: 200,
614 },
615 stts.entries[2].clone(),
616 ],
617 },
618 trim_middle_samples_partial => |stts| TrimDurationTestCase {
619 trim_duration: vec![(Bound::Included(240), Bound::Excluded(850))],
626 expect_removed_duration: 400,
627 expect_removed_samples: vec![2..4],
628 expect_entries: vec![
629 stts.entries[0].clone(),
630 TimeToSampleEntry {
631 sample_count: 2,
632 sample_duration: 200,
633 },
634 stts.entries[2].clone(),
635 ],
636 },
637 trim_everything => |_| TrimDurationTestCase {
638 trim_duration: vec![(Bound::Unbounded, Bound::Unbounded)],
639 expect_removed_duration: 1_300,
640 expect_removed_samples: vec![0..9],
641 expect_entries: Vec::new(),
642 },
643 trim_middle_from_large_entry ({
644 TimeToSampleAtom::builder().entry(
645 TimeToSampleEntry {
656 sample_count: 10,
657 sample_duration: 10_000,
658 },
659 ).build()
660 }) => |stts| TrimDurationTestCase {
661 trim_duration: vec![(Bound::Excluded(19_999), Bound::Included(50_000))],
662 expect_removed_duration: 30_000,
663 expect_removed_samples: vec![2..5],
664 expect_entries: stts.entries.iter().cloned().map(|mut entry| {
665 entry.sample_count = 7;
666 entry
667 }).collect::<Vec<_>>(),
668 },
669 trim_start_and_end => |stts| {
670 let mut expect_entries = stts.entries[1..].to_vec();
671 expect_entries.last_mut().unwrap().sample_count = 1;
672 TrimDurationTestCase {
673 trim_duration: vec![
674 (Bound::Included(0), Bound::Excluded(100)),
675 (Bound::Included(1_000), Bound::Excluded(1_300)),
676 ],
677 expect_removed_duration: 100 + 300,
678 expect_removed_samples: vec![0..1, 6..9],
679 expect_entries,
680 }
681 },
682 trim_start_and_end_single_entry ({
683 TimeToSampleAtom::builder().entry(
684 TimeToSampleEntry {
685 sample_count: 100,
686 sample_duration: 1,
687 },
688 ).build()
689 }) => |stts| {
690 let mut expect_entry = stts.entries[0].clone();
691 expect_entry.sample_count -= 20 + 20;
692 TrimDurationTestCase {
693 trim_duration: vec![
694 (Bound::Unbounded, Bound::Excluded(20)),
695 (Bound::Included(80), Bound::Unbounded),
696 ],
697 expect_removed_duration: 20 + 20,
698 expect_removed_samples: vec![0..20, 80..100],
699 expect_entries: vec![expect_entry],
700 }
701 },
702 trim_start_end_single_sample ({
703 TimeToSampleAtom::builder().entry(
704 TimeToSampleEntry {
705 sample_count: 1,
706 sample_duration: 100,
707 },
708 ).build()
709 }) => |stts| {
710 let expect_entry = stts.entries[0].clone();
711 TrimDurationTestCase {
712 trim_duration: vec![
713 (Bound::Included(50), Bound::Included(100)),
714 ],
715 expect_removed_duration: 0,
716 expect_removed_samples: vec![],
717 expect_entries: vec![expect_entry],
718 }
719 },
720 );
721}