use std::{ fmt::{Debug, Display}, path::PathBuf, process::Command, str::FromStr, }; use clap::Parser; #[derive(Debug)] #[non_exhaustive] enum ParamParseError { AudioOption, } impl Display for ParamParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ::fmt(self, f) } } #[derive(Debug, Clone, Copy, clap::ValueEnum)] enum AudioOption { Copy, None, } impl FromStr for AudioOption { type Err = ParamParseError; fn from_str(s: &str) -> Result { match s { "c" | "none" | "None" => Ok(AudioOption::None), "n" | "copy" | "Copy" => Ok(AudioOption::Copy), _ => Err(ParamParseError::AudioOption), } } } #[derive(Debug, clap::Parser)] struct Params { #[clap(short, long)] output: PathBuf, #[clap(short, long)] input: PathBuf, #[clap(short, long, default_value = "10.0")] target: f32, #[clap(short, long)] scale: Option, #[clap(short, long)] framerate: Option, #[clap(short, long, default_value = "copy")] audio: AudioOption, } fn main() { let params = Params::parse(); let mut pass0 = Command::new("./ffprobe.exe"); pass0.args( [ "-v", "error", "-hide_banner", "-select_streams", "v:0", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", "-i", params.input.to_str().unwrap(), ] .iter(), ); let bitrate = pass0 .output() .map(|o| { let mut s = String::from_utf8(o.stdout).unwrap(); let duration: f32 = s.trim().parse().expect("Couldn't parse ffprobe output"); let bitrate = params.target * 1024.0 * 1024.0 * 8.0 / duration / 1000.0; s = bitrate.to_string(); s.push('k'); s }) .expect("Couldn't run ffprobe"); let mut args = vec![ "-v", "warning", "-y", "-i", params.input.to_str().unwrap(), "-c:v", "libx264", "-preset", "medium", "-b:v", &bitrate, "-f", "mp4", ]; let mut scaling = String::from("scale=-1:"); if let Some(scale) = params.scale { scaling.push_str(&scale.to_string()); args.extend(["-vf", &scaling].iter()); } match params.audio { AudioOption::Copy => { args.extend(["-c:a", "copy"].iter()); } AudioOption::None => { args.push("-an"); } } let fps: String; if let Some(framerate) = params.framerate { fps = framerate.to_string(); args.extend(["-r", &fps].iter()); } let mut pass1 = Command::new("./ffmpeg.exe"); pass1.args(args.iter()).arg("-pass").arg("1").arg("./junk"); dbg!(pass1) .spawn() .expect("Couldn't run ffmpeg") .wait() .expect("Ffmpeg exited unexpectedly"); let mut pass2 = Command::new("./ffmpeg.exe"); pass2 .args(args) .arg("-pass") .arg("2") .arg(params.output.to_str().unwrap()); pass2 .spawn() .expect("Couldn't run ffmpeg") .wait() .expect("Ffmpeg exited unexpectedly"); for file in ["junk", "ffmpeg2pass-0.log", "ffmpeg2pass-0.log.mbtree"] { std::fs::remove_file(file).expect("Failed to cleanup temporary files"); } }