move relative file handing code into its own module, seperate from new AudioFile

This commit is contained in:
chaos 2023-11-08 16:28:02 +00:00
parent abe16ae53b
commit 4603fc33e3
No known key found for this signature in database
17 changed files with 276 additions and 110 deletions

5
Cargo.lock generated
View file

@ -683,6 +683,7 @@ dependencies = [
"lazy_static",
"metaflac",
"notify",
"relative_file",
"replaygain",
"serde",
"serde_json",
@ -877,6 +878,10 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "relative_file"
version = "0.1.0"
[[package]]
name = "replaygain"
version = "0.1.0"

View file

@ -5,13 +5,19 @@ edition = "2021"
[workspace]
members = [
"modules/taglib",
"modules/ascii_reduce",
"modules/ffprobe",
"modules/relative_file",
"modules/replaygain",
"modules/taglib"
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
relative_file = { path = "./modules/relative_file" }
# for decode/encoding yaml/json for transcode config & ffprobe output
serde = { version = "1.0.0", features = ["derive"] }
serde_yaml = "0.9"

View file

@ -0,0 +1,4 @@
[package]
name = "relative_file"
version = "0.1.0"
edition = "2021"

View file

@ -0,0 +1,141 @@
use std::path::PathBuf;
pub mod traits {
use std::path::PathBuf;
pub trait RelativeFileTrait {
fn filename(&self) -> String;
fn change_filename(&mut self, filename: String);
fn extension(&self) -> Option<String>;
fn path_to(&self) -> PathBuf;
fn path_from_source(&self) -> PathBuf;
fn join_filename(&self) -> String;
fn join_path_to(&self) -> PathBuf;
fn join_path_from_source(&self) -> PathBuf;
}
}
#[derive(Debug, Clone)]
pub struct RelativeFile {
filename: String,
extension: Option<String>,
// relative path to file's folder
path_to: PathBuf,
// path to folder from source
path_from_source: PathBuf,
}
impl RelativeFile {
pub fn from_path(source_dir: String, full_path: PathBuf) -> crate::RelativeFile {
let full_file_path = PathBuf::from(&source_dir).join(full_path);
let filename_without_extension = full_file_path
.file_stem()
.expect("filename is invalid")
.to_string_lossy()
.to_string();
let extension = full_file_path.extension();
let extension = if let Some(extension) = extension {
Some(extension.to_string_lossy().to_string())
} else {
None
};
let path_from_src = full_file_path
.strip_prefix(&source_dir)
.expect("couldn't get path relative to source");
let mut folder_path_from_src = path_from_src.to_path_buf();
folder_path_from_src.pop();
let path_to = PathBuf::from(&source_dir).join(&folder_path_from_src);
RelativeFile {
filename: filename_without_extension,
extension,
path_from_source: folder_path_from_src,
path_to,
}
}
}
impl traits::RelativeFileTrait for RelativeFile {
fn join_filename(&self) -> String {
return match &self.extension {
Some(extension) => format!("{}.{}", self.filename, extension),
None => self.filename.clone(),
};
}
fn join_path_to(&self) -> PathBuf {
PathBuf::from(&self.path_to).join(self.join_filename())
}
fn join_path_from_source(&self) -> PathBuf {
PathBuf::from(&self.path_from_source).join(self.join_filename())
}
fn path_to(&self) -> PathBuf {
self.path_to.clone()
}
fn path_from_source(&self) -> PathBuf {
self.path_from_source.clone()
}
fn filename(&self) -> String {
self.filename.clone()
}
fn extension(&self) -> Option<String> {
self.extension.clone()
}
fn change_filename(&mut self, filename: String) {
self.filename = filename
}
}
#[cfg(test)]
mod tests {
use crate::traits::RelativeFileTrait;
use std::path::PathBuf;
#[test]
fn filename() {
let file =
crate::RelativeFile::from_path("source/".to_string(), PathBuf::from("path/file.txt"));
assert_eq!(file.join_filename(), "file.txt")
}
#[test]
fn change_filename() {
let mut file =
crate::RelativeFile::from_path("source/".to_string(), PathBuf::from("path/file.txt"));
file.change_filename("new_file".to_string());
assert_eq!(file.join_filename(), "new_file.txt")
}
#[test]
fn missing_extension() {
let file =
crate::RelativeFile::from_path("source/".to_string(), PathBuf::from("path/file"));
assert_eq!(file.join_filename(), "file");
assert_eq!(file.extension(), None);
}
#[test]
fn paths() {
let file = crate::RelativeFile::from_path(
"source/".to_string(),
PathBuf::from("path/to/file.txt"),
);
assert_eq!(file.path_to(), PathBuf::from("source/path/to"));
assert_eq!(file.path_from_source(), PathBuf::from("path/to"));
assert_eq!(file.extension(), Some("txt".to_string()));
}
}

View file

@ -8,9 +8,11 @@ use std::{
thread::scope,
};
use relative_file::traits::RelativeFileTrait;
use crate::{
args::CLIArgs,
types::File,
types::AudioFile,
utils::{
formats::get_format_handler,
scan_for_music,
@ -90,7 +92,7 @@ pub fn copy_command(
let mut directories: Vec<String> = Vec::new();
for file in files.iter() {
let file_directory = file.path_from_source.to_string_lossy().to_string();
let file_directory = file.path_from_source().to_string_lossy().to_string();
if !directories.contains(&file_directory) {
directories.push(file_directory.clone());
@ -121,14 +123,14 @@ pub fn copy_command(
Ok(())
}
fn copy_file(file: &File, copy_args: &CopyCommandArgs) -> Result<(), Box<dyn std::error::Error>> {
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.clone())
.join(file.path_from_source())
.join(file.join_filename()),
};
@ -147,11 +149,11 @@ fn copy_file(file: &File, copy_args: &CopyCommandArgs) -> Result<(), Box<dyn std
}
fn copy_files(
files: &[File],
files: &[AudioFile],
copy_args: &CopyCommandArgs,
) -> Result<(), Box<dyn std::error::Error>> {
for file in files.iter() {
copy_file(file, copy_args)?;
copy_file(file.as_relative_file(), copy_args)?;
if !file.extra_files.is_empty() {
for extra_file in file.extra_files.iter() {
@ -164,13 +166,13 @@ fn copy_files(
}
fn transcode_file(
file: &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),
Some(ext) => format!("{}.{}", file.filename(), ext),
None => {
panic!("file_extension is required in custom transcode configs");
}
@ -180,7 +182,7 @@ fn transcode_file(
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.clone())
.join(file.path_from_source())
.join(new_filename_full),
};
@ -213,7 +215,7 @@ fn transcode_file(
}
fn transcode_files(
files: &[File],
files: &[AudioFile],
copy_args: &CopyCommandArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let transcode_config = transcode_preset_or_config(
@ -227,7 +229,7 @@ fn transcode_files(
if threads > 1 {
let files_copy = files.to_vec();
let jobs: Arc<Mutex<Vec<File>>> = Arc::new(Mutex::new(files_copy));
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);

View file

@ -1,5 +1,5 @@
use crate::args::CLIArgs;
use crate::types::File;
use crate::types::AudioFile;
use crate::utils::formats::get_format_handler;
use crate::utils::scan_for_music;
use std::cmp::Ordering;
@ -7,6 +7,7 @@ use std::ffi::OsStr;
use std::io::Write;
use html_escape::encode_text;
use relative_file::traits::RelativeFileTrait;
use urlencoding::encode as url_encode;
#[derive(Debug, Clone, clap::Args)]
@ -21,7 +22,7 @@ pub struct GenHTMLCommandArgs {
pub link_base: Option<String>,
}
fn table_for_files(files: Vec<File>, includes_path: bool, link_base: &Option<String>) -> String {
fn table_for_files(files: Vec<AudioFile>, includes_path: bool, link_base: &Option<String>) -> String {
let mut html_content = String::new();
let mut path_head = String::new();
@ -74,7 +75,7 @@ fn table_for_files(files: Vec<File>, includes_path: bool, link_base: &Option<Str
let mut path_data = String::new();
if includes_path {
let file_directory = file.path_from_source.to_string_lossy().to_string();
let file_directory = file.path_from_source().to_string_lossy().to_string();
path_data.push_str(format!("<td>{}</td>", encode_text(&file_directory)).as_str());
}
@ -154,8 +155,8 @@ pub fn genhtml_command(
}
files.sort_by(|a, b| -> Ordering {
if a.path_from_source != b.path_from_source {
return a.path_from_source.cmp(&b.path_from_source);
if a.path_from_source() != b.path_from_source() {
return a.path_from_source().cmp(&b.path_from_source());
}
let a_tags = &a.info.tags;

View file

@ -4,11 +4,13 @@ use std::thread::scope;
use crate::args::CLIArgs;
use crate::types::AudioFileInfo;
use crate::types::File;
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;
@ -30,7 +32,7 @@ pub struct ProcessCommandArgs {
pub analyze_threads: Option<u32>,
}
fn rename_file(process_args: &ProcessCommandArgs, file: &mut File) {
fn rename_file(process_args: &ProcessCommandArgs, file: &mut AudioFile) {
let title = &file.info.tags.title;
let artist = &file.info.tags.artist;
@ -65,20 +67,20 @@ fn rename_file(process_args: &ProcessCommandArgs, file: &mut File) {
}
};
if filename == file.filename {
if filename == file.filename() {
return;
}
// Step 5: Make new File with filename set
let mut new_file = file.clone();
new_file.filename = filename.clone();
new_file.change_filename(filename.clone());
let extension = &file.extension;
let extension = &file.extension().unwrap();
// Step 6: Rename File
println!(
"Renaming File from {}.{extension} to {}.{extension}",
file.filename, filename
file.filename(), filename
);
if !process_args.dry_run {
if std::path::Path::new(&new_file.join_path_to()).exists() {
@ -96,17 +98,18 @@ fn rename_file(process_args: &ProcessCommandArgs, file: &mut File) {
}
if !file.extra_files.is_empty() {
let mut new_extra_files: Vec<File> = Vec::new();
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.filename = filename.clone();
let extra_extension = &extra_file.extension;
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
file.filename(), filename
);
if !process_args.dry_run {
@ -125,12 +128,12 @@ fn rename_file(process_args: &ProcessCommandArgs, file: &mut File) {
}
if !process_args.dry_run {
file.filename = filename;
file.change_filename(filename);
}
}
#[cfg(feature = "replaygain")]
pub fn add_replaygain_tags(file: &File, force: bool) -> Result<(), Box<dyn std::error::Error>> {
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",
@ -169,7 +172,7 @@ pub fn add_replaygain_tags(file: &File, force: bool) -> Result<(), Box<dyn std::
Ok(())
}
fn analyze_file(file: &File) -> Result<AudioFileInfo, Box<dyn std::error::Error>> {
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)?;
@ -192,8 +195,8 @@ pub fn process_command(
file.info = info;
}
} else {
let jobs: Arc<Mutex<Vec<File>>> = Arc::new(Mutex::new(files.clone()));
let new_files: Arc<Mutex<Vec<File>>> = Arc::new(Mutex::new(Vec::new()));
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 {
@ -243,7 +246,7 @@ pub fn process_command(
return Ok(());
} else {
let jobs: Arc<Mutex<Vec<File>>> = Arc::new(Mutex::new(files));
let jobs: Arc<Mutex<Vec<AudioFile>>> = Arc::new(Mutex::new(files));
scope(|s| {
for _ in 0..threads {

View file

@ -1,10 +1,11 @@
use std::collections::HashMap;
use std::path::PathBuf;
use relative_file::traits::RelativeFileTrait;
use serde::Serialize;
use crate::args::CLIArgs;
use crate::types::File;
use crate::types::AudioFile;
use crate::utils::formats::get_format_handler;
#[derive(Debug, Clone, clap::Args)]
@ -33,10 +34,10 @@ pub fn get_tags_command(
_args: CLIArgs,
get_tags_args: &GetTagsCommandArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let mut files: Vec<File> = Vec::new();
let mut files: Vec<AudioFile> = Vec::new();
for file in get_tags_args.files.iter() {
files.push(File::from_path("".to_string(), PathBuf::from(file)));
files.push(AudioFile::from_path("".to_string(), PathBuf::from(file)));
}
for file in files.iter_mut() {

View file

@ -1,7 +1,7 @@
use std::path::PathBuf;
use crate::args::CLIArgs;
use crate::types::File;
use crate::types::AudioFile;
use crate::utils::formats::get_format_handler;
#[derive(Debug, Clone, clap::Args)]
@ -17,10 +17,10 @@ pub fn set_tags_command(
_args: CLIArgs,
add_tags_args: &SetTagsCommandArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let mut files: Vec<File> = Vec::new();
let mut files: Vec<AudioFile> = Vec::new();
for file in add_tags_args.files.iter() {
files.push(File::from_path("".to_string(), PathBuf::from(file)));
files.push(AudioFile::from_path("".to_string(), PathBuf::from(file)));
}
for file in files.iter() {

View file

@ -4,8 +4,10 @@ use std::sync::mpsc;
use std::thread;
use std::thread::JoinHandle;
use relative_file::traits::RelativeFileTrait;
use crate::args::CLIArgs;
use crate::types::File;
use crate::types::AudioFile;
use crate::utils::transcoder::presets::print_presets;
use crate::utils::transcoder::presets::transcode_preset_or_config;
use crate::utils::transcoder::transcode;
@ -43,12 +45,12 @@ pub fn transcode_command(
println!("Transcoding");
let input_file = File::from_path("".to_string(), PathBuf::from(&transcode_args.source));
let output_file = File::from_path("".to_string(), PathBuf::from(&transcode_args.dest));
let input_file = AudioFile::from_path("".to_string(), PathBuf::from(&transcode_args.source));
let output_file = AudioFile::from_path("".to_string(), PathBuf::from(&transcode_args.dest));
if !transcode_args.ignore_extension {
if let Some(ref file_extension) = transcode_config.file_extension {
if file_extension != &output_file.extension {
if Some(file_extension) != output_file.extension().as_ref() {
panic!(
concat!(
"{} is not the recommended ",
@ -56,7 +58,7 @@ pub fn transcode_command(
"please change it to {} ",
"or run with --ignore-extension"
),
output_file.extension, file_extension
output_file.extension().unwrap(), file_extension
);
}
}

View file

@ -1,5 +1,6 @@
use std::path::PathBuf;
use relative_file::RelativeFile;
use serde::Deserialize;
use crate::utils::format_detection::FileFormat;
@ -32,68 +33,64 @@ pub struct AudioFileInfo {
}
#[derive(Debug, Clone)]
pub struct File {
pub filename: String,
pub extension: String,
pub struct AudioFile {
file_base: RelativeFile,
// relative path to file's folder
pub path_to: PathBuf,
// path to folder from source
pub path_from_source: PathBuf,
pub extra_files: Vec<File>,
pub extra_files: Vec<RelativeFile>,
pub info: AudioFileInfo,
pub folder_meta: FolderMeta,
}
impl File {
pub fn from_path(source_dir: String, full_path: PathBuf) -> File {
let full_file_path = PathBuf::from(&source_dir).join(full_path);
impl AudioFile {
pub fn from_path(source_dir: String, full_path: PathBuf) -> AudioFile {
let file_base = RelativeFile::from_path(source_dir, full_path);
let filename_without_extension = full_file_path
.file_stem()
.expect("filename invalid")
.to_string_lossy()
.to_string();
let extension = full_file_path.extension();
let extension = if let Some(extension) = extension {
extension.to_string_lossy().to_string()
} else {
"".to_string()
};
let path_from_src = full_file_path
.strip_prefix(&source_dir)
.expect("couldn't get path relative to source");
let mut folder_path_from_src = path_from_src.to_path_buf();
folder_path_from_src.pop();
let path_to = PathBuf::from(&source_dir).join(&folder_path_from_src);
File {
filename: filename_without_extension,
extension,
path_from_source: folder_path_from_src,
path_to,
AudioFile {
file_base,
extra_files: Vec::new(),
info: AudioFileInfo::default(),
folder_meta: FolderMeta::default(),
}
}
pub fn join_filename(&self) -> String {
format!("{}.{}", self.filename, self.extension)
pub fn as_relative_file(&self) -> &RelativeFile {
&self.file_base
}
pub fn join_path_to(&self) -> PathBuf {
PathBuf::from(&self.path_to).join(self.join_filename())
}
impl relative_file::traits::RelativeFileTrait for AudioFile {
fn filename(&self) -> String {
self.file_base.filename()
}
pub fn join_path_from_source(&self) -> PathBuf {
PathBuf::from(&self.path_from_source).join(self.join_filename())
fn extension(&self) -> Option<String> {
self.file_base.extension()
}
fn path_to(&self) -> PathBuf {
self.file_base.path_to()
}
fn path_from_source(&self) -> PathBuf {
self.file_base.path_from_source()
}
fn join_filename(&self) -> String {
self.file_base.join_filename()
}
fn join_path_to(&self) -> PathBuf {
self.file_base.join_path_to()
}
fn join_path_from_source(&self) -> PathBuf {
self.file_base.join_path_from_source()
}
fn change_filename(&mut self, filename: String) {
self.file_base.change_filename(filename)
}
}

View file

@ -3,10 +3,11 @@ mod handlers;
use std::error::Error;
use std::path::Path;
use relative_file::traits::RelativeFileTrait;
use thiserror::Error;
use super::format_detection::detect_format;
use crate::types::{AudioFileInfo, File, Tags};
use crate::types::{AudioFileInfo, AudioFile, Tags};
#[cfg(feature = "replaygain")]
use replaygain::ReplayGainRawData;
@ -41,12 +42,12 @@ pub enum AudioFormatError {
MissingArtist,
}
pub fn get_format_handler(file: &File) -> Result<Box<dyn FormatHandler>, BoxedError> {
pub fn get_format_handler(file: &AudioFile) -> Result<Box<dyn FormatHandler>, BoxedError> {
let format = detect_format(&file.join_path_to())?;
let path = file.join_path_to();
for handler in handlers::HANDLERS.iter() {
if !handler.supported_extensions.contains(&file.extension) {
if !handler.supported_extensions.contains(&file.extension().unwrap()) {
continue;
}

View file

@ -1,17 +1,18 @@
use std::{fs, path::PathBuf};
use crate::types::{File, FolderMeta};
use crate::types::{AudioFile, FolderMeta};
use relative_file::{traits::RelativeFileTrait, RelativeFile};
use walkdir::WalkDir;
use super::is_supported_file;
pub fn find_extra_files(
src_dir: String,
file: &File,
) -> Result<Vec<File>, Box<dyn std::error::Error>> {
let mut extra_files: Vec<File> = Vec::new();
file: &AudioFile,
) -> Result<Vec<RelativeFile>, Box<dyn std::error::Error>> {
let mut extra_files: Vec<RelativeFile> = Vec::new();
for entry in fs::read_dir(&file.path_to)? {
for entry in fs::read_dir(&file.path_to())? {
let entry = entry?;
if !entry.metadata()?.is_file() {
continue;
@ -23,18 +24,18 @@ pub fn find_extra_files(
continue;
}
if entry_path.file_stem().unwrap().to_string_lossy() == file.filename
&& extension.unwrap().to_string_lossy() != file.extension
if entry_path.file_stem().unwrap().to_string_lossy() == file.filename()
&& extension.map(|x| x.to_string_lossy().to_string()) != file.extension()
{
extra_files.push(File::from_path(src_dir.clone(), entry_path.clone()));
extra_files.push(RelativeFile::from_path(src_dir.clone(), entry_path.clone()));
}
}
Ok(extra_files)
}
pub fn scan_for_music(src_dir: &String) -> Result<Vec<File>, Box<dyn std::error::Error>> {
let mut files: Vec<File> = Vec::new();
pub fn scan_for_music(src_dir: &String) -> Result<Vec<AudioFile>, Box<dyn std::error::Error>> {
let mut files: Vec<AudioFile> = Vec::new();
for entry in WalkDir::new(src_dir) {
let entry = entry.unwrap();
@ -45,7 +46,7 @@ pub fn scan_for_music(src_dir: &String) -> Result<Vec<File>, Box<dyn std::error:
}
if is_supported_file(&entry_path) {
let mut file = File::from_path(src_dir.clone(), entry_path.clone());
let mut file = AudioFile::from_path(src_dir.clone(), entry_path.clone());
let mut folder_meta_path = PathBuf::from(entry_path.parent().unwrap().clone());
folder_meta_path.push("folder.meta");

View file

@ -1,6 +1,7 @@
pub mod presets;
pub mod types;
mod progress_monitor;
#[allow(clippy::module_inception)]
mod transcoder;
pub use self::progress_monitor::progress_monitor;

View file

@ -83,7 +83,7 @@ pub fn transcode_preset_or_config(
} else if let Some(config_path) = config_path {
return match TranscodeConfig::load(config_path.to_string()) {
Ok(config) => Ok(config),
Err(e) => Err(PresetError::LoadError(Box::from(e))),
Err(e) => Err(PresetError::LoadError(e)),
};
} else {
Err(PresetError::NoneSpecified)

View file

@ -1,12 +1,13 @@
use std::{fs, process::Command, sync::mpsc::Sender, thread::JoinHandle};
use crate::types::File;
use crate::types::AudioFile;
use relative_file::traits::RelativeFileTrait;
use string_error::static_err;
use super::{progress_monitor, types::TranscodeConfig};
pub fn transcode(
file: File,
file: AudioFile,
dest: String,
config: &TranscodeConfig,
progress_sender: Option<Sender<String>>,

View file

@ -39,13 +39,13 @@ impl TranscodeConfig {
Some("json") => {
let conf: TranscodeConfig =
serde_json::from_reader(reader).expect("error while reading json");
return Ok(conf);
Ok(conf)
}
Some("yml") | Some("yaml") | Some(&_) | None => {
let conf: TranscodeConfig =
serde_json::from_reader(reader).expect("error while reading json");
return Ok(conf);
Ok(conf)
}
};
}
}
}