mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 13:42:34 +00:00
Cleanup errors, add generic read_from_reader function, remove multiple useless File::open calls
Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
parent
ce76d861b6
commit
ca0c03ddf1
3 changed files with 107 additions and 66 deletions
40
src/error.rs
40
src/error.rs
|
@ -1,27 +1,37 @@
|
|||
/// Errors that could occur within Lofty.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum LoftyError {
|
||||
// File extension/format related errors
|
||||
/// Unknown file extension.
|
||||
#[error("Failed to guess the metadata format based on the file extension.")]
|
||||
UnknownFileExtension,
|
||||
|
||||
/// Unsupported file extension
|
||||
#[error("Unsupported format: {0}")]
|
||||
UnsupportedFormat(String),
|
||||
/// Unable to guess the format
|
||||
#[error("No format could be determined from the provided file.")]
|
||||
UnknownFormat,
|
||||
|
||||
// File data related errors
|
||||
/// Provided an empty file
|
||||
#[error("File contains no data")]
|
||||
EmptyFile,
|
||||
/// Provided a file with invalid/malformed data
|
||||
#[error("File has invalid data")]
|
||||
InvalidData,
|
||||
#[error("File has invalid data: {0}")]
|
||||
InvalidData(&'static str),
|
||||
/// Attempting to write an abnormally large amount of data
|
||||
#[error("An abnormally large amount of data was provided, and an overflow occurred")]
|
||||
TooMuchData,
|
||||
|
||||
/// Unsupported file extension
|
||||
#[error("Unsupported format: {0}")]
|
||||
UnsupportedFormat(String),
|
||||
// Picture related errors
|
||||
/// Picture has an unsupported mime type
|
||||
#[error("Unsupported mime type: {0}")]
|
||||
UnsupportedMimeType(String),
|
||||
/// Provided an invalid picture
|
||||
#[error("Picture contains invalid data")]
|
||||
NotAPicture,
|
||||
|
||||
// Tag related errors
|
||||
/// Any error from [`ape`]
|
||||
#[error(transparent)]
|
||||
ApeTag(#[from] ape::Error),
|
||||
|
@ -46,26 +56,16 @@ pub enum LoftyError {
|
|||
Ogg(#[from] ogg::OggReadError),
|
||||
/// Errors that arise while reading/writing to wav files
|
||||
#[error("Invalid Riff file: {0}")]
|
||||
Riff(String),
|
||||
/// Errors that arise while reading/writing to opus files
|
||||
#[error("Invalid Opus file: {0}")]
|
||||
Opus(String),
|
||||
Riff(&'static str),
|
||||
|
||||
/// Arises when provided an invalid picture
|
||||
#[error("Picture contains invalid data")]
|
||||
NotAPicture,
|
||||
|
||||
/// If a string isn't Utf8
|
||||
#[error(transparent)]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
// Conversions for std Errors
|
||||
/// Unable to convert bytes to a String
|
||||
#[error(transparent)]
|
||||
FromUtf8(#[from] std::string::FromUtf8Error),
|
||||
/// Represents all cases of `std::io::Error`.
|
||||
#[error(transparent)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
IO(#[from] std::io::Error),
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
/// Type for the result of tag operations.
|
||||
/// Result of tag operations.
|
||||
pub type Result<T> = std::result::Result<T, LoftyError>;
|
||||
|
|
|
@ -118,7 +118,7 @@ pub use crate::types::{
|
|||
};
|
||||
|
||||
mod tag;
|
||||
pub use crate::tag::{Id3Format, Tag, TagType, VorbisFormat};
|
||||
pub use crate::tag::{Id3Format, OggFormat, Tag, TagType};
|
||||
|
||||
mod error;
|
||||
pub use crate::error::{LoftyError, Result};
|
||||
|
|
131
src/tag.rs
131
src/tag.rs
|
@ -1,7 +1,7 @@
|
|||
#[allow(clippy::wildcard_imports)]
|
||||
use crate::components::tags::*;
|
||||
use crate::{AudioTag, LoftyError, Result};
|
||||
use std::io::Seek;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "format-ape")]
|
||||
|
@ -50,24 +50,28 @@ impl Tag {
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `path` either has no extension, or the extension is not valid unicode
|
||||
/// * `path` does not exist
|
||||
/// * `path` either has no extension, or the extension is not valid UTF-8
|
||||
/// * `path` has an unsupported/unknown extension
|
||||
///
|
||||
/// # Warning
|
||||
/// Using this on a `wav`/`wave`/`riff` file will **always** assume there's an ID3 tag.
|
||||
/// [`read_from_path_signature`](Tag::read_from_path_signature) is recommended, in the event that a RIFF INFO list is present instead.
|
||||
pub fn read_from_path(&self, path: impl AsRef<Path>) -> Result<Box<dyn AudioTag>> {
|
||||
let mut c = Cursor::new(std::fs::read(&path)?);
|
||||
|
||||
let tag_type = self.0.clone().unwrap_or({
|
||||
let extension = path
|
||||
.as_ref()
|
||||
.extension()
|
||||
.ok_or(LoftyError::UnknownFileExtension)?;
|
||||
|
||||
let extension_str = extension.to_str().ok_or(LoftyError::UnknownFileExtension)?;
|
||||
|
||||
TagType::try_from_ext(extension_str)?
|
||||
});
|
||||
|
||||
Self::match_tag(path, tag_type)
|
||||
Self::match_tag(&mut c, tag_type)
|
||||
}
|
||||
|
||||
/// Attempts to get the tag format based on the file signature
|
||||
|
@ -84,30 +88,48 @@ impl Tag {
|
|||
/// # Warning
|
||||
/// In the event that a riff file contains both an ID3 tag *and* a RIFF INFO chunk, the ID3 tag will **always** be chosen.
|
||||
pub fn read_from_path_signature(&self, path: impl AsRef<Path>) -> Result<Box<dyn AudioTag>> {
|
||||
let tag_type = self
|
||||
.0
|
||||
.clone()
|
||||
.unwrap_or(TagType::try_from_sig(&std::fs::read(path.as_ref())?)?);
|
||||
let mut c = Cursor::new(std::fs::read(&path)?);
|
||||
|
||||
Self::match_tag(path, tag_type)
|
||||
let tag_type = self.0.clone().unwrap_or(TagType::try_from_sig(&mut c)?);
|
||||
|
||||
Self::match_tag(&mut c, tag_type)
|
||||
}
|
||||
|
||||
fn match_tag(path: impl AsRef<Path>, tag_type: TagType) -> Result<Box<dyn AudioTag>> {
|
||||
/// Attempts to get the tag format based on the data in the reader
|
||||
///
|
||||
/// See [`read_from_path_signature`] for important notes, errors, and warnings.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Same as [`read_from_path_signature`]
|
||||
pub fn read_from_reader<R>(&self, reader: &mut R) -> Result<Box<dyn AudioTag>>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let tag_type = self.0.clone().unwrap_or(TagType::try_from_sig(reader)?);
|
||||
|
||||
Self::match_tag(reader, tag_type)
|
||||
}
|
||||
|
||||
fn match_tag<R>(reader: &mut R, tag_type: TagType) -> Result<Box<dyn AudioTag>>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
match tag_type {
|
||||
#[cfg(feature = "format-ape")]
|
||||
TagType::Ape => Ok(Box::new(ApeTag::read_from_path(path)?)),
|
||||
TagType::Ape => Ok(Box::new(ApeTag::read_from(reader)?)),
|
||||
#[cfg(feature = "format-id3")]
|
||||
TagType::Id3v2(format) => Ok(Box::new(Id3v2Tag::read_from_path(path, &format)?)),
|
||||
TagType::Id3v2(format) => Ok(Box::new(Id3v2Tag::read_from(reader, &format)?)),
|
||||
#[cfg(feature = "format-mp4")]
|
||||
TagType::Mp4 => Ok(Box::new(Mp4Tag::read_from_path(path)?)),
|
||||
TagType::Mp4 => Ok(Box::new(Mp4Tag::read_from(reader)?)),
|
||||
#[cfg(feature = "format-riff")]
|
||||
TagType::RiffInfo => Ok(Box::new(RiffTag::read_from_path(path)?)),
|
||||
TagType::RiffInfo => Ok(Box::new(RiffTag::read_from(reader)?)),
|
||||
#[cfg(any(
|
||||
feature = "format-vorbis",
|
||||
feature = "format-flac",
|
||||
feature = "format-opus"
|
||||
))]
|
||||
TagType::Vorbis(format) => Ok(Box::new(VorbisTag::read_from_path(path, &format)?)),
|
||||
TagType::Ogg(format) => Ok(Box::new(VorbisTag::read_from(reader, &format)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,8 +151,8 @@ pub enum TagType {
|
|||
feature = "format-opus",
|
||||
feature = "format-flac"
|
||||
))]
|
||||
/// Represents multiple formats, see [`VorbisFormat`](VorbisFormat) for extensions.
|
||||
Vorbis(VorbisFormat),
|
||||
/// Represents multiple formats, see [`OggFormat`](OggFormat) for extensions.
|
||||
Ogg(OggFormat),
|
||||
#[cfg(feature = "format-riff")]
|
||||
/// Metadata stored in a RIFF INFO chunk
|
||||
/// Common file extensions: `.wav, .wave, .riff`
|
||||
|
@ -144,10 +166,10 @@ pub enum TagType {
|
|||
feature = "format-flac"
|
||||
))]
|
||||
/// File formats using vorbis comments
|
||||
pub enum VorbisFormat {
|
||||
pub enum OggFormat {
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
/// Common file extensions: `.ogg, .oga`
|
||||
Ogg,
|
||||
Vorbis,
|
||||
#[cfg(feature = "format-opus")]
|
||||
/// Common file extensions: `.opus`
|
||||
Opus,
|
||||
|
@ -180,32 +202,41 @@ impl TagType {
|
|||
#[cfg(all(feature = "format-riff", feature = "format-id3"))]
|
||||
"wav" | "wave" | "riff" => Ok(Self::Id3v2(Id3Format::Riff)),
|
||||
#[cfg(feature = "format-opus")]
|
||||
"opus" => Ok(Self::Vorbis(VorbisFormat::Opus)),
|
||||
"opus" => Ok(Self::Ogg(OggFormat::Opus)),
|
||||
#[cfg(feature = "format-flac")]
|
||||
"flac" => Ok(Self::Vorbis(VorbisFormat::Flac)),
|
||||
"flac" => Ok(Self::Ogg(OggFormat::Flac)),
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
"ogg" | "oga" => Ok(Self::Vorbis(VorbisFormat::Ogg)),
|
||||
"ogg" | "oga" => Ok(Self::Ogg(OggFormat::Vorbis)),
|
||||
#[cfg(feature = "format-mp4")]
|
||||
"m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::Mp4),
|
||||
_ => Err(LoftyError::UnsupportedFormat(ext.to_owned())),
|
||||
_ => Err(LoftyError::UnsupportedFormat(ext.to_string())),
|
||||
}
|
||||
}
|
||||
fn try_from_sig(data: &[u8]) -> Result<Self> {
|
||||
if data.is_empty() {
|
||||
|
||||
fn try_from_sig<R>(data: &mut R) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
if data.seek(SeekFrom::End(0))? == 0 {
|
||||
return Err(LoftyError::EmptyFile);
|
||||
}
|
||||
|
||||
match data[0] {
|
||||
#[cfg(feature = "format-ape")]
|
||||
77 if data.starts_with(&MAC) => Ok(Self::Ape),
|
||||
#[cfg(feature = "format-id3")]
|
||||
73 if data.starts_with(&ID3) => Ok(Self::Id3v2(Id3Format::Default)),
|
||||
#[cfg(feature = "format-id3")]
|
||||
70 if data.starts_with(&FORM) => {
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||
use std::io::{Cursor, SeekFrom};
|
||||
data.seek(SeekFrom::Start(0))?;
|
||||
|
||||
let mut sig = vec![0; 8];
|
||||
data.read_exact(&mut sig)?;
|
||||
|
||||
data.seek(SeekFrom::Start(0))?;
|
||||
|
||||
match sig.first().unwrap() {
|
||||
#[cfg(feature = "format-ape")]
|
||||
77 if sig.starts_with(&MAC) => Ok(Self::Ape),
|
||||
#[cfg(feature = "format-id3")]
|
||||
73 if sig.starts_with(&ID3) => Ok(Self::Id3v2(Id3Format::Default)),
|
||||
#[cfg(feature = "format-id3")]
|
||||
70 if sig.starts_with(&FORM) => {
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||
|
||||
let mut data = Cursor::new(data);
|
||||
let mut found_id3 = false;
|
||||
|
||||
while let (Ok(fourcc), Ok(size)) = (
|
||||
|
@ -227,6 +258,8 @@ impl TagType {
|
|||
))))?;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Start(0))?;
|
||||
|
||||
if found_id3 {
|
||||
return Ok(Self::Id3v2(Id3Format::Form));
|
||||
}
|
||||
|
@ -235,27 +268,33 @@ impl TagType {
|
|||
Err(LoftyError::UnknownFormat)
|
||||
},
|
||||
#[cfg(feature = "format-flac")]
|
||||
102 if data.starts_with(&FLAC) => Ok(Self::Vorbis(VorbisFormat::Flac)),
|
||||
102 if sig.starts_with(&FLAC) => Ok(Self::Ogg(OggFormat::Flac)),
|
||||
#[cfg(any(feature = "format-vorbis", feature = "format-opus"))]
|
||||
79 if data.starts_with(&OGGS) => {
|
||||
if data[29..35] == VORBIS {
|
||||
return Ok(Self::Vorbis(VorbisFormat::Ogg));
|
||||
79 if sig.starts_with(&OGGS) => {
|
||||
data.seek(SeekFrom::Start(28))?;
|
||||
|
||||
let mut ident_sig = vec![0; 8];
|
||||
data.read_exact(&mut ident_sig)?;
|
||||
|
||||
data.seek(SeekFrom::Start(0))?;
|
||||
|
||||
if ident_sig[1..7] == VORBIS {
|
||||
return Ok(Self::Ogg(OggFormat::Vorbis));
|
||||
}
|
||||
|
||||
if data[28..36] == OPUSHEAD {
|
||||
return Ok(Self::Vorbis(VorbisFormat::Opus));
|
||||
if ident_sig[..] == OPUSHEAD {
|
||||
return Ok(Self::Ogg(OggFormat::Opus));
|
||||
}
|
||||
|
||||
Err(LoftyError::UnknownFormat)
|
||||
},
|
||||
#[cfg(feature = "format-riff")]
|
||||
82 if data.starts_with(&RIFF) => {
|
||||
82 if sig.starts_with(&RIFF) => {
|
||||
#[cfg(feature = "format-id3")]
|
||||
{
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use std::io::Cursor;
|
||||
|
||||
let mut data = Cursor::new(&data[12..]);
|
||||
data.seek(SeekFrom::Start(12))?;
|
||||
|
||||
let mut found_id3 = false;
|
||||
|
||||
|
@ -268,9 +307,11 @@ impl TagType {
|
|||
break;
|
||||
}
|
||||
|
||||
data.set_position(data.position() + u64::from(size))
|
||||
data.seek(SeekFrom::Current(i64::from(size)))?;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Start(0))?;
|
||||
|
||||
if found_id3 {
|
||||
return Ok(Self::Id3v2(Id3Format::Riff));
|
||||
}
|
||||
|
@ -279,7 +320,7 @@ impl TagType {
|
|||
Ok(Self::RiffInfo)
|
||||
},
|
||||
#[cfg(feature = "format-mp4")]
|
||||
_ if data[4..8] == FTYP => Ok(Self::Mp4),
|
||||
_ if sig[4..8] == FTYP => Ok(Self::Mp4),
|
||||
_ => Err(LoftyError::UnknownFormat),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue