use std::path::{Path, PathBuf};
use std::process::Command;
use crate::error::{AppError, Result};
#[derive(Clone, Debug)]
pub struct Ffmpeg {
executable: PathBuf,
}
impl Ffmpeg {
pub fn resolve(explicit: Option<&Path>) -> Result<Self> {
if let Some(path) = explicit {
let path = path.to_path_buf();
if !path.exists() {
return Err(AppError::InvalidPath(path));
}
return Ok(Self { executable: path });
}
which_ffmpeg().map(|executable| Self { executable })
}
pub fn path(&self) -> &Path {
&self.executable
}
pub fn run(&self, args: &[&str]) -> Result<std::process::Output> {
self.run_in(None, args)
}
pub fn run_in(&self, cwd: Option<&Path>, args: &[&str]) -> Result<std::process::Output> {
let mut cmd = Command::new(self.path());
if let Some(cwd) = cwd {
cmd.current_dir(cwd);
}
let output = cmd.args(args).output()?;
if output.status.success() {
Ok(output)
} else {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
Err(AppError::FfmpegFailed {
code: output.status.code(),
stderr,
})
}
}
}
pub fn check_ffmpeg(explicit: Option<&Path>) -> Result<()> {
Ffmpeg::resolve(explicit).map(|_| ())
}
fn which_ffmpeg() -> Result<PathBuf> {
let candidates = ["ffmpeg", "ffmpeg.exe"];
for name in candidates {
if let Ok(output) = Command::new(name).arg("-version").output() {
if output.status.success() {
return Ok(PathBuf::from(name));
}
}
}
Err(AppError::FfmpegNotFound)
}