video_subtitle/ffmpeg/
util.rs1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use crate::error::{AppError, Result};
5
6#[derive(Clone, Debug)]
11pub struct Ffmpeg {
12 executable: PathBuf,
13}
14
15impl Ffmpeg {
16 pub fn resolve(explicit: Option<&Path>) -> Result<Self> {
21 if let Some(path) = explicit {
22 let path = path.to_path_buf();
23 if !path.exists() {
24 return Err(AppError::InvalidPath(path));
25 }
26 return Ok(Self { executable: path });
27 }
28
29 which_ffmpeg().map(|executable| Self { executable })
30 }
31
32 pub fn path(&self) -> &Path {
34 &self.executable
35 }
36
37 pub fn run(&self, args: &[&str]) -> Result<std::process::Output> {
41 self.run_in(None, args)
42 }
43
44 pub fn run_in(&self, cwd: Option<&Path>, args: &[&str]) -> Result<std::process::Output> {
46 let mut cmd = Command::new(self.path());
47 if let Some(cwd) = cwd {
48 cmd.current_dir(cwd);
49 }
50 let output = cmd.args(args).output()?;
51
52 if output.status.success() {
53 Ok(output)
54 } else {
55 let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
56 Err(AppError::FfmpegFailed {
57 code: output.status.code(),
58 stderr,
59 })
60 }
61 }
62}
63
64pub fn check_ffmpeg(explicit: Option<&Path>) -> Result<()> {
66 Ffmpeg::resolve(explicit).map(|_| ())
67}
68
69fn which_ffmpeg() -> Result<PathBuf> {
71 let candidates = ["ffmpeg", "ffmpeg.exe"];
72 for name in candidates {
73 if let Ok(output) = Command::new(name).arg("-version").output() {
74 if output.status.success() {
75 return Ok(PathBuf::from(name));
76 }
77 }
78 }
79 Err(AppError::FfmpegNotFound)
80}