1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct FrameRate {
20 fps: f64,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum FrameRateError {
25 InvalidFps,
26 InvalidDuration,
27}
28
29fn validate_fps(fps: f64) -> Result<f64, FrameRateError> {
30 if !fps.is_finite() || fps <= 0.0 {
31 Err(FrameRateError::InvalidFps)
32 } else {
33 Ok(fps)
34 }
35}
36
37fn validate_duration(duration_seconds: f64) -> Result<f64, FrameRateError> {
38 if !duration_seconds.is_finite() || duration_seconds < 0.0 {
39 Err(FrameRateError::InvalidDuration)
40 } else {
41 Ok(duration_seconds)
42 }
43}
44
45impl FrameRate {
46 pub fn new(fps: f64) -> Result<Self, FrameRateError> {
47 Ok(Self {
48 fps: validate_fps(fps)?,
49 })
50 }
51
52 #[must_use]
53 pub fn fps(&self) -> f64 {
54 self.fps
55 }
56
57 #[must_use]
58 pub fn frame_duration_seconds(&self) -> f64 {
59 1.0 / self.fps
60 }
61
62 #[must_use]
63 pub fn frame_duration_millis(&self) -> f64 {
64 self.frame_duration_seconds() * 1_000.0
65 }
66}
67
68pub fn frame_duration_seconds(fps: f64) -> Result<f64, FrameRateError> {
69 Ok(1.0 / validate_fps(fps)?)
70}
71
72pub fn frame_count(duration_seconds: f64, fps: f64) -> Result<u64, FrameRateError> {
73 let duration_seconds = validate_duration(duration_seconds)?;
74 let fps = validate_fps(fps)?;
75 Ok((duration_seconds * fps).round() as u64)
76}
77
78pub fn duration_from_frames(frame_count: u64, fps: f64) -> Result<f64, FrameRateError> {
79 Ok(frame_count as f64 / validate_fps(fps)?)
80}
81
82#[must_use]
83pub fn is_standard_frame_rate(fps: f64) -> bool {
84 const STANDARD_FRAME_RATES: [f64; 9] =
85 [23.976, 24.0, 25.0, 29.97, 30.0, 50.0, 59.94, 60.0, 120.0];
86
87 fps.is_finite()
88 && STANDARD_FRAME_RATES
89 .into_iter()
90 .any(|standard| (fps - standard).abs() <= 0.01)
91}
92
93#[cfg(test)]
94mod tests {
95 use super::{
96 FrameRate, FrameRateError, duration_from_frames, frame_count, frame_duration_seconds,
97 is_standard_frame_rate,
98 };
99
100 #[test]
101 fn computes_frame_durations_and_counts() {
102 let rate = FrameRate::new(24.0).unwrap();
103
104 assert_eq!(rate.fps(), 24.0);
105 assert!((rate.frame_duration_seconds() - (1.0 / 24.0)).abs() < 1.0e-12);
106 assert!((rate.frame_duration_millis() - 41.666_666_666_666_664).abs() < 1.0e-12);
107 assert_eq!(frame_count(2.5, 24.0).unwrap(), 60);
108 assert!((duration_from_frames(60, 24.0).unwrap() - 2.5).abs() < 1.0e-12);
109 assert!((frame_duration_seconds(25.0).unwrap() - 0.04).abs() < 1.0e-12);
110 }
111
112 #[test]
113 fn detects_standard_frame_rates() {
114 assert!(is_standard_frame_rate(23.976));
115 assert!(is_standard_frame_rate(23.98));
116 assert!(is_standard_frame_rate(60.0));
117 assert!(!is_standard_frame_rate(27.0));
118 }
119
120 #[test]
121 fn rejects_invalid_frame_rate_inputs() {
122 assert_eq!(FrameRate::new(0.0), Err(FrameRateError::InvalidFps));
123 assert_eq!(
124 frame_count(-1.0, 24.0),
125 Err(FrameRateError::InvalidDuration)
126 );
127 assert_eq!(
128 duration_from_frames(10, f64::NAN),
129 Err(FrameRateError::InvalidFps)
130 );
131 }
132}