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, Copy, 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 has_no_pts(&self) -> bool {
111 self.time == Some(ffmpeg::ffi::AV_NOPTS_VALUE)
112 }
113
114 pub fn aligned_with(&self, rhs: Time) -> Aligned {
125 Aligned {
126 lhs: self.time,
127 rhs: rhs
128 .time
129 .map(|rhs_time| rhs_time.rescale(rhs.time_base, self.time_base)),
130 time_base: self.time_base,
131 }
132 }
133
134 pub fn as_secs(&self) -> f32 {
136 if let Some(time) = self.time {
137 (time as f32)
138 * (self.time_base.numerator() as f32 / self.time_base.denominator() as f32)
139 } else {
140 0.0
141 }
142 }
143
144 pub fn as_secs_f64(&self) -> f64 {
146 if let Some(time) = self.time {
147 (time as f64)
148 * (self.time_base.numerator() as f64 / self.time_base.denominator() as f64)
149 } else {
150 0.0
151 }
152 }
153
154 pub fn into_parts(self) -> (Option<i64>, AvRational) {
156 (self.time, self.time_base)
157 }
158
159 pub fn into_value(self) -> Option<i64> {
166 self.time
167 }
168
169 pub(crate) fn aligned_with_rational(&self, time_base: AvRational) -> Time {
175 Time {
176 time: self
177 .time
178 .map(|time| time.rescale(self.time_base, time_base)),
179 time_base,
180 }
181 }
182}
183
184impl From<Duration> for Time {
185 #[inline]
187 fn from(duration: Duration) -> Self {
188 Time::from_secs_f64(duration.as_secs_f64())
189 }
190}
191
192impl From<Time> for Duration {
193 fn from(timestamp: Time) -> Self {
195 Duration::from_secs_f64(timestamp.as_secs_f64().max(0.0))
196 }
197}
198
199impl std::fmt::Display for Time {
200 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
205 if let Some(time) = self.time {
206 let num = self.time_base.numerator() as i64 * time;
207 let den = self.time_base.denominator();
208 write!(f, "{num}/{den} secs")
209 } else {
210 write!(f, "none")
211 }
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct Aligned {
221 lhs: Option<i64>,
222 rhs: Option<i64>,
223 time_base: AvRational,
224}
225
226impl Aligned {
227 pub fn add(self) -> Time {
229 self.apply(|lhs, rhs| lhs + rhs)
230 }
231
232 pub fn subtract(self) -> Time {
234 self.apply(|lhs, rhs| lhs - rhs)
235 }
236
237 fn apply<F>(self, f: F) -> Time
247 where
248 F: FnOnce(i64, i64) -> i64,
249 {
250 match (self.lhs, self.rhs) {
251 (Some(lhs_time), Some(rhs_time)) => Time {
252 time: Some(f(lhs_time, rhs_time)),
253 time_base: self.time_base,
254 },
255 _ => Time {
256 time: None,
257 time_base: self.time_base,
258 },
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_new() {
269 let time = Time::new(Some(2), AvRational::new(3, 9));
270 assert!(time.has_value());
271 assert_eq!(time.as_secs(), 2.0 / 3.0);
272 assert_eq!(time.into_value(), Some(2));
273 }
274
275 #[test]
276 fn test_with_time_base() {
277 let time = Time::new(Some(2), AvRational::new(3, 9));
278 assert_eq!(time.as_secs(), 2.0 / 3.0);
279 let time = time.with_time_base(AvRational::new(1, 9));
280 assert_eq!(time.as_secs(), 2.0 / 3.0);
281 assert_eq!(time.into_value(), Some(6));
282 }
283
284 #[test]
285 fn test_from_nth_of_a_second() {
286 let time = Time::from_nth_of_a_second(4);
287 assert!(time.has_value());
288 assert_eq!(time.as_secs(), 0.25);
289 assert_eq!(time.as_secs_f64(), 0.25);
290 assert_eq!(Duration::from(time), Duration::from_millis(250));
291 }
292
293 #[test]
294 fn test_from_secs() {
295 let time = Time::from_secs(2.5);
296 assert!(time.has_value());
297 assert_eq!(time.as_secs(), 2.5);
298 assert_eq!(time.as_secs_f64(), 2.5);
299 assert_eq!(Duration::from(time), Duration::from_millis(2500));
300 }
301
302 #[test]
303 fn test_from_secs_f64() {
304 let time = Time::from_secs(4.0);
305 assert!(time.has_value());
306 assert_eq!(time.as_secs_f64(), 4.0);
307 }
308
309 #[test]
310 fn test_from_units() {
311 let time = Time::from_units(3, 5);
312 assert!(time.has_value());
313 assert_eq!(time.as_secs(), 3.0 / 5.0);
314 assert_eq!(Duration::from(time), Duration::from_millis(600));
315 }
316
317 #[test]
318 fn test_zero() {
319 let time = Time::zero();
320 assert!(time.has_value());
321 assert_eq!(time.as_secs(), 0.0);
322 assert_eq!(time.as_secs_f64(), 0.0);
323 assert_eq!(Duration::from(time), Duration::ZERO);
324 let time = Time::zero();
325 assert_eq!(time.into_value(), Some(0));
326 }
327
328 #[test]
329 fn test_aligned_with() {
330 let a = Time::from_units(3, 16);
331 let b = Time::from_units(1, 8);
332 let aligned = a.aligned_with(b);
333 assert_eq!(aligned.lhs, Some(3));
334 assert_eq!(aligned.rhs, Some(2));
335 }
336
337 #[test]
338 fn test_into_aligned_with() {
339 let a = Time::from_units(2, 7);
340 let b = Time::from_units(2, 3);
341 let aligned = a.aligned_with(b);
342 assert_eq!(aligned.lhs, Some(2));
343 assert_eq!(aligned.rhs, Some(5));
344 }
345
346 #[test]
347 fn test_as_secs() {
348 let time = Time::from_nth_of_a_second(4);
349 assert_eq!(time.as_secs(), 0.25);
350 let time = Time::from_secs(0.3);
351 assert_eq!(time.as_secs(), 0.3);
352 let time = Time::new(None, AvRational::new(0, 0));
353 assert_eq!(time.as_secs(), 0.0);
354 }
355
356 #[test]
357 fn test_as_secs_f64() {
358 let time = Time::from_nth_of_a_second(4);
359 assert_eq!(time.as_secs_f64(), 0.25);
360 let time = Time::from_secs_f64(0.3);
361 assert_eq!(time.as_secs_f64(), 0.3);
362 let time = Time::new(None, AvRational::new(0, 0));
363 assert_eq!(time.as_secs_f64(), 0.0);
364 }
365
366 #[test]
367 fn test_into_parts() {
368 let time = Time::new(Some(1), AvRational::new(2, 3));
369 assert_eq!(time.into_parts(), (Some(1), AvRational::new(2, 3)));
370 }
371
372 #[test]
373 fn test_into_value_none() {
374 let time = Time::new(None, AvRational::new(0, 0));
375 assert_eq!(time.into_value(), None);
376 }
377
378 #[test]
379 fn test_add() {
380 let a = Time::from_secs(0.2);
381 let b = Time::from_secs(0.3);
382 assert_eq!(a.aligned_with(b).add(), Time::from_secs(0.5));
383 }
384
385 #[test]
386 fn test_subtract() {
387 let a = Time::from_secs(0.8);
388 let b = Time::from_secs(0.4);
389 assert_eq!(a.aligned_with(b).subtract(), Time::from_secs(0.4));
390 }
391
392 #[test]
393 fn test_apply() {
394 let a = Time::from_secs(2.0);
395 let b = Time::from_secs(0.25);
396 assert_eq!(
397 a.aligned_with(b).apply(|x, y| (2 * x) + (3 * y)),
398 Time::from_secs(4.75)
399 );
400 }
401
402 #[test]
403 fn test_apply_different_time_bases() {
404 let a = Time::new(Some(3), AvRational::new(2, 32));
405 let b = Time::from_nth_of_a_second(4);
406 assert!(
407 (a.aligned_with(b).apply(|x, y| x + y).as_secs()
408 - Time::from_secs(7.0 / 16.0).as_secs())
409 .abs()
410 < 0.001
411 );
412 }
413
414 #[test]
415 fn test_negative_into_duration_clamps() {
416 assert_eq!(
417 Duration::from(Time::new(Some(-100), AvRational::new(0, 0))),
418 Duration::ZERO,
419 )
420 }
421
422 #[test]
423 fn test_av_no_pts_value() {
424 let nopts = Time::new(Some(ffmpeg::ffi::AV_NOPTS_VALUE), AvRational::new(0, 0));
425 assert_eq!(nopts.into_value(), Some(ffmpeg::ffi::AV_NOPTS_VALUE));
426 assert_eq!(Duration::from(nopts).as_secs_f32(), 0.0);
427 }
428}