use crate::file_resolver::VideoFile;
use crate::temp::{TempError, TempGuard, create_temp_file};
use ffmpeg_sidecar::command::{FfmpegCommand, ffmpeg_is_installed};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AudioExtractionError {
#[error(
"FFmpeg is not installed. Please install FFmpeg and ensure it's in your PATH, or place it in the same directory as this executable."
)]
FfmpegNotInstalled,
#[error("Invalid video file path: {0}")]
InvalidVideoPath(PathBuf),
#[error("Invalid temporary file path")]
InvalidTempPath,
#[error("Failed to spawn FFmpeg process: {0}")]
FfmpegSpawnFailed(String),
#[error("FFmpeg execution failed: {0}")]
FfmpegExecutionFailed(String),
#[error("Failed to create temporary file: {0}")]
TempFileError(#[from] TempError),
}
#[derive(Debug)]
pub(crate) struct AudioFile {
temp_file: TempGuard,
}
impl AudioFile {
fn new(temp_file: TempGuard) -> Self {
Self { temp_file }
}
}
impl Deref for AudioFile {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.temp_file
}
}
pub(crate) fn audio_from_video(video: &VideoFile) -> Result<AudioFile, AudioExtractionError> {
if !ffmpeg_is_installed() {
return Err(AudioExtractionError::FfmpegNotInstalled);
}
let temp_audio = create_temp_file("audio_extract", "wav")?;
FfmpegCommand::new()
.input(
video
.path
.to_str()
.ok_or_else(|| AudioExtractionError::InvalidVideoPath(video.path.clone()))?,
)
.args(["-vn"]) .args(["-ar", "16000"]) .args(["-ac", "1"]) .args(["-c:a", "pcm_s16le"]) .args(["-y"]) .output(
temp_audio
.path()
.to_str()
.ok_or_else(|| AudioExtractionError::InvalidTempPath)?,
)
.spawn()
.map_err(|e| AudioExtractionError::FfmpegSpawnFailed(e.to_string()))?
.iter()
.map_err(|e| AudioExtractionError::FfmpegExecutionFailed(e.to_string()))?
.for_each(|_event| {
});
Ok(AudioFile::new(temp_audio))
}