refactor format handlers to make it easier to tell what extensions and formats are allowed by each

This commit is contained in:
chaos 2023-10-19 17:11:13 +01:00
parent 8b70b2b851
commit 1dc74c2cb0
No known key found for this signature in database
7 changed files with 191 additions and 165 deletions

View file

@ -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
ffprobe_extractor = [] # If to allow using ffmpeg/ffprobe as a fallback tag extractor

View file

@ -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<String>,
artist: Option<String>,
replaygain_data: Option<ReplayGainRawData>,
}
#[derive(Default)]
@ -25,7 +27,7 @@ struct ExtractedData {
}
pub struct GenericFFMpegAudioFormat {
format_type: FileFormat,
file_format: FileFormat,
path: Box<PathBuf>,
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<Tags, BoxedError> {
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<FileFormat>,
) -> Result<GenericFFMpegAudioFormat, BoxedError> {
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(),

View file

@ -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<String> {
}
}
impl AudioContainerFormat for FLACAudioFormat {
impl FormatHandler for FLACAudioFormat {
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> {
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<FLACAudioFormat, BoxedError> {
pub fn new_handler(path: &PathBuf) -> Result<FLACAudioFormat, BoxedError> {
Ok(FLACAudioFormat {
flac_tags: metaflac::Tag::read_from_path(path)?,
path: Box::new(path.clone()),

View file

@ -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<PathBuf>,
}
impl AudioContainerFormat for ID3AudioFormat {
impl FormatHandler for ID3AudioFormat {
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> {
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<ID3AudioFormat, BoxedError> {
pub fn new_handler(path: &PathBuf) -> Result<ID3AudioFormat, BoxedError> {
let id3_tags = id3::Tag::read_from_path(path)?;
Ok(ID3AudioFormat {

View file

@ -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;
mod id3;
type NewHandlerFuncReturn = Result<Box<dyn FormatHandler>, BoxedError>;
type NewHandlerFunc = fn(path: &PathBuf, file_format: Option<FileFormat>) -> NewHandlerFuncReturn;
pub struct Handler {
pub supported_extensions: Vec<String>,
pub supported_formats: Vec<FileFormat>,
pub new: NewHandlerFunc
}
lazy_static! {
pub static ref SUPPORTED_EXTENSIONS: Vec<String> = {
let mut extensions: Vec<String> = 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<FileFormat> = {
let mut formats: Vec<FileFormat> = 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<Handler> = {
let mut handlers: Vec<Handler> = 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
};
}

View file

@ -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<FileFormat>,
}
impl AudioContainerFormat for TaglibAudioFormat {
impl FormatHandler for TaglibAudioFormat {
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> {
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<FileFormat>,
) -> Result<TaglibAudioFormat, BoxedError> {
@ -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,
}
}

View file

@ -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<dyn Error>;
pub trait AudioContainerFormat {
pub trait FormatHandler {
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError>;
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<Box<dyn AudioContainerFormat>, BoxedError> {
pub fn get_format_handler(file: &File) -> Result<Box<dyn FormatHandler>, 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)
}