extern crate ffmpeg_next as ffmpeg;
use std::time::Duration;
use ffmpeg::util::mathematics::rescale::{Rescale, TIME_BASE};
use ffmpeg::Rational as AvRational;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Time {
time: Option<i64>,
time_base: AvRational,
}
impl Time {
pub fn new(time: Option<i64>, time_base: AvRational) -> Time {
Self { time, time_base }
}
#[inline]
pub fn with_time_base(&self, time_base: AvRational) -> Self {
self.aligned_with_rational(time_base)
}
pub fn from_nth_of_a_second(nth: usize) -> Self {
Self {
time: Some(1),
time_base: AvRational::new(1, nth as i32),
}
}
pub fn from_secs(secs: f32) -> Self {
Self {
time: Some((secs * TIME_BASE.denominator() as f32).round() as i64),
time_base: TIME_BASE,
}
}
pub fn from_secs_f64(secs: f64) -> Self {
Self {
time: Some((secs * TIME_BASE.denominator() as f64).round() as i64),
time_base: TIME_BASE,
}
}
pub fn from_units(time: usize, base_den: usize) -> Self {
Self {
time: Some(time as i64),
time_base: AvRational::new(1, base_den as i32),
}
}
pub fn zero() -> Self {
Time {
time: Some(0),
time_base: (1, 90000).into(),
}
}
pub fn has_value(&self) -> bool {
self.time.is_some()
}
pub fn aligned_with(&self, rhs: &Time) -> Aligned {
Aligned {
lhs: self.time,
rhs: rhs
.time
.map(|rhs_time| rhs_time.rescale(rhs.time_base, self.time_base)),
time_base: self.time_base,
}
}
pub fn as_secs(&self) -> f32 {
if let Some(time) = self.time {
(time as f32)
* (self.time_base.numerator() as f32 / self.time_base.denominator() as f32)
} else {
0.0
}
}
pub fn as_secs_f64(&self) -> f64 {
if let Some(time) = self.time {
(time as f64)
* (self.time_base.numerator() as f64 / self.time_base.denominator() as f64)
} else {
0.0
}
}
pub fn into_parts(self) -> (Option<i64>, AvRational) {
(self.time, self.time_base)
}
pub fn into_value(self) -> Option<i64> {
self.time
}
pub(crate) fn aligned_with_rational(&self, time_base: AvRational) -> Time {
Time {
time: self
.time
.map(|time| time.rescale(self.time_base, time_base)),
time_base,
}
}
}
impl From<Duration> for Time {
#[inline]
fn from(duration: Duration) -> Self {
Time::from_secs_f64(duration.as_secs_f64())
}
}
impl From<Time> for Duration {
fn from(timestamp: Time) -> Self {
Duration::from_secs_f64(timestamp.as_secs_f64())
}
}
impl std::fmt::Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(time) = self.time {
let num = self.time_base.numerator() as i64 * time;
let den = self.time_base.denominator();
write!(f, "{num}/{den} secs")
} else {
write!(f, "none")
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Aligned {
lhs: Option<i64>,
rhs: Option<i64>,
time_base: AvRational,
}
impl Aligned {
pub fn add(self) -> Time {
self.apply(|lhs, rhs| lhs + rhs)
}
pub fn subtract(self) -> Time {
self.apply(|lhs, rhs| lhs - rhs)
}
fn apply<F>(self, f: F) -> Time
where
F: FnOnce(i64, i64) -> i64,
{
match (self.lhs, self.rhs) {
(Some(lhs_time), Some(rhs_time)) => Time {
time: Some(f(lhs_time, rhs_time)),
time_base: self.time_base,
},
_ => Time {
time: None,
time_base: self.time_base,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let time = Time::new(Some(2), AvRational::new(3, 9));
assert!(time.has_value());
assert_eq!(time.as_secs(), 2.0 / 3.0);
assert_eq!(time.into_value(), Some(2));
}
#[test]
fn test_with_time_base() {
let time = Time::new(Some(2), AvRational::new(3, 9));
assert_eq!(time.as_secs(), 2.0 / 3.0);
let time = time.with_time_base(AvRational::new(1, 9));
assert_eq!(time.as_secs(), 2.0 / 3.0);
assert_eq!(time.into_value(), Some(6));
}
#[test]
fn test_from_nth_of_a_second() {
let time = Time::from_nth_of_a_second(4);
assert!(time.has_value());
assert_eq!(time.as_secs(), 0.25);
assert_eq!(time.as_secs_f64(), 0.25);
assert_eq!(Duration::from(time), Duration::from_millis(250));
}
#[test]
fn test_from_secs() {
let time = Time::from_secs(2.5);
assert!(time.has_value());
assert_eq!(time.as_secs(), 2.5);
assert_eq!(time.as_secs_f64(), 2.5);
assert_eq!(Duration::from(time), Duration::from_millis(2500));
}
#[test]
fn test_from_secs_f64() {
let time = Time::from_secs(4.0);
assert!(time.has_value());
assert_eq!(time.as_secs_f64(), 4.0);
}
#[test]
fn test_from_units() {
let time = Time::from_units(3, 5);
assert!(time.has_value());
assert_eq!(time.as_secs(), 3.0 / 5.0);
assert_eq!(Duration::from(time), Duration::from_millis(600));
}
#[test]
fn test_zero() {
let time = Time::zero();
assert!(time.has_value());
assert_eq!(time.as_secs(), 0.0);
assert_eq!(time.as_secs_f64(), 0.0);
assert_eq!(Duration::from(time), Duration::ZERO);
let time = Time::zero();
assert_eq!(time.into_value(), Some(0));
}
#[test]
fn test_aligned_with() {
let a = Time::from_units(3, 16);
let b = Time::from_units(1, 8);
let aligned = a.aligned_with(&b);
assert_eq!(aligned.lhs, Some(3));
assert_eq!(aligned.rhs, Some(2));
}
#[test]
fn test_into_aligned_with() {
let a = Time::from_units(2, 7);
let b = Time::from_units(2, 3);
let aligned = a.aligned_with(&b);
assert_eq!(aligned.lhs, Some(2));
assert_eq!(aligned.rhs, Some(5));
}
#[test]
fn test_as_secs() {
let time = Time::from_nth_of_a_second(4);
assert_eq!(time.as_secs(), 0.25);
let time = Time::from_secs(0.3);
assert_eq!(time.as_secs(), 0.3);
let time = Time::new(None, AvRational::new(0, 0));
assert_eq!(time.as_secs(), 0.0);
}
#[test]
fn test_as_secs_f64() {
let time = Time::from_nth_of_a_second(4);
assert_eq!(time.as_secs_f64(), 0.25);
let time = Time::from_secs_f64(0.3);
assert_eq!(time.as_secs_f64(), 0.3);
let time = Time::new(None, AvRational::new(0, 0));
assert_eq!(time.as_secs_f64(), 0.0);
}
#[test]
fn test_into_parts() {
let time = Time::new(Some(1), AvRational::new(2, 3));
assert_eq!(time.into_parts(), (Some(1), AvRational::new(2, 3)));
}
#[test]
fn test_into_value_none() {
let time = Time::new(None, AvRational::new(0, 0));
assert_eq!(time.into_value(), None);
}
#[test]
fn test_add() {
let a = Time::from_secs(0.2);
let b = Time::from_secs(0.3);
assert_eq!(a.aligned_with(&b).add(), Time::from_secs(0.5));
}
#[test]
fn test_subtract() {
let a = Time::from_secs(0.8);
let b = Time::from_secs(0.4);
assert_eq!(a.aligned_with(&b).subtract(), Time::from_secs(0.4));
}
#[test]
fn test_apply() {
let a = Time::from_secs(2.0);
let b = Time::from_secs(0.25);
assert_eq!(
a.aligned_with(&b).apply(|x, y| (2 * x) + (3 * y)),
Time::from_secs(4.75)
);
}
#[test]
fn test_apply_different_time_bases() {
let a = Time::new(Some(3), AvRational::new(2, 32));
let b = Time::from_nth_of_a_second(4);
assert!(
(a.aligned_with(&b).apply(|x, y| x + y).as_secs()
- Time::from_secs(7.0 / 16.0).as_secs())
.abs()
< 0.001
);
}
}