video_rs_adder_dep/
time.rs

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/// Represents a time or duration.
9///
10/// [`Time`] may represent a PTS (presentation timestamp), DTS (decoder timestamp) or a duration,
11/// depending on the function that returns it.
12///
13/// [`Time`] may represent a non-existing time, in which case [`Time::has_value`] will return
14/// `false`, and conversions to seconds will return `0.0`.
15///
16/// A [`Time`] object may be aligned with another [`Time`] object, which produces an [`Aligned`]
17/// object, on which arithmetic operations can be performed.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Time {
20    time: Option<i64>,
21    time_base: AvRational,
22}
23
24impl Time {
25    /// Create a new time by its time value and time base in which the time is expressed.
26    ///
27    /// # Arguments
28    ///
29    /// * `time` - Relative time in `time_base` units.
30    /// * `time_base` - Time base of source.
31    pub fn new(time: Option<i64>, time_base: AvRational) -> Time {
32        Self { time, time_base }
33    }
34
35    /// Align the timestamp with a different time base.
36    ///
37    /// # Arguments
38    ///
39    /// # Return value
40    ///
41    /// The same timestamp, with the time base changed.
42    #[inline]
43    pub fn with_time_base(&self, time_base: AvRational) -> Self {
44        self.aligned_with_rational(time_base)
45    }
46
47    /// Creates a new timestamp that reprsents `nth` of a second.
48    ///
49    /// # Arguments
50    ///
51    /// * `nth` - Denominator of the time in seconds as in `1 / nth`.
52    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    /// Creates a new timestamp from a number of seconds.
60    ///
61    /// # Arguments
62    ///
63    /// * `secs` - Number of seconds.
64    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    /// Creates a new timestamp from a number of seconds.
72    ///
73    /// # Arguments
74    ///
75    /// * `secs` - Number of seconds.
76    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    /// Creates a new timestamp with `time` time units, each represents one / `base_den` seconds.
84    ///
85    /// # Arguments
86    ///
87    /// * `time` - Relative time in `time_base` units.
88    /// * `base_den` - Time base denominator i.e. time base is `1 / base_den`.
89    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    /// Create a new zero-valued timestamp.
97    pub fn zero() -> Self {
98        Time {
99            time: Some(0),
100            time_base: (1, 90000).into(),
101        }
102    }
103
104    /// Whether or not the [`Time`] has a time at all.
105    pub fn has_value(&self) -> bool {
106        self.time.is_some()
107    }
108
109    /// Align the timestamp with another timestamp, which will convert the `rhs` timestamp to the
110    /// same time base, such that operations can be performed upon the aligned timestamps.
111    ///
112    /// # Arguments
113    ///
114    /// * `rhs` - Right-hand side timestamp.
115    ///
116    /// # Return value
117    ///
118    /// Two timestamps that are aligned.
119    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    /// Get number of seconds as floating point value.
130    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    /// Get number of seconds as floating point value.
140    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    /// Convert to underlying parts: the `time` and `time_base`.
150    pub fn into_parts(self) -> (Option<i64>, AvRational) {
151        (self.time, self.time_base)
152    }
153
154    /// Convert to underlying time to `i64` (the number of time units).
155    ///
156    /// # Safety
157    ///
158    /// Assumes that the caller knows the time base and applies it correctly when doing arithmetic
159    /// operations on the time value.
160    pub fn into_value(self) -> Option<i64> {
161        self.time
162    }
163
164    /// Align the timestamp along another `time_base`.
165    ///
166    /// # Arguments
167    ///
168    /// * `time_base` - Target time base.
169    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    /// Convert from a [`Duration`] to [`Time`].
181    #[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    /// Convert from a [`Time`] to a Rust-native [`Duration`].
189    fn from(timestamp: Time) -> Self {
190        Duration::from_secs_f64(timestamp.as_secs_f64())
191    }
192}
193
194impl std::fmt::Display for Time {
195    /// Format [`Time`] as follows:
196    ///
197    /// * If the inner value is not `None`: `time/time_base`.
198    /// * If the inner value is `None`: `none`.
199    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/// This is a virtual object that represents two aligned times.
211///
212/// On this object, arthmetic operations can be performed that operate on the two contained times.
213/// This virtual object ensures that the interface to these operations is safe.
214#[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    /// Add two timestamps together.
223    pub fn add(self) -> Time {
224        self.apply(|lhs, rhs| lhs + rhs)
225    }
226
227    /// Subtract the right-hand side timestamp from the left-hand side timestamp.
228    pub fn subtract(self) -> Time {
229        self.apply(|lhs, rhs| lhs - rhs)
230    }
231
232    /// Apply operation `f` on aligned timestamps.
233    ///
234    /// # Safety
235    ///
236    /// The closure operates on the numerator of two aligned times.
237    ///
238    /// # Arguments
239    ///
240    /// * `f` - Function to apply on the two aligned time numerator values.
241    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}