oximedia_timecode/
frame_rate.rs1#![allow(dead_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct FrameRateRatio {
11 pub numerator: u32,
13 pub denominator: u32,
15}
16
17impl FrameRateRatio {
18 pub fn new(numerator: u32, denominator: u32) -> Option<Self> {
22 if denominator == 0 {
23 None
24 } else {
25 Some(Self {
26 numerator,
27 denominator,
28 })
29 }
30 }
31
32 #[allow(clippy::cast_precision_loss)]
34 pub fn fps_f64(&self) -> f64 {
35 self.numerator as f64 / self.denominator as f64
36 }
37
38 pub fn is_drop_frame_compatible(&self) -> bool {
43 matches!(
44 (self.numerator, self.denominator),
45 (30000, 1001) | (60000, 1001)
46 )
47 }
48
49 pub fn matches(&self, other: &FrameRateRatio) -> bool {
51 (self.numerator as u64) * (other.denominator as u64)
53 == (other.numerator as u64) * (self.denominator as u64)
54 }
55
56 pub fn common_frame_rates() -> Vec<FrameRateRatio> {
58 vec![
59 FrameRateRatio {
60 numerator: 24000,
61 denominator: 1001,
62 }, FrameRateRatio {
64 numerator: 24,
65 denominator: 1,
66 }, FrameRateRatio {
68 numerator: 25,
69 denominator: 1,
70 }, FrameRateRatio {
72 numerator: 30000,
73 denominator: 1001,
74 }, FrameRateRatio {
76 numerator: 30,
77 denominator: 1,
78 }, FrameRateRatio {
80 numerator: 50,
81 denominator: 1,
82 }, FrameRateRatio {
84 numerator: 60000,
85 denominator: 1001,
86 }, FrameRateRatio {
88 numerator: 60,
89 denominator: 1,
90 }, ]
92 }
93
94 #[allow(clippy::cast_possible_truncation)]
96 pub fn nominal_fps(&self) -> u32 {
97 ((self.numerator as f64 / self.denominator as f64).round()) as u32
98 }
99}
100
101impl std::fmt::Display for FrameRateRatio {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 if self.denominator == 1 {
104 write!(f, "{}", self.numerator)
105 } else {
106 write!(f, "{}/{}", self.numerator, self.denominator)
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_new_valid() {
117 let fr = FrameRateRatio::new(25, 1).unwrap();
118 assert_eq!(fr.numerator, 25);
119 assert_eq!(fr.denominator, 1);
120 }
121
122 #[test]
123 fn test_new_zero_denominator() {
124 assert!(FrameRateRatio::new(30, 0).is_none());
125 }
126
127 #[test]
128 fn test_fps_f64_exact() {
129 let fr = FrameRateRatio::new(25, 1).unwrap();
130 assert!((fr.fps_f64() - 25.0).abs() < 1e-9);
131 }
132
133 #[test]
134 fn test_fps_f64_fractional() {
135 let fr = FrameRateRatio::new(30000, 1001).unwrap();
136 assert!((fr.fps_f64() - 29.97002997).abs() < 1e-6);
137 }
138
139 #[test]
140 fn test_is_drop_frame_compatible_true() {
141 let fr2997 = FrameRateRatio::new(30000, 1001).unwrap();
142 let fr5994 = FrameRateRatio::new(60000, 1001).unwrap();
143 assert!(fr2997.is_drop_frame_compatible());
144 assert!(fr5994.is_drop_frame_compatible());
145 }
146
147 #[test]
148 fn test_is_drop_frame_compatible_false() {
149 let fr = FrameRateRatio::new(25, 1).unwrap();
150 assert!(!fr.is_drop_frame_compatible());
151 }
152
153 #[test]
154 fn test_matches_equal() {
155 let a = FrameRateRatio::new(25, 1).unwrap();
156 let b = FrameRateRatio::new(50, 2).unwrap();
157 assert!(a.matches(&b));
158 }
159
160 #[test]
161 fn test_matches_not_equal() {
162 let a = FrameRateRatio::new(24, 1).unwrap();
163 let b = FrameRateRatio::new(25, 1).unwrap();
164 assert!(!a.matches(&b));
165 }
166
167 #[test]
168 fn test_common_frame_rates_count() {
169 let rates = FrameRateRatio::common_frame_rates();
170 assert_eq!(rates.len(), 8);
171 }
172
173 #[test]
174 fn test_common_frame_rates_contains_25() {
175 let rates = FrameRateRatio::common_frame_rates();
176 assert!(rates
177 .iter()
178 .any(|r| r.numerator == 25 && r.denominator == 1));
179 }
180
181 #[test]
182 fn test_nominal_fps_exact() {
183 let fr = FrameRateRatio::new(30, 1).unwrap();
184 assert_eq!(fr.nominal_fps(), 30);
185 }
186
187 #[test]
188 fn test_nominal_fps_rounded() {
189 let fr = FrameRateRatio::new(30000, 1001).unwrap();
190 assert_eq!(fr.nominal_fps(), 30);
192 }
193
194 #[test]
195 fn test_display_integer() {
196 let fr = FrameRateRatio::new(25, 1).unwrap();
197 assert_eq!(fr.to_string(), "25");
198 }
199
200 #[test]
201 fn test_display_fractional() {
202 let fr = FrameRateRatio::new(30000, 1001).unwrap();
203 assert_eq!(fr.to_string(), "30000/1001");
204 }
205}