mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-14 06:32:33 +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.
|
/// Errors that could occur within Lofty.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum LoftyError {
|
pub enum LoftyError {
|
||||||
|
// File extension/format related errors
|
||||||
/// Unknown file extension.
|
/// Unknown file extension.
|
||||||
#[error("Failed to guess the metadata format based on the file extension.")]
|
#[error("Failed to guess the metadata format based on the file extension.")]
|
||||||
UnknownFileExtension,
|
UnknownFileExtension,
|
||||||
|
/// Unsupported file extension
|
||||||
|
#[error("Unsupported format: {0}")]
|
||||||
|
UnsupportedFormat(String),
|
||||||
/// Unable to guess the format
|
/// Unable to guess the format
|
||||||
#[error("No format could be determined from the provided file.")]
|
#[error("No format could be determined from the provided file.")]
|
||||||
UnknownFormat,
|
UnknownFormat,
|
||||||
|
|
||||||
|
// File data related errors
|
||||||
/// Provided an empty file
|
/// Provided an empty file
|
||||||
#[error("File contains no data")]
|
#[error("File contains no data")]
|
||||||
EmptyFile,
|
EmptyFile,
|
||||||
/// Provided a file with invalid/malformed data
|
/// Provided a file with invalid/malformed data
|
||||||
#[error("File has invalid data")]
|
#[error("File has invalid data: {0}")]
|
||||||
InvalidData,
|
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
|
// Picture related errors
|
||||||
#[error("Unsupported format: {0}")]
|
|
||||||
UnsupportedFormat(String),
|
|
||||||
/// Picture has an unsupported mime type
|
/// Picture has an unsupported mime type
|
||||||
#[error("Unsupported mime type: {0}")]
|
#[error("Unsupported mime type: {0}")]
|
||||||
UnsupportedMimeType(String),
|
UnsupportedMimeType(String),
|
||||||
|
/// Provided an invalid picture
|
||||||
|
#[error("Picture contains invalid data")]
|
||||||
|
NotAPicture,
|
||||||
|
|
||||||
|
// Tag related errors
|
||||||
/// Any error from [`ape`]
|
/// Any error from [`ape`]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ApeTag(#[from] ape::Error),
|
ApeTag(#[from] ape::Error),
|
||||||
|
@ -46,26 +56,16 @@ pub enum LoftyError {
|
||||||
Ogg(#[from] ogg::OggReadError),
|
Ogg(#[from] ogg::OggReadError),
|
||||||
/// Errors that arise while reading/writing to wav files
|
/// Errors that arise while reading/writing to wav files
|
||||||
#[error("Invalid Riff file: {0}")]
|
#[error("Invalid Riff file: {0}")]
|
||||||
Riff(String),
|
Riff(&'static str),
|
||||||
/// Errors that arise while reading/writing to opus files
|
|
||||||
#[error("Invalid Opus file: {0}")]
|
|
||||||
Opus(String),
|
|
||||||
|
|
||||||
/// Arises when provided an invalid picture
|
// Conversions for std Errors
|
||||||
#[error("Picture contains invalid data")]
|
|
||||||
NotAPicture,
|
|
||||||
|
|
||||||
/// If a string isn't Utf8
|
|
||||||
#[error(transparent)]
|
|
||||||
Utf8(#[from] std::str::Utf8Error),
|
|
||||||
/// Unable to convert bytes to a String
|
/// Unable to convert bytes to a String
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
FromUtf8(#[from] std::string::FromUtf8Error),
|
FromUtf8(#[from] std::string::FromUtf8Error),
|
||||||
/// Represents all cases of `std::io::Error`.
|
/// Represents all cases of `std::io::Error`.
|
||||||
#[error(transparent)]
|
#[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>;
|
pub type Result<T> = std::result::Result<T, LoftyError>;
|
||||||
|
|
|
@ -118,7 +118,7 @@ pub use crate::types::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod tag;
|
mod tag;
|
||||||
pub use crate::tag::{Id3Format, Tag, TagType, VorbisFormat};
|
pub use crate::tag::{Id3Format, OggFormat, Tag, TagType};
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
pub use crate::error::{LoftyError, Result};
|
pub use crate::error::{LoftyError, Result};
|
||||||
|
|
131
src/tag.rs
131
src/tag.rs
|
@ -1,7 +1,7 @@
|
||||||
#[allow(clippy::wildcard_imports)]
|
#[allow(clippy::wildcard_imports)]
|
||||||
use crate::components::tags::*;
|
use crate::components::tags::*;
|
||||||
use crate::{AudioTag, LoftyError, Result};
|
use crate::{AudioTag, LoftyError, Result};
|
||||||
use std::io::Seek;
|
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(feature = "format-ape")]
|
#[cfg(feature = "format-ape")]
|
||||||
|
@ -50,24 +50,28 @@ impl Tag {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # 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
|
/// * `path` has an unsupported/unknown extension
|
||||||
///
|
///
|
||||||
/// # Warning
|
/// # Warning
|
||||||
/// Using this on a `wav`/`wave`/`riff` file will **always** assume there's an ID3 tag.
|
/// 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.
|
/// [`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>> {
|
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 tag_type = self.0.clone().unwrap_or({
|
||||||
let extension = path
|
let extension = path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.extension()
|
.extension()
|
||||||
.ok_or(LoftyError::UnknownFileExtension)?;
|
.ok_or(LoftyError::UnknownFileExtension)?;
|
||||||
|
|
||||||
let extension_str = extension.to_str().ok_or(LoftyError::UnknownFileExtension)?;
|
let extension_str = extension.to_str().ok_or(LoftyError::UnknownFileExtension)?;
|
||||||
|
|
||||||
TagType::try_from_ext(extension_str)?
|
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
|
/// Attempts to get the tag format based on the file signature
|
||||||
|
@ -84,30 +88,48 @@ impl Tag {
|
||||||
/// # Warning
|
/// # 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.
|
/// 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>> {
|
pub fn read_from_path_signature(&self, path: impl AsRef<Path>) -> Result<Box<dyn AudioTag>> {
|
||||||
let tag_type = self
|
let mut c = Cursor::new(std::fs::read(&path)?);
|
||||||
.0
|
|
||||||
.clone()
|
|
||||||
.unwrap_or(TagType::try_from_sig(&std::fs::read(path.as_ref())?)?);
|
|
||||||
|
|
||||||
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 {
|
match tag_type {
|
||||||
#[cfg(feature = "format-ape")]
|
#[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")]
|
#[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")]
|
#[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")]
|
#[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(
|
#[cfg(any(
|
||||||
feature = "format-vorbis",
|
feature = "format-vorbis",
|
||||||
feature = "format-flac",
|
feature = "format-flac",
|
||||||
feature = "format-opus"
|
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-opus",
|
||||||
feature = "format-flac"
|
feature = "format-flac"
|
||||||
))]
|
))]
|
||||||
/// Represents multiple formats, see [`VorbisFormat`](VorbisFormat) for extensions.
|
/// Represents multiple formats, see [`OggFormat`](OggFormat) for extensions.
|
||||||
Vorbis(VorbisFormat),
|
Ogg(OggFormat),
|
||||||
#[cfg(feature = "format-riff")]
|
#[cfg(feature = "format-riff")]
|
||||||
/// Metadata stored in a RIFF INFO chunk
|
/// Metadata stored in a RIFF INFO chunk
|
||||||
/// Common file extensions: `.wav, .wave, .riff`
|
/// Common file extensions: `.wav, .wave, .riff`
|
||||||
|
@ -144,10 +166,10 @@ pub enum TagType {
|
||||||
feature = "format-flac"
|
feature = "format-flac"
|
||||||
))]
|
))]
|
||||||
/// File formats using vorbis comments
|
/// File formats using vorbis comments
|
||||||
pub enum VorbisFormat {
|
pub enum OggFormat {
|
||||||
#[cfg(feature = "format-vorbis")]
|
#[cfg(feature = "format-vorbis")]
|
||||||
/// Common file extensions: `.ogg, .oga`
|
/// Common file extensions: `.ogg, .oga`
|
||||||
Ogg,
|
Vorbis,
|
||||||
#[cfg(feature = "format-opus")]
|
#[cfg(feature = "format-opus")]
|
||||||
/// Common file extensions: `.opus`
|
/// Common file extensions: `.opus`
|
||||||
Opus,
|
Opus,
|
||||||
|
@ -180,32 +202,41 @@ impl TagType {
|
||||||
#[cfg(all(feature = "format-riff", feature = "format-id3"))]
|
#[cfg(all(feature = "format-riff", feature = "format-id3"))]
|
||||||
"wav" | "wave" | "riff" => Ok(Self::Id3v2(Id3Format::Riff)),
|
"wav" | "wave" | "riff" => Ok(Self::Id3v2(Id3Format::Riff)),
|
||||||
#[cfg(feature = "format-opus")]
|
#[cfg(feature = "format-opus")]
|
||||||
"opus" => Ok(Self::Vorbis(VorbisFormat::Opus)),
|
"opus" => Ok(Self::Ogg(OggFormat::Opus)),
|
||||||
#[cfg(feature = "format-flac")]
|
#[cfg(feature = "format-flac")]
|
||||||
"flac" => Ok(Self::Vorbis(VorbisFormat::Flac)),
|
"flac" => Ok(Self::Ogg(OggFormat::Flac)),
|
||||||
#[cfg(feature = "format-vorbis")]
|
#[cfg(feature = "format-vorbis")]
|
||||||
"ogg" | "oga" => Ok(Self::Vorbis(VorbisFormat::Ogg)),
|
"ogg" | "oga" => Ok(Self::Ogg(OggFormat::Vorbis)),
|
||||||
#[cfg(feature = "format-mp4")]
|
#[cfg(feature = "format-mp4")]
|
||||||
"m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::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);
|
return Err(LoftyError::EmptyFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
match data[0] {
|
data.seek(SeekFrom::Start(0))?;
|
||||||
#[cfg(feature = "format-ape")]
|
|
||||||
77 if data.starts_with(&MAC) => Ok(Self::Ape),
|
let mut sig = vec![0; 8];
|
||||||
#[cfg(feature = "format-id3")]
|
data.read_exact(&mut sig)?;
|
||||||
73 if data.starts_with(&ID3) => Ok(Self::Id3v2(Id3Format::Default)),
|
|
||||||
#[cfg(feature = "format-id3")]
|
data.seek(SeekFrom::Start(0))?;
|
||||||
70 if data.starts_with(&FORM) => {
|
|
||||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
match sig.first().unwrap() {
|
||||||
use std::io::{Cursor, SeekFrom};
|
#[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;
|
let mut found_id3 = false;
|
||||||
|
|
||||||
while let (Ok(fourcc), Ok(size)) = (
|
while let (Ok(fourcc), Ok(size)) = (
|
||||||
|
@ -227,6 +258,8 @@ impl TagType {
|
||||||
))))?;
|
))))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.seek(SeekFrom::Start(0))?;
|
||||||
|
|
||||||
if found_id3 {
|
if found_id3 {
|
||||||
return Ok(Self::Id3v2(Id3Format::Form));
|
return Ok(Self::Id3v2(Id3Format::Form));
|
||||||
}
|
}
|
||||||
|
@ -235,27 +268,33 @@ impl TagType {
|
||||||
Err(LoftyError::UnknownFormat)
|
Err(LoftyError::UnknownFormat)
|
||||||
},
|
},
|
||||||
#[cfg(feature = "format-flac")]
|
#[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"))]
|
#[cfg(any(feature = "format-vorbis", feature = "format-opus"))]
|
||||||
79 if data.starts_with(&OGGS) => {
|
79 if sig.starts_with(&OGGS) => {
|
||||||
if data[29..35] == VORBIS {
|
data.seek(SeekFrom::Start(28))?;
|
||||||
return Ok(Self::Vorbis(VorbisFormat::Ogg));
|
|
||||||
|
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 {
|
if ident_sig[..] == OPUSHEAD {
|
||||||
return Ok(Self::Vorbis(VorbisFormat::Opus));
|
return Ok(Self::Ogg(OggFormat::Opus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(LoftyError::UnknownFormat)
|
Err(LoftyError::UnknownFormat)
|
||||||
},
|
},
|
||||||
#[cfg(feature = "format-riff")]
|
#[cfg(feature = "format-riff")]
|
||||||
82 if data.starts_with(&RIFF) => {
|
82 if sig.starts_with(&RIFF) => {
|
||||||
#[cfg(feature = "format-id3")]
|
#[cfg(feature = "format-id3")]
|
||||||
{
|
{
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
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;
|
let mut found_id3 = false;
|
||||||
|
|
||||||
|
@ -268,9 +307,11 @@ impl TagType {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.set_position(data.position() + u64::from(size))
|
data.seek(SeekFrom::Current(i64::from(size)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.seek(SeekFrom::Start(0))?;
|
||||||
|
|
||||||
if found_id3 {
|
if found_id3 {
|
||||||
return Ok(Self::Id3v2(Id3Format::Riff));
|
return Ok(Self::Id3v2(Id3Format::Riff));
|
||||||
}
|
}
|
||||||
|
@ -279,7 +320,7 @@ impl TagType {
|
||||||
Ok(Self::RiffInfo)
|
Ok(Self::RiffInfo)
|
||||||
},
|
},
|
||||||
#[cfg(feature = "format-mp4")]
|
#[cfg(feature = "format-mp4")]
|
||||||
_ if data[4..8] == FTYP => Ok(Self::Mp4),
|
_ if sig[4..8] == FTYP => Ok(Self::Mp4),
|
||||||
_ => Err(LoftyError::UnknownFormat),
|
_ => Err(LoftyError::UnknownFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue