1#![allow(dead_code)]
2use crate::{FrameRate, Timecode, TimecodeError};
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct TimecodeRun {
12 pub start: Timecode,
14 pub frame_count: u64,
16}
17
18impl TimecodeRun {
19 pub fn new(start: Timecode, frame_count: u64) -> Result<Self, TimecodeError> {
25 if frame_count == 0 {
26 return Err(TimecodeError::InvalidConfiguration);
27 }
28 Ok(Self { start, frame_count })
29 }
30
31 #[allow(clippy::cast_precision_loss)]
33 pub fn duration_secs(&self) -> f64 {
34 let fps = self.start.frame_rate.fps as f64;
35 self.frame_count as f64 / fps
36 }
37
38 pub fn contains(&self, tc: &Timecode) -> bool {
40 if tc.frame_rate != self.start.frame_rate {
41 return false;
42 }
43 let start_f = self.start.to_frames();
44 let tc_f = tc.to_frames();
45 tc_f >= start_f && tc_f < start_f + self.frame_count
46 }
47
48 pub fn end_timecode(&self, rate: FrameRate) -> Result<Timecode, TimecodeError> {
54 let end_frame = self.start.to_frames() + self.frame_count - 1;
55 Timecode::from_frames(end_frame, rate)
56 }
57}
58
59#[derive(Debug, Clone)]
62pub struct TimecodeSequence {
63 runs: Vec<TimecodeRun>,
65 total_frames: u64,
67}
68
69impl TimecodeSequence {
70 pub fn new() -> Self {
72 Self {
73 runs: Vec::new(),
74 total_frames: 0,
75 }
76 }
77
78 pub fn push(&mut self, run: TimecodeRun) {
80 self.total_frames += run.frame_count;
81 self.runs.push(run);
82 }
83
84 pub fn total_frames(&self) -> u64 {
86 self.total_frames
87 }
88
89 pub fn run_count(&self) -> usize {
91 self.runs.len()
92 }
93
94 pub fn get(&self, index: usize) -> Option<&TimecodeRun> {
96 self.runs.get(index)
97 }
98
99 pub fn is_contiguous(&self, a_idx: usize, b_idx: usize) -> bool {
102 let (Some(a), Some(b)) = (self.runs.get(a_idx), self.runs.get(b_idx)) else {
103 return false;
104 };
105 if a.start.frame_rate != b.start.frame_rate {
106 return false;
107 }
108 let a_end = a.start.to_frames() + a.frame_count;
109 let b_start = b.start.to_frames();
110 a_end == b_start
111 }
112
113 #[allow(clippy::cast_precision_loss)]
115 pub fn total_duration_secs(&self) -> f64 {
116 self.runs.iter().map(TimecodeRun::duration_secs).sum()
117 }
118
119 pub fn find_run_at_offset(&self, offset: u64) -> Option<(usize, u64)> {
122 let mut cumulative = 0u64;
123 for (i, run) in self.runs.iter().enumerate() {
124 if offset < cumulative + run.frame_count {
125 return Some((i, offset - cumulative));
126 }
127 cumulative += run.frame_count;
128 }
129 None
130 }
131
132 pub fn compact(&mut self) {
134 if self.runs.len() < 2 {
135 return;
136 }
137 let mut merged: Vec<TimecodeRun> = Vec::with_capacity(self.runs.len());
138 merged.push(self.runs[0].clone());
139 for run in self.runs.iter().skip(1) {
140 let last = merged
141 .last_mut()
142 .expect("merged is non-empty: initial element was pushed before this loop");
143 if last.start.frame_rate == run.start.frame_rate {
144 let last_end = last.start.to_frames() + last.frame_count;
145 if last_end == run.start.to_frames() {
146 last.frame_count += run.frame_count;
147 continue;
148 }
149 }
150 merged.push(run.clone());
151 }
152 self.runs = merged;
153 }
154
155 pub fn iter(&self) -> std::slice::Iter<'_, TimecodeRun> {
157 self.runs.iter()
158 }
159
160 pub fn detect_gaps(&self) -> Vec<(usize, i64)> {
162 let mut gaps = Vec::new();
163 for i in 0..self.runs.len().saturating_sub(1) {
164 let a = &self.runs[i];
165 let b = &self.runs[i + 1];
166 if a.start.frame_rate == b.start.frame_rate {
167 let a_end = a.start.to_frames() + a.frame_count;
168 let b_start = b.start.to_frames();
169 let gap = b_start as i64 - a_end as i64;
170 if gap != 0 {
171 gaps.push((i, gap));
172 }
173 }
174 }
175 gaps
176 }
177}
178
179impl Default for TimecodeSequence {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185pub fn build_sequence(items: &[(Timecode, u64)]) -> Result<TimecodeSequence, TimecodeError> {
187 let mut seq = TimecodeSequence::new();
188 for (tc, count) in items {
189 let run = TimecodeRun::new(*tc, *count)?;
190 seq.push(run);
191 }
192 Ok(seq)
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 fn tc(h: u8, m: u8, s: u8, f: u8) -> Timecode {
200 Timecode::new(h, m, s, f, FrameRate::Fps25).expect("valid timecode")
201 }
202
203 #[test]
204 fn test_run_creation() {
205 let run = TimecodeRun::new(tc(1, 0, 0, 0), 100).expect("valid timecode run");
206 assert_eq!(run.frame_count, 100);
207 }
208
209 #[test]
210 fn test_run_zero_frames_error() {
211 let result = TimecodeRun::new(tc(0, 0, 0, 0), 0);
212 assert!(result.is_err());
213 }
214
215 #[test]
216 fn test_run_duration_secs() {
217 let run = TimecodeRun::new(tc(0, 0, 0, 0), 50).expect("valid timecode run");
218 let dur = run.duration_secs();
219 assert!((dur - 2.0).abs() < 1e-9);
220 }
221
222 #[test]
223 fn test_run_contains() {
224 let run = TimecodeRun::new(tc(0, 0, 0, 0), 25).expect("valid timecode run");
225 assert!(run.contains(&tc(0, 0, 0, 10)));
226 assert!(run.contains(&tc(0, 0, 0, 24)));
227 assert!(!run.contains(&tc(0, 0, 1, 0)));
228 }
229
230 #[test]
231 fn test_sequence_push_and_total() {
232 let mut seq = TimecodeSequence::new();
233 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 25).expect("valid timecode run"));
234 seq.push(TimecodeRun::new(tc(0, 0, 1, 0), 25).expect("valid timecode run"));
235 assert_eq!(seq.total_frames(), 50);
236 assert_eq!(seq.run_count(), 2);
237 }
238
239 #[test]
240 fn test_sequence_is_contiguous() {
241 let mut seq = TimecodeSequence::new();
242 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 25).expect("valid timecode run"));
243 seq.push(TimecodeRun::new(tc(0, 0, 1, 0), 25).expect("valid timecode run"));
244 assert!(seq.is_contiguous(0, 1));
245 }
246
247 #[test]
248 fn test_sequence_not_contiguous() {
249 let mut seq = TimecodeSequence::new();
250 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 10).expect("valid timecode run"));
251 seq.push(TimecodeRun::new(tc(0, 0, 1, 0), 10).expect("valid timecode run"));
252 assert!(!seq.is_contiguous(0, 1));
253 }
254
255 #[test]
256 fn test_find_run_at_offset() {
257 let mut seq = TimecodeSequence::new();
258 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 25).expect("valid timecode run"));
259 seq.push(TimecodeRun::new(tc(0, 0, 1, 0), 25).expect("valid timecode run"));
260 assert_eq!(seq.find_run_at_offset(0), Some((0, 0)));
261 assert_eq!(seq.find_run_at_offset(24), Some((0, 24)));
262 assert_eq!(seq.find_run_at_offset(25), Some((1, 0)));
263 assert_eq!(seq.find_run_at_offset(50), None);
264 }
265
266 #[test]
267 fn test_compact_merges_contiguous() {
268 let mut seq = TimecodeSequence::new();
269 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 25).expect("valid timecode run"));
270 seq.push(TimecodeRun::new(tc(0, 0, 1, 0), 25).expect("valid timecode run"));
271 assert_eq!(seq.run_count(), 2);
272 seq.compact();
273 assert_eq!(seq.run_count(), 1);
274 assert_eq!(seq.total_frames(), 50);
275 }
276
277 #[test]
278 fn test_compact_preserves_gaps() {
279 let mut seq = TimecodeSequence::new();
280 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 10).expect("valid timecode run"));
281 seq.push(TimecodeRun::new(tc(0, 0, 2, 0), 10).expect("valid timecode run"));
282 seq.compact();
283 assert_eq!(seq.run_count(), 2);
284 }
285
286 #[test]
287 fn test_detect_gaps() {
288 let mut seq = TimecodeSequence::new();
289 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 10).expect("valid timecode run"));
290 seq.push(TimecodeRun::new(tc(0, 0, 1, 0), 10).expect("valid timecode run"));
291 let gaps = seq.detect_gaps();
292 assert_eq!(gaps.len(), 1);
293 assert_eq!(gaps[0].0, 0);
294 assert_eq!(gaps[0].1, 15); }
296
297 #[test]
298 fn test_build_sequence() {
299 let items = vec![(tc(0, 0, 0, 0), 25u64), (tc(0, 0, 1, 0), 50)];
300 let seq = build_sequence(&items).expect("build sequence should succeed");
301 assert_eq!(seq.run_count(), 2);
302 assert_eq!(seq.total_frames(), 75);
303 }
304
305 #[test]
306 fn test_total_duration_secs() {
307 let mut seq = TimecodeSequence::new();
308 seq.push(TimecodeRun::new(tc(0, 0, 0, 0), 25).expect("valid timecode run"));
309 seq.push(TimecodeRun::new(tc(0, 0, 1, 0), 50).expect("valid timecode run"));
310 let dur = seq.total_duration_secs();
311 assert!((dur - 3.0).abs() < 1e-9);
312 }
313
314 #[test]
315 fn test_end_timecode() {
316 let run = TimecodeRun::new(tc(0, 0, 0, 0), 26).expect("valid timecode run");
317 let end = run
318 .end_timecode(FrameRate::Fps25)
319 .expect("end timecode should succeed");
320 assert_eq!(end.hours, 0);
321 assert_eq!(end.minutes, 0);
322 assert_eq!(end.seconds, 1);
323 assert_eq!(end.frames, 0);
324 }
325}