jugar_probar/video_quality/
types.rs1use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct VideoProbe {
11 pub codec: String,
13 pub width: u32,
15 pub height: u32,
17 pub fps_fraction: String,
19 pub fps: f64,
21 pub duration_secs: f64,
23 pub bitrate_bps: u64,
25 pub pixel_format: String,
27 pub audio_codec: Option<String>,
29 pub audio_sample_rate: Option<u32>,
31 pub audio_channels: Option<u32>,
33}
34
35#[derive(Clone, Debug)]
37pub struct VideoExpectations {
38 pub width: Option<u32>,
40 pub height: Option<u32>,
42 pub fps: Option<f64>,
44 pub codec: Option<String>,
46 pub min_duration_secs: Option<f64>,
48 pub max_duration_secs: Option<f64>,
50 pub require_audio: bool,
52 pub fps_tolerance: f64,
54}
55
56impl Default for VideoExpectations {
57 fn default() -> Self {
58 Self {
59 width: None,
60 height: None,
61 fps: None,
62 codec: None,
63 min_duration_secs: None,
64 max_duration_secs: None,
65 require_audio: false,
66 fps_tolerance: 0.01,
67 }
68 }
69}
70
71impl VideoExpectations {
72 #[must_use]
74 pub const fn with_resolution(mut self, width: u32, height: u32) -> Self {
75 self.width = Some(width);
76 self.height = Some(height);
77 self
78 }
79
80 #[must_use]
82 pub fn with_fps(mut self, fps: f64) -> Self {
83 self.fps = Some(fps);
84 self
85 }
86
87 #[must_use]
89 pub fn with_codec(mut self, codec: impl Into<String>) -> Self {
90 self.codec = Some(codec.into());
91 self
92 }
93
94 #[must_use]
96 pub fn with_min_duration(mut self, secs: f64) -> Self {
97 self.min_duration_secs = Some(secs);
98 self
99 }
100
101 #[must_use]
103 pub fn with_max_duration(mut self, secs: f64) -> Self {
104 self.max_duration_secs = Some(secs);
105 self
106 }
107
108 #[must_use]
110 pub const fn with_require_audio(mut self, require: bool) -> Self {
111 self.require_audio = require;
112 self
113 }
114}
115
116#[derive(Clone, Debug, Serialize)]
118pub struct VideoQualityReport {
119 pub source: String,
121 pub verdict: VideoVerdict,
123 pub probe: VideoProbe,
125 pub checks: Vec<VideoCheck>,
127 pub passed_count: usize,
129 pub total_count: usize,
131}
132
133#[derive(Clone, Debug, Serialize)]
135pub struct VideoCheck {
136 pub name: String,
138 pub expected: String,
140 pub actual: String,
142 pub passed: bool,
144}
145
146#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
148pub enum VideoVerdict {
149 Pass,
151 Fail,
153 ProbeError,
155}
156
157impl std::fmt::Display for VideoVerdict {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 match self {
160 Self::Pass => write!(f, "PASS"),
161 Self::Fail => write!(f, "FAIL"),
162 Self::ProbeError => write!(f, "PROBE ERROR"),
163 }
164 }
165}
166
167#[cfg(test)]
168#[allow(clippy::unwrap_used)]
169mod tests {
170 use super::*;
171
172 fn sample_probe() -> VideoProbe {
173 VideoProbe {
174 codec: "h264".to_string(),
175 width: 1920,
176 height: 1080,
177 fps_fraction: "24/1".to_string(),
178 fps: 24.0,
179 duration_secs: 120.0,
180 bitrate_bps: 5_000_000,
181 pixel_format: "yuv420p".to_string(),
182 audio_codec: Some("aac".to_string()),
183 audio_sample_rate: Some(48000),
184 audio_channels: Some(2),
185 }
186 }
187
188 #[test]
189 fn test_video_verdict_display() {
190 assert_eq!(VideoVerdict::Pass.to_string(), "PASS");
191 assert_eq!(VideoVerdict::Fail.to_string(), "FAIL");
192 assert_eq!(VideoVerdict::ProbeError.to_string(), "PROBE ERROR");
193 }
194
195 #[test]
196 fn test_video_verdict_equality() {
197 assert_eq!(VideoVerdict::Pass, VideoVerdict::Pass);
198 assert_ne!(VideoVerdict::Pass, VideoVerdict::Fail);
199 }
200
201 #[test]
202 fn test_expectations_default() {
203 let exp = VideoExpectations::default();
204 assert!(exp.width.is_none());
205 assert!(exp.height.is_none());
206 assert!(exp.fps.is_none());
207 assert!(!exp.require_audio);
208 }
209
210 #[test]
211 fn test_expectations_builders() {
212 let exp = VideoExpectations::default()
213 .with_resolution(1920, 1080)
214 .with_fps(24.0)
215 .with_codec("h264")
216 .with_min_duration(10.0)
217 .with_max_duration(300.0)
218 .with_require_audio(true);
219 assert_eq!(exp.width, Some(1920));
220 assert_eq!(exp.height, Some(1080));
221 assert!((exp.fps.unwrap() - 24.0).abs() < f64::EPSILON);
222 assert_eq!(exp.codec.as_deref(), Some("h264"));
223 assert!((exp.min_duration_secs.unwrap() - 10.0).abs() < f64::EPSILON);
224 assert!((exp.max_duration_secs.unwrap() - 300.0).abs() < f64::EPSILON);
225 assert!(exp.require_audio);
226 }
227
228 #[test]
229 fn test_probe_serialization() {
230 let probe = sample_probe();
231 let json = serde_json::to_string(&probe).unwrap();
232 assert!(json.contains("\"codec\":\"h264\""));
233 assert!(json.contains("\"width\":1920"));
234 }
235
236 #[test]
237 fn test_probe_deserialization() {
238 let json = r#"{
239 "codec": "h264",
240 "width": 1920,
241 "height": 1080,
242 "fps_fraction": "24/1",
243 "fps": 24.0,
244 "duration_secs": 120.0,
245 "bitrate_bps": 5000000,
246 "pixel_format": "yuv420p",
247 "audio_codec": "aac",
248 "audio_sample_rate": 48000,
249 "audio_channels": 2
250 }"#;
251 let probe: VideoProbe = serde_json::from_str(json).unwrap();
252 assert_eq!(probe.codec, "h264");
253 assert_eq!(probe.width, 1920);
254 }
255
256 #[test]
257 fn test_video_check() {
258 let check = VideoCheck {
259 name: "resolution".to_string(),
260 expected: "1920x1080".to_string(),
261 actual: "1920x1080".to_string(),
262 passed: true,
263 };
264 assert!(check.passed);
265 }
266
267 #[test]
268 fn test_video_quality_report_serialization() {
269 let report = VideoQualityReport {
270 source: "test.mp4".to_string(),
271 verdict: VideoVerdict::Pass,
272 probe: sample_probe(),
273 checks: vec![VideoCheck {
274 name: "codec".to_string(),
275 expected: "h264".to_string(),
276 actual: "h264".to_string(),
277 passed: true,
278 }],
279 passed_count: 1,
280 total_count: 1,
281 };
282 let json = serde_json::to_string(&report).unwrap();
283 assert!(json.contains("\"verdict\":\"Pass\""));
284 }
285}