1use bon::bon;
2use derive_more::{Deref, DerefMut};
3use std::fmt;
4
5use crate::{
6 atom::{
7 util::{DebugList, DebugUpperHex},
8 FourCC,
9 },
10 parser::ParseAtomData,
11 writer::SerializeAtom,
12 ParseError,
13};
14
15#[cfg(feature = "experimental-trim")]
16use {crate::atom::stsz::RemovedSampleSizes, anyhow::anyhow, std::ops::Range};
17
18pub const STCO: FourCC = FourCC::new(b"stco");
19pub const CO64: FourCC = FourCC::new(b"co64");
20
21#[derive(Default, Clone, Deref, DerefMut, PartialEq, Eq)]
22pub struct ChunkOffsets(Vec<u64>);
23
24impl ChunkOffsets {
25 pub fn into_inner(self) -> Vec<u64> {
26 self.0
27 }
28
29 pub fn inner(&self) -> &[u64] {
30 &self.0
31 }
32}
33
34impl From<Vec<u64>> for ChunkOffsets {
35 fn from(value: Vec<u64>) -> Self {
36 Self(value)
37 }
38}
39
40impl FromIterator<u64> for ChunkOffsets {
41 fn from_iter<T: IntoIterator<Item = u64>>(iter: T) -> Self {
42 Self(Vec::from_iter(iter))
43 }
44}
45
46impl fmt::Debug for ChunkOffsets {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 fmt::Debug::fmt(&DebugList::new(self.0.iter().map(DebugUpperHex), 10), f)
49 }
50}
51
52#[derive(Default, Debug, Clone)]
54pub struct ChunkOffsetAtom {
55 pub version: u8,
57 pub flags: [u8; 3],
59 pub chunk_offsets: ChunkOffsets,
61 pub is_64bit: bool,
63}
64
65#[cfg(feature = "experimental-trim")]
66#[derive(Debug)]
67pub(crate) enum ChunkOffsetOperationUnresolved {
68 Remove(Range<usize>),
69 Insert {
70 chunk_index_unadjusted: usize,
72 chunk_index: usize,
74 sample_indices: Range<usize>,
76 },
77 ShiftRight {
78 chunk_index_unadjusted: usize,
80 chunk_index: usize,
82 sample_indices: Range<usize>,
84 },
85}
86
87#[cfg(feature = "experimental-trim")]
88impl ChunkOffsetOperationUnresolved {
89 pub fn resolve(
90 self,
91 chunk_offsets: &ChunkOffsets,
92 removed_sample_sizes: &RemovedSampleSizes,
93 ) -> anyhow::Result<ChunkOffsetOperation> {
94 let derive_new_offset = |chunk_index: usize, sample_indices: Range<usize>| {
95 let prev_offset = *chunk_offsets
96 .get(chunk_index)
97 .ok_or_else(|| anyhow!("chunk index {chunk_index} not found"))?;
98
99 let delta = removed_sample_sizes
100 .get_sizes(sample_indices.clone())
101 .ok_or_else(|| anyhow!("sample indices {sample_indices:?} not found"))?
102 .iter()
103 .map(|s| *s as u64)
104 .sum::<u64>();
105
106 Ok::<u64, anyhow::Error>(prev_offset + delta)
107 };
108
109 Ok(match self {
110 Self::Remove(chunk_offsets) => ChunkOffsetOperation::Remove(chunk_offsets),
111 Self::Insert {
112 chunk_index_unadjusted,
113 chunk_index,
114 sample_indices,
115 } => {
116 let offset = derive_new_offset(chunk_index_unadjusted - 1, sample_indices)?;
117 ChunkOffsetOperation::Insert(chunk_index, offset)
118 }
119 Self::ShiftRight {
120 chunk_index_unadjusted,
121 chunk_index,
122 sample_indices,
123 } => {
124 let new_offset = derive_new_offset(chunk_index_unadjusted, sample_indices)?;
125 ChunkOffsetOperation::Replace(chunk_index, new_offset)
126 }
127 })
128 }
129}
130
131#[cfg(feature = "experimental-trim")]
132#[derive(Debug)]
133pub(crate) enum ChunkOffsetOperation {
134 Remove(Range<usize>),
135 Insert(usize, u64),
136 Replace(usize, u64),
137}
138
139#[bon]
140impl ChunkOffsetAtom {
141 #[builder]
142 pub fn new(
143 #[builder(default = 0)] version: u8,
144 #[builder(default = [0u8; 3])] flags: [u8; 3],
145 #[builder(with = FromIterator::from_iter)] chunk_offsets: Vec<u64>,
146 #[builder(default = false)] is_64bit: bool,
147 ) -> Self {
148 Self {
149 version,
150 flags,
151 chunk_offsets: chunk_offsets.into(),
152 is_64bit,
153 }
154 }
155
156 pub fn chunk_count(&self) -> usize {
158 self.chunk_offsets.len()
159 }
160
161 #[cfg(feature = "experimental-trim")]
163 pub(crate) fn apply_operations(&mut self, ops: Vec<ChunkOffsetOperation>) {
164 for op in ops {
165 match op {
166 ChunkOffsetOperation::Remove(chunk_indices_to_remove) => {
167 self.chunk_offsets.drain(chunk_indices_to_remove);
168 }
169 ChunkOffsetOperation::Insert(chunk_index, offset) => {
170 self.chunk_offsets.insert(chunk_index, offset);
171 }
172 ChunkOffsetOperation::Replace(chunk_index, new_offset) => {
173 let chunk = self
174 .chunk_offsets
175 .get_mut(chunk_index)
176 .expect("chunk offset must exist");
177 *chunk = new_offset;
178 }
179 }
180 }
181 }
182}
183
184impl<S: chunk_offset_atom_builder::State> ChunkOffsetAtomBuilder<S> {
185 pub fn chunk_offset(
186 self,
187 chunk_offset: impl Into<u64>,
188 ) -> ChunkOffsetAtomBuilder<chunk_offset_atom_builder::SetChunkOffsets<S>>
189 where
190 S::ChunkOffsets: chunk_offset_atom_builder::IsUnset,
191 {
192 self.chunk_offsets(vec![chunk_offset.into()])
193 }
194}
195
196impl ParseAtomData for ChunkOffsetAtom {
197 fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
198 crate::atom::util::parser::assert_atom_type!(atom_type, STCO, CO64);
199 use crate::atom::util::parser::stream;
200 use winnow::Parser;
201 Ok(match atom_type {
202 STCO => parser::parse_stco_data.parse(stream(input))?,
203 CO64 => parser::parse_co64_data.parse(stream(input))?,
204 _ => unreachable!(),
205 })
206 }
207}
208
209impl SerializeAtom for ChunkOffsetAtom {
210 fn atom_type(&self) -> FourCC {
211 if self.is_64bit {
213 CO64
214 } else {
215 STCO
216 }
217 }
218
219 fn into_body_bytes(self) -> Vec<u8> {
220 serializer::serialize_stco_co64_data(self)
221 }
222}
223
224mod serializer {
225 use crate::atom::{util::serializer::be_u32, ChunkOffsetAtom};
226
227 pub fn serialize_stco_co64_data(atom: ChunkOffsetAtom) -> Vec<u8> {
228 let mut data = Vec::new();
229
230 data.push(atom.version);
231 data.extend(atom.flags);
232 data.extend(be_u32(
233 atom.chunk_offsets
234 .len()
235 .try_into()
236 .expect("chunk offsets length must fit in u32"),
237 ));
238
239 atom.chunk_offsets.0.into_iter().for_each(|offset| {
240 if atom.is_64bit {
241 data.extend(offset.to_be_bytes());
242 } else {
243 data.extend(be_u32(
244 offset.try_into().expect("chunk offset must fit in u32"),
245 ))
246 }
247 });
248
249 data
250 }
251}
252
253mod parser {
254 use winnow::{
255 binary::{be_u32, be_u64},
256 combinator::{empty, repeat, seq, trace},
257 error::{ContextError, ErrMode, StrContext},
258 ModalResult, Parser,
259 };
260
261 use super::{ChunkOffsetAtom, ChunkOffsets};
262 use crate::atom::util::parser::{byte_array, version, Stream};
263
264 pub fn parse_stco_data(input: &mut Stream<'_>) -> ModalResult<ChunkOffsetAtom> {
265 parse_stco_co64_data_inner(false).parse_next(input)
266 }
267
268 pub fn parse_co64_data(input: &mut Stream<'_>) -> ModalResult<ChunkOffsetAtom> {
269 parse_stco_co64_data_inner(true).parse_next(input)
270 }
271
272 fn parse_stco_co64_data_inner<'i>(
273 is_64bit: bool,
274 ) -> impl Parser<Stream<'i>, ChunkOffsetAtom, ErrMode<ContextError>> {
275 trace(
276 if is_64bit { "co64" } else { "stco" },
277 move |input: &mut Stream<'_>| {
278 seq!(ChunkOffsetAtom {
279 version: version,
280 flags: byte_array.context(StrContext::Label("flags")),
281 chunk_offsets: chunk_offsets(is_64bit)
282 .map(ChunkOffsets)
283 .context(StrContext::Label("chunk_offsets")),
284 is_64bit: empty.value(is_64bit),
285 })
286 .parse_next(input)
287 },
288 )
289 }
290
291 fn chunk_offsets<'i>(
292 is_64bit: bool,
293 ) -> impl Parser<Stream<'i>, Vec<u64>, ErrMode<ContextError>> {
294 trace("chunk_offsets", move |input: &mut Stream<'_>| {
295 let entry_count = be_u32.parse_next(input)?;
296 repeat(entry_count as usize, chunk_offset(is_64bit)).parse_next(input)
297 })
298 }
299
300 fn chunk_offset<'i>(is_64bit: bool) -> impl Parser<Stream<'i>, u64, ErrMode<ContextError>> {
301 trace("chunk_offset", move |input: &mut Stream<'_>| {
302 if is_64bit {
303 be_u64.parse_next(input)
304 } else {
305 be_u32.map(|v| v as u64).parse_next(input)
306 }
307 })
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::atom::test_utils::test_atom_roundtrip;
315
316 #[test]
318 fn test_stco_co64_roundtrip() {
319 test_atom_roundtrip::<ChunkOffsetAtom>(STCO);
320 test_atom_roundtrip::<ChunkOffsetAtom>(CO64);
321 }
322}