feats: added renice module added jobs added ffmpeg command builders.

This commit is contained in:
Didier Slof 2023-05-06 23:21:12 +02:00
parent c43fd906cd
commit a905cdd4b9
Signed by: didier
GPG key ID: 01E71F18AA4398E5
10 changed files with 205 additions and 36 deletions

View file

@ -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"

View file

@ -0,0 +1,21 @@
use crate::configuration::ConfigFFmpeg;
use crate::ffmpeg::FFmpegCommandOptions;
impl ConfigFFmpeg {
pub fn build_command_options<S: Into<String>>(&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 },
}
}
}

View file

@ -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<bool>,
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,
}
}
}

88
src/ffmpeg.rs Normal file
View file

@ -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<u32>,
pub video_crf: Option<u8>,
pub audio_codec: String,
pub audio_bitrate: Option<u32>,
pub threads: Option<u8>,
pub niceness: Option<u8>
}
impl FFmpegCommandOptions {
pub fn to_args(&self) -> Vec<String> {
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
}

View file

@ -1,4 +1,5 @@
use std::path::PathBuf;
use crate::configuration::Config;
pub fn get_files<S: Into<String>>(path: S) -> Vec<PathBuf> {
let mut files = Vec::new();

View file

@ -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::<Vec<_>>();
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();
}

View file

@ -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!();
}
}

10
src/renice.rs Normal file
View file

@ -0,0 +1,10 @@
use std::error::Error;
use std::process::Command;
pub fn renice(pid: u32, niceness: u8) -> Result<(), Box<dyn Error>> {
let mut command = Command::new("renice");
command.arg(niceness.to_string());
command.arg(pid.to_string());
command.spawn()?;
Ok(())
}

33
src/transcode/job.rs Normal file
View file

@ -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<S: Into<String>>(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<Child, std::io::Error> {
let mut command = self.build_command(ffmpeg_config);
command.spawn()
}
}

1
src/transcode/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod job;