add album set support & copy --skip-same-extension flag
This commit is contained in:
parent
ed04740908
commit
dd720c9144
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
src/types.rs
13
src/types.rs
|
@ -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,
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue