#![allow(unsafe_code)]
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::DecodeError;
pub struct SceneDetector {
input: PathBuf,
threshold: f64,
}
impl SceneDetector {
pub fn new(input: impl AsRef<Path>) -> Self {
Self {
input: input.as_ref().to_path_buf(),
threshold: 0.4,
}
}
#[must_use]
pub fn threshold(self, t: f64) -> Self {
Self {
threshold: t,
..self
}
}
pub fn run(self) -> Result<Vec<Duration>, DecodeError> {
if !(0.0..=1.0).contains(&self.threshold) {
return Err(DecodeError::AnalysisFailed {
reason: format!("threshold must be in [0.0, 1.0], got {}", self.threshold),
});
}
if !self.input.exists() {
return Err(DecodeError::AnalysisFailed {
reason: format!("file not found: {}", self.input.display()),
});
}
unsafe { super::analysis_inner::detect_scenes_unsafe(&self.input, self.threshold) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scene_detector_invalid_threshold_below_zero_should_return_analysis_failed() {
let result = SceneDetector::new("irrelevant.mp4").threshold(-0.1).run();
assert!(
matches!(result, Err(DecodeError::AnalysisFailed { .. })),
"expected AnalysisFailed for threshold=-0.1, got {result:?}"
);
}
#[test]
fn scene_detector_invalid_threshold_above_one_should_return_analysis_failed() {
let result = SceneDetector::new("irrelevant.mp4").threshold(1.1).run();
assert!(
matches!(result, Err(DecodeError::AnalysisFailed { .. })),
"expected AnalysisFailed for threshold=1.1, got {result:?}"
);
}
#[test]
fn scene_detector_missing_file_should_return_analysis_failed() {
let result = SceneDetector::new("does_not_exist_99999.mp4").run();
assert!(
matches!(result, Err(DecodeError::AnalysisFailed { .. })),
"expected AnalysisFailed for missing file, got {result:?}"
);
}
#[test]
fn scene_detector_boundary_thresholds_should_be_valid() {
let r0 = SceneDetector::new("irrelevant.mp4").threshold(0.0).run();
let r1 = SceneDetector::new("irrelevant.mp4").threshold(1.0).run();
assert!(
matches!(r0, Err(DecodeError::AnalysisFailed { .. })),
"expected AnalysisFailed (file), got {r0:?}"
);
assert!(
matches!(r1, Err(DecodeError::AnalysisFailed { .. })),
"expected AnalysisFailed (file), got {r1:?}"
);
}
}