oximedia_timecode/
duration.rs1#[allow(dead_code)]
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct TcDuration {
10 pub frames: i64,
12 pub frame_rate_num: u32,
14 pub frame_rate_den: u32,
16}
17
18impl TcDuration {
19 #[must_use]
21 pub fn new(frames: i64, frame_rate_num: u32, frame_rate_den: u32) -> Self {
22 Self {
23 frames,
24 frame_rate_num,
25 frame_rate_den,
26 }
27 }
28
29 #[allow(clippy::cast_precision_loss)]
31 #[must_use]
32 pub fn to_seconds(&self) -> f64 {
33 if self.frame_rate_num == 0 {
34 return 0.0;
35 }
36 self.frames as f64 * self.frame_rate_den as f64 / self.frame_rate_num as f64
37 }
38
39 #[must_use]
41 pub fn to_milliseconds(&self) -> f64 {
42 self.to_seconds() * 1000.0
43 }
44
45 #[must_use]
47 pub fn add(&self, other: &TcDuration) -> TcDuration {
48 TcDuration {
49 frames: self.frames + other.frames,
50 frame_rate_num: self.frame_rate_num,
51 frame_rate_den: self.frame_rate_den,
52 }
53 }
54
55 #[must_use]
57 pub fn subtract(&self, other: &TcDuration) -> Option<TcDuration> {
58 let result = self.frames - other.frames;
59 if result < 0 {
60 None
61 } else {
62 Some(TcDuration {
63 frames: result,
64 frame_rate_num: self.frame_rate_num,
65 frame_rate_den: self.frame_rate_den,
66 })
67 }
68 }
69
70 #[must_use]
72 pub fn is_negative(&self) -> bool {
73 self.frames < 0
74 }
75}
76
77#[allow(dead_code)]
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct DurationRange {
81 pub start_frames: i64,
83 pub end_frames: i64,
85}
86
87impl DurationRange {
88 #[must_use]
90 pub fn new(start_frames: i64, end_frames: i64) -> Self {
91 Self {
92 start_frames,
93 end_frames,
94 }
95 }
96
97 #[must_use]
99 pub fn duration_frames(&self) -> i64 {
100 self.end_frames - self.start_frames
101 }
102
103 #[must_use]
105 pub fn contains(&self, frame: i64) -> bool {
106 frame >= self.start_frames && frame < self.end_frames
107 }
108
109 #[must_use]
111 pub fn overlaps(&self, other: &DurationRange) -> bool {
112 self.start_frames < other.end_frames && other.start_frames < self.end_frames
113 }
114}
115
116#[must_use]
118pub fn timecode_subtract(
119 tc1_frames: i64,
120 tc2_frames: i64,
121 fps_num: u32,
122 fps_den: u32,
123) -> TcDuration {
124 TcDuration::new(tc1_frames - tc2_frames, fps_num, fps_den)
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 fn dur(frames: i64) -> TcDuration {
132 TcDuration::new(frames, 25, 1)
133 }
134
135 #[test]
136 fn test_to_seconds_25fps() {
137 assert!((dur(25).to_seconds() - 1.0).abs() < f64::EPSILON);
139 }
140
141 #[test]
142 fn test_to_seconds_zero_fps() {
143 let d = TcDuration::new(100, 0, 1);
144 assert_eq!(d.to_seconds(), 0.0);
145 }
146
147 #[test]
148 fn test_to_milliseconds() {
149 assert!((dur(25).to_milliseconds() - 1000.0).abs() < 1e-9);
151 }
152
153 #[test]
154 fn test_add() {
155 let result = dur(10).add(&dur(15));
156 assert_eq!(result.frames, 25);
157 }
158
159 #[test]
160 fn test_subtract_positive() {
161 let result = dur(30).subtract(&dur(10));
162 assert!(result.is_some());
163 assert_eq!(result.unwrap().frames, 20);
164 }
165
166 #[test]
167 fn test_subtract_to_zero() {
168 let result = dur(10).subtract(&dur(10));
169 assert!(result.is_some());
170 assert_eq!(result.unwrap().frames, 0);
171 }
172
173 #[test]
174 fn test_subtract_negative_returns_none() {
175 let result = dur(5).subtract(&dur(10));
176 assert!(result.is_none());
177 }
178
179 #[test]
180 fn test_is_negative_false() {
181 assert!(!dur(10).is_negative());
182 }
183
184 #[test]
185 fn test_is_negative_true() {
186 assert!(TcDuration::new(-1, 25, 1).is_negative());
187 }
188
189 #[test]
190 fn test_duration_range_duration_frames() {
191 let r = DurationRange::new(100, 200);
192 assert_eq!(r.duration_frames(), 100);
193 }
194
195 #[test]
196 fn test_duration_range_contains_true() {
197 let r = DurationRange::new(100, 200);
198 assert!(r.contains(100));
199 assert!(r.contains(150));
200 assert!(r.contains(199));
201 }
202
203 #[test]
204 fn test_duration_range_contains_false() {
205 let r = DurationRange::new(100, 200);
206 assert!(!r.contains(99));
207 assert!(!r.contains(200));
208 }
209
210 #[test]
211 fn test_duration_range_overlaps_true() {
212 let a = DurationRange::new(0, 100);
213 let b = DurationRange::new(50, 150);
214 assert!(a.overlaps(&b));
215 assert!(b.overlaps(&a));
216 }
217
218 #[test]
219 fn test_duration_range_overlaps_false() {
220 let a = DurationRange::new(0, 50);
221 let b = DurationRange::new(50, 100);
222 assert!(!a.overlaps(&b));
223 }
224
225 #[test]
226 fn test_timecode_subtract_positive() {
227 let d = timecode_subtract(100, 40, 25, 1);
228 assert_eq!(d.frames, 60);
229 assert!(!d.is_negative());
230 }
231
232 #[test]
233 fn test_timecode_subtract_negative() {
234 let d = timecode_subtract(40, 100, 25, 1);
235 assert_eq!(d.frames, -60);
236 assert!(d.is_negative());
237 }
238}