#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameRateRatio {
pub numerator: u32,
pub denominator: u32,
}
impl FrameRateRatio {
pub fn new(numerator: u32, denominator: u32) -> Option<Self> {
if denominator == 0 {
None
} else {
Some(Self {
numerator,
denominator,
})
}
}
#[allow(clippy::cast_precision_loss)]
pub fn fps_f64(&self) -> f64 {
self.numerator as f64 / self.denominator as f64
}
pub fn is_drop_frame_compatible(&self) -> bool {
matches!(
(self.numerator, self.denominator),
(30000, 1001) | (60000, 1001)
)
}
pub fn matches(&self, other: &FrameRateRatio) -> bool {
(self.numerator as u64) * (other.denominator as u64)
== (other.numerator as u64) * (self.denominator as u64)
}
pub fn common_frame_rates() -> Vec<FrameRateRatio> {
vec![
FrameRateRatio {
numerator: 24000,
denominator: 1001,
}, FrameRateRatio {
numerator: 24,
denominator: 1,
}, FrameRateRatio {
numerator: 25,
denominator: 1,
}, FrameRateRatio {
numerator: 30000,
denominator: 1001,
}, FrameRateRatio {
numerator: 30,
denominator: 1,
}, FrameRateRatio {
numerator: 50,
denominator: 1,
}, FrameRateRatio {
numerator: 60000,
denominator: 1001,
}, FrameRateRatio {
numerator: 60,
denominator: 1,
}, ]
}
#[allow(clippy::cast_possible_truncation)]
pub fn nominal_fps(&self) -> u32 {
((self.numerator as f64 / self.denominator as f64).round()) as u32
}
}
impl std::fmt::Display for FrameRateRatio {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.denominator == 1 {
write!(f, "{}", self.numerator)
} else {
write!(f, "{}/{}", self.numerator, self.denominator)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_valid() {
let fr = FrameRateRatio::new(25, 1).expect("valid frame rate ratio");
assert_eq!(fr.numerator, 25);
assert_eq!(fr.denominator, 1);
}
#[test]
fn test_new_zero_denominator() {
assert!(FrameRateRatio::new(30, 0).is_none());
}
#[test]
fn test_fps_f64_exact() {
let fr = FrameRateRatio::new(25, 1).expect("valid frame rate ratio");
assert!((fr.fps_f64() - 25.0).abs() < 1e-9);
}
#[test]
fn test_fps_f64_fractional() {
let fr = FrameRateRatio::new(30000, 1001).expect("valid frame rate ratio");
assert!((fr.fps_f64() - 29.97002997).abs() < 1e-6);
}
#[test]
fn test_is_drop_frame_compatible_true() {
let fr2997 = FrameRateRatio::new(30000, 1001).expect("valid frame rate ratio");
let fr5994 = FrameRateRatio::new(60000, 1001).expect("valid frame rate ratio");
assert!(fr2997.is_drop_frame_compatible());
assert!(fr5994.is_drop_frame_compatible());
}
#[test]
fn test_is_drop_frame_compatible_false() {
let fr = FrameRateRatio::new(25, 1).expect("valid frame rate ratio");
assert!(!fr.is_drop_frame_compatible());
}
#[test]
fn test_matches_equal() {
let a = FrameRateRatio::new(25, 1).expect("valid frame rate ratio");
let b = FrameRateRatio::new(50, 2).expect("valid frame rate ratio");
assert!(a.matches(&b));
}
#[test]
fn test_matches_not_equal() {
let a = FrameRateRatio::new(24, 1).expect("valid frame rate ratio");
let b = FrameRateRatio::new(25, 1).expect("valid frame rate ratio");
assert!(!a.matches(&b));
}
#[test]
fn test_common_frame_rates_count() {
let rates = FrameRateRatio::common_frame_rates();
assert_eq!(rates.len(), 8);
}
#[test]
fn test_common_frame_rates_contains_25() {
let rates = FrameRateRatio::common_frame_rates();
assert!(rates
.iter()
.any(|r| r.numerator == 25 && r.denominator == 1));
}
#[test]
fn test_nominal_fps_exact() {
let fr = FrameRateRatio::new(30, 1).expect("valid frame rate ratio");
assert_eq!(fr.nominal_fps(), 30);
}
#[test]
fn test_nominal_fps_rounded() {
let fr = FrameRateRatio::new(30000, 1001).expect("valid frame rate ratio");
assert_eq!(fr.nominal_fps(), 30);
}
#[test]
fn test_display_integer() {
let fr = FrameRateRatio::new(25, 1).expect("valid frame rate ratio");
assert_eq!(fr.to_string(), "25");
}
#[test]
fn test_display_fractional() {
let fr = FrameRateRatio::new(30000, 1001).expect("valid frame rate ratio");
assert_eq!(fr.to_string(), "30000/1001");
}
}