1#![allow(dead_code)]
2use crate::{FrameRateInfo, Timecode, TimecodeError};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct TcRange {
15 start_frames: u64,
17 end_frames: u64,
19 fps: u8,
21 drop_frame: bool,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct SplitResult {
28 pub before: Option<TcRange>,
30 pub after: Option<TcRange>,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum OverlapKind {
37 None,
39 Equal,
41 Contains,
43 ContainedBy,
45 Partial,
47}
48
49impl TcRange {
50 pub fn new(start: &Timecode, end: &Timecode) -> Result<Self, TimecodeError> {
56 if start.frame_rate != end.frame_rate {
57 return Err(TimecodeError::InvalidConfiguration);
58 }
59 let s = start.to_frames();
60 let e = end.to_frames();
61 if s >= e {
62 return Err(TimecodeError::InvalidConfiguration);
63 }
64 Ok(Self {
65 start_frames: s,
66 end_frames: e,
67 fps: start.frame_rate.fps,
68 drop_frame: start.frame_rate.drop_frame,
69 })
70 }
71
72 pub fn from_frames(
78 start: u64,
79 end: u64,
80 fps: u8,
81 drop_frame: bool,
82 ) -> Result<Self, TimecodeError> {
83 if start >= end {
84 return Err(TimecodeError::InvalidConfiguration);
85 }
86 Ok(Self {
87 start_frames: start,
88 end_frames: end,
89 fps,
90 drop_frame,
91 })
92 }
93
94 pub fn start_frames(&self) -> u64 {
96 self.start_frames
97 }
98
99 pub fn end_frames(&self) -> u64 {
101 self.end_frames
102 }
103
104 pub fn duration_frames(&self) -> u64 {
106 self.end_frames - self.start_frames
107 }
108
109 #[allow(clippy::cast_precision_loss)]
111 pub fn duration_seconds(&self) -> f64 {
112 self.duration_frames() as f64 / self.fps as f64
113 }
114
115 pub fn contains_frame(&self, frame: u64) -> bool {
117 frame >= self.start_frames && frame < self.end_frames
118 }
119
120 pub fn contains_timecode(&self, tc: &Timecode) -> bool {
122 self.contains_frame(tc.to_frames())
123 }
124
125 pub fn frame_rate_info(&self) -> FrameRateInfo {
127 FrameRateInfo {
128 fps: self.fps,
129 drop_frame: self.drop_frame,
130 }
131 }
132
133 pub fn overlaps(&self, other: &Self) -> bool {
135 self.start_frames < other.end_frames && other.start_frames < self.end_frames
136 }
137
138 pub fn overlap_kind(&self, other: &Self) -> OverlapKind {
140 if self == other {
141 return OverlapKind::Equal;
142 }
143 if !self.overlaps(other) {
144 return OverlapKind::None;
145 }
146 if self.start_frames <= other.start_frames && self.end_frames >= other.end_frames {
147 return OverlapKind::Contains;
148 }
149 if other.start_frames <= self.start_frames && other.end_frames >= self.end_frames {
150 return OverlapKind::ContainedBy;
151 }
152 OverlapKind::Partial
153 }
154
155 pub fn intersect(&self, other: &Self) -> Option<Self> {
157 if !self.overlaps(other) {
158 return None;
159 }
160 let s = self.start_frames.max(other.start_frames);
161 let e = self.end_frames.min(other.end_frames);
162 Some(Self {
163 start_frames: s,
164 end_frames: e,
165 fps: self.fps,
166 drop_frame: self.drop_frame,
167 })
168 }
169
170 pub fn union(&self, other: &Self) -> Option<Self> {
172 if self.end_frames < other.start_frames || other.end_frames < self.start_frames {
173 return None;
174 }
175 let s = self.start_frames.min(other.start_frames);
176 let e = self.end_frames.max(other.end_frames);
177 Some(Self {
178 start_frames: s,
179 end_frames: e,
180 fps: self.fps,
181 drop_frame: self.drop_frame,
182 })
183 }
184
185 pub fn split_at_frame(&self, frame: u64) -> SplitResult {
187 if frame <= self.start_frames {
188 SplitResult {
189 before: None,
190 after: Some(self.clone()),
191 }
192 } else if frame >= self.end_frames {
193 SplitResult {
194 before: Some(self.clone()),
195 after: None,
196 }
197 } else {
198 SplitResult {
199 before: Some(Self {
200 start_frames: self.start_frames,
201 end_frames: frame,
202 fps: self.fps,
203 drop_frame: self.drop_frame,
204 }),
205 after: Some(Self {
206 start_frames: frame,
207 end_frames: self.end_frames,
208 fps: self.fps,
209 drop_frame: self.drop_frame,
210 }),
211 }
212 }
213 }
214
215 pub fn offset(&self, delta: i64) -> Result<Self, TimecodeError> {
221 let s = if delta >= 0 {
222 self.start_frames + delta as u64
223 } else {
224 let abs = (-delta) as u64;
225 if abs > self.start_frames {
226 return Err(TimecodeError::InvalidFrames);
227 }
228 self.start_frames - abs
229 };
230 let e = if delta >= 0 {
231 self.end_frames + delta as u64
232 } else {
233 let abs = (-delta) as u64;
234 if abs > self.end_frames {
235 return Err(TimecodeError::InvalidFrames);
236 }
237 self.end_frames - abs
238 };
239 Ok(Self {
240 start_frames: s,
241 end_frames: e,
242 fps: self.fps,
243 drop_frame: self.drop_frame,
244 })
245 }
246
247 pub fn frame_iter(&self) -> impl Iterator<Item = u64> {
249 self.start_frames..self.end_frames
250 }
251
252 pub fn extend(&self, head_frames: u64, tail_frames: u64) -> Self {
254 let s = self.start_frames.saturating_sub(head_frames);
255 let e = self.end_frames.saturating_add(tail_frames);
256 Self {
257 start_frames: s,
258 end_frames: e,
259 fps: self.fps,
260 drop_frame: self.drop_frame,
261 }
262 }
263
264 pub fn trim(&self, head_frames: u64, tail_frames: u64) -> Option<Self> {
268 let s = self.start_frames.saturating_add(head_frames);
269 let e = self.end_frames.saturating_sub(tail_frames);
270 if s >= e {
271 return None;
272 }
273 Some(Self {
274 start_frames: s,
275 end_frames: e,
276 fps: self.fps,
277 drop_frame: self.drop_frame,
278 })
279 }
280}
281
282pub fn merge_ranges(mut ranges: Vec<TcRange>) -> Vec<TcRange> {
284 if ranges.is_empty() {
285 return vec![];
286 }
287 ranges.sort_by_key(|r| r.start_frames);
288 let mut merged: Vec<TcRange> = vec![ranges[0].clone()];
289 for r in &ranges[1..] {
290 let last = merged
291 .last_mut()
292 .expect("merged is non-empty: element 0 was pushed above");
293 if let Some(u) = last.union(r) {
294 *last = u;
295 } else {
296 merged.push(r.clone());
297 }
298 }
299 merged
300}
301
302pub fn total_coverage(ranges: Vec<TcRange>) -> u64 {
304 merge_ranges(ranges)
305 .iter()
306 .map(|r| r.duration_frames())
307 .sum()
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 fn make_range(start: u64, end: u64) -> TcRange {
315 TcRange::from_frames(start, end, 25, false).expect("valid timecode range")
316 }
317
318 #[test]
319 fn test_create_range() {
320 let r = make_range(0, 100);
321 assert_eq!(r.start_frames(), 0);
322 assert_eq!(r.end_frames(), 100);
323 }
324
325 #[test]
326 fn test_duration_frames() {
327 let r = make_range(10, 110);
328 assert_eq!(r.duration_frames(), 100);
329 }
330
331 #[test]
332 fn test_duration_seconds() {
333 let r = make_range(0, 25);
334 let d = r.duration_seconds();
335 assert!((d - 1.0).abs() < 0.001);
336 }
337
338 #[test]
339 fn test_contains_frame() {
340 let r = make_range(10, 20);
341 assert!(r.contains_frame(10));
342 assert!(r.contains_frame(19));
343 assert!(!r.contains_frame(20));
344 assert!(!r.contains_frame(9));
345 }
346
347 #[test]
348 fn test_overlaps() {
349 let a = make_range(0, 50);
350 let b = make_range(25, 75);
351 let c = make_range(50, 100);
352 assert!(a.overlaps(&b));
353 assert!(!a.overlaps(&c));
354 }
355
356 #[test]
357 fn test_overlap_kind_equal() {
358 let a = make_range(0, 100);
359 let b = make_range(0, 100);
360 assert_eq!(a.overlap_kind(&b), OverlapKind::Equal);
361 }
362
363 #[test]
364 fn test_overlap_kind_contains() {
365 let a = make_range(0, 100);
366 let b = make_range(10, 50);
367 assert_eq!(a.overlap_kind(&b), OverlapKind::Contains);
368 }
369
370 #[test]
371 fn test_overlap_kind_partial() {
372 let a = make_range(0, 50);
373 let b = make_range(25, 75);
374 assert_eq!(a.overlap_kind(&b), OverlapKind::Partial);
375 }
376
377 #[test]
378 fn test_intersect() {
379 let a = make_range(0, 50);
380 let b = make_range(25, 75);
381 let inter = a.intersect(&b).expect("intersect should succeed");
382 assert_eq!(inter.start_frames(), 25);
383 assert_eq!(inter.end_frames(), 50);
384 }
385
386 #[test]
387 fn test_intersect_none() {
388 let a = make_range(0, 10);
389 let b = make_range(20, 30);
390 assert!(a.intersect(&b).is_none());
391 }
392
393 #[test]
394 fn test_union() {
395 let a = make_range(0, 50);
396 let b = make_range(50, 100);
397 let u = a.union(&b).expect("union should succeed");
398 assert_eq!(u.start_frames(), 0);
399 assert_eq!(u.end_frames(), 100);
400 }
401
402 #[test]
403 fn test_split_at_frame() {
404 let r = make_range(0, 100);
405 let split = r.split_at_frame(50);
406 let before = split.before.expect("should succeed");
407 let after = split.after.expect("should succeed");
408 assert_eq!(before.duration_frames(), 50);
409 assert_eq!(after.duration_frames(), 50);
410 }
411
412 #[test]
413 fn test_offset_positive() {
414 let r = make_range(10, 20);
415 let shifted = r.offset(5).expect("offset should succeed");
416 assert_eq!(shifted.start_frames(), 15);
417 assert_eq!(shifted.end_frames(), 25);
418 }
419
420 #[test]
421 fn test_offset_negative() {
422 let r = make_range(10, 20);
423 let shifted = r.offset(-5).expect("offset should succeed");
424 assert_eq!(shifted.start_frames(), 5);
425 assert_eq!(shifted.end_frames(), 15);
426 }
427
428 #[test]
429 fn test_extend_and_trim() {
430 let r = make_range(50, 100);
431 let ext = r.extend(10, 10);
432 assert_eq!(ext.start_frames(), 40);
433 assert_eq!(ext.end_frames(), 110);
434 let trimmed = ext.trim(10, 10).expect("trim should succeed");
435 assert_eq!(trimmed.start_frames(), 50);
436 assert_eq!(trimmed.end_frames(), 100);
437 }
438
439 #[test]
440 fn test_merge_ranges() {
441 let ranges = vec![make_range(0, 30), make_range(20, 50), make_range(60, 80)];
442 let merged = merge_ranges(ranges);
443 assert_eq!(merged.len(), 2);
444 assert_eq!(merged[0].start_frames(), 0);
445 assert_eq!(merged[0].end_frames(), 50);
446 assert_eq!(merged[1].start_frames(), 60);
447 }
448
449 #[test]
450 fn test_total_coverage() {
451 let ranges = vec![make_range(0, 30), make_range(20, 50), make_range(60, 80)];
452 assert_eq!(total_coverage(ranges), 70); }
454
455 #[test]
456 fn test_invalid_range() {
457 assert!(TcRange::from_frames(100, 50, 25, false).is_err());
458 }
459
460 #[test]
461 fn test_frame_iter_count() {
462 let r = make_range(0, 10);
463 assert_eq!(r.frame_iter().count(), 10);
464 }
465}