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:
Serial 2021-06-26 14:46:30 -04:00
parent ce76d861b6
commit ca0c03ddf1
3 changed files with 107 additions and 66 deletions

View file

@ -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>;

View file

@ -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};

View file

@ -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),
}
}