ff_analysis/analysis/
black_frame_detector.rs1use std::path::{Path, PathBuf};
4use std::time::Duration;
5
6use crate::AnalysisError;
7
8pub struct BlackFrameDetector {
28 input: PathBuf,
29 threshold: f64,
30}
31
32impl BlackFrameDetector {
33 pub fn new(input: impl AsRef<Path>) -> Self {
38 Self {
39 input: input.as_ref().to_path_buf(),
40 threshold: 0.1,
41 }
42 }
43
44 #[must_use]
53 pub fn threshold(self, t: f64) -> Self {
54 Self {
55 threshold: t,
56 ..self
57 }
58 }
59
60 pub fn run(self) -> Result<Vec<Duration>, AnalysisError> {
68 if !(0.0..=1.0).contains(&self.threshold) {
69 return Err(AnalysisError::Failed {
70 reason: format!("threshold must be in [0.0, 1.0], got {}", self.threshold),
71 });
72 }
73 if !self.input.exists() {
74 return Err(AnalysisError::Failed {
75 reason: format!("file not found: {}", self.input.display()),
76 });
77 }
78 super::analysis_inner::detect_black_frames(&self.input, self.threshold)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn black_frame_detector_invalid_threshold_below_zero_should_return_analysis_failed() {
88 let result = BlackFrameDetector::new("irrelevant.mp4")
89 .threshold(-0.1)
90 .run();
91 assert!(
92 matches!(result, Err(AnalysisError::Failed { .. })),
93 "expected Failed for threshold=-0.1, got {result:?}"
94 );
95 }
96
97 #[test]
98 fn black_frame_detector_invalid_threshold_above_one_should_return_analysis_failed() {
99 let result = BlackFrameDetector::new("irrelevant.mp4")
100 .threshold(1.1)
101 .run();
102 assert!(
103 matches!(result, Err(AnalysisError::Failed { .. })),
104 "expected Failed for threshold=1.1, got {result:?}"
105 );
106 }
107
108 #[test]
109 fn black_frame_detector_missing_file_should_return_analysis_failed() {
110 let result = BlackFrameDetector::new("does_not_exist_99999.mp4").run();
111 assert!(
112 matches!(result, Err(AnalysisError::Failed { .. })),
113 "expected Failed for missing file, got {result:?}"
114 );
115 }
116
117 #[test]
118 fn black_frame_detector_boundary_thresholds_should_be_valid() {
119 let r0 = BlackFrameDetector::new("irrelevant.mp4")
121 .threshold(0.0)
122 .run();
123 let r1 = BlackFrameDetector::new("irrelevant.mp4")
124 .threshold(1.0)
125 .run();
126 assert!(
127 matches!(r0, Err(AnalysisError::Failed { .. })),
128 "expected Failed (file not found) for threshold=0.0, got {r0:?}"
129 );
130 assert!(
131 matches!(r1, Err(AnalysisError::Failed { .. })),
132 "expected Failed (file not found) for threshold=1.0, got {r1:?}"
133 );
134 }
135}