1use bon::{bon, Builder};
2use derive_more::{Deref, DerefMut};
3
4use std::fmt;
5
6use crate::{
7 atom::{util::DebugList, FourCC},
8 parser::ParseAtomData,
9 writer::SerializeAtom,
10 ParseError,
11};
12
13#[cfg(feature = "experimental-trim")]
14use {
15 crate::atom::stco_co64::ChunkOffsetOperationUnresolved,
16 rangemap::RangeSet,
17 std::{iter::Peekable, ops::Range, slice},
18};
19
20pub const STSC: FourCC = FourCC::new(b"stsc");
21
22#[derive(Default, Clone, Deref, DerefMut)]
23pub struct SampleToChunkEntries(Vec<SampleToChunkEntry>);
24
25impl SampleToChunkEntries {
26 pub fn inner(&self) -> &[SampleToChunkEntry] {
27 &self.0
28 }
29
30 #[cfg(feature = "experimental-trim")]
31 fn expanded_iter(&self, total_chunks: usize) -> ExpandedSampleToChunkEntryIter<'_> {
32 ExpandedSampleToChunkEntryIter::new(total_chunks, &self.0)
33 }
34}
35
36impl From<Vec<SampleToChunkEntry>> for SampleToChunkEntries {
37 fn from(inner: Vec<SampleToChunkEntry>) -> Self {
38 Self(inner)
39 }
40}
41
42impl fmt::Debug for SampleToChunkEntries {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 fmt::Debug::fmt(&DebugList::new(self.0.iter(), 10), f)
45 }
46}
47
48#[derive(Debug, Clone)]
49#[cfg(feature = "experimental-trim")]
50struct ExpandedSampleToChunkEntry {
51 pub chunk_index: usize,
52 pub sample_indices: Range<usize>,
53 pub samples_per_chunk: u32,
54 pub sample_description_index: u32,
55}
56
57#[cfg(feature = "experimental-trim")]
59struct ExpandedSampleToChunkEntryIter<'a> {
60 total_chunks: usize,
61 next_sample_index: usize,
62 current_entry: Option<(&'a SampleToChunkEntry, usize, usize)>,
63 iter: Peekable<slice::Iter<'a, SampleToChunkEntry>>,
64}
65
66#[cfg(feature = "experimental-trim")]
67impl<'a> ExpandedSampleToChunkEntryIter<'a> {
68 fn new(total_chunks: usize, entries: &'a [SampleToChunkEntry]) -> Self {
69 let iter = entries.iter().peekable();
70 Self {
71 total_chunks,
72 next_sample_index: 0,
73 current_entry: None,
74 iter,
75 }
76 }
77}
78
79#[cfg(feature = "experimental-trim")]
80impl<'a> Iterator for ExpandedSampleToChunkEntryIter<'a> {
81 type Item = ExpandedSampleToChunkEntry;
82
83 fn next(&mut self) -> Option<Self::Item> {
84 let (entry, chunk_index, chunk_count) = self.current_entry.take().or_else(|| {
85 let entry = self.iter.next()?;
86 let chunk_index = entry.first_chunk as usize - 1;
87 let chunk_count = match self.iter.peek() {
88 Some(next_entry) => (next_entry.first_chunk - entry.first_chunk) as usize,
89 None => self.total_chunks - chunk_index,
90 };
91 Some((entry, chunk_index, chunk_count))
92 })?;
93
94 let first_sample_index = self.next_sample_index;
95 self.next_sample_index += entry.samples_per_chunk as usize;
96
97 if chunk_count > 1 {
98 self.current_entry = Some((entry, chunk_index + 1, chunk_count - 1));
99 }
100
101 let sample_count = entry.samples_per_chunk as usize;
102 let last_sample_index = first_sample_index + sample_count.saturating_sub(1);
103 let sample_indices = first_sample_index..last_sample_index + 1;
104
105 Some(ExpandedSampleToChunkEntry {
107 chunk_index,
108 sample_indices,
109 samples_per_chunk: entry.samples_per_chunk,
110 sample_description_index: entry.sample_description_index,
111 })
112 }
113
114 fn size_hint(&self) -> (usize, Option<usize>) {
115 (self.total_chunks, Some(self.total_chunks))
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Builder)]
121pub struct SampleToChunkEntry {
122 pub first_chunk: u32,
124 pub samples_per_chunk: u32,
126 pub sample_description_index: u32,
128}
129
130#[derive(Default, Debug, Clone)]
132pub struct SampleToChunkAtom {
133 pub version: u8,
135 pub flags: [u8; 3],
137 pub entries: SampleToChunkEntries,
139}
140
141#[bon]
142impl SampleToChunkAtom {
143 #[builder]
144 pub fn new(
145 #[builder(default = 0)] version: u8,
146 #[builder(default = [0u8; 3])] flags: [u8; 3],
147 #[builder(with = FromIterator::from_iter)] entries: Vec<SampleToChunkEntry>,
148 ) -> Self {
149 Self {
150 version,
151 flags,
152 entries: entries.into(),
153 }
154 }
155
156 #[cfg(feature = "experimental-trim")]
167 pub(crate) fn remove_sample_indices(
168 &mut self,
169 sample_indices_to_remove: &RangeSet<usize>,
170 total_chunks: usize,
171 ) -> Vec<ChunkOffsetOperationUnresolved> {
172 let mut chunk_ops = Vec::new();
173
174 let mut num_removed_chunks = 0usize;
175 let mut num_inserted_chunks = 0usize;
176
177 struct Context<'a> {
178 sample_indices_to_remove: &'a RangeSet<usize>,
179
180 chunk_ops: &'a mut Vec<ChunkOffsetOperationUnresolved>,
181
182 num_removed_chunks: &'a mut usize,
183 num_inserted_chunks: &'a mut usize,
184
185 next_entries: &'a mut Vec<SampleToChunkEntry>,
186 }
187
188 impl<'a> Context<'a> {
189 pub fn process_entry(&mut self, entry: ExpandedSampleToChunkEntry) {
190 if let Some(sample_indices_to_remove) =
192 entry_samples_to_remove(&entry.sample_indices, self.sample_indices_to_remove)
193 .first()
194 {
195 if sample_indices_to_remove.len() >= entry.sample_indices.len() {
196 self.remove_chunk_offset(entry.chunk_index);
198 *self.num_removed_chunks += 1;
199 } else {
200 self.process_entry_partial_match(sample_indices_to_remove, entry);
201 }
202 } else {
203 match self.next_entries.last() {
205 Some(prev_entry)
206 if prev_entry.samples_per_chunk == entry.samples_per_chunk
207 && prev_entry.sample_description_index
208 == entry.sample_description_index =>
209 {
210 }
212 _ => {
213 self.insert_or_update_chunk_entry(SampleToChunkEntry {
214 first_chunk: (entry.chunk_index + 1) as u32,
215 samples_per_chunk: entry.samples_per_chunk,
216 sample_description_index: entry.sample_description_index,
217 });
218 }
219 }
220 }
221 }
222
223 fn process_entry_partial_match(
224 &mut self,
225 sample_indices_to_remove: &Range<usize>,
226 entry: ExpandedSampleToChunkEntry,
227 ) {
228 if sample_indices_to_remove.start == entry.sample_indices.start {
229 self.shift_chunk_offset_right(
243 entry.chunk_index,
244 sample_indices_to_remove.clone(),
245 );
246
247 self.insert_or_update_chunk_entry(SampleToChunkEntry {
249 first_chunk: entry.chunk_index as u32 + 1,
250 samples_per_chunk: entry.samples_per_chunk
251 - sample_indices_to_remove.len() as u32,
252 sample_description_index: entry.sample_description_index,
253 });
254
255 self.process_entry(ExpandedSampleToChunkEntry {
257 chunk_index: entry.chunk_index,
258 sample_indices: sample_indices_to_remove.end..entry.sample_indices.end,
259 samples_per_chunk: entry.samples_per_chunk
260 - sample_indices_to_remove.len() as u32,
261 sample_description_index: entry.sample_description_index,
262 });
263 } else if sample_indices_to_remove.end == entry.sample_indices.end {
264 self.insert_or_update_chunk_entry(SampleToChunkEntry {
277 first_chunk: entry.chunk_index as u32 + 1,
278 samples_per_chunk: entry.samples_per_chunk
279 - sample_indices_to_remove.len() as u32,
280 sample_description_index: entry.sample_description_index,
281 });
282
283 } else {
286 self.insert_chunk_offset(
301 entry.chunk_index + 1,
302 sample_indices_to_remove.clone(),
303 );
304
305 self.insert_or_update_chunk_entry(SampleToChunkEntry {
307 first_chunk: entry.chunk_index as u32 + 1,
308 samples_per_chunk: (entry.sample_indices.len()
309 - (sample_indices_to_remove.start..entry.sample_indices.end).len())
310 as u32,
311 sample_description_index: entry.sample_description_index,
312 });
313
314 self.insert_or_update_chunk_entry(SampleToChunkEntry {
316 first_chunk: entry.chunk_index as u32 + 2,
317 samples_per_chunk: (entry.sample_indices.len()
318 - (entry.sample_indices.start..sample_indices_to_remove.end).len())
319 as u32,
320 sample_description_index: entry.sample_description_index,
321 });
322
323 *self.num_inserted_chunks += 1;
325
326 self.process_entry(ExpandedSampleToChunkEntry {
328 chunk_index: entry.chunk_index + 1,
329 sample_indices: sample_indices_to_remove.end..entry.sample_indices.end,
330 samples_per_chunk: entry.samples_per_chunk,
331 sample_description_index: entry.sample_description_index,
332 });
333 }
334 }
335
336 fn adjusted_chunk_index(&self, chunk_index: usize) -> usize {
337 chunk_index + *self.num_inserted_chunks - *self.num_removed_chunks
338 }
339
340 fn insert_or_update_chunk_entry(&mut self, mut entry: SampleToChunkEntry) {
341 entry.first_chunk = self.adjusted_chunk_index(entry.first_chunk as usize) as u32;
342 match self.next_entries.last_mut() {
343 Some(prev_entry) if prev_entry.first_chunk == entry.first_chunk => {
344 *prev_entry = entry
345 }
346 _ => {
347 self.next_entries.push(entry);
348 }
349 }
350 }
351
352 fn shift_chunk_offset_right(
354 &mut self,
355 chunk_index: usize,
356 removed_sample_indices: Range<usize>,
357 ) {
358 self.chunk_ops
359 .push(ChunkOffsetOperationUnresolved::ShiftRight {
360 chunk_index_unadjusted: chunk_index,
361 chunk_index: self.adjusted_chunk_index(chunk_index),
362 sample_indices: removed_sample_indices,
363 });
364 }
365
366 fn insert_chunk_offset(
367 &mut self,
368 chunk_index: usize,
369 removed_sample_indices: Range<usize>,
370 ) {
371 self.chunk_ops.push(ChunkOffsetOperationUnresolved::Insert {
372 chunk_index_unadjusted: chunk_index,
373 chunk_index: self.adjusted_chunk_index(chunk_index),
374 sample_indices: removed_sample_indices,
375 });
376 }
377
378 fn remove_chunk_offset(&mut self, chunk_index: usize) {
379 let chunk_index = self.adjusted_chunk_index(chunk_index);
380 match self.chunk_ops.last_mut() {
381 Some(ChunkOffsetOperationUnresolved::Remove(prev_op))
382 if prev_op.start == chunk_index =>
383 {
384 prev_op.end += 1;
386 }
387 _ => {
388 self.chunk_ops.push(ChunkOffsetOperationUnresolved::Remove(
390 chunk_index..chunk_index + 1,
391 ));
392 }
393 }
394 }
395 }
396
397 let num_sample_ranges_to_remove = sample_indices_to_remove.iter().count();
398 let prev_len = self.entries.len();
399 self.entries = SampleToChunkEntries(self.entries.expanded_iter(total_chunks).fold(
400 Vec::with_capacity(prev_len + (num_sample_ranges_to_remove * 4)),
402 |mut next_entries, entry| {
403 let mut ctx = Context {
404 sample_indices_to_remove,
405
406 chunk_ops: &mut chunk_ops,
407
408 num_removed_chunks: &mut num_removed_chunks,
409 num_inserted_chunks: &mut num_inserted_chunks,
410
411 next_entries: &mut next_entries,
412 };
413
414 ctx.process_entry(entry);
415
416 next_entries
417 },
418 ));
419 self.entries.shrink_to_fit();
420
421 chunk_ops
422 }
423}
424
425#[cfg(feature = "experimental-trim")]
426fn entry_samples_to_remove(
427 entry_sample_indices: &Range<usize>,
428 sample_indices_to_remove: &RangeSet<usize>,
429) -> RangeSet<usize> {
430 let mut entry_samples_to_remove = RangeSet::new();
431
432 for range in sample_indices_to_remove.overlapping(entry_sample_indices) {
433 let range =
434 range.start.max(entry_sample_indices.start)..range.end.min(entry_sample_indices.end);
435 entry_samples_to_remove.insert(range);
436 }
437
438 entry_samples_to_remove
439}
440
441impl<S: sample_to_chunk_atom_builder::State> SampleToChunkAtomBuilder<S> {
442 pub fn entry(
443 self,
444 entry: impl Into<SampleToChunkEntry>,
445 ) -> SampleToChunkAtomBuilder<sample_to_chunk_atom_builder::SetEntries<S>>
446 where
447 S::Entries: sample_to_chunk_atom_builder::IsUnset,
448 {
449 self.entries(vec![entry.into()])
450 }
451}
452
453impl From<Vec<SampleToChunkEntry>> for SampleToChunkAtom {
454 fn from(entries: Vec<SampleToChunkEntry>) -> Self {
455 SampleToChunkAtom {
456 version: 0,
457 flags: [0u8; 3],
458 entries: entries.into(),
459 }
460 }
461}
462
463impl ParseAtomData for SampleToChunkAtom {
464 fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
465 crate::atom::util::parser::assert_atom_type!(atom_type, STSC);
466 use crate::atom::util::parser::stream;
467 use winnow::Parser;
468 Ok(parser::parse_stsc_data.parse(stream(input))?)
469 }
470}
471
472impl SerializeAtom for SampleToChunkAtom {
473 fn atom_type(&self) -> FourCC {
474 STSC
475 }
476
477 fn into_body_bytes(self) -> Vec<u8> {
478 serializer::serialize_stsc_atom(self)
479 }
480}
481
482mod serializer {
483 use crate::atom::util::serializer::be_u32;
484
485 use super::SampleToChunkAtom;
486
487 pub fn serialize_stsc_atom(stsc: SampleToChunkAtom) -> Vec<u8> {
488 let mut data = Vec::new();
489
490 data.push(stsc.version);
491 data.extend(stsc.flags);
492
493 data.extend(be_u32(
494 stsc.entries
495 .len()
496 .try_into()
497 .expect("entries len should fit in u32"),
498 ));
499
500 for entry in stsc.entries.iter() {
501 data.extend(entry.first_chunk.to_be_bytes());
502 data.extend(entry.samples_per_chunk.to_be_bytes());
503 data.extend(entry.sample_description_index.to_be_bytes());
504 }
505
506 data
507 }
508}
509
510mod parser {
511 use winnow::{
512 binary::{be_u32, length_repeat},
513 combinator::{seq, trace},
514 error::{StrContext, StrContextValue},
515 ModalResult, Parser,
516 };
517
518 use super::{SampleToChunkAtom, SampleToChunkEntries, SampleToChunkEntry};
519 use crate::atom::util::parser::{flags3, version, Stream};
520
521 pub fn parse_stsc_data(input: &mut Stream<'_>) -> ModalResult<SampleToChunkAtom> {
522 trace(
523 "stsc",
524 seq!(SampleToChunkAtom {
525 version: version,
526 flags: flags3,
527 entries: length_repeat(be_u32, entry)
528 .map(SampleToChunkEntries)
529 .context(StrContext::Label("entries")),
530 })
531 .context(StrContext::Label("stsc")),
532 )
533 .parse_next(input)
534 }
535
536 fn entry(input: &mut Stream<'_>) -> ModalResult<SampleToChunkEntry> {
537 trace(
538 "entry",
539 seq!(SampleToChunkEntry {
540 first_chunk: be_u32
541 .verify(|first_chunk| *first_chunk > 0)
542 .context(StrContext::Label("first_chunk"))
543 .context(StrContext::Expected(StrContextValue::Description(
544 "1-based index"
545 ))),
546 samples_per_chunk: be_u32
547 .verify(|samples_per_chunk| *samples_per_chunk > 0)
548 .context(StrContext::Label("samples_per_chunk"))
549 .context(StrContext::Expected(StrContextValue::Description(
550 "sample count > 0"
551 ))),
552 sample_description_index: be_u32
553 .context(StrContext::Label("sample_description_index"))
554 .context(StrContext::Expected(StrContextValue::Description(
555 "1-based index"
556 ))),
557 })
558 .context(StrContext::Label("entry")),
559 )
560 .parse_next(input)
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use crate::atom::test_utils::test_atom_roundtrip;
568
569 #[test]
571 fn test_stsc_roundtrip() {
572 test_atom_roundtrip::<SampleToChunkAtom>(STSC);
573 }
574}
575
576#[cfg(feature = "experimental-trim")]
577#[cfg(test)]
578mod trim_tests {
579 use super::*;
580
581 struct EntrySamplesToRemoveTestCase {
582 entry_start_sample_index: usize,
583 entry_end_sample_index: usize,
584 sample_indices_to_remove: Vec<Range<usize>>,
585 expected_entry_samples_to_remove: Vec<Range<usize>>,
586 }
587
588 fn test_entry_samples_to_remove(tc: EntrySamplesToRemoveTestCase) {
589 let sample_indices_to_remove = RangeSet::from_iter(tc.sample_indices_to_remove.into_iter());
590 let entry_sample_indices = tc.entry_start_sample_index..tc.entry_end_sample_index + 1;
591 let actual_entry_samples_to_remove =
592 entry_samples_to_remove(&entry_sample_indices, &sample_indices_to_remove);
593 let expected_entry_samples_to_remove =
594 RangeSet::from_iter(tc.expected_entry_samples_to_remove.into_iter());
595 assert_eq!(
596 actual_entry_samples_to_remove,
597 expected_entry_samples_to_remove
598 );
599 }
600
601 mod test_entry_samples_to_remove {
602 use super::*;
603
604 macro_rules! test_entry_samples_to_remove {
605 ($(
606 $name:ident => {
607 $( $field:ident: $value:expr ),+ $(,)?
608 },
609 )*) => {
610 $(
611 #[test]
612 fn $name() {
613 test_entry_samples_to_remove!(@inner $( $field: $value ),+);
614 }
615 )*
616 };
617
618 (@inner $( $field:ident: $value:expr ),+) => {
619 let tc = EntrySamplesToRemoveTestCase {
620 $( $field: $value ),+,
621 };
622
623 test_entry_samples_to_remove(tc);
624 };
625 }
626
627 test_entry_samples_to_remove!(
628 entry_contained_in_single_range => {
629 entry_start_sample_index: 800,
630 entry_end_sample_index: 1200,
631 sample_indices_to_remove: vec![300..2000],
632 expected_entry_samples_to_remove: vec![800..1201],
633 },
634 entry_contained_in_multiple_ranges => {
635 entry_start_sample_index: 800,
636 entry_end_sample_index: 1200,
637 sample_indices_to_remove: vec![300..900, 1000..2000],
638 expected_entry_samples_to_remove: vec![800..900, 1000..1201],
639 },
640 entry_starts_in_single_range => {
641 entry_start_sample_index: 800,
642 entry_end_sample_index: 1200,
643 sample_indices_to_remove: vec![1000..2000],
644 expected_entry_samples_to_remove: vec![1000..1201],
645 },
646 entry_ends_in_single_range => {
647 entry_start_sample_index: 800,
648 entry_end_sample_index: 1200,
649 sample_indices_to_remove: vec![100..1000],
650 expected_entry_samples_to_remove: vec![800..1000],
651 },
652 single_range_contained_in_entry => {
653 entry_start_sample_index: 800,
654 entry_end_sample_index: 1200,
655 sample_indices_to_remove: vec![900..1000],
656 expected_entry_samples_to_remove: vec![900..1000],
657 },
658 );
659 }
660
661 #[derive(Builder)]
662 struct RemoveSampleIndicesTestCase {
663 #[builder(default = 20)]
664 total_chunks: usize,
665 sample_indices_to_remove: Vec<Range<usize>>,
666 expected_removed_chunk_indices: Vec<Range<usize>>,
667 expected_entries: Vec<SampleToChunkEntry>,
668 }
669
670 fn test_remove_sample_indices_default_stsc() -> SampleToChunkAtom {
671 SampleToChunkAtom::builder()
672 .entries(vec![
673 SampleToChunkEntry {
676 first_chunk: 1,
677 samples_per_chunk: 10,
678 sample_description_index: 1,
679 },
680 SampleToChunkEntry {
683 first_chunk: 2,
684 samples_per_chunk: 20,
685 sample_description_index: 1,
686 },
687 SampleToChunkEntry {
690 first_chunk: 4,
691 samples_per_chunk: 10,
692 sample_description_index: 1,
693 },
694 SampleToChunkEntry {
698 first_chunk: 10,
699 samples_per_chunk: 10,
700 sample_description_index: 2,
702 },
703 ])
704 .build()
705 }
706
707 fn test_remove_sample_indices<F>(mut stsc: SampleToChunkAtom, test_case: F)
708 where
709 F: FnOnce(&SampleToChunkAtom) -> RemoveSampleIndicesTestCase,
710 {
711 let test_case = test_case(&stsc);
712 let total_chunks = test_case.total_chunks;
713 let sample_indices_to_remove =
714 RangeSet::from_iter(test_case.sample_indices_to_remove.into_iter());
715 let actual_chunk_offset_ops =
716 stsc.remove_sample_indices(&sample_indices_to_remove, total_chunks);
717
718 let actual_removed_chunk_indices = actual_chunk_offset_ops
720 .iter()
721 .filter_map(|op| match op {
722 ChunkOffsetOperationUnresolved::Remove(chunk_offsets) => Some(chunk_offsets),
723 _ => None,
724 })
725 .cloned()
726 .scan(0usize, |n_removed, range| {
729 let range = (range.start + *n_removed)..(range.end + *n_removed);
730 *n_removed += range.len();
731 Some(range)
732 })
733 .collect::<Vec<_>>();
734
735 assert_eq!(
736 actual_removed_chunk_indices, test_case.expected_removed_chunk_indices,
737 "removed chunk indices don't match what's expected",
738 );
739
740 stsc.entries
741 .iter()
742 .zip(test_case.expected_entries.iter())
743 .enumerate()
744 .for_each(|(index, (actual, expected))| {
745 assert_eq!(
746 actual, expected,
747 "sample to chunk entries[{index}] doesn't match what's expected\n{:#?}",
748 stsc.entries,
749 );
750 });
751
752 assert_eq!(
753 stsc.entries.0.len(),
754 test_case.expected_entries.len(),
755 "expected {} sample to chunk entries, got {}: {:#?}",
756 test_case.expected_entries.len(),
757 stsc.entries.len(),
758 stsc.entries,
759 );
760 }
761
762 macro_rules! test_remove_sample_indices {
763 ($($name:ident $($stsc:expr)? => $test_case:expr,)*) => {
764 $(
765 #[test]
766 fn $name() {
767 test_remove_sample_indices!(@inner $($stsc)? => $test_case);
768 }
769 )*
770 };
771 (@inner => $test_case:expr) => {
772 test_remove_sample_indices!(@inner test_remove_sample_indices_default_stsc() => $test_case);
773 };
774 (@inner $stsc:expr => $test_case:expr) => {
775 test_remove_sample_indices($stsc, $test_case);
776 };
777 }
778
779 mod test_remove_sample_indices {
781 use super::*;
782
783 test_remove_sample_indices!(
784 remove_first_entry => |stsc| RemoveSampleIndicesTestCase::builder().
785 sample_indices_to_remove(vec![0..10]).
786 expected_removed_chunk_indices(vec![0..1]).
787 expected_entries(stsc.entries[1..].iter().cloned().map(|mut entry| {
788 entry.first_chunk -= 1;
789 entry
790 }).collect::<Vec<_>>()).build(),
791 remove_first_sample_from_first_entry => |stsc| {
792 let mut expected_entries = stsc.entries.0.clone();
793 expected_entries[0].samples_per_chunk -= 1;
794 RemoveSampleIndicesTestCase::builder().
795 sample_indices_to_remove(vec![0..1]).
796 expected_removed_chunk_indices(vec![]).
797 expected_entries(expected_entries).build()
798 },
799 remove_last_sample_from_first_entry => |stsc| {
800 let mut expected_entries = stsc.entries.0.clone();
801 expected_entries[0].samples_per_chunk -= 1;
803 RemoveSampleIndicesTestCase::builder().
804 sample_indices_to_remove(vec![9..10]).
805 expected_removed_chunk_indices(vec![]).
806 expected_entries(expected_entries).build()
807 },
808 remove_sample_from_second_entry => |stsc| {
809 let mut expected_entries = stsc.entries.0.clone();
810 let mut inserted_entry = expected_entries[1].clone();
811 expected_entries[1].first_chunk += 1;
812 inserted_entry.samples_per_chunk -= 1;
813 expected_entries.insert(1, inserted_entry);
814 RemoveSampleIndicesTestCase::builder().
815 sample_indices_to_remove(vec![10..11]).
816 expected_removed_chunk_indices(vec![]).
817 expected_entries(expected_entries).build()
818 },
819 remove_first_chunk_from_second_entry => |stsc| {
820 let mut expected_entries = stsc.entries.0.clone();
821 expected_entries.iter_mut().skip(2).for_each(|entry| entry.first_chunk -= 1);
822 RemoveSampleIndicesTestCase::builder().
823 sample_indices_to_remove(vec![10..30]).
824 expected_removed_chunk_indices(vec![1..2]).
825 expected_entries(expected_entries).build()
826 },
827 remove_five_samples_from_last_entry_middle => |stsc| {
828 RemoveSampleIndicesTestCase::builder().
829 sample_indices_to_remove(vec![151..156]).
830 expected_removed_chunk_indices(vec![]).
831 expected_entries(vec![
834 stsc.entries[0].clone(),
835 stsc.entries[1].clone(),
836 stsc.entries[2].clone(),
837 SampleToChunkEntry {
841 first_chunk: 10,
842 samples_per_chunk: 10,
843 sample_description_index: 2,
844 },
845 SampleToChunkEntry {
850 first_chunk: 14,
851 samples_per_chunk: 1,
852 sample_description_index: 2,
853 },
854 SampleToChunkEntry {
859 first_chunk: 15,
860 samples_per_chunk: 4,
861 sample_description_index: 2,
862 },
863 SampleToChunkEntry {
868 first_chunk: 16,
869 samples_per_chunk: 10,
870 sample_description_index: 2,
871 },
872 ]).build()
873 },
874 remove_fifteen_samples_from_last_entry_middle => |stsc| {
875 RemoveSampleIndicesTestCase::builder().
876 sample_indices_to_remove(vec![150..165]).
877 expected_removed_chunk_indices(vec![13..14]).
878 expected_entries(vec![
882 stsc.entries[0].clone(),
883 stsc.entries[1].clone(),
884 stsc.entries[2].clone(),
885 SampleToChunkEntry {
889 first_chunk: 10,
890 samples_per_chunk: 10,
891 sample_description_index: 2,
892 },
893 SampleToChunkEntry {
898 first_chunk: 14,
899 samples_per_chunk: 5,
900 sample_description_index: 2,
901 },
902 SampleToChunkEntry {
907 first_chunk: 15,
908 samples_per_chunk: 10,
909 sample_description_index: 2,
910 },
911 ]).build()
912 },
913 remove_second_entry_merge_first_and_third => |stsc| RemoveSampleIndicesTestCase::builder().
914 sample_indices_to_remove(vec![10..50]).
915 expected_removed_chunk_indices(vec![1..3]).
916 expected_entries(vec![
917 stsc.entries.first().cloned().unwrap(),
918 stsc.entries.last().cloned().map(|mut entry| {
919 entry.first_chunk -= 2;
920 entry
921 }).unwrap(),
922 ]).build(),
923 remove_second_and_third_entry_no_merge => |stsc| RemoveSampleIndicesTestCase::builder().
924 sample_indices_to_remove(vec![10..110]).
925 expected_removed_chunk_indices(vec![1..9]).
926 expected_entries(vec![
927 stsc.entries.first().cloned().unwrap(),
928 stsc.entries.last().cloned().map(|mut entry| {
929 entry.first_chunk -= 8;
930 entry
931 }).unwrap(),
932 ]).build(),
933 remove_first_and_last_entry => |stsc| RemoveSampleIndicesTestCase::builder().
934 sample_indices_to_remove(vec![0..10, 110..220]).
935 expected_removed_chunk_indices(vec![0..1, 9..20]).
936 expected_entries(vec![
937 stsc.entries.get(1).cloned().map(|mut entry| {
938 entry.first_chunk = 1;
939 entry
940 }).unwrap(),
941 stsc.entries.get(2).cloned().map(|mut entry| {
942 entry.first_chunk = 3;
943 entry
944 }).unwrap(),
945 ]).build(),
946
947 remove_last_chunk_single_entry {
948 SampleToChunkAtom::builder().
949 entry(SampleToChunkEntry::builder().
950 first_chunk(1).
951 samples_per_chunk(2).
952 sample_description_index(1).
953 build(),
954 ).build()
955 } => |stsc| RemoveSampleIndicesTestCase::builder().
956 sample_indices_to_remove(vec![38..40]).
957 expected_removed_chunk_indices(vec![19..20]).
958 expected_entries(vec![
959 stsc.entries.first().cloned().unwrap(),
960 ]).build(),
961
962 remove_multiple_ranges_from_single_entry {
963 SampleToChunkAtom::builder().
964 entry(SampleToChunkEntry::builder().
965 first_chunk(1).
966 samples_per_chunk(1).
967 sample_description_index(1).
968 build(),
969 ).build()
970 } => |stsc| RemoveSampleIndicesTestCase::builder().
971 total_chunks(100).
972 sample_indices_to_remove(vec![20..41, 60..81]).
973 expected_removed_chunk_indices(vec![20..41, 60..81]).
974 expected_entries(vec![
975 stsc.entries.first().cloned().unwrap(),
976 ]).build(),
977
978 remove_mid_second_chunk_to_mid_last_chunk => |stsc| {
979 RemoveSampleIndicesTestCase::builder().
980 sample_indices_to_remove(vec![15..215]).
981 expected_removed_chunk_indices(vec![2..19]).
982 expected_entries(vec![
983 stsc.entries[0].clone(),
985 SampleToChunkEntry {
987 first_chunk: 2,
988 samples_per_chunk: 5,
989 sample_description_index: 1,
990 },
991 SampleToChunkEntry {
993 first_chunk: 3,
994 samples_per_chunk: 5,
995 sample_description_index: 2,
996 },
997 ]).build()
998 },
999
1000 remove_mid_fifth_chunk_to_mid_last_chunk => |stsc| {
1001 RemoveSampleIndicesTestCase::builder().
1002 sample_indices_to_remove(vec![65..215]).
1003 expected_removed_chunk_indices(vec![5..19]).
1004 expected_entries(vec![
1005 stsc.entries[0].clone(),
1007 stsc.entries[1].clone(),
1008 SampleToChunkEntry {
1010 first_chunk: 4,
1011 samples_per_chunk: 10,
1012 sample_description_index: 1,
1013 },
1014 SampleToChunkEntry {
1016 first_chunk: 5,
1017 samples_per_chunk: 5,
1018 sample_description_index: 1,
1019 },
1020 SampleToChunkEntry {
1022 first_chunk: 6,
1023 samples_per_chunk: 5,
1024 sample_description_index: 2,
1025 },
1026 ]).build()
1027 },
1028 );
1029 }
1030}