feats: added renice module added jobs added ffmpeg command builders.
This commit is contained in:
parent
c43fd906cd
commit
a905cdd4b9
10 changed files with 205 additions and 36 deletions
|
@ -9,4 +9,3 @@ edition = "2021"
|
||||||
simple-log = "1.6.0"
|
simple-log = "1.6.0"
|
||||||
toml = "0.7.3"
|
toml = "0.7.3"
|
||||||
serde = { version = "1.0.162", features = ["derive"] }
|
serde = { version = "1.0.162", features = ["derive"] }
|
||||||
glob = "0.3.0"
|
|
21
src/configuration/ffmpeg.rs
Normal file
21
src/configuration/ffmpeg.rs
Normal 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 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
use std::error::Error;
|
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
|
#[allow(non_camel_case_types)] // this is allowed cuz we want to use snake case in the config file
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -72,6 +77,7 @@ pub struct ConfigFFmpeg {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
pub debug: Option<bool>,
|
||||||
pub files: ConfigFiles,
|
pub files: ConfigFiles,
|
||||||
pub ffmpeg: ConfigFFmpeg,
|
pub ffmpeg: ConfigFFmpeg,
|
||||||
}
|
}
|
||||||
|
@ -79,6 +85,7 @@ pub struct Config {
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn new() -> Config {
|
pub fn new() -> Config {
|
||||||
Config {
|
Config {
|
||||||
|
debug: None,
|
||||||
files: ConfigFiles {
|
files: ConfigFiles {
|
||||||
keep_file_structure: false,
|
keep_file_structure: false,
|
||||||
input_path: String::from("/data/input"),
|
input_path: String::from("/data/input"),
|
||||||
|
@ -123,10 +130,15 @@ impl Config {
|
||||||
match toml::from_str(&config_file) {
|
match toml::from_str(&config_file) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to parse config file: {}", e.message());
|
panic!("Failed to parse config file: {}", e.message());
|
||||||
error!("Please check your config file and try again.");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_debug(&self) -> bool {
|
||||||
|
match self.debug {
|
||||||
|
Some(debug) => debug,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
88
src/ffmpeg.rs
Normal file
88
src/ffmpeg.rs
Normal 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
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use crate::configuration::Config;
|
||||||
|
|
||||||
pub fn get_files<S: Into<String>>(path: S) -> Vec<PathBuf> {
|
pub fn get_files<S: Into<String>>(path: S) -> Vec<PathBuf> {
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
|
|
44
src/main.rs
44
src/main.rs
|
@ -2,15 +2,20 @@
|
||||||
extern crate simple_log;
|
extern crate simple_log;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
use simple_log::LogConfigBuilder;
|
use simple_log::LogConfigBuilder;
|
||||||
|
use renice::renice;
|
||||||
|
use transcode::job::TranscodeJob;
|
||||||
|
|
||||||
mod configuration;
|
mod configuration;
|
||||||
mod files;
|
mod files;
|
||||||
mod processing;
|
mod ffmpeg;
|
||||||
|
mod renice;
|
||||||
|
mod transcode;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
setup_logger();
|
|
||||||
let config = configuration::Config::from_file(&env::var("CONFIG").unwrap_or(String::from("./config.toml")));
|
let config = configuration::Config::from_file(&env::var("CONFIG").unwrap_or(String::from("./config.toml")));
|
||||||
|
setup_logger(&config);
|
||||||
debug!("Config: {:#?}", &config);
|
debug!("Config: {:#?}", &config);
|
||||||
|
|
||||||
let input_files = files::get_files(&config.files.input_path)
|
let input_files = files::get_files(&config.files.input_path)
|
||||||
|
@ -22,22 +27,43 @@ fn main() {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
info!("Found {} file(s) to be processed.", input_files.len());
|
info!("Found {} file(s) to be processed.", input_files.len());
|
||||||
|
|
||||||
for files in input_files {
|
for file in input_files {
|
||||||
let operation = processing::TranscodeOperation::new(&config.ffmpeg, files);
|
let mut output_path = file.clone();
|
||||||
operation.run();
|
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() {
|
fn setup_logger(config: &configuration::Config) {
|
||||||
let config = LogConfigBuilder::builder()
|
let log_config = LogConfigBuilder::builder()
|
||||||
.path("backups.log")
|
.path("backups.log")
|
||||||
.size(10 * 1000)
|
.size(10 * 1000)
|
||||||
.roll_count(10)
|
.roll_count(10)
|
||||||
.time_format("%Y-%m-%d %H:%M:%S")
|
.time_format("%Y-%m-%d %H:%M:%S")
|
||||||
.level("debug")
|
.level(if config.is_debug() { "debug" } else { "info" })
|
||||||
.output_file()
|
.output_file()
|
||||||
.output_console()
|
.output_console()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
simple_log::new(config).unwrap();
|
simple_log::new(log_config).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
10
src/renice.rs
Normal 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
33
src/transcode/job.rs
Normal 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
1
src/transcode/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod job;
|
Loading…
Reference in a new issue