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.last_mut().unwrap();
291 if let Some(u) = last.union(r) {
292 *last = u;
293 } else {
294 merged.push(r.clone());
295 }
296 }
297 merged
298}
299
300pub fn total_coverage(ranges: Vec<TcRange>) -> u64 {
302 merge_ranges(ranges)
303 .iter()
304 .map(|r| r.duration_frames())
305 .sum()
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 fn make_range(start: u64, end: u64) -> TcRange {
313 TcRange::from_frames(start, end, 25, false).unwrap()
314 }
315
316 #[test]
317 fn test_create_range() {
318 let r = make_range(0, 100);
319 assert_eq!(r.start_frames(), 0);
320 assert_eq!(r.end_frames(), 100);
321 }
322
323 #[test]
324 fn test_duration_frames() {
325 let r = make_range(10, 110);
326 assert_eq!(r.duration_frames(), 100);
327 }
328
329 #[test]
330 fn test_duration_seconds() {
331 let r = make_range(0, 25);
332 let d = r.duration_seconds();
333 assert!((d - 1.0).abs() < 0.001);
334 }
335
336 #[test]
337 fn test_contains_frame() {
338 let r = make_range(10, 20);
339 assert!(r.contains_frame(10));
340 assert!(r.contains_frame(19));
341 assert!(!r.contains_frame(20));
342 assert!(!r.contains_frame(9));
343 }
344
345 #[test]
346 fn test_overlaps() {
347 let a = make_range(0, 50);
348 let b = make_range(25, 75);
349 let c = make_range(50, 100);
350 assert!(a.overlaps(&b));
351 assert!(!a.overlaps(&c));
352 }
353
354 #[test]
355 fn test_overlap_kind_equal() {
356 let a = make_range(0, 100);
357 let b = make_range(0, 100);
358 assert_eq!(a.overlap_kind(&b), OverlapKind::Equal);
359 }
360
361 #[test]
362 fn test_overlap_kind_contains() {
363 let a = make_range(0, 100);
364 let b = make_range(10, 50);
365 assert_eq!(a.overlap_kind(&b), OverlapKind::Contains);
366 }
367
368 #[test]
369 fn test_overlap_kind_partial() {
370 let a = make_range(0, 50);
371 let b = make_range(25, 75);
372 assert_eq!(a.overlap_kind(&b), OverlapKind::Partial);
373 }
374
375 #[test]
376 fn test_intersect() {
377 let a = make_range(0, 50);
378 let b = make_range(25, 75);
379 let inter = a.intersect(&b).unwrap();
380 assert_eq!(inter.start_frames(), 25);
381 assert_eq!(inter.end_frames(), 50);
382 }
383
384 #[test]
385 fn test_intersect_none() {
386 let a = make_range(0, 10);
387 let b = make_range(20, 30);
388 assert!(a.intersect(&b).is_none());
389 }
390
391 #[test]
392 fn test_union() {
393 let a = make_range(0, 50);
394 let b = make_range(50, 100);
395 let u = a.union(&b).unwrap();
396 assert_eq!(u.start_frames(), 0);
397 assert_eq!(u.end_frames(), 100);
398 }
399
400 #[test]
401 fn test_split_at_frame() {
402 let r = make_range(0, 100);
403 let split = r.split_at_frame(50);
404 let before = split.before.unwrap();
405 let after = split.after.unwrap();
406 assert_eq!(before.duration_frames(), 50);
407 assert_eq!(after.duration_frames(), 50);
408 }
409
410 #[test]
411 fn test_offset_positive() {
412 let r = make_range(10, 20);
413 let shifted = r.offset(5).unwrap();
414 assert_eq!(shifted.start_frames(), 15);
415 assert_eq!(shifted.end_frames(), 25);
416 }
417
418 #[test]
419 fn test_offset_negative() {
420 let r = make_range(10, 20);
421 let shifted = r.offset(-5).unwrap();
422 assert_eq!(shifted.start_frames(), 5);
423 assert_eq!(shifted.end_frames(), 15);
424 }
425
426 #[test]
427 fn test_extend_and_trim() {
428 let r = make_range(50, 100);
429 let ext = r.extend(10, 10);
430 assert_eq!(ext.start_frames(), 40);
431 assert_eq!(ext.end_frames(), 110);
432 let trimmed = ext.trim(10, 10).unwrap();
433 assert_eq!(trimmed.start_frames(), 50);
434 assert_eq!(trimmed.end_frames(), 100);
435 }
436
437 #[test]
438 fn test_merge_ranges() {
439 let ranges = vec![make_range(0, 30), make_range(20, 50), make_range(60, 80)];
440 let merged = merge_ranges(ranges);
441 assert_eq!(merged.len(), 2);
442 assert_eq!(merged[0].start_frames(), 0);
443 assert_eq!(merged[0].end_frames(), 50);
444 assert_eq!(merged[1].start_frames(), 60);
445 }
446
447 #[test]
448 fn test_total_coverage() {
449 let ranges = vec![make_range(0, 30), make_range(20, 50), make_range(60, 80)];
450 assert_eq!(total_coverage(ranges), 70); }
452
453 #[test]
454 fn test_invalid_range() {
455 assert!(TcRange::from_frames(100, 50, 25, false).is_err());
456 }
457
458 #[test]
459 fn test_frame_iter_count() {
460 let r = make_range(0, 10);
461 assert_eq!(r.frame_iter().count(), 10);
462 }
463}