1extern crate ffmpeg_next as ffmpeg;
2
3use std::time::Duration;
4
5use ffmpeg::util::mathematics::rescale::{Rescale, TIME_BASE};
6use ffmpeg::Rational as AvRational;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Time {
20 time: Option<i64>,
21 time_base: AvRational,
22}
23
24impl Time {
25 pub fn new(time: Option<i64>, time_base: AvRational) -> Time {
32 Self { time, time_base }
33 }
34
35 #[inline]
43 pub fn with_time_base(&self, time_base: AvRational) -> Self {
44 self.aligned_with_rational(time_base)
45 }
46
47 pub fn from_nth_of_a_second(nth: usize) -> Self {
53 Self {
54 time: Some(1),
55 time_base: AvRational::new(1, nth as i32),
56 }
57 }
58
59 pub fn from_secs(secs: f32) -> Self {
65 Self {
66 time: Some((secs * TIME_BASE.denominator() as f32).round() as i64),
67 time_base: TIME_BASE,
68 }
69 }
70
71 pub fn from_secs_f64(secs: f64) -> Self {
77 Self {
78 time: Some((secs * TIME_BASE.denominator() as f64).round() as i64),
79 time_base: TIME_BASE,
80 }
81 }
82
83 pub fn from_units(time: usize, base_den: usize) -> Self {
90 Self {
91 time: Some(time as i64),
92 time_base: AvRational::new(1, base_den as i32),
93 }
94 }
95
96 pub fn zero() -> Self {
98 Time {
99 time: Some(0),
100 time_base: (1, 90000).into(),
101 }
102 }
103
104 pub fn has_value(&self) -> bool {
106 self.time.is_some()
107 }
108
109 pub fn aligned_with(&self, rhs: &Time) -> Aligned {
120 Aligned {
121 lhs: self.time,
122 rhs: rhs
123 .time
124 .map(|rhs_time| rhs_time.rescale(rhs.time_base, self.time_base)),
125 time_base: self.time_base,
126 }
127 }
128
129 pub fn as_secs(&self) -> f32 {
131 if let Some(time) = self.time {
132 (time as f32)
133 * (self.time_base.numerator() as f32 / self.time_base.denominator() as f32)
134 } else {
135 0.0
136 }
137 }
138
139 pub fn as_secs_f64(&self) -> f64 {
141 if let Some(time) = self.time {
142 (time as f64)
143 * (self.time_base.numerator() as f64 / self.time_base.denominator() as f64)
144 } else {
145 0.0
146 }
147 }
148
149 pub fn into_parts(self) -> (Option<i64>, AvRational) {
151 (self.time, self.time_base)
152 }
153
154 pub fn into_value(self) -> Option<i64> {
161 self.time
162 }
163
164 pub(crate) fn aligned_with_rational(&self, time_base: AvRational) -> Time {
170 Time {
171 time: self
172 .time
173 .map(|time| time.rescale(self.time_base, time_base)),
174 time_base,
175 }
176 }
177}
178
179impl From<Duration> for Time {
180 #[inline]
182 fn from(duration: Duration) -> Self {
183 Time::from_secs_f64(duration.as_secs_f64())
184 }
185}
186
187impl From<Time> for Duration {
188 fn from(timestamp: Time) -> Self {
190 Duration::from_secs_f64(timestamp.as_secs_f64())
191 }
192}
193
194impl std::fmt::Display for Time {
195 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
200 if let Some(time) = self.time {
201 let num = self.time_base.numerator() as i64 * time;
202 let den = self.time_base.denominator();
203 write!(f, "{num}/{den} secs")
204 } else {
205 write!(f, "none")
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
215pub struct Aligned {
216 lhs: Option<i64>,
217 rhs: Option<i64>,
218 time_base: AvRational,
219}
220
221impl Aligned {
222 pub fn add(self) -> Time {
224 self.apply(|lhs, rhs| lhs + rhs)
225 }
226
227 pub fn subtract(self) -> Time {
229 self.apply(|lhs, rhs| lhs - rhs)
230 }
231
232 fn apply<F>(self, f: F) -> Time
242 where
243 F: FnOnce(i64, i64) -> i64,
244 {
245 match (self.lhs, self.rhs) {
246 (Some(lhs_time), Some(rhs_time)) => Time {
247 time: Some(f(lhs_time, rhs_time)),
248 time_base: self.time_base,
249 },
250 _ => Time {
251 time: None,
252 time_base: self.time_base,
253 },
254 }
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_new() {
264 let time = Time::new(Some(2), AvRational::new(3, 9));
265 assert!(time.has_value());
266 assert_eq!(time.as_secs(), 2.0 / 3.0);
267 assert_eq!(time.into_value(), Some(2));
268 }
269
270 #[test]
271 fn test_with_time_base() {
272 let time = Time::new(Some(2), AvRational::new(3, 9));
273 assert_eq!(time.as_secs(), 2.0 / 3.0);
274 let time = time.with_time_base(AvRational::new(1, 9));
275 assert_eq!(time.as_secs(), 2.0 / 3.0);
276 assert_eq!(time.into_value(), Some(6));
277 }
278
279 #[test]
280 fn test_from_nth_of_a_second() {
281 let time = Time::from_nth_of_a_second(4);
282 assert!(time.has_value());
283 assert_eq!(time.as_secs(), 0.25);
284 assert_eq!(time.as_secs_f64(), 0.25);
285 assert_eq!(Duration::from(time), Duration::from_millis(250));
286 }
287
288 #[test]
289 fn test_from_secs() {
290 let time = Time::from_secs(2.5);
291 assert!(time.has_value());
292 assert_eq!(time.as_secs(), 2.5);
293 assert_eq!(time.as_secs_f64(), 2.5);
294 assert_eq!(Duration::from(time), Duration::from_millis(2500));
295 }
296
297 #[test]
298 fn test_from_secs_f64() {
299 let time = Time::from_secs(4.0);
300 assert!(time.has_value());
301 assert_eq!(time.as_secs_f64(), 4.0);
302 }
303
304 #[test]
305 fn test_from_units() {
306 let time = Time::from_units(3, 5);
307 assert!(time.has_value());
308 assert_eq!(time.as_secs(), 3.0 / 5.0);
309 assert_eq!(Duration::from(time), Duration::from_millis(600));
310 }
311
312 #[test]
313 fn test_zero() {
314 let time = Time::zero();
315 assert!(time.has_value());
316 assert_eq!(time.as_secs(), 0.0);
317 assert_eq!(time.as_secs_f64(), 0.0);
318 assert_eq!(Duration::from(time), Duration::ZERO);
319 let time = Time::zero();
320 assert_eq!(time.into_value(), Some(0));
321 }
322
323 #[test]
324 fn test_aligned_with() {
325 let a = Time::from_units(3, 16);
326 let b = Time::from_units(1, 8);
327 let aligned = a.aligned_with(&b);
328 assert_eq!(aligned.lhs, Some(3));
329 assert_eq!(aligned.rhs, Some(2));
330 }
331
332 #[test]
333 fn test_into_aligned_with() {
334 let a = Time::from_units(2, 7);
335 let b = Time::from_units(2, 3);
336 let aligned = a.aligned_with(&b);
337 assert_eq!(aligned.lhs, Some(2));
338 assert_eq!(aligned.rhs, Some(5));
339 }
340
341 #[test]
342 fn test_as_secs() {
343 let time = Time::from_nth_of_a_second(4);
344 assert_eq!(time.as_secs(), 0.25);
345 let time = Time::from_secs(0.3);
346 assert_eq!(time.as_secs(), 0.3);
347 let time = Time::new(None, AvRational::new(0, 0));
348 assert_eq!(time.as_secs(), 0.0);
349 }
350
351 #[test]
352 fn test_as_secs_f64() {
353 let time = Time::from_nth_of_a_second(4);
354 assert_eq!(time.as_secs_f64(), 0.25);
355 let time = Time::from_secs_f64(0.3);
356 assert_eq!(time.as_secs_f64(), 0.3);
357 let time = Time::new(None, AvRational::new(0, 0));
358 assert_eq!(time.as_secs_f64(), 0.0);
359 }
360
361 #[test]
362 fn test_into_parts() {
363 let time = Time::new(Some(1), AvRational::new(2, 3));
364 assert_eq!(time.into_parts(), (Some(1), AvRational::new(2, 3)));
365 }
366
367 #[test]
368 fn test_into_value_none() {
369 let time = Time::new(None, AvRational::new(0, 0));
370 assert_eq!(time.into_value(), None);
371 }
372
373 #[test]
374 fn test_add() {
375 let a = Time::from_secs(0.2);
376 let b = Time::from_secs(0.3);
377 assert_eq!(a.aligned_with(&b).add(), Time::from_secs(0.5));
378 }
379
380 #[test]
381 fn test_subtract() {
382 let a = Time::from_secs(0.8);
383 let b = Time::from_secs(0.4);
384 assert_eq!(a.aligned_with(&b).subtract(), Time::from_secs(0.4));
385 }
386
387 #[test]
388 fn test_apply() {
389 let a = Time::from_secs(2.0);
390 let b = Time::from_secs(0.25);
391 assert_eq!(
392 a.aligned_with(&b).apply(|x, y| (2 * x) + (3 * y)),
393 Time::from_secs(4.75)
394 );
395 }
396
397 #[test]
398 fn test_apply_different_time_bases() {
399 let a = Time::new(Some(3), AvRational::new(2, 32));
400 let b = Time::from_nth_of_a_second(4);
401 assert!(
402 (a.aligned_with(&b).apply(|x, y| x + y).as_secs()
403 - Time::from_secs(7.0 / 16.0).as_secs())
404 .abs()
405 < 0.001
406 );
407 }
408}