1use std::time::Duration;
2
3use bon::bon;
4
5use crate::{
6 atom::{util::scaled_duration, FourCC},
7 parser::ParseAtomData,
8 writer::SerializeAtom,
9 ParseError,
10};
11
12pub const ELST: FourCC = FourCC::new(b"elst");
13
14#[derive(Default, Debug, Clone)]
15pub struct EditListAtom {
16 pub version: u8,
18 pub flags: [u8; 3],
20 pub entries: Vec<EditEntry>,
22}
23
24impl EditListAtom {
25 pub fn new(entries: impl Into<Vec<EditEntry>>) -> Self {
26 Self {
27 entries: entries.into(),
28 ..Default::default()
29 }
30 }
31
32 pub fn replace_entries(&mut self, entries: impl Into<Vec<EditEntry>>) -> &mut Self {
33 self.entries = entries.into();
34 self
35 }
36}
37
38pub struct MediaTime {
39 start_offset: Option<Duration>,
41}
42
43impl Default for MediaTime {
44 fn default() -> Self {
45 Self {
46 start_offset: Some(Duration::from_secs(0)),
47 }
48 }
49}
50
51impl MediaTime {
52 pub fn new(start_offset: Duration) -> Self {
54 Self {
55 start_offset: Some(start_offset),
56 }
57 }
58
59 pub fn new_empty() -> Self {
61 Self { start_offset: None }
62 }
63
64 pub fn scaled(&self, movie_timescale: u64) -> i64 {
65 match self.start_offset {
66 Some(start_offset) if start_offset.is_zero() => 0,
67 Some(start_offset) => i64::try_from(scaled_duration(start_offset, movie_timescale))
68 .expect("scaled duration should fit in i64"),
69 None => -1,
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
75pub struct EditEntry {
76 pub segment_duration: u64,
78 pub media_time: i64,
81 pub media_rate: f32,
83}
84
85#[bon]
86impl EditEntry {
87 #[builder]
88 pub fn new(
89 movie_timescale: u64,
90 segment_duration: Duration,
91 #[builder(default = Default::default())] media_time: MediaTime,
92 #[builder(default = 1.0)] media_rate: f32,
93 ) -> Self {
94 Self {
95 segment_duration: scaled_duration(segment_duration, movie_timescale),
96 media_time: media_time.scaled(movie_timescale),
97 media_rate,
98 }
99 }
100}
101
102impl ParseAtomData for EditListAtom {
103 fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
104 crate::atom::util::parser::assert_atom_type!(atom_type, ELST);
105 use crate::atom::util::parser::stream;
106 use winnow::Parser;
107 Ok(parser::parse_elst_data.parse(stream(input))?)
108 }
109}
110
111impl SerializeAtom for EditListAtom {
112 fn atom_type(&self) -> FourCC {
113 ELST
114 }
115
116 fn into_body_bytes(self) -> Vec<u8> {
117 serializer::serialize_elst_atom(self)
118 }
119}
120
121mod serializer {
122 use crate::atom::{elst::EditEntry, util::serializer::fixed_point_16x16};
123
124 use super::EditListAtom;
125
126 pub fn serialize_elst_atom(atom: EditListAtom) -> Vec<u8> {
127 vec![
128 version(atom.version),
129 flags(atom.flags),
130 entry_count(atom.entries.len()),
131 entries(atom.version, atom.entries),
132 ]
133 .into_iter()
134 .flatten()
135 .collect()
136 }
137
138 fn version(version: u8) -> Vec<u8> {
139 vec![version]
140 }
141
142 fn flags(flags: [u8; 3]) -> Vec<u8> {
143 flags.to_vec()
144 }
145
146 fn entry_count(count: usize) -> Vec<u8> {
147 u32::try_from(count)
148 .expect("entries len should fit in u32")
149 .to_be_bytes()
150 .to_vec()
151 }
152
153 fn entries(version: u8, entries: Vec<EditEntry>) -> Vec<u8> {
154 match version {
155 1 => entries.into_iter().flat_map(entry_64).collect(),
156 _ => entries.into_iter().flat_map(entry_32).collect(),
157 }
158 }
159
160 fn entry_32(entry: EditEntry) -> Vec<u8> {
161 vec![
162 u32::try_from(entry.segment_duration)
163 .expect("segument_duration should fit in u32")
164 .to_be_bytes()
165 .to_vec(),
166 i32::try_from(entry.media_time)
167 .expect("media_time should fit in i32")
168 .to_be_bytes()
169 .to_vec(),
170 media_rate(entry.media_rate),
171 ]
172 .into_iter()
173 .flatten()
174 .collect()
175 }
176
177 fn entry_64(entry: EditEntry) -> Vec<u8> {
178 vec![
179 entry.segment_duration.to_be_bytes().to_vec(),
180 entry.media_time.to_be_bytes().to_vec(),
181 media_rate(entry.media_rate),
182 ]
183 .into_iter()
184 .flatten()
185 .collect()
186 }
187
188 fn media_rate(media_rate: f32) -> Vec<u8> {
189 fixed_point_16x16(media_rate).to_vec()
190 }
191}
192
193mod parser {
194 use winnow::{
195 binary::{be_i64, be_u32, be_u64, length_repeat},
196 combinator::{seq, trace},
197 error::StrContext,
198 ModalResult, Parser,
199 };
200
201 use super::EditListAtom;
202 use crate::atom::{
203 elst::EditEntry,
204 util::parser::{be_i32_as, be_u32_as, fixed_point_16x16, flags3, version, Stream},
205 };
206
207 pub fn parse_elst_data(input: &mut Stream<'_>) -> ModalResult<EditListAtom> {
208 trace(
209 "elst",
210 seq!(EditListAtom {
211 version: version,
212 flags: flags3,
213 entries: length_repeat(
214 be_u32.context(StrContext::Label("entry_count")),
215 match version {
216 1 => entry_64,
217 _ => entry_32,
218 }
219 ),
220 })
221 .context(StrContext::Label("elst")),
222 )
223 .parse_next(input)
224 }
225
226 fn entry_32(input: &mut Stream<'_>) -> ModalResult<EditEntry> {
227 trace(
228 "entry_32",
229 seq!(EditEntry {
230 segment_duration: be_u32_as.context(StrContext::Label("segment_duration")),
231 media_time: be_i32_as.context(StrContext::Label("media_time")),
232 media_rate: media_rate,
233 })
234 .context(StrContext::Label("entry")),
235 )
236 .parse_next(input)
237 }
238
239 fn entry_64(input: &mut Stream<'_>) -> ModalResult<EditEntry> {
240 trace(
241 "entry_64",
242 seq!(EditEntry {
243 segment_duration: be_u64.context(StrContext::Label("segment_duration")),
244 media_time: be_i64.context(StrContext::Label("media_time")),
245 media_rate: media_rate,
246 })
247 .context(StrContext::Label("entry")),
248 )
249 .parse_next(input)
250 }
251
252 fn media_rate(input: &mut Stream<'_>) -> ModalResult<f32> {
253 trace(
254 "media_rate",
255 fixed_point_16x16.context(StrContext::Label("media_rate")),
256 )
257 .parse_next(input)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::atom::test_utils::test_atom_roundtrip;
265
266 #[test]
268 fn test_elst_roundtrip() {
269 test_atom_roundtrip::<EditListAtom>(ELST);
270 }
271}