refactor format handlers to make it easier to tell what extensions and formats are allowed by each
This commit is contained in:
parent
8b70b2b851
commit
1dc74c2cb0
|
@ -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
|
|
@ -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(),
|
|
@ -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()),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue