#![allow(unsafe_code)]
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::DecodeError;
#[derive(Debug, Clone, PartialEq)]
pub struct SilenceRange {
pub start: Duration,
pub end: Duration,
}
pub struct SilenceDetector {
input: PathBuf,
threshold_db: f32,
min_duration: Duration,
}
impl SilenceDetector {
pub fn new(input: impl AsRef<Path>) -> Self {
Self {
input: input.as_ref().to_path_buf(),
threshold_db: -40.0,
min_duration: Duration::from_millis(500),
}
}
#[must_use]
pub fn threshold_db(self, db: f32) -> Self {
Self {
threshold_db: db,
..self
}
}
#[must_use]
pub fn min_duration(self, d: Duration) -> Self {
Self {
min_duration: d,
..self
}
}
pub fn run(self) -> Result<Vec<SilenceRange>, DecodeError> {
if !self.input.exists() {
return Err(DecodeError::AnalysisFailed {
reason: format!("file not found: {}", self.input.display()),
});
}
unsafe {
super::analysis_inner::detect_silence_unsafe(
&self.input,
self.threshold_db,
self.min_duration,
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn silence_detector_missing_file_should_return_analysis_failed() {
let result = SilenceDetector::new("does_not_exist_99999.mp3").run();
assert!(
matches!(result, Err(DecodeError::AnalysisFailed { .. })),
"expected AnalysisFailed for missing file, got {result:?}"
);
}
#[test]
fn silence_detector_default_threshold_should_be_minus_40_db() {
let result = SilenceDetector::new("does_not_exist_99999.mp3")
.threshold_db(-40.0)
.run();
assert!(
matches!(result, Err(DecodeError::AnalysisFailed { .. })),
"expected AnalysisFailed (missing file) when threshold_db=-40, got {result:?}"
);
}
}