From 1dc74c2cb07d22d629996a15bb99a6ed4b14e01d Mon Sep 17 00:00:00 2001 From: chaos Date: Thu, 19 Oct 2023 17:11:13 +0100 Subject: [PATCH] refactor format handlers to make it easier to tell what extensions and formats are allowed by each --- Cargo.toml | 4 +- .../{generic_ffmpeg.rs => ffprobe.rs} | 49 +++--- src/utils/formats/handlers/flac.rs | 6 +- src/utils/formats/handlers/id3.rs | 6 +- src/utils/formats/handlers/mod.rs | 143 +++++++++++++++++- .../handlers/{generic_taglib.rs => taglib.rs} | 15 +- src/utils/formats/mod.rs | 133 ++-------------- 7 files changed, 191 insertions(+), 165 deletions(-) rename src/utils/formats/handlers/{generic_ffmpeg.rs => ffprobe.rs} (80%) rename src/utils/formats/handlers/{generic_taglib.rs => taglib.rs} (90%) diff --git a/Cargo.toml b/Cargo.toml index de15f27..8040d9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,10 +51,10 @@ tempfile = "3" notify = "6" [features] -default = ["taglib_extractor", "flac_extractor", "mp3_extractor", "ffmpeg_extractor"] +default = ["taglib_extractor", "flac_extractor", "mp3_extractor", "ffprobe_extractor"] # Formats taglib_extractor = ["dep:taglib"] flac_extractor = ["dep:metaflac"] mp3_extractor = ["dep:id3"] -ffmpeg_extractor = [] # If to allow using ffmpeg as a fallback tag extractor \ No newline at end of file +ffprobe_extractor = [] # If to allow using ffmpeg/ffprobe as a fallback tag extractor \ No newline at end of file diff --git a/src/utils/formats/handlers/generic_ffmpeg.rs b/src/utils/formats/handlers/ffprobe.rs similarity index 80% rename from src/utils/formats/handlers/generic_ffmpeg.rs rename to src/utils/formats/handlers/ffprobe.rs index 3377c73..0eea72e 100644 --- a/src/utils/formats/handlers/generic_ffmpeg.rs +++ b/src/utils/formats/handlers/ffprobe.rs @@ -7,15 +7,17 @@ use string_error::into_err; use crate::{ types::{AudioFileInfo, ReplayGainData, ReplayGainRawData, Tags}, - utils::{format_detection::FileFormat, ffprobe}, - utils::formats::{AudioContainerFormat, AudioFormatError, BoxedError}, + utils::formats::{AudioFormatError, BoxedError, FormatHandler}, + utils::{ + ffprobe, + format_detection::{detect_format, FileFormat}, + }, }; #[derive(Default)] struct Changes { title: Option, artist: Option, - replaygain_data: Option, } #[derive(Default)] @@ -25,7 +27,7 @@ struct ExtractedData { } pub struct GenericFFMpegAudioFormat { - format_type: FileFormat, + file_format: FileFormat, path: Box, extracted_data: ExtractedData, @@ -39,7 +41,7 @@ impl GenericFFMpegAudioFormat { if let Err(err) = output { return Err(into_err(format!("{}", err))); } - + let output = output.unwrap(); self.extracted_data.tags = Tags { @@ -47,7 +49,9 @@ impl GenericFFMpegAudioFormat { artist: output.tags.artist, }; - if output.tags.replaygain_track_gain.is_some() && output.tags.replaygain_track_peak.is_some() { + if output.tags.replaygain_track_gain.is_some() + && output.tags.replaygain_track_peak.is_some() + { self.extracted_data.replaygain_data = Some(ReplayGainData { track_gain: output.tags.replaygain_track_gain.unwrap(), track_peak: output.tags.replaygain_track_peak.unwrap(), @@ -60,7 +64,7 @@ impl GenericFFMpegAudioFormat { } } -impl AudioContainerFormat for GenericFFMpegAudioFormat { +impl FormatHandler for GenericFFMpegAudioFormat { fn get_tags(&self, allow_missing: bool) -> Result { let mut tags = self.extracted_data.tags.clone(); @@ -85,14 +89,6 @@ impl AudioContainerFormat for GenericFFMpegAudioFormat { } fn contains_replaygain_tags(&self) -> bool { - if self.changes.replaygain_data.is_some() { - return true; - } - - if self.extracted_data.replaygain_data.is_some() { - return true; - }; - false } @@ -110,16 +106,12 @@ impl AudioContainerFormat for GenericFFMpegAudioFormat { Ok(()) } - fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { - self.changes.replaygain_data = Some(data); - Ok(()) + fn set_replaygain_data(&mut self, _data: ReplayGainRawData) -> Result<(), BoxedError> { + panic!("ffprobe doesn't support setting replaygain data, check supports_replaygain() f") } fn save_changes(&mut self) -> Result<(), BoxedError> { - if self.changes.title.is_none() - && self.changes.artist.is_none() - && self.changes.replaygain_data.is_none() - { + if self.changes.title.is_none() && self.changes.artist.is_none() { return Ok(()); } @@ -170,17 +162,22 @@ impl AudioContainerFormat for GenericFFMpegAudioFormat { tags: self.get_tags(allow_missing_tags)?, contains_replaygain: self.contains_replaygain_tags(), supports_replaygain: self.supports_replaygain(), - format: Some(self.format_type), + format: Some(self.file_format), }) } } -pub fn new_generic_ffmpeg_format_handler( +pub fn new_handler( path: &Path, - format_type: FileFormat, + file_format: Option, ) -> Result { + let mut file_format = file_format; + if file_format.is_none() { + file_format = Some(detect_format(path)?); + } + let mut handler = GenericFFMpegAudioFormat { - format_type, + file_format: file_format.unwrap(), path: Box::new(path.to_path_buf()), extracted_data: ExtractedData::default(), changes: Changes::default(), diff --git a/src/utils/formats/handlers/flac.rs b/src/utils/formats/handlers/flac.rs index ea9523e..6079300 100644 --- a/src/utils/formats/handlers/flac.rs +++ b/src/utils/formats/handlers/flac.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use crate::{ types::{AudioFileInfo, ReplayGainRawData, Tags}, utils::format_detection::FileFormat, - utils::formats::{AudioContainerFormat, AudioFormatError, BoxedError} + utils::formats::{FormatHandler, AudioFormatError, BoxedError} }; @@ -24,7 +24,7 @@ fn flac_get_first(tag: &metaflac::Tag, key: &str) -> Option { } } -impl AudioContainerFormat for FLACAudioFormat { +impl FormatHandler for FLACAudioFormat { fn get_tags(&self, allow_missing: bool) -> Result { let title = flac_get_first(&self.flac_tags, "TITLE"); let artist = flac_get_first(&self.flac_tags, "ARTIST"); @@ -107,7 +107,7 @@ impl AudioContainerFormat for FLACAudioFormat { } } -pub fn new_flac_format_handler(path: &PathBuf) -> Result { +pub fn new_handler(path: &PathBuf) -> Result { Ok(FLACAudioFormat { flac_tags: metaflac::Tag::read_from_path(path)?, path: Box::new(path.clone()), diff --git a/src/utils/formats/handlers/id3.rs b/src/utils/formats/handlers/id3.rs index 1fb4c29..69a97c0 100644 --- a/src/utils/formats/handlers/id3.rs +++ b/src/utils/formats/handlers/id3.rs @@ -5,7 +5,7 @@ use id3::TagLike; use crate::{ types::{AudioFileInfo, ReplayGainRawData, Tags}, utils::format_detection::FileFormat, - utils::formats::{AudioContainerFormat, AudioFormatError, BoxedError}, + utils::formats::{FormatHandler, AudioFormatError, BoxedError}, }; pub struct ID3AudioFormat { @@ -13,7 +13,7 @@ pub struct ID3AudioFormat { path: Box, } -impl AudioContainerFormat for ID3AudioFormat { +impl FormatHandler for ID3AudioFormat { fn get_tags(&self, allow_missing: bool) -> Result { let title = self.id3_tags.title(); let artist = self.id3_tags.artist(); @@ -123,7 +123,7 @@ impl AudioContainerFormat for ID3AudioFormat { } } -pub fn new_id3_format_handler(path: &PathBuf) -> Result { +pub fn new_handler(path: &PathBuf) -> Result { let id3_tags = id3::Tag::read_from_path(path)?; Ok(ID3AudioFormat { diff --git a/src/utils/formats/handlers/mod.rs b/src/utils/formats/handlers/mod.rs index 356b9f1..6e00e44 100644 --- a/src/utils/formats/handlers/mod.rs +++ b/src/utils/formats/handlers/mod.rs @@ -1,8 +1,141 @@ -#[cfg(feature = "ffmpeg_extractor")] -pub mod generic_ffmpeg; +use std::path::PathBuf; + +use crate::utils::format_detection::FileFormat; + +use lazy_static::lazy_static; + +use super::{BoxedError, FormatHandler}; + #[cfg(feature = "flac_extractor")] -pub mod flac; +mod flac; +#[cfg(feature = "ffprobe_extractor")] +mod ffprobe; #[cfg(feature = "taglib_extractor")] -pub mod generic_taglib; +mod taglib; #[cfg(feature = "mp3_extractor")] -pub mod id3; \ No newline at end of file +mod id3; + +type NewHandlerFuncReturn = Result, BoxedError>; +type NewHandlerFunc = fn(path: &PathBuf, file_format: Option) -> NewHandlerFuncReturn; + +pub struct Handler { + pub supported_extensions: Vec, + pub supported_formats: Vec, + pub new: NewHandlerFunc +} + +lazy_static! { + pub static ref SUPPORTED_EXTENSIONS: Vec = { + let mut extensions: Vec = Vec::new(); + for handler in HANDLERS.iter() { + for extension in handler.supported_extensions.iter() { + if !extensions.contains(extension) { + extensions.push(extension.clone()); + } + } + } + + extensions + }; + + pub static ref SUPPORTED_FORMATS: Vec = { + let mut formats: Vec = Vec::new(); + for handler in HANDLERS.iter() { + for format in handler.supported_formats.iter() { + if !formats.contains(format) { + formats.push(*format); + } + } + } + + formats + }; + + pub static ref HANDLERS: Vec = { + let mut handlers: Vec = Vec::new(); + + #[cfg(feature = "mp3_extractor")] + handlers.push(Handler { + supported_extensions: vec!["mp3".to_string()], + supported_formats: vec![FileFormat::MP3], + new: |path, _file_format| -> NewHandlerFuncReturn { + let handler = id3::new_handler(path)?; + + Ok(Box::from(handler)) + }, + }); + + #[cfg(feature = "flac_extractor")] + handlers.push(Handler { + supported_extensions: vec!["flac".to_string()], + supported_formats: vec![FileFormat::FLAC], + new: |path, _file_format| -> NewHandlerFuncReturn { + let handler = flac::new_handler(path)?; + + Ok(Box::from(handler)) + }, + }); + + #[cfg(feature = "taglib_extractor")] + handlers.push(Handler { + supported_extensions: vec![ + "mp3".to_string(), + "flac".to_string(), + "ogg".to_string(), + "opus".to_string(), + "wav".to_string(), + "wv".to_string(), + "aiff".to_string(), + ], + supported_formats: vec![ + FileFormat::MP3, + FileFormat::FLAC, + FileFormat::OggVorbis, + FileFormat::OggOpus, + FileFormat::OggFLAC, + FileFormat::OggSpeex, + FileFormat::OggTheora, + FileFormat::Wav, + FileFormat::WavPack, + FileFormat::AIFF, + ], + new: |path, file_format| -> NewHandlerFuncReturn { + let handler = taglib::new_handler(path, file_format)?; + + Ok(Box::from(handler)) + }, + }); + + #[cfg(feature = "ffprobe_extractor")] + handlers.push(Handler { + supported_extensions: vec![ + "mp3".to_string(), + "flac".to_string(), + "ogg".to_string(), + "opus".to_string(), + "wav".to_string(), + "wv".to_string(), + "aiff".to_string(), + ], + supported_formats: vec![ + FileFormat::MP3, + FileFormat::FLAC, + FileFormat::OggVorbis, + FileFormat::OggOpus, + FileFormat::OggFLAC, + FileFormat::OggSpeex, + FileFormat::OggTheora, + FileFormat::Wav, + FileFormat::WavPack, + FileFormat::AIFF, + ], + new: |path, file_format| -> NewHandlerFuncReturn { + let handler = ffprobe::new_handler(path, file_format)?; + + Ok(Box::from(handler)) + }, + }); + + handlers + }; +} diff --git a/src/utils/formats/handlers/generic_taglib.rs b/src/utils/formats/handlers/taglib.rs similarity index 90% rename from src/utils/formats/handlers/generic_taglib.rs rename to src/utils/formats/handlers/taglib.rs index c3cc77b..c794a02 100644 --- a/src/utils/formats/handlers/generic_taglib.rs +++ b/src/utils/formats/handlers/taglib.rs @@ -9,7 +9,7 @@ use taglib::{ use crate::{ types::{AudioFileInfo, ReplayGainRawData, Tags}, utils::format_detection::FileFormat, - utils::formats::{AudioContainerFormat, AudioFormatError, BoxedError} + utils::formats::{FormatHandler, AudioFormatError, BoxedError} }; @@ -18,7 +18,7 @@ pub struct TaglibAudioFormat { file_format: Option, } -impl AudioContainerFormat for TaglibAudioFormat { +impl FormatHandler for TaglibAudioFormat { fn get_tags(&self, allow_missing: bool) -> Result { let tags = self.file.tag()?; @@ -75,10 +75,10 @@ impl AudioContainerFormat for TaglibAudioFormat { FileFormat::OggVorbis | FileFormat::OggOpus | FileFormat::OggFLAC - | FileFormat::OggSpeex // Both "support" ReplayGain but not implemented yet - - // FileFormat::Wav | - // FileFormat::WavPack + | FileFormat::OggSpeex + // Both "support" ReplayGain but not implemented yet + // FileFormat::Wav | + // FileFormat::WavPack ); } @@ -143,7 +143,7 @@ impl AudioContainerFormat for TaglibAudioFormat { } } -pub fn new_taglib_format_handler( +pub fn new_handler( path: &Path, file_format: Option, ) -> Result { @@ -154,7 +154,6 @@ pub fn new_taglib_format_handler( FileFormat::OggOpus => Some(TagLibFileType::OggOpus), FileFormat::OggFLAC => Some(TagLibFileType::OggFLAC), FileFormat::OggSpeex => Some(TagLibFileType::OggSpeex), - FileFormat::OggTheora => panic!("Ogg Theora is not supported by taglib"), _ => None, } } diff --git a/src/utils/formats/mod.rs b/src/utils/formats/mod.rs index 68ecc91..ca582a6 100644 --- a/src/utils/formats/mod.rs +++ b/src/utils/formats/mod.rs @@ -7,20 +7,12 @@ use thiserror::Error; use crate::types::{AudioFileInfo, File, ReplayGainRawData, Tags}; -#[cfg(feature = "flac_extractor")] -use self::handlers::flac::new_flac_format_handler; -#[cfg(feature = "taglib_extractor")] -use self::handlers::generic_taglib::new_taglib_format_handler; -#[cfg(feature = "mp3_extractor")] -use self::handlers::id3::new_id3_format_handler; -#[cfg(feature = "ffmpeg_extractor")] -use self::handlers::generic_ffmpeg::new_generic_ffmpeg_format_handler; -use super::format_detection::{detect_format, FileFormat}; +use super::format_detection::detect_format; type BoxedError = Box; -pub trait AudioContainerFormat { +pub trait FormatHandler { fn get_tags(&self, allow_missing: bool) -> Result; fn contains_replaygain_tags(&self) -> bool; fn supports_replaygain(&self) -> bool; @@ -45,82 +37,29 @@ pub enum AudioFormatError { MissingArtist, } -pub fn get_format_handler(file: &File) -> Result, BoxedError> { +pub fn get_format_handler(file: &File) -> Result, BoxedError> { let format = detect_format(&file.join_path_to())?; let path = file.join_path_to(); - #[cfg(feature = "taglib_extractor")] - { - match format { - FileFormat::OggFLAC - | FileFormat::OggSpeex - | FileFormat::OggVorbis - | FileFormat::OggOpus - | FileFormat::Wav - | FileFormat::WavPack - | FileFormat::AIFF => { - return Ok(Box::new(new_taglib_format_handler(&path, Some(format))?)); - } - _ => {} + for handler in handlers::HANDLERS.iter() { + if !handler.supported_extensions.contains(&file.extension) { + continue } - } - #[cfg(feature = "mp3_extractor")] - if format == FileFormat::MP3 { - // Native MP3 support - return Ok(Box::new(new_id3_format_handler(&path)?)); - } - - #[cfg(feature = "flac_extractor")] - if format == FileFormat::FLAC { - // Native FLAC support - return Ok(Box::new(new_flac_format_handler(&path)?)); - } - - #[cfg(feature = "ffmpeg_extractor")] - match format { - FileFormat::MP3 - | FileFormat::FLAC - | FileFormat::OggVorbis - | FileFormat::OggOpus - | FileFormat::OggFLAC - | FileFormat::OggSpeex - | FileFormat::OggTheora - | FileFormat::Wav - | FileFormat::WavPack - | FileFormat::AIFF => { - return Ok(Box::new(new_generic_ffmpeg_format_handler(&path, format)?)); - } - #[allow(unreachable_patterns)] - _ => {}, + if !handler.supported_formats.contains(&format) { + continue } + let new = handler.new; + + return new(&path, Some(format)); + } + panic!("no supported handler found"); } fn is_supported_extension(ext: &str) -> bool { - #[cfg(feature = "taglib_extractor")] - if matches!(ext, "ogg" | "opus" | "wav" | "wv" | "aiff") { - return true; - } - - #[cfg(feature = "mp3_extractor")] - if ext == "mp3" { - return true; - } - - #[cfg(feature = "flac_extractor")] - if ext == "flac" { - return true; - } - - // FFMpeg Fallback - #[cfg(feature = "ffmpeg_extractor")] - if matches!(ext, "mp3" | "flac" | "ogg" | "opus" | "wav" | "wv" | "aiff") { - return true; - } - - false + handlers::SUPPORTED_EXTENSIONS.contains(&ext.to_string()) } pub fn is_supported_file(file_path: &Path) -> bool { @@ -143,47 +82,5 @@ pub fn is_supported_file(file_path: &Path) -> bool { let format = format.unwrap(); - #[cfg(feature = "taglib_extractor")] - match format { - FileFormat::OggVorbis - | FileFormat::OggOpus - | FileFormat::OggFLAC - | FileFormat::OggSpeex - | FileFormat::Wav - | FileFormat::WavPack - | FileFormat::AIFF => { - return true; - } - _ => {} - } - - #[cfg(feature = "mp3_extractor")] - if format == FileFormat::MP3 { - return true; - } - - #[cfg(feature = "flac_extractor")] - if format == FileFormat::FLAC { - return true; - } - - #[cfg(feature = "ffmpeg_extractor")] - match format { - FileFormat::MP3 - | FileFormat::FLAC - | FileFormat::OggVorbis - | FileFormat::OggOpus - | FileFormat::OggFLAC - | FileFormat::OggSpeex - | FileFormat::OggTheora - | FileFormat::Wav - | FileFormat::WavPack - | FileFormat::AIFF => { - return true; - } - #[allow(unreachable_patterns)] - _ => {} - } - - false + handlers::SUPPORTED_FORMATS.contains(&format) }