feat: add initial functionality

This commit is contained in:
Didier Slof 2023-05-06 21:38:34 +02:00
parent b85c1e6220
commit c43fd906cd
Signed by: didier
GPG key ID: 01E71F18AA4398E5
7 changed files with 278 additions and 0 deletions

13
.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
# IDE files
*.iml
.idea/
# build files
/target
# runtime files
*.log
/files
# config
config.toml

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "bumblebee"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
simple-log = "1.6.0"
toml = "0.7.3"
serde = { version = "1.0.162", features = ["derive"] }
glob = "0.3.0"

35
config.example.toml Normal file
View file

@ -0,0 +1,35 @@
[files]
input_path = "/data/input"
output_path = "/data/output"
include = [ 'mp4', 'avi', 'mkv' ] # file extensions to include
keep_file_structure = true # e.g. /data/input/foo/bar.mp4 -> /data/output/foo/bar.webm
[files.cleanup]
enabled = true
original_cleanup_behavior = "delete" # delete, archive or keep
[files.cleanup.archive]
path = "/data/archive"
keep_file_structure = true # e.g. /data/input/foo/bar.mp4 -> /data/archive/foo/bar.mp4
[files.cleanup.delete]
remove_empty_directories = true # if folder is empty after deleting file, delete folder
[ffmpeg]
path = "/usr/bin/ffmpeg" # path to ffmpeg executable
[ffmpeg.process]
niceness = 10 # 0 = highest priority
threads = 4 # 0 = auto
[ffmpeg.output]
format = "webm" # webm, mp4, mkv, etc.
[ffmpeg.output.video]
codec = "libsvtav1"
bitrate = 0 # 0 = auto
crf = 30 # 0 = lossless
[ffmpeg.output.audio]
codec = "libopus"
bitrate = 0 # 0 = auto

132
src/configuration.rs Normal file
View file

@ -0,0 +1,132 @@
use std::error::Error;
use serde::{Serialize, Deserialize};
#[allow(non_camel_case_types)] // this is allowed cuz we want to use snake case in the config file
#[derive(Serialize, Deserialize, Debug)]
pub enum ConfigFilesCleanupOriginalBehavior {
delete,
archive,
keep,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFilesCleanupArchive {
pub path: String,
pub keep_file_structure: bool,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFilesCleanupDelete {
pub remove_empty_directories: bool,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFilesCleanup {
pub enabled: bool,
pub original_cleanup_behavior: ConfigFilesCleanupOriginalBehavior,
pub archive: ConfigFilesCleanupArchive,
pub delete: ConfigFilesCleanupDelete,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFiles {
pub keep_file_structure: bool,
pub input_path: String,
pub output_path: String,
pub include: Vec<String>,
pub cleanup: ConfigFilesCleanup,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFFmpegProcess {
pub threads: u8,
pub niceness: u8,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFFmpegOutputVideo {
pub codec: String,
pub bitrate: u32,
pub crf: u8,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFFmpegOutputAudio {
pub codec: String,
pub bitrate: u32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFFmpegOutput {
pub format: String,
pub video: ConfigFFmpegOutputVideo,
pub audio: ConfigFFmpegOutputAudio,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigFFmpeg {
pub path: String,
pub process: ConfigFFmpegProcess,
pub output: ConfigFFmpegOutput,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub files: ConfigFiles,
pub ffmpeg: ConfigFFmpeg,
}
impl Config {
pub fn new() -> Config {
Config {
files: ConfigFiles {
keep_file_structure: false,
input_path: String::from("/data/input"),
output_path: String::from("/data/output"),
include: Vec::new(),
cleanup: ConfigFilesCleanup {
enabled: false,
original_cleanup_behavior: ConfigFilesCleanupOriginalBehavior::delete,
archive: ConfigFilesCleanupArchive {
path: String::from("/data/archive"),
keep_file_structure: false,
},
delete: ConfigFilesCleanupDelete {
remove_empty_directories: false
},
},
},
ffmpeg: ConfigFFmpeg {
path: String::from("/usr/bin/ffmpeg"),
process: ConfigFFmpegProcess {
threads: 0,
niceness: 0,
},
output: ConfigFFmpegOutput {
format: String::from("webm"),
video: ConfigFFmpegOutputVideo {
codec: String::from("libsvtav1"),
bitrate: 0,
crf: 0,
},
audio: ConfigFFmpegOutputAudio {
codec: String::from("libopus"),
bitrate: 0,
},
},
},
}
}
pub fn from_file(path: &str) -> Config {
let config_file = std::fs::read_to_string(path).expect("Failed to read config file");
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);
}
}
}
}

21
src/files.rs Normal file
View file

@ -0,0 +1,21 @@
use std::path::PathBuf;
pub fn get_files<S: Into<String>>(path: S) -> Vec<PathBuf> {
let mut files = Vec::new();
let mut dirs = Vec::new();
dirs.push(PathBuf::from(path.into()));
while let Some(dir) = dirs.pop() {
for entry in std::fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
dirs.push(path);
} else {
files.push(path);
}
}
}
files
}

43
src/main.rs Normal file
View file

@ -0,0 +1,43 @@
#[macro_use]
extern crate simple_log;
use std::env;
use simple_log::LogConfigBuilder;
mod configuration;
mod files;
mod processing;
fn main() {
setup_logger();
let config = configuration::Config::from_file(&env::var("CONFIG").unwrap_or(String::from("./config.toml")));
debug!("Config: {:#?}", &config);
let input_files = files::get_files(&config.files.input_path)
.into_iter()
.filter(|path| {
let path = path.to_str().unwrap();
config.files.include.iter().any(|include| path.contains(include))
})
.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();
}
}
fn setup_logger() {
let config = LogConfigBuilder::builder()
.path("backups.log")
.size(10 * 1000)
.roll_count(10)
.time_format("%Y-%m-%d %H:%M:%S")
.level("debug")
.output_file()
.output_console()
.build();
simple_log::new(config).unwrap();
}

22
src/processing.rs Normal file
View file

@ -0,0 +1,22 @@
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!();
}
}