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

274 lines
6.4 KiB
Rust

use std::sync::Arc;
use std::sync::Mutex;
use std::thread::scope;
use crate::args::CLIArgs;
use crate::types::AudioFileInfo;
use crate::types::AudioFile;
use crate::utils::formats::get_format_handler;
use crate::utils::scan_for_music;
use ascii_reduce::reduce;
use relative_file::RelativeFile;
use relative_file::traits::RelativeFileTrait;
#[cfg(feature = "replaygain")]
use replaygain::analyze_replaygain_track;
#[derive(Debug, Clone, clap::Args)]
pub struct ProcessCommandArgs {
pub source: String,
#[clap(long)]
pub dry_run: bool,
#[cfg(feature = "replaygain")]
#[clap(long)]
pub skip_replaygain: bool,
#[cfg(feature = "replaygain")]
#[clap(long)]
pub force_replaygain: bool,
#[cfg(feature = "replaygain")]
#[clap(long)]
pub replaygain_threads: Option<u32>,
#[clap(long)]
pub analyze_threads: Option<u32>,
}
fn rename_file(process_args: &ProcessCommandArgs, file: &mut AudioFile) {
let title = &file.info.tags.title;
let artist = &file.info.tags.artist;
let replace_char = "_".to_string();
// Step 1: Remove Newlines
let title = title.replace('\n', "");
let artist = artist.replace('\n', "");
// Step 2: Strip ASCII
let title = reduce(title);
let artist = reduce(artist);
// Step 3: Remove File Seperators
let title = title.replace('\\', &replace_char);
let title = title.replace('/', &replace_char);
let artist = artist.replace('\\', &replace_char);
let artist = artist.replace('/', &replace_char);
// Step 4: Join Filename
let filename = match file.folder_meta.is_album {
true => {
format!(
"{}. {} - {}",
file.info.tags.track_number.unwrap(),
title,
artist
)
}
false => {
format!("{} - {}", artist, title)
}
};
if filename == file.filename() {
return;
}
// Step 5: Make new File with filename set
let mut new_file = file.clone();
new_file.change_filename(filename.clone());
let extension = &file.extension().unwrap();
// Step 6: Rename File
println!(
"Renaming File from {}.{extension} to {}.{extension}",
file.filename(), filename
);
if !process_args.dry_run {
if std::path::Path::new(&new_file.join_path_to()).exists() {
panic!(
"Refusing to rename {} to {}, please retag to be distinct",
&file.join_filename(),
&new_file.join_filename()
);
} else {
let err = std::fs::rename(&file.join_path_to(), &new_file.join_path_to());
if err.is_err() {
panic!("Could not rename {:?}", err)
}
}
}
if !file.extra_files.is_empty() {
let mut new_extra_files: Vec<RelativeFile> = Vec::new();
for extra_file in file.extra_files.iter() {
let mut new_extra_file = extra_file.clone();
new_extra_file.change_filename(filename.clone());
let extra_extension = &extra_file.extension().unwrap();
println!(
"Renaming Extra File from {}.{extra_extension} to {}.{extra_extension}",
file.filename(), filename
);
if !process_args.dry_run {
let err =
std::fs::rename(&extra_file.join_path_to(), &new_extra_file.join_path_to());
if err.is_err() {
panic!("Could not rename {:?}", err)
}
new_extra_files.push(new_extra_file);
}
}
if !process_args.dry_run {
file.extra_files.clear();
file.extra_files.extend(new_extra_files);
}
}
if !process_args.dry_run {
file.change_filename(filename);
}
}
#[cfg(feature = "replaygain")]
pub fn add_replaygain_tags(file: &AudioFile, force: bool) -> Result<(), Box<dyn std::error::Error>> {
if !file.info.supports_replaygain {
println!(
"Skipping replaygain for {:?}, not supported",
file.join_path_from_source()
);
return Ok(());
}
if file.info.contains_replaygain && !force {
println!(
"Skipping replaygain for {:?}, contains already",
file.join_path_from_source()
);
return Ok(());
}
println!(
"Analyzing replaygain for {:?}",
file.join_path_from_source()
);
let replaygain_data = analyze_replaygain_track(file.join_path_to(), Some(crate::meta::FFMPEG))
.expect("could not analyze replaygain");
let mut handler = get_format_handler(file)?;
handler.set_replaygain_data(replaygain_data)?;
handler.save_changes()?;
println!(
"Applied replaygain tags for {:?}",
file.join_path_from_source()
);
Ok(())
}
fn analyze_file(file: &AudioFile) -> Result<AudioFileInfo, Box<dyn std::error::Error>> {
println!("Analysing: {:?}", file.join_path_from_source());
let mut handler = get_format_handler(file)?;
handler.get_audio_file_info(false)
}
pub fn process_command(
_args: CLIArgs,
process_args: &ProcessCommandArgs,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Scanning For Music");
let mut files = scan_for_music(&process_args.source)?;
println!("Analysing Files");
let threads = process_args.analyze_threads.unwrap_or(0);
if threads <= 1 {
for file in files.iter_mut() {
let info = analyze_file(file)?;
file.info = info;
}
} else {
let jobs: Arc<Mutex<Vec<AudioFile>>> = Arc::new(Mutex::new(files.clone()));
let new_files: Arc<Mutex<Vec<AudioFile>>> = Arc::new(Mutex::new(Vec::new()));
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 = analyze_file(&job);
if let Err(err) = result {
panic!("Error analyzing: {}", err)
} else {
let mut file = job;
file.info = result.unwrap();
new_files.lock().unwrap().push(file);
}
} else {
break;
}
});
}
});
let new_files = new_files.lock().unwrap();
files = Vec::new();
for file in new_files.iter() {
files.push(file.clone())
}
}
println!("Renaming Files");
for file in files.iter_mut() {
rename_file(process_args, file);
}
#[cfg(feature = "replaygain")]
if !process_args.skip_replaygain && !process_args.dry_run {
println!("Adding ReplayGain Tags to Files");
let threads = process_args.replaygain_threads.unwrap_or(2);
if threads <= 1 {
for file in files.iter_mut() {
add_replaygain_tags(file, process_args.force_replaygain)?;
}
return Ok(());
} else {
let jobs: Arc<Mutex<Vec<AudioFile>>> = Arc::new(Mutex::new(files));
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 = add_replaygain_tags(&job, process_args.force_replaygain);
if let Err(err) = result {
panic!("Error doing replaygain: {}", err)
}
} else {
break;
}
});
}
});
}
}
Ok(())
}