add album set support & copy --skip-same-extension flag

This commit is contained in:
chaos 2023-11-12 13:15:50 +00:00
parent ed04740908
commit dd720c9144
No known key found for this signature in database
10 changed files with 88 additions and 20 deletions

View file

@ -37,6 +37,8 @@ pub struct CopyCommandArgs {
#[clap(long)] #[clap(long)]
pub no_skip_existing: bool, pub no_skip_existing: bool,
#[clap(long)] #[clap(long)]
pub skip_same_extension: bool,
#[clap(long)]
pub single_directory: bool, pub single_directory: bool,
} }
@ -123,7 +125,10 @@ pub fn copy_command(
Ok(()) Ok(())
} }
fn copy_file(file: &relative_file::RelativeFile, 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 from_path = file.join_path_to();
let to_path_dest = PathBuf::from_str(copy_args.dest.as_str()).expect("invalid destination"); let to_path_dest = PathBuf::from_str(copy_args.dest.as_str()).expect("invalid destination");
@ -178,6 +183,12 @@ fn transcode_file(
} }
}; };
if copy_args.skip_same_extension {
if 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_dest = PathBuf::from_str(copy_args.dest.as_str()).expect("invalid destination");
let to_path = match copy_args.single_directory { let to_path = match copy_args.single_directory {
true => to_path_dest.join(new_filename_full), true => to_path_dest.join(new_filename_full),

View file

@ -19,6 +19,7 @@ pub struct GetTagsCommandArgs {
struct Tags { struct Tags {
title: String, title: String,
artist: String, artist: String,
album: Option<String>,
track_number: Option<u64>, track_number: Option<u64>,
} }
@ -26,6 +27,7 @@ fn from_main_tags(tags: &crate::types::Tags) -> Tags {
Tags { Tags {
title: tags.title.clone(), title: tags.title.clone(),
artist: tags.artist.clone(), artist: tags.artist.clone(),
album: tags.album.clone(),
track_number: tags.track_number, track_number: tags.track_number,
} }
} }

View file

@ -11,6 +11,8 @@ pub struct SetTagsCommandArgs {
pub title: Option<String>, pub title: Option<String>,
#[clap(long)] #[clap(long)]
pub artist: Option<String>, pub artist: Option<String>,
#[clap(long)]
pub album: Option<String>,
} }
pub fn set_tags_command( pub fn set_tags_command(
@ -34,6 +36,10 @@ pub fn set_tags_command(
handler.set_artist(artist.clone())?; handler.set_artist(artist.clone())?;
} }
if let Some(album) = &add_tags_args.album {
handler.set_album(album.clone())?;
}
handler.save_changes()?; handler.save_changes()?;
} }

View file

@ -5,23 +5,14 @@ use serde::Deserialize;
use crate::utils::format_detection::FileFormat; use crate::utils::format_detection::FileFormat;
#[derive(Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct Tags { pub struct Tags {
pub title: String, pub title: String,
pub artist: String, pub artist: String,
pub album: Option<String>,
pub track_number: Option<u64>, pub track_number: Option<u64>,
} }
impl Default for Tags {
fn default() -> Self {
Tags {
title: "".to_string(),
artist: "".to_string(),
track_number: None,
}
}
}
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct AudioFileInfo { pub struct AudioFileInfo {
pub tags: Tags, pub tags: Tags,

View file

@ -13,11 +13,7 @@ use crate::{
#[cfg(feature = "replaygain")] #[cfg(feature = "replaygain")]
use replaygain::{ReplayGainData, ReplayGainRawData}; use replaygain::{ReplayGainData, ReplayGainRawData};
#[derive(Default)] use super::Changes;
struct Changes {
title: Option<String>,
artist: Option<String>,
}
#[derive(Default)] #[derive(Default)]
struct ExtractedData { struct ExtractedData {
@ -47,6 +43,7 @@ impl GenericFFMpegAudioFormat {
self.extracted_data.tags = Tags { self.extracted_data.tags = Tags {
title: output.tags.title, title: output.tags.title,
artist: output.tags.artist, artist: output.tags.artist,
album: output.tags.album,
track_number: output.tags.track_number, track_number: output.tags.track_number,
}; };
@ -78,6 +75,10 @@ impl FormatHandler for GenericFFMpegAudioFormat {
tags.artist = artist.clone(); tags.artist = artist.clone();
} }
if let Some(album) = &self.changes.album {
tags.album = Some(album.clone());
}
if !allow_missing { if !allow_missing {
if tags.title.is_empty() { if tags.title.is_empty() {
return Err(Box::new(AudioFormatError::MissingTitle)); return Err(Box::new(AudioFormatError::MissingTitle));
@ -110,13 +111,18 @@ impl FormatHandler for GenericFFMpegAudioFormat {
Ok(()) Ok(())
} }
fn set_album(&mut self, album: String) -> Result<(), BoxedError> {
self.changes.album = Some(album);
Ok(())
}
#[cfg(feature = "replaygain")] #[cfg(feature = "replaygain")]
fn set_replaygain_data(&mut self, _data: ReplayGainRawData) -> Result<(), BoxedError> { fn set_replaygain_data(&mut self, _data: ReplayGainRawData) -> Result<(), BoxedError> {
panic!("ffprobe doesn't support setting replaygain data, check supports_replaygain()") panic!("ffprobe doesn't support setting replaygain data, check supports_replaygain()")
} }
fn save_changes(&mut self) -> Result<(), BoxedError> { fn save_changes(&mut self) -> Result<(), BoxedError> {
if self.changes.title.is_none() && self.changes.artist.is_none() { if !self.changes.changed() {
return Ok(()); return Ok(());
} }
@ -148,6 +154,13 @@ impl FormatHandler for GenericFFMpegAudioFormat {
]) ])
} }
if let Some(album) = &self.changes.album {
args.extend(vec![
"-metadata".to_string(),
format!("album={}", album.as_str()),
])
}
args.push(temp_file.to_string_lossy().to_string()); args.push(temp_file.to_string_lossy().to_string());
let output = Command::new(crate::meta::FFMPEG).args(args).output()?; let output = Command::new(crate::meta::FFMPEG).args(args).output()?;

View file

@ -30,6 +30,8 @@ impl FormatHandler for FLACAudioFormat {
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> { fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> {
let title = flac_get_first(&self.flac_tags, "TITLE"); let title = flac_get_first(&self.flac_tags, "TITLE");
let artist = flac_get_first(&self.flac_tags, "ARTIST"); let artist = flac_get_first(&self.flac_tags, "ARTIST");
let album = flac_get_first(&self.flac_tags, "ALBUM");
let mut track_number = flac_get_first(&self.flac_tags, "TRACKNUMBER"); let mut track_number = flac_get_first(&self.flac_tags, "TRACKNUMBER");
if !allow_missing { if !allow_missing {
@ -51,6 +53,7 @@ impl FormatHandler for FLACAudioFormat {
Ok(Tags { Ok(Tags {
title: title.unwrap(), title: title.unwrap(),
artist: artist.unwrap(), artist: artist.unwrap(),
album,
track_number: match track_number { track_number: match track_number {
Some(num) => match num.parse::<u64>() { Some(num) => match num.parse::<u64>() {
Ok(n) => Some(n), Ok(n) => Some(n),
@ -92,6 +95,13 @@ impl FormatHandler for FLACAudioFormat {
Ok(()) Ok(())
} }
fn set_album(&mut self, album: String) -> Result<(), BoxedError> {
self.flac_tags.remove_vorbis("ALBUM");
self.flac_tags.set_vorbis("ALBUM", vec![album]);
Ok(())
}
#[cfg(feature = "replaygain")] #[cfg(feature = "replaygain")]
fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> {
self.flac_tags.remove_vorbis("REPLAYGAIN_TRACK_GAIN"); self.flac_tags.remove_vorbis("REPLAYGAIN_TRACK_GAIN");

View file

@ -20,6 +20,7 @@ impl FormatHandler for ID3AudioFormat {
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> { fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> {
let title = self.id3_tags.title(); let title = self.id3_tags.title();
let artist = self.id3_tags.artist(); let artist = self.id3_tags.artist();
let album = self.id3_tags.album();
if !allow_missing { if !allow_missing {
if title.is_none() { if title.is_none() {
@ -33,10 +34,10 @@ impl FormatHandler for ID3AudioFormat {
Ok(Tags { Ok(Tags {
title: String::from(title.unwrap()), title: String::from(title.unwrap()),
artist: String::from(artist.unwrap()), artist: String::from(artist.unwrap()),
album: album.map(|f| f.to_string()),
track_number: self track_number: self
.id3_tags .id3_tags
.track() .track().map(|track_number| track_number as u64),
.map(|track_number| track_number as u64),
}) })
} }
@ -82,6 +83,12 @@ impl FormatHandler for ID3AudioFormat {
Ok(()) Ok(())
} }
fn set_album(&mut self, album: String) -> Result<(), BoxedError> {
self.id3_tags.set_album(album);
Ok(())
}
#[cfg(feature = "replaygain")] #[cfg(feature = "replaygain")]
fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> {
let frames = self.id3_tags.remove("TXXX"); let frames = self.id3_tags.remove("TXXX");

View file

@ -23,6 +23,25 @@ mod taglib;
)))] )))]
compile_error!("at least one extractor feature must be enabled"); compile_error!("at least one extractor feature must be enabled");
// For changed tags on implementations that call a command line utility to set tags
#[derive(Default)]
struct Changes {
title: Option<String>,
artist: Option<String>,
album: Option<String>,
track_number: Option<u64>,
}
impl Changes {
#[inline]
fn changed(&self) -> bool {
[self.title.is_some(),
self.artist.is_some(),
self.album.is_some(),
self.track_number.is_some()].contains(&true)
}
}
type NewHandlerFuncReturn = Result<Box<dyn FormatHandler>, BoxedError>; type NewHandlerFuncReturn = Result<Box<dyn FormatHandler>, BoxedError>;
type NewHandlerFunc = fn(path: &PathBuf, file_format: Option<FileFormat>) -> NewHandlerFuncReturn; type NewHandlerFunc = fn(path: &PathBuf, file_format: Option<FileFormat>) -> NewHandlerFuncReturn;

View file

@ -45,6 +45,7 @@ impl FormatHandler for TaglibAudioFormat {
Ok(Tags { Ok(Tags {
title: title.unwrap(), title: title.unwrap(),
artist: artist.unwrap(), artist: artist.unwrap(),
album: tags.album(),
track_number: tags.track(), track_number: tags.track(),
}) })
} }
@ -104,6 +105,13 @@ impl FormatHandler for TaglibAudioFormat {
Ok(()) Ok(())
} }
fn set_album(&mut self, album: String) -> Result<(), BoxedError> {
let mut tags = self.file.tag()?;
tags.set_album(album);
Ok(())
}
#[cfg(feature = "replaygain")] #[cfg(feature = "replaygain")]
fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> {
if let Some(format) = self.file_format { if let Some(format) = self.file_format {

View file

@ -23,6 +23,7 @@ pub trait FormatHandler {
fn set_title(&mut self, title: String) -> Result<(), BoxedError>; fn set_title(&mut self, title: String) -> Result<(), BoxedError>;
fn set_artist(&mut self, artist: String) -> Result<(), BoxedError>; fn set_artist(&mut self, artist: String) -> Result<(), BoxedError>;
fn set_album(&mut self, artist: String) -> Result<(), BoxedError>;
#[cfg(feature = "replaygain")] #[cfg(feature = "replaygain")]
fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError>; fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError>;