feat: add initial functionality
This commit is contained in:
parent
b85c1e6220
commit
c43fd906cd
7 changed files with 278 additions and 0 deletions
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# IDE files
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# build files
|
||||||
|
/target
|
||||||
|
|
||||||
|
# runtime files
|
||||||
|
*.log
|
||||||
|
/files
|
||||||
|
|
||||||
|
# config
|
||||||
|
config.toml
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal 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
35
config.example.toml
Normal 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
132
src/configuration.rs
Normal 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
21
src/files.rs
Normal 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
43
src/main.rs
Normal 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
22
src/processing.rs
Normal 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!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue