1#![allow(dead_code)]
8
9use crate::{FrameRate, Timecode};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SmpteBoundary {
16 FullDay,
18 FirstHalf,
20 SecondHalf,
22 SingleHour(u8),
24}
25
26impl std::fmt::Display for SmpteBoundary {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 Self::FullDay => write!(f, "full-day"),
30 Self::FirstHalf => write!(f, "first-half"),
31 Self::SecondHalf => write!(f, "second-half"),
32 Self::SingleHour(h) => write!(f, "hour-{h:02}"),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct SmpteRange {
42 pub start_frames: u64,
44 pub end_frames: u64,
46 pub fps: u8,
48}
49
50impl SmpteRange {
51 pub fn from_timecodes(start: &Timecode, end: &Timecode) -> Self {
53 Self {
54 start_frames: start.to_frames(),
55 end_frames: end.to_frames(),
56 fps: start.frame_rate.fps,
57 }
58 }
59
60 pub fn from_frames(start: u64, end: u64, fps: u8) -> Self {
62 Self {
63 start_frames: start,
64 end_frames: end,
65 fps,
66 }
67 }
68
69 pub fn full_day(frame_rate: FrameRate) -> Self {
71 let fps = frame_rate.frames_per_second();
72 let max_frame = (fps as u64) * 86400 - 1;
73 Self {
74 start_frames: 0,
75 end_frames: max_frame,
76 fps: fps as u8,
77 }
78 }
79
80 pub fn from_boundary(boundary: SmpteBoundary, frame_rate: FrameRate) -> Self {
82 let fps = frame_rate.frames_per_second() as u64;
83 match boundary {
84 SmpteBoundary::FullDay => Self::from_frames(0, fps * 86400 - 1, fps as u8),
85 SmpteBoundary::FirstHalf => Self::from_frames(0, fps * 43200 - 1, fps as u8),
86 SmpteBoundary::SecondHalf => Self::from_frames(fps * 43200, fps * 86400 - 1, fps as u8),
87 SmpteBoundary::SingleHour(h) => {
88 let start = fps * 3600 * h as u64;
89 let end = start + fps * 3600 - 1;
90 Self::from_frames(start, end, fps as u8)
91 }
92 }
93 }
94
95 pub fn contains(&self, tc: &Timecode) -> bool {
97 let pos = tc.to_frames();
98 pos >= self.start_frames && pos <= self.end_frames
99 }
100
101 pub fn duration_frames(&self) -> u64 {
103 if self.end_frames >= self.start_frames {
104 self.end_frames - self.start_frames + 1
105 } else {
106 0
107 }
108 }
109
110 #[allow(clippy::cast_precision_loss)]
112 pub fn duration_seconds(&self) -> f64 {
113 self.duration_frames() as f64 / self.fps as f64
114 }
115
116 pub fn overlaps(&self, other: &SmpteRange) -> bool {
118 self.start_frames <= other.end_frames && other.start_frames <= self.end_frames
119 }
120
121 pub fn intersection(&self, other: &SmpteRange) -> Option<SmpteRange> {
123 if !self.overlaps(other) {
124 return None;
125 }
126 Some(SmpteRange {
127 start_frames: self.start_frames.max(other.start_frames),
128 end_frames: self.end_frames.min(other.end_frames),
129 fps: self.fps,
130 })
131 }
132
133 pub fn encompasses(&self, other: &SmpteRange) -> bool {
135 self.start_frames <= other.start_frames && self.end_frames >= other.end_frames
136 }
137
138 pub fn split_at(&self, split_at: u64) -> Option<(SmpteRange, SmpteRange)> {
142 if split_at < self.start_frames || split_at >= self.end_frames {
143 return None;
144 }
145 let left = SmpteRange::from_frames(self.start_frames, split_at, self.fps);
146 let right = SmpteRange::from_frames(split_at + 1, self.end_frames, self.fps);
147 Some((left, right))
148 }
149}
150
151#[derive(Debug, Clone)]
155pub struct BoundaryChecker {
156 ranges: Vec<SmpteRange>,
158}
159
160impl BoundaryChecker {
161 pub fn new() -> Self {
163 Self { ranges: Vec::new() }
164 }
165
166 pub fn add_range(&mut self, range: SmpteRange) {
168 self.ranges.push(range);
169 }
170
171 pub fn from_ranges(ranges: &[SmpteRange]) -> Self {
173 Self {
174 ranges: ranges.to_vec(),
175 }
176 }
177
178 pub fn is_allowed(&self, tc: &Timecode) -> bool {
180 self.ranges.iter().any(|r| r.contains(tc))
181 }
182
183 pub fn matching_ranges(&self, tc: &Timecode) -> usize {
185 self.ranges.iter().filter(|r| r.contains(tc)).count()
186 }
187}
188
189impl Default for BoundaryChecker {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195fn raw_tc(hours: u8, minutes: u8, seconds: u8, frames: u8, fps: u8) -> Timecode {
199 Timecode::from_raw_fields(hours, minutes, seconds, frames, fps, false, 0)
200}
201
202#[cfg(test)]
205mod tests {
206 use super::*;
207
208 fn tc25(h: u8, m: u8, s: u8, f: u8) -> Timecode {
209 Timecode::new(h, m, s, f, FrameRate::Fps25).expect("valid timecode")
210 }
211
212 #[test]
213 fn test_range_from_timecodes() {
214 let start = tc25(1, 0, 0, 0);
215 let end = tc25(1, 0, 10, 0);
216 let range = SmpteRange::from_timecodes(&start, &end);
217 assert!(range.start_frames < range.end_frames);
218 assert_eq!(range.fps, 25);
219 }
220
221 #[test]
222 fn test_range_contains_inside() {
223 let range = SmpteRange::from_frames(100, 200, 25);
224 let tc = raw_tc(0, 0, 6, 0, 25); assert!(range.contains(&tc));
226 }
227
228 #[test]
229 fn test_range_contains_outside() {
230 let range = SmpteRange::from_frames(100, 200, 25);
231 let tc = raw_tc(0, 0, 0, 5, 25); assert!(!range.contains(&tc));
233 }
234
235 #[test]
236 fn test_range_contains_on_boundary() {
237 let range = SmpteRange::from_frames(100, 200, 25);
238 let tc = raw_tc(0, 0, 4, 0, 25); assert!(range.contains(&tc));
240 }
241
242 #[test]
243 fn test_duration_frames() {
244 let range = SmpteRange::from_frames(0, 99, 25);
245 assert_eq!(range.duration_frames(), 100);
246 }
247
248 #[test]
249 fn test_duration_seconds() {
250 let range = SmpteRange::from_frames(0, 49, 25);
251 let dur = range.duration_seconds();
252 assert!((dur - 2.0).abs() < 1e-6);
253 }
254
255 #[test]
256 fn test_full_day_range() {
257 let range = SmpteRange::full_day(FrameRate::Fps25);
258 assert_eq!(range.start_frames, 0);
259 assert_eq!(range.duration_frames(), 25 * 86400);
260 }
261
262 #[test]
263 fn test_from_boundary_first_half() {
264 let range = SmpteRange::from_boundary(SmpteBoundary::FirstHalf, FrameRate::Fps25);
265 assert_eq!(range.start_frames, 0);
266 assert_eq!(range.duration_frames(), 25 * 43200);
267 }
268
269 #[test]
270 fn test_from_boundary_single_hour() {
271 let range = SmpteRange::from_boundary(SmpteBoundary::SingleHour(2), FrameRate::Fps25);
272 assert_eq!(range.start_frames, 25 * 3600 * 2);
273 assert_eq!(range.duration_frames(), 25 * 3600);
274 }
275
276 #[test]
277 fn test_overlaps_true() {
278 let a = SmpteRange::from_frames(0, 100, 25);
279 let b = SmpteRange::from_frames(50, 150, 25);
280 assert!(a.overlaps(&b));
281 assert!(b.overlaps(&a));
282 }
283
284 #[test]
285 fn test_overlaps_false() {
286 let a = SmpteRange::from_frames(0, 50, 25);
287 let b = SmpteRange::from_frames(100, 200, 25);
288 assert!(!a.overlaps(&b));
289 }
290
291 #[test]
292 fn test_intersection_some() {
293 let a = SmpteRange::from_frames(0, 100, 25);
294 let b = SmpteRange::from_frames(50, 150, 25);
295 let inter = a.intersection(&b).expect("intersection should succeed");
296 assert_eq!(inter.start_frames, 50);
297 assert_eq!(inter.end_frames, 100);
298 }
299
300 #[test]
301 fn test_intersection_none() {
302 let a = SmpteRange::from_frames(0, 50, 25);
303 let b = SmpteRange::from_frames(100, 200, 25);
304 assert!(a.intersection(&b).is_none());
305 }
306
307 #[test]
308 fn test_encompasses() {
309 let outer = SmpteRange::from_frames(0, 1000, 25);
310 let inner = SmpteRange::from_frames(100, 500, 25);
311 assert!(outer.encompasses(&inner));
312 assert!(!inner.encompasses(&outer));
313 }
314
315 #[test]
316 fn test_split_at() {
317 let range = SmpteRange::from_frames(0, 200, 25);
318 let (left, right) = range.split_at(100).expect("split should succeed");
319 assert_eq!(left.end_frames, 100);
320 assert_eq!(right.start_frames, 101);
321 }
322
323 #[test]
324 fn test_split_at_invalid() {
325 let range = SmpteRange::from_frames(100, 200, 25);
326 assert!(range.split_at(50).is_none());
327 assert!(range.split_at(200).is_none());
328 }
329
330 #[test]
331 fn test_boundary_checker_allowed() {
332 let range = SmpteRange::from_frames(0, 1000, 25);
333 let checker = BoundaryChecker::from_ranges(&[range]);
334 let tc = tc25(0, 0, 1, 0);
335 assert!(checker.is_allowed(&tc));
336 }
337
338 #[test]
339 fn test_boundary_checker_not_allowed() {
340 let range = SmpteRange::from_frames(0, 10, 25);
341 let checker = BoundaryChecker::from_ranges(&[range]);
342 let tc = tc25(1, 0, 0, 0);
343 assert!(!checker.is_allowed(&tc));
344 }
345
346 #[test]
347 fn test_boundary_checker_matching_ranges() {
348 let r1 = SmpteRange::from_frames(0, 1000, 25);
349 let r2 = SmpteRange::from_frames(500, 2000, 25);
350 let checker = BoundaryChecker::from_ranges(&[r1, r2]);
351 let tc = raw_tc(0, 0, 24, 0, 25); assert_eq!(checker.matching_ranges(&tc), 2);
353 }
354
355 #[test]
356 fn test_boundary_display() {
357 assert_eq!(SmpteBoundary::FullDay.to_string(), "full-day");
358 assert_eq!(SmpteBoundary::SingleHour(5).to_string(), "hour-05");
359 }
360
361 #[test]
362 fn test_empty_range_duration() {
363 let range = SmpteRange::from_frames(200, 100, 25);
364 assert_eq!(range.duration_frames(), 0);
365 }
366}