272 lines
6.1 KiB
Rust
272 lines
6.1 KiB
Rust
use std::{
|
|
collections::{hash_map::Entry, HashMap},
|
|
fs,
|
|
path::PathBuf,
|
|
process::exit,
|
|
str::FromStr,
|
|
sync::{Arc, Mutex},
|
|
thread::scope,
|
|
};
|
|
|
|
use relative_file::traits::RelativeFileTrait;
|
|
|
|
use crate::{
|
|
args::CLIArgs,
|
|
types::AudioFile,
|
|
utils::{
|
|
formats::get_format_handler,
|
|
scan_for_music,
|
|
transcoder::{
|
|
presets::{print_presets, transcode_preset_or_config},
|
|
transcode,
|
|
types::TranscodeConfig,
|
|
},
|
|
},
|
|
};
|
|
|
|
#[derive(Debug, Clone, clap::Args)]
|
|
pub struct CopyCommandArgs {
|
|
pub source: String,
|
|
pub dest: String,
|
|
#[clap(long)]
|
|
pub preset: Option<String>,
|
|
#[clap(long)]
|
|
pub transcode_config: Option<String>,
|
|
#[clap(long)]
|
|
pub threads: Option<u32>,
|
|
#[clap(long)]
|
|
pub no_skip_existing: bool,
|
|
#[clap(long)]
|
|
pub skip_same_extension: bool,
|
|
#[clap(long)]
|
|
pub single_directory: bool,
|
|
}
|
|
|
|
pub fn copy_command(
|
|
_args: CLIArgs,
|
|
copy_args: &CopyCommandArgs,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
if copy_args.transcode_config.is_none() && copy_args.preset.is_none() {
|
|
panic!("Please provide Transcode Preset/Config");
|
|
}
|
|
|
|
if let Some(preset) = ©_args.preset {
|
|
if preset == "list" {
|
|
print_presets();
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
println!("Scanning For Music");
|
|
let mut files = scan_for_music(©_args.source)?;
|
|
|
|
println!("Analysing Files");
|
|
for file in files.iter_mut() {
|
|
println!("Analysing: {:?}", file.join_path_from_source());
|
|
|
|
let mut handler = get_format_handler(file)?;
|
|
|
|
file.info = handler.get_audio_file_info(true)?;
|
|
}
|
|
|
|
if copy_args.single_directory {
|
|
println!("Checking for Duplicates");
|
|
let mut seen: HashMap<String, bool> = HashMap::new();
|
|
let mut dupes: Vec<String> = Vec::new();
|
|
|
|
for file in files.iter() {
|
|
let filename = file.join_filename();
|
|
|
|
if let Entry::Vacant(entry) = seen.entry(filename.clone()) {
|
|
entry.insert(true);
|
|
} else {
|
|
dupes.push(filename);
|
|
}
|
|
}
|
|
|
|
if !dupes.is_empty() {
|
|
panic!("Duplicates Found: {}", dupes.join(","))
|
|
}
|
|
}
|
|
|
|
if !copy_args.single_directory {
|
|
println!("Creating Directories");
|
|
|
|
let mut directories: Vec<String> = Vec::new();
|
|
for file in files.iter() {
|
|
let file_directory = file.path_from_source().to_string_lossy().to_string();
|
|
|
|
if !directories.contains(&file_directory) {
|
|
directories.push(file_directory.clone());
|
|
}
|
|
}
|
|
for directory in directories.iter() {
|
|
fs::create_dir_all(
|
|
PathBuf::from_str(copy_args.dest.as_str())
|
|
.expect("invalid destination")
|
|
.join(directory),
|
|
)?;
|
|
}
|
|
}
|
|
|
|
if copy_args
|
|
.preset
|
|
.as_ref()
|
|
.unwrap_or(&"".to_string())
|
|
== "copy"
|
|
{
|
|
println!("Copying Files Into Dest");
|
|
copy_files(&files, copy_args)?;
|
|
} else {
|
|
println!("Transcoding Files");
|
|
transcode_files(&files, copy_args)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn copy_file(
|
|
file: &relative_file::RelativeFile,
|
|
copy_args: &CopyCommandArgs,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let from_path = file.join_path_to();
|
|
|
|
let to_path_dest = PathBuf::from_str(copy_args.dest.as_str()).expect("invalid destination");
|
|
let to_path = match copy_args.single_directory {
|
|
true => to_path_dest.join(file.join_filename()),
|
|
false => to_path_dest
|
|
.join(file.path_from_source())
|
|
.join(file.join_filename()),
|
|
};
|
|
|
|
let to_path_string = to_path.to_string_lossy();
|
|
|
|
if !copy_args.no_skip_existing && to_path.exists() {
|
|
println!(
|
|
"Skipping {} as already exists in destination",
|
|
to_path_string
|
|
);
|
|
} else {
|
|
println!("Copying {:?} to {}", from_path, to_path_string);
|
|
fs::copy(from_path, to_path)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn copy_files(
|
|
files: &[AudioFile],
|
|
copy_args: &CopyCommandArgs,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
for file in files.iter() {
|
|
copy_file(file.as_relative_file(), copy_args)?;
|
|
|
|
if !file.extra_files.is_empty() {
|
|
for extra_file in file.extra_files.iter() {
|
|
copy_file(extra_file, copy_args)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn transcode_file(
|
|
file: &AudioFile,
|
|
copy_args: &CopyCommandArgs,
|
|
config: &TranscodeConfig,
|
|
is_threaded: bool,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let new_filename_full: String = match config.file_extension.clone() {
|
|
Some(ext) => format!("{}.{}", file.filename(), ext),
|
|
None => {
|
|
panic!("file_extension is required in custom transcode configs");
|
|
}
|
|
};
|
|
|
|
if copy_args.skip_same_extension && config.file_extension.as_ref().unwrap() == &file.extension().unwrap() {
|
|
return copy_file(file.as_relative_file(), copy_args);
|
|
}
|
|
|
|
let to_path_dest = PathBuf::from_str(copy_args.dest.as_str()).expect("invalid destination");
|
|
let to_path = match copy_args.single_directory {
|
|
true => to_path_dest.join(new_filename_full),
|
|
false => to_path_dest
|
|
.join(file.path_from_source())
|
|
.join(new_filename_full),
|
|
};
|
|
|
|
let to_path_string = to_path.to_string_lossy();
|
|
|
|
if !file.extra_files.is_empty() {
|
|
for extra_file in file.extra_files.iter() {
|
|
copy_file(extra_file, copy_args)?;
|
|
}
|
|
}
|
|
|
|
if !copy_args.no_skip_existing && to_path.exists() {
|
|
println!(
|
|
"Skipping transcode for {} as file already exists",
|
|
to_path_string
|
|
);
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
println!("Transcoding {}", to_path_string);
|
|
|
|
transcode(file.to_owned(), to_path_string.to_string(), config, None)?;
|
|
|
|
if is_threaded {
|
|
println!("Finished Transcoding {}", to_path_string);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn transcode_files(
|
|
files: &[AudioFile],
|
|
copy_args: &CopyCommandArgs,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let transcode_config = transcode_preset_or_config(
|
|
copy_args.preset.as_ref(),
|
|
copy_args.transcode_config.as_ref(),
|
|
)
|
|
.expect("transcode config error");
|
|
|
|
let threads = copy_args.threads.unwrap_or(2);
|
|
|
|
if threads > 1 {
|
|
let files_copy = files.to_vec();
|
|
|
|
let jobs: Arc<Mutex<Vec<AudioFile>>> = Arc::new(Mutex::new(files_copy));
|
|
|
|
let copy_args = Arc::new(copy_args);
|
|
let transcode_config = Arc::new(transcode_config);
|
|
|
|
scope(|s| {
|
|
for _ in 0..threads {
|
|
s.spawn(|| loop {
|
|
let mut jobs = jobs.lock().unwrap();
|
|
let job = jobs.pop();
|
|
drop(jobs);
|
|
|
|
if let Some(job) = job {
|
|
let result = transcode_file(&job, ©_args, &transcode_config, true);
|
|
if let Err(err) = result {
|
|
panic!("Error Transcoding: {}", err)
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
for file in files.iter() {
|
|
transcode_file(file, copy_args, &transcode_config, false)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|