From a905cdd4b975427675fab288e3d5dbc813508867 Mon Sep 17 00:00:00 2001 From: Didier Date: Sat, 6 May 2023 23:21:12 +0200 Subject: [PATCH] feats: added renice module added jobs added ffmpeg command builders. --- Cargo.toml | 1 - src/configuration/ffmpeg.rs | 21 +++++ .../mod.rs} | 20 ++++- src/ffmpeg.rs | 88 +++++++++++++++++++ src/files.rs | 1 + src/main.rs | 44 ++++++++-- src/processing.rs | 22 ----- src/renice.rs | 10 +++ src/transcode/job.rs | 33 +++++++ src/transcode/mod.rs | 1 + 10 files changed, 205 insertions(+), 36 deletions(-) create mode 100644 src/configuration/ffmpeg.rs rename src/{configuration.rs => configuration/mod.rs} (91%) create mode 100644 src/ffmpeg.rs delete mode 100644 src/processing.rs create mode 100644 src/renice.rs create mode 100644 src/transcode/job.rs create mode 100644 src/transcode/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 6ecd614..1a99af9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,3 @@ edition = "2021" simple-log = "1.6.0" toml = "0.7.3" serde = { version = "1.0.162", features = ["derive"] } -glob = "0.3.0" \ No newline at end of file diff --git a/src/configuration/ffmpeg.rs b/src/configuration/ffmpeg.rs new file mode 100644 index 0000000..8b4dde3 --- /dev/null +++ b/src/configuration/ffmpeg.rs @@ -0,0 +1,21 @@ +use crate::configuration::ConfigFFmpeg; +use crate::ffmpeg::FFmpegCommandOptions; + +impl ConfigFFmpeg { + pub fn build_command_options>(&self, input_path: S, output_path: S) -> FFmpegCommandOptions { + FFmpegCommandOptions { + input: input_path.into(), + output: output_path.into(), + + video_codec: self.output.video.codec.clone(), + video_bitrate: if self.output.video.bitrate > 0 { Some(self.output.video.bitrate) } else { None }, + video_crf: if self.output.video.crf > 0 { Some(self.output.video.crf) } else { None }, + + audio_codec: self.output.audio.codec.clone(), + audio_bitrate: if self.output.audio.bitrate > 0 { Some(self.output.audio.bitrate) } else { None }, + + threads: if self.process.threads > 0 { Some(self.process.threads) } else { None }, + niceness: if self.process.niceness > 0 { Some(self.process.niceness) } else { None }, + } + } +} \ No newline at end of file diff --git a/src/configuration.rs b/src/configuration/mod.rs similarity index 91% rename from src/configuration.rs rename to src/configuration/mod.rs index acb31cf..b1ea78b 100644 --- a/src/configuration.rs +++ b/src/configuration/mod.rs @@ -1,5 +1,10 @@ use std::error::Error; -use serde::{Serialize, Deserialize}; + +use serde::{Deserialize, Serialize}; + +use crate::ffmpeg::FFmpegCommandOptions; + +mod ffmpeg; #[allow(non_camel_case_types)] // this is allowed cuz we want to use snake case in the config file #[derive(Serialize, Deserialize, Debug)] @@ -72,6 +77,7 @@ pub struct ConfigFFmpeg { #[derive(Serialize, Deserialize, Debug)] pub struct Config { + pub debug: Option, pub files: ConfigFiles, pub ffmpeg: ConfigFFmpeg, } @@ -79,6 +85,7 @@ pub struct Config { impl Config { pub fn new() -> Config { Config { + debug: None, files: ConfigFiles { keep_file_structure: false, input_path: String::from("/data/input"), @@ -123,10 +130,15 @@ impl Config { match toml::from_str(&config_file) { Ok(config) => config, Err(e) => { - error!("Failed to parse config file: {}", e.message()); - error!("Please check your config file and try again."); - std::process::exit(1); + panic!("Failed to parse config file: {}", e.message()); } } } + + pub fn is_debug(&self) -> bool { + match self.debug { + Some(debug) => debug, + None => false, + } + } } \ No newline at end of file diff --git a/src/ffmpeg.rs b/src/ffmpeg.rs new file mode 100644 index 0000000..74adb63 --- /dev/null +++ b/src/ffmpeg.rs @@ -0,0 +1,88 @@ +use std::process::Command; + +pub struct FFmpegCommandOptions { + pub input: String, + pub output: String, + + pub video_codec: String, + pub video_bitrate: Option, + pub video_crf: Option, + + pub audio_codec: String, + pub audio_bitrate: Option, + + pub threads: Option, + pub niceness: Option +} + +impl FFmpegCommandOptions { + pub fn to_args(&self) -> Vec { + let mut args = Vec::new(); + args.push("-i".to_string()); + args.push(self.input.clone()); + args.push("-c:v".to_string()); + args.push(self.video_codec.clone()); + args.push("-c:a".to_string()); + args.push(self.audio_codec.clone()); + + if let Some(bitrate) = self.video_bitrate { + args.push("-b:v".to_string()); + args.push(bitrate.to_string()); + } + + if let Some(crf) = self.video_crf { + args.push("-crf".to_string()); + args.push(crf.to_string()); + } + + if let Some(bitrate) = self.audio_bitrate { + args.push("-b:a".to_string()); + args.push(bitrate.to_string()); + } + + if let Some(threads) = self.threads { + args.push("-threads".to_string()); + args.push(threads.to_string()); + } + + if let Some(niceness) = self.niceness { + args.push("-threads".to_string()); + args.push(niceness.to_string()); + } + + args.push(self.output.clone()); + + args + } +} + +pub fn build_command(program: &str, options: &FFmpegCommandOptions) -> Command { + let mut command = Command::new(program); + command.arg("-i").arg(&options.input); + command.arg("-c:v").arg(&options.video_codec); + command.arg("-c:a").arg(&options.audio_codec); + + if let Some(bitrate) = options.video_bitrate { + command.arg("-b:v").arg(bitrate.to_string()); + } + + if let Some(crf) = options.video_crf { + command.arg("-crf").arg(crf.to_string()); + } + + if let Some(bitrate) = options.audio_bitrate { + command.arg("-b:a").arg(bitrate.to_string()); + } + + if let Some(threads) = options.threads { + command.arg("-threads").arg(threads.to_string()); + } + + if let Some(niceness) = options.niceness { + command.arg("-threads").arg(niceness.to_string()); + } + + command.arg(&options.output); + + command +} \ No newline at end of file diff --git a/src/files.rs b/src/files.rs index 3ed35c2..a1bf1db 100644 --- a/src/files.rs +++ b/src/files.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use crate::configuration::Config; pub fn get_files>(path: S) -> Vec { let mut files = Vec::new(); diff --git a/src/main.rs b/src/main.rs index e6d9d1a..ac2c993 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,20 @@ extern crate simple_log; use std::env; +use std::path::PathBuf; use simple_log::LogConfigBuilder; +use renice::renice; +use transcode::job::TranscodeJob; mod configuration; mod files; -mod processing; +mod ffmpeg; +mod renice; +mod transcode; fn main() { - setup_logger(); let config = configuration::Config::from_file(&env::var("CONFIG").unwrap_or(String::from("./config.toml"))); + setup_logger(&config); debug!("Config: {:#?}", &config); let input_files = files::get_files(&config.files.input_path) @@ -22,22 +27,43 @@ fn main() { .collect::>(); info!("Found {} file(s) to be processed.", input_files.len()); - for files in input_files { - let operation = processing::TranscodeOperation::new(&config.ffmpeg, files); - operation.run(); + for file in input_files { + let mut output_path = file.clone(); + output_path.set_extension(&config.ffmpeg.output.format); + let output_path = PathBuf::from(&config.files.output_path).join(output_path.file_name().unwrap()); // TODO: This is a bit of a mess. + + let job = TranscodeJob::new( + file.to_str().unwrap(), + output_path.to_str().unwrap() + ); + + if job.check_if_exists() { + info!("Skipping file {} because it already exists.", job.output); + continue; + } + + info!("Processing file {}.", job.input); + let mut child = job.run(&config.ffmpeg).unwrap(); + if (config.ffmpeg.process.niceness > 0) { + renice(child.id(), config.ffmpeg.process.niceness) + .expect("Failed to renice process."); + } + child.wait().expect("Failed to wait for process."); + + // TODO: Cleanup } } -fn setup_logger() { - let config = LogConfigBuilder::builder() +fn setup_logger(config: &configuration::Config) { + let log_config = LogConfigBuilder::builder() .path("backups.log") .size(10 * 1000) .roll_count(10) .time_format("%Y-%m-%d %H:%M:%S") - .level("debug") + .level(if config.is_debug() { "debug" } else { "info" }) .output_file() .output_console() .build(); - simple_log::new(config).unwrap(); + simple_log::new(log_config).unwrap(); } diff --git a/src/processing.rs b/src/processing.rs deleted file mode 100644 index 22dd37c..0000000 --- a/src/processing.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::path::PathBuf; -use crate::configuration::ConfigFFmpeg; - -pub struct TranscodeOperation<'f> { - pub ffmpeg: &'f ConfigFFmpeg, - pub file: PathBuf, -} - -impl<'f> TranscodeOperation<'f> { - pub fn new(ffmpeg: &'f ConfigFFmpeg, file: PathBuf) -> TranscodeOperation { - TranscodeOperation { - ffmpeg, - file, - } - } - - pub fn run(&self) { - info!("Transcoding file: {:?}...", &self.file); - todo!(); - } -} - diff --git a/src/renice.rs b/src/renice.rs new file mode 100644 index 0000000..ece236b --- /dev/null +++ b/src/renice.rs @@ -0,0 +1,10 @@ +use std::error::Error; +use std::process::Command; + +pub fn renice(pid: u32, niceness: u8) -> Result<(), Box> { + let mut command = Command::new("renice"); + command.arg(niceness.to_string()); + command.arg(pid.to_string()); + command.spawn()?; + Ok(()) +} \ No newline at end of file diff --git a/src/transcode/job.rs b/src/transcode/job.rs new file mode 100644 index 0000000..5fb5177 --- /dev/null +++ b/src/transcode/job.rs @@ -0,0 +1,33 @@ +use std::path::PathBuf; +use std::process::{Child, Command}; +use crate::configuration::{Config, ConfigFFmpeg}; + +pub struct TranscodeJob { + pub input: String, + pub output: String +} + +impl TranscodeJob { + pub fn new>(input: S, output: S) -> TranscodeJob { + TranscodeJob { + input: input.into(), + output: output.into() + } + } + + pub fn build_command(&self, ffmpeg_config: &ConfigFFmpeg) -> Command { + let options = ffmpeg_config.build_command_options(&self.input, &self.output); + let mut command = Command::new("ffmpeg"); + command.args(options.to_args()); + command + } + + pub fn check_if_exists(&self) -> bool { + std::path::Path::new(&self.output).exists() + } + + pub fn run(&self, ffmpeg_config: &ConfigFFmpeg) -> Result { + let mut command = self.build_command(ffmpeg_config); + command.spawn() + } +} \ No newline at end of file diff --git a/src/transcode/mod.rs b/src/transcode/mod.rs new file mode 100644 index 0000000..80daa3e --- /dev/null +++ b/src/transcode/mod.rs @@ -0,0 +1 @@ +pub mod job;