use crate::dry_run::ShellScript; use std::io::Result; use std::path::Path; use std::process::Command; use std::io; pub struct FFmpegCommand { inputs: Vec, output: Option, codec: Option, overwrite: bool, } impl FFmpegCommand { pub fn new() -> Self { Self { inputs: Vec::new(), output: None, codec: None, overwrite: false, } } pub fn input(mut self, path: &Path) -> Self { self.inputs.push(path.display().to_string()); self } pub fn input_var(mut self, var: &str) -> Self { self.inputs.push(format!("${}", var)); self } pub fn output(mut self, path: &Path) -> Self { self.output = Some(path.display().to_string()); self } pub fn output_var(mut self, var: &str) -> Self { self.output = Some(format!("${}", var)); self } pub fn codec(mut self, codec: &str) -> Self { self.codec = Some(codec.to_string()); self } pub fn overwrite(mut self, overwrite: bool) -> Self { self.overwrite = overwrite; self } pub fn execute(self, dry_run: bool) -> Result<()> { if dry_run { self.execute_dry_run() } else { self.execute_real() } } fn execute_dry_run(self) -> Result<()> { let mut script = ShellScript::new(); let mut cmd_parts = vec!["ffmpeg".to_string()]; if self.overwrite { cmd_parts.push("-y".to_string()); } cmd_parts.push("-hide_banner".to_string()); for input in &self.inputs { cmd_parts.push("-i".to_string()); cmd_parts.push(input.clone()); } if let Some(ref codec) = self.codec { cmd_parts.push("-c".to_string()); cmd_parts.push(codec.clone()); } if let Some(ref output) = self.output { cmd_parts.push(output.clone()); } let cmd_str = cmd_parts.iter() .map(|arg| { if arg.starts_with('$') { format!("\"{}\"", arg) } else if arg.contains(' ') || arg.contains('$') { format!("\"{}\"", arg.replace('"', r#"\""#)) } else { arg.clone() } }) .collect::>() .join(" "); script.add_command(&cmd_str); crate::dry_run::print_shell_script(&script); Ok(()) } fn execute_real(self) -> Result<()> { let mut cmd = Command::new("ffmpeg"); if self.overwrite { cmd.arg("-y"); } cmd.arg("-hide_banner"); for input in &self.inputs { cmd.arg("-i"); if input.starts_with('$') { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("Cannot execute with variable input: {}", input) )); } cmd.arg(input); } if let Some(ref codec) = self.codec { cmd.arg("-c"); cmd.arg(codec); } if let Some(ref output) = self.output { if output.starts_with('$') { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("Cannot execute with variable output: {}", output) )); } cmd.arg(output); } let output = cmd.spawn()?.wait_with_output()?; if !output.status.success() { return Err(io::Error::new( io::ErrorKind::Other, format!("ffmpeg exited with status {}", output.status) )); } Ok(()) } }