oximedia_timecode/
timecode_range.rs1#![allow(dead_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct TimecodeRange {
7 pub start: u64,
9 pub end: u64,
11}
12
13impl TimecodeRange {
14 pub fn new(start: u64, end: u64) -> Option<Self> {
18 if end < start {
19 None
20 } else {
21 Some(Self { start, end })
22 }
23 }
24
25 pub fn duration_frames(&self) -> u64 {
27 self.end - self.start + 1
28 }
29
30 pub fn contains_frame(&self, frame: u64) -> bool {
32 frame >= self.start && frame <= self.end
33 }
34
35 pub fn overlaps(&self, other: &TimecodeRange) -> bool {
37 self.start <= other.end && other.start <= self.end
38 }
39
40 pub fn split_at(&self, frame: u64) -> Option<(TimecodeRange, TimecodeRange)> {
46 if frame < self.start || frame >= self.end {
47 return None;
48 }
49 let left = TimecodeRange {
50 start: self.start,
51 end: frame,
52 };
53 let right = TimecodeRange {
54 start: frame + 1,
55 end: self.end,
56 };
57 Some((left, right))
58 }
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct TimecodeRangeList {
64 ranges: Vec<TimecodeRange>,
65}
66
67impl TimecodeRangeList {
68 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub fn add(&mut self, range: TimecodeRange) {
75 self.ranges.push(range);
76 }
77
78 pub fn total_frames(&self) -> u64 {
80 self.ranges.iter().map(|r| r.duration_frames()).sum()
81 }
82
83 pub fn merge_adjacent(&self) -> TimecodeRangeList {
85 let mut sorted = self.ranges.clone();
86 sorted.sort_by_key(|r| r.start);
87
88 let mut merged: Vec<TimecodeRange> = Vec::new();
89 for range in sorted {
90 if let Some(last) = merged.last_mut() {
91 if range.start <= last.end + 1 {
92 if range.end > last.end {
94 last.end = range.end;
95 }
96 continue;
97 }
98 }
99 merged.push(range);
100 }
101
102 TimecodeRangeList { ranges: merged }
103 }
104
105 pub fn iter(&self) -> std::slice::Iter<'_, TimecodeRange> {
107 self.ranges.iter()
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_new_valid() {
117 let r = TimecodeRange::new(10, 20).unwrap();
118 assert_eq!(r.start, 10);
119 assert_eq!(r.end, 20);
120 }
121
122 #[test]
123 fn test_new_invalid_returns_none() {
124 assert!(TimecodeRange::new(20, 10).is_none());
125 }
126
127 #[test]
128 fn test_new_same_start_end() {
129 let r = TimecodeRange::new(5, 5).unwrap();
130 assert_eq!(r.duration_frames(), 1);
131 }
132
133 #[test]
134 fn test_duration_frames() {
135 let r = TimecodeRange::new(0, 24).unwrap();
136 assert_eq!(r.duration_frames(), 25);
137 }
138
139 #[test]
140 fn test_contains_frame_inside() {
141 let r = TimecodeRange::new(10, 20).unwrap();
142 assert!(r.contains_frame(10));
143 assert!(r.contains_frame(15));
144 assert!(r.contains_frame(20));
145 }
146
147 #[test]
148 fn test_contains_frame_outside() {
149 let r = TimecodeRange::new(10, 20).unwrap();
150 assert!(!r.contains_frame(9));
151 assert!(!r.contains_frame(21));
152 }
153
154 #[test]
155 fn test_overlaps_true() {
156 let a = TimecodeRange::new(0, 10).unwrap();
157 let b = TimecodeRange::new(5, 15).unwrap();
158 assert!(a.overlaps(&b));
159 assert!(b.overlaps(&a));
160 }
161
162 #[test]
163 fn test_overlaps_adjacent_no_overlap() {
164 let a = TimecodeRange::new(0, 9).unwrap();
165 let b = TimecodeRange::new(10, 20).unwrap();
166 assert!(!a.overlaps(&b));
168 }
169
170 #[test]
171 fn test_overlaps_touching() {
172 let a = TimecodeRange::new(0, 10).unwrap();
173 let b = TimecodeRange::new(10, 20).unwrap();
174 assert!(a.overlaps(&b));
176 }
177
178 #[test]
179 fn test_split_at_valid() {
180 let r = TimecodeRange::new(0, 9).unwrap();
181 let (left, right) = r.split_at(4).unwrap();
182 assert_eq!(left.start, 0);
183 assert_eq!(left.end, 4);
184 assert_eq!(right.start, 5);
185 assert_eq!(right.end, 9);
186 }
187
188 #[test]
189 fn test_split_at_boundary_invalid() {
190 let r = TimecodeRange::new(0, 9).unwrap();
191 assert!(r.split_at(9).is_none());
193 assert!(r.split_at(u64::MAX).is_none());
195 }
196
197 #[test]
198 fn test_list_total_frames() {
199 let mut list = TimecodeRangeList::new();
200 list.add(TimecodeRange::new(0, 9).unwrap()); list.add(TimecodeRange::new(20, 24).unwrap()); assert_eq!(list.total_frames(), 15);
203 }
204
205 #[test]
206 fn test_list_merge_adjacent() {
207 let mut list = TimecodeRangeList::new();
208 list.add(TimecodeRange::new(10, 20).unwrap());
209 list.add(TimecodeRange::new(21, 30).unwrap()); list.add(TimecodeRange::new(50, 60).unwrap()); let merged = list.merge_adjacent();
212 let ranges: Vec<_> = merged.iter().cloned().collect();
213 assert_eq!(ranges.len(), 2);
214 assert_eq!(ranges[0].start, 10);
215 assert_eq!(ranges[0].end, 30);
216 assert_eq!(ranges[1].start, 50);
217 assert_eq!(ranges[1].end, 60);
218 }
219
220 #[test]
221 fn test_list_merge_overlapping() {
222 let mut list = TimecodeRangeList::new();
223 list.add(TimecodeRange::new(0, 15).unwrap());
224 list.add(TimecodeRange::new(10, 25).unwrap()); let merged = list.merge_adjacent();
226 let ranges: Vec<_> = merged.iter().cloned().collect();
227 assert_eq!(ranges.len(), 1);
228 assert_eq!(ranges[0].end, 25);
229 }
230}