use std::error::Error as StdError;
use std::io::{Read, Seek, Write};
use std::path::Path;
use std::time::Duration;
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Serialize, Serializer};
#[macro_use]
mod tracing_macros;
#[cfg(feature = "serde")]
pub mod serde_helpers;
#[cfg(feature = "serde")]
pub mod snapshot;
pub use file::{DynamicFileType, DynamicStreamInfo, File};
pub const VERSION_STRING: &str = env!("CARGO_PKG_VERSION");
pub mod constants;
pub mod diff;
mod file;
pub mod iff;
pub mod limits;
pub mod replaygain;
pub mod riff;
pub mod tagmap;
pub mod tags;
pub mod util;
pub mod vorbis;
pub mod asf;
pub mod id3;
pub mod m4a;
pub mod mp3;
pub mod mp4;
pub mod aac;
pub mod ac3;
pub mod aiff;
pub mod apev2;
pub mod dsdiff;
pub mod dsf;
pub mod easyid3;
pub mod easymp4;
pub mod flac;
pub mod monkeysaudio;
pub mod musepack;
pub mod ogg;
pub mod oggflac;
pub mod oggopus;
pub mod oggspeex;
pub mod oggtheora;
pub mod oggvorbis;
pub mod optimfrog;
pub mod smf;
pub mod tak;
pub mod trueaudio;
pub mod wave;
pub mod wavpack;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum AudexError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Unsupported format: {0}")]
UnsupportedFormat(String),
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Header not found")]
HeaderNotFound,
#[error("Format-specific error: {0}")]
FormatError(Box<dyn StdError + Send + Sync>),
#[error("Operation not supported: {0}")]
Unsupported(String),
#[error("Feature not implemented: {0}")]
NotImplemented(String),
#[error("Invalid operation: {0}")]
InvalidOperation(String),
#[error("Internal error: {0}")]
InternalError(String),
#[error("ASF error: {0}")]
ASF(#[from] asf::util::ASFError),
#[error("FLAC: No header found")]
FLACNoHeader,
#[error("FLAC: Vorbis comment error")]
FLACVorbis,
#[error("APE: No header found")]
APENoHeader,
#[error("APE: Bad item error: {0}")]
APEBadItem(String),
#[error("APE: Unsupported version")]
APEUnsupportedVersion,
#[error("WAV error: {0}")]
WAVError(String),
#[error("WAV: Invalid chunk: {0}")]
WAVInvalidChunk(String),
#[error("AAC error: {0}")]
AACError(String),
#[error("AC3 error: {0}")]
AC3Error(String),
#[error("AIFF error: {0}")]
AIFFError(String),
#[error("IFF error: {0}")]
IFFError(String),
#[error("Musepack header error: {0}")]
MusepackHeaderError(String),
#[error("TrueAudio header error: {0}")]
TrueAudioHeaderError(String),
#[error("TAK header error: {0}")]
TAKHeaderError(String),
#[error("WAVPACK header error: {0}")]
WavPackHeaderError(String),
#[error("OptimFROG header error: {0}")]
OptimFROGHeaderError(String),
#[error("Monkey's Audio header error: {0}")]
MonkeysAudioHeaderError(String),
#[error("DSF error: {0}")]
DSFError(String),
#[error("Method not implemented: {0}")]
NotImplementedMethod(String),
#[error("Tag operation not supported for this format: {0}")]
TagOperationUnsupported(String),
#[error("ID3 header not found - file doesn't start with an ID3 tag")]
ID3NoHeaderError,
#[error("ID3 bad unsynchronization data")]
ID3BadUnsynchData,
#[error("ID3 frame too short: expected at least {expected} bytes, got {actual}")]
ID3FrameTooShort { expected: usize, actual: usize },
#[error("Nesting depth exceeds maximum allowed depth of {max_depth}")]
DepthLimitExceeded { max_depth: u32 },
}
#[cfg(feature = "serde")]
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct SerializableError {
pub kind: String,
pub message: String,
}
#[cfg(feature = "serde")]
impl From<&AudexError> for SerializableError {
fn from(err: &AudexError) -> Self {
let kind = match err {
AudexError::Io(_) => "Io",
AudexError::UnsupportedFormat(_) => "UnsupportedFormat",
AudexError::InvalidData(_) => "InvalidData",
AudexError::ParseError(_) => "ParseError",
AudexError::HeaderNotFound => "HeaderNotFound",
AudexError::FormatError(_) => "FormatError",
AudexError::Unsupported(_) => "Unsupported",
AudexError::NotImplemented(_) => "NotImplemented",
AudexError::InvalidOperation(_) => "InvalidOperation",
AudexError::ASF(_) => "ASF",
AudexError::FLACNoHeader => "FLACNoHeader",
AudexError::FLACVorbis => "FLACVorbis",
AudexError::APENoHeader => "APENoHeader",
AudexError::APEBadItem(_) => "APEBadItem",
AudexError::APEUnsupportedVersion => "APEUnsupportedVersion",
AudexError::WAVError(_) => "WAVError",
AudexError::WAVInvalidChunk(_) => "WAVInvalidChunk",
AudexError::AACError(_) => "AACError",
AudexError::AC3Error(_) => "AC3Error",
AudexError::AIFFError(_) => "AIFFError",
AudexError::IFFError(_) => "IFFError",
AudexError::MusepackHeaderError(_) => "MusepackHeaderError",
AudexError::TrueAudioHeaderError(_) => "TrueAudioHeaderError",
AudexError::TAKHeaderError(_) => "TAKHeaderError",
AudexError::WavPackHeaderError(_) => "WavPackHeaderError",
AudexError::OptimFROGHeaderError(_) => "OptimFROGHeaderError",
AudexError::MonkeysAudioHeaderError(_) => "MonkeysAudioHeaderError",
AudexError::DSFError(_) => "DSFError",
AudexError::NotImplementedMethod(_) => "NotImplementedMethod",
AudexError::TagOperationUnsupported(_) => "TagOperationUnsupported",
AudexError::ID3NoHeaderError => "ID3NoHeaderError",
AudexError::ID3BadUnsynchData => "ID3BadUnsynchData",
AudexError::ID3FrameTooShort { .. } => "ID3FrameTooShort",
AudexError::DepthLimitExceeded { .. } => "DepthLimitExceeded",
AudexError::InternalError(_) => "InternalError",
};
SerializableError {
kind: kind.to_string(),
message: err.to_string(),
}
}
}
#[cfg(feature = "serde")]
impl AudexError {
pub fn to_serializable(&self) -> SerializableError {
SerializableError::from(self)
}
}
#[cfg(feature = "serde")]
impl Serialize for AudexError {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
self.to_serializable().serialize(serializer)
}
}
pub type Result<T> = std::result::Result<T, AudexError>;
pub use tags::{BasicTags, Metadata, MetadataFields, PaddingInfo, Tags};
pub use tagmap::{
ConversionOptions, ConversionReport, SkipReason, StandardField, TagMap, convert_tags,
convert_tags_with_options,
};
pub use diff::{
DiffOptions, FieldChange, FieldEntry, StreamInfoDiff, TagDiff, diff_normalized,
diff_normalized_with_options,
};
#[cfg(feature = "serde")]
pub use snapshot::{StreamInfoSnapshot, TagSnapshot};
#[cfg(feature = "async")]
pub use file::detect_format_async;
pub use file::detect_ogg_format;
#[cfg(feature = "async")]
pub use mp4::clear_async as mp4_clear_async;
#[cfg(feature = "async")]
pub use ogg::seek_end_async;
#[cfg(feature = "async")]
pub use iff::{
IffChunkAsync, IffFileAsync, RiffFileAsync, delete_chunk_async, insert_iff_chunk_async,
insert_riff_chunk_async, resize_iff_chunk_async, resize_riff_chunk_async,
update_iff_file_size_async, update_riff_file_size_async,
};
#[cfg(feature = "async")]
pub use aiff::{clear_async as aiff_clear_async, open_async as aiff_open_async};
#[cfg(feature = "async")]
pub use wave::{clear_async as wave_clear_async, open_async as wave_open_async};
#[cfg(feature = "async")]
pub use util::{
delete_bytes_async, insert_bytes_async, loadfile_read_async, loadfile_write_async,
read_full_async, resize_bytes_async,
};
pub use util::DEFAULT_BUFFER_SIZE;
pub use limits::ParseLimits;
pub use file::detect_format;
pub use file::detect_format_from_bytes;
pub trait ReadSeek: Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}
pub trait ReadWriteSeek: Read + Write + Seek {}
impl<T: Read + Write + Seek> ReadWriteSeek for T {}
pub trait FileType {
type Tags: Tags;
type Info: StreamInfo;
fn load<P: AsRef<Path>>(path: P) -> Result<Self>
where
Self: Sized;
fn save(&mut self) -> Result<()>;
fn clear(&mut self) -> Result<()>;
fn save_to_writer(&mut self, writer: &mut dyn ReadWriteSeek) -> Result<()> {
let _ = writer;
Err(AudexError::Unsupported(
"This format does not support saving to a writer".to_string(),
))
}
fn clear_writer(&mut self, writer: &mut dyn ReadWriteSeek) -> Result<()> {
let _ = writer;
Err(AudexError::Unsupported(
"This format does not support clearing via a writer".to_string(),
))
}
fn save_to_path(&mut self, path: &Path) -> Result<()> {
let _ = path;
Err(AudexError::Unsupported(
"This format does not support saving to a path".to_string(),
))
}
fn tags(&self) -> Option<&Self::Tags>;
fn tags_mut(&mut self) -> Option<&mut Self::Tags>;
fn info(&self) -> &Self::Info;
fn score(filename: &str, header: &[u8]) -> i32;
fn mime_types() -> &'static [&'static str];
fn format_id() -> &'static str
where
Self: Sized,
{
let full = std::any::type_name::<Self>();
match full.rsplit("::").next() {
Some(short) => short,
None => full,
}
}
fn load_from_reader(reader: &mut dyn ReadSeek) -> Result<Self>
where
Self: Sized,
{
let _ = reader;
Err(AudexError::Unsupported(
"This format does not support loading from a reader".to_string(),
))
}
fn add_tags(&mut self) -> Result<()> {
Err(AudexError::NotImplemented(
"Adding tags not supported for this format".to_string(),
))
}
fn filename(&self) -> Option<&str> {
None
}
fn mime(&self) -> Vec<&'static str> {
Self::mime_types().to_vec()
}
fn pprint(&self) -> String {
let stream_info = self.info().pprint();
let mime_types = self.mime();
let mime_type = mime_types.first().unwrap_or(&"application/octet-stream");
let stream_line = format!("{} ({})", stream_info, mime_type);
if let Some(tags) = self.tags() {
let tags_str = tags.pprint();
if !tags_str.is_empty() {
format!("{}\n{}", stream_line, tags_str)
} else {
stream_line
}
} else {
stream_line
}
}
fn get(&self, key: &str) -> Option<Vec<String>> {
self.tags()?.get(key).map(|slice| slice.to_vec())
}
fn set(&mut self, key: &str, values: Vec<String>) -> Result<()> {
if let Some(tags) = self.tags_mut() {
tags.set(key, values);
Ok(())
} else {
Err(AudexError::Unsupported(
"This format does not support tags".to_string(),
))
}
}
fn remove(&mut self, key: &str) -> Result<()> {
if let Some(tags) = self.tags_mut() {
tags.remove(key);
Ok(())
} else {
Err(AudexError::Unsupported(
"This format does not support tags".to_string(),
))
}
}
fn keys(&self) -> Vec<String> {
self.tags().map(|t| t.keys()).unwrap_or_default()
}
fn contains_key(&self, key: &str) -> bool {
self.tags().map(|t| t.contains_key(key)).unwrap_or(false)
}
fn get_first(&self, key: &str) -> Option<String> {
self.get(key)?.into_iter().next()
}
fn set_single(&mut self, key: &str, value: String) -> Result<()> {
self.set(key, vec![value])
}
fn len(&self) -> usize {
self.keys().len()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn items(&self) -> Vec<(String, Vec<String>)> {
let mut items = Vec::new();
for key in self.keys() {
if let Some(values) = self.get(&key) {
items.push((key, values));
}
}
items
}
fn values(&self) -> Vec<Vec<String>> {
self.items().into_iter().map(|(_, v)| v).collect()
}
fn update(&mut self, other: Vec<(String, Vec<String>)>) -> Result<()> {
for (key, values) in other {
self.set(&key, values)?;
}
Ok(())
}
fn get_or(&self, key: &str, default: Vec<String>) -> Vec<String> {
self.get(key).unwrap_or(default)
}
fn pop(&mut self, key: &str) -> Result<Option<Vec<String>>> {
let values = self.get(key);
if values.is_some() {
self.remove(key)?;
}
Ok(values)
}
fn pop_or(&mut self, key: &str, default: Vec<String>) -> Result<Vec<String>> {
Ok(self.pop(key)?.unwrap_or(default))
}
}
pub trait StreamInfo {
fn length(&self) -> Option<Duration>;
fn bitrate(&self) -> Option<u32>;
fn sample_rate(&self) -> Option<u32>;
fn channels(&self) -> Option<u16>;
fn bits_per_sample(&self) -> Option<u16>;
fn pprint(&self) -> String {
let mut output = String::new();
if let Some(length) = self.length() {
output.push_str(&format!("Length: {:.2}s\n", length.as_secs_f64()));
}
if let Some(bitrate) = self.bitrate() {
output.push_str(&format!("Bitrate: {} bps\n", bitrate));
}
if let Some(sample_rate) = self.sample_rate() {
output.push_str(&format!("Sample Rate: {} Hz\n", sample_rate));
}
if let Some(channels) = self.channels() {
output.push_str(&format!("Channels: {}\n", channels));
}
if let Some(bits_per_sample) = self.bits_per_sample() {
output.push_str(&format!("Bits per Sample: {}\n", bits_per_sample));
}
if output.is_empty() {
"<No stream information available>".to_string()
} else {
output.trim_end().to_string()
}
}
}