1
0
Fork 0
musicutil/src/commands/copy.rs

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) = &copy_args.preset {
if preset == "list" {
print_presets();
exit(0);
}
}
println!("Scanning For Music");
let mut files = scan_for_music(&copy_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, &copy_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(())
}