Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "3"

members = [
"aud_io",
"lofty",
"lofty_attr",
"ogg_pager",
Expand All @@ -10,17 +11,21 @@ members = [
]

[workspace.package]
authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"]
edition = "2024"
rust-version = "1.85"
repository = "https://github.com/Serial-ATA/lofty-rs"
license = "MIT OR Apache-2.0"

[workspace.dependencies]
aud_io = { version = "0.1.0", path = "aud_io" }
lofty = { version = "0.22.4", path = "lofty" }
lofty_attr = { version = "0.11.1", path = "lofty_attr" }
ogg_pager = { version = "0.7.0", path = "ogg_pager" }

byteorder = "1.5.0"
log = "0.4.28"
test-log = "0.2.18"

[workspace.lints.rust]
missing_docs = "deny"
Expand Down
23 changes: 23 additions & 0 deletions aud_io/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "aud_io"
version = "0.1.0"
description = "" # TODO
keywords = ["audio", "mp4"]
categories = ["multimedia", "multimedia::audio", "parser-implementations"]
readme = "" # TODO
include = ["src", "../LICENSE-APACHE", "../LICENSE-MIT"]
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
byteorder = { workspace = true }
log = { workspace = true }

[dev-dependencies]
test-log = { workspace = true }

# TODO
#[lints]
#workspace = true
18 changes: 18 additions & 0 deletions aud_io/src/aac/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::fmt::Display;

#[derive(Debug)]
pub enum AacError {
BadSampleRate,
}

impl Display for AacError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
AacError::BadSampleRate => {
f.write_str("File contains an invalid sample frequency index")
},
}
}
}

impl core::error::Error for AacError {}
42 changes: 21 additions & 21 deletions lofty/src/aac/header.rs → aud_io/src/aac/header.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
use super::error::AacError;
use crate::config::ParsingMode;
use crate::error::Result;
use crate::macros::decode_err;
use crate::mp4::{AudioObjectType, SAMPLE_RATES};
use crate::mpeg::MpegVersion;

use std::io::{Read, Seek, SeekFrom};

// Used to compare the headers up to the home bit.
// If they aren't equal, something is broken.
pub(super) const HEADER_MASK: u32 = 0xFFFF_FFE0;
use std::io::Read;

#[derive(Copy, Clone)]
pub(crate) struct ADTSHeader {
pub(crate) version: MpegVersion,
pub(crate) audio_object_ty: AudioObjectType,
pub(crate) sample_rate: u32,
pub(crate) channels: u8,
pub(crate) copyright: bool,
pub(crate) original: bool,
pub(crate) len: u16,
pub(crate) bitrate: u32,
pub(crate) bytes: [u8; 7],
pub(crate) has_crc: bool,
pub struct ADTSHeader {
pub version: MpegVersion,
pub audio_object_ty: AudioObjectType,
pub sample_rate: u32,
pub channels: u8,
pub copyright: bool,
pub original: bool,
pub len: u16,
pub bitrate: u32,
pub bytes: [u8; 7],
pub has_crc: bool,
}

impl ADTSHeader {
pub(super) fn read<R>(reader: &mut R, _parse_mode: ParsingMode) -> Result<Option<Self>>
/// Used to compare ADTS headers up to the `home` bit. If they aren't equal, then something's broken
/// in the input.
pub const COMPARISON_MASK: u32 = 0xFFFF_FFE0;

pub fn read<R>(reader: &mut R, _parse_mode: ParsingMode) -> Result<Option<Self>>
where
R: Read + Seek,
R: Read,
{
// The ADTS header consists of 7 bytes, or 9 bytes with a CRC
let mut needs_crc_skip = false;
Expand Down Expand Up @@ -81,7 +81,7 @@ impl ADTSHeader {
let sample_rate_idx = (byte3 >> 2) & 0b1111;
if sample_rate_idx == 15 {
// 15 is forbidden
decode_err!(@BAIL Aac, "File contains an invalid sample frequency index");
return Err(AacError::BadSampleRate.into());
}

let sample_rate = SAMPLE_RATES[sample_rate_idx as usize];
Expand All @@ -106,7 +106,7 @@ impl ADTSHeader {

if needs_crc_skip {
log::debug!("Skipping CRC");
reader.seek(SeekFrom::Current(2))?;
reader.read_exact(&mut [0; 2])?;
}

Ok(Some(ADTSHeader {
Expand Down
4 changes: 4 additions & 0 deletions aud_io/src/aac/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod header;
pub use header::*;

pub mod error;
28 changes: 14 additions & 14 deletions lofty/src/util/alloc.rs → aud_io/src/alloc.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::error::Result;
use crate::macros::err;

use crate::config::global_options;
use crate::err;
use crate::error::Result;

/// Provides the `fallible_repeat` method on `Vec`
/// Provides the `fallible_repeat` method on [`Vec`]
///
/// It is intended to be used in [`try_vec!`](crate::macros::try_vec).
trait VecFallibleRepeat<T>: Sized {
/// It is intended to be used in [`try_vec!`](crate::try_vec).
pub trait VecFallibleRepeat<T>: Sized {
fn fallible_repeat(self, element: T, expected_size: usize) -> Result<Self>
where
T: Clone;
Expand Down Expand Up @@ -46,23 +45,24 @@ impl<T> VecFallibleRepeat<T> for Vec<T> {

/// **DO NOT USE DIRECTLY**
///
/// Creates a `Vec` of the specified length, containing copies of `element`.
/// Creates a [`Vec`] of the specified length, containing copies of `element`.
///
/// This should be used through [`try_vec!`](crate::macros::try_vec)
pub(crate) fn fallible_vec_from_element<T>(element: T, expected_size: usize) -> Result<Vec<T>>
/// This should be used through [`try_vec!`](crate::try_vec)
#[doc(hidden)]
pub fn fallible_vec_from_element<T>(element: T, expected_size: usize) -> Result<Vec<T>>
where
T: Clone,
{
Vec::new().fallible_repeat(element, expected_size)
}

/// Provides the `try_with_capacity` method on `Vec`
/// Provides the `try_with_capacity` method on [`Vec`]
///
/// This can be used directly.
pub(crate) trait VecFallibleCapacity<T>: Sized {
/// Same as `Vec::with_capacity`, but takes `GlobalOptions::allocation_limit` into account.
pub trait VecFallibleCapacity<T>: Sized {
/// Same as [`Vec::with_capacity()`], but takes [`GlobalOptions::allocation_limit()`] into account.
///
/// Named `try_with_capacity_stable` to avoid conflicts with the nightly `Vec::try_with_capacity`.
/// Named `try_with_capacity_stable` to avoid conflicts with the nightly [`Vec::try_with_capacity()`].
fn try_with_capacity_stable(capacity: usize) -> Result<Self>;
}

Expand All @@ -81,7 +81,7 @@ impl<T> VecFallibleCapacity<T> for Vec<T> {

#[cfg(test)]
mod tests {
use crate::util::alloc::fallible_vec_from_element;
use super::fallible_vec_from_element;

#[test_log::test]
fn vec_fallible_repeat() {
Expand Down
103 changes: 103 additions & 0 deletions aud_io/src/config/global_options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::cell::UnsafeCell;

thread_local! {
static GLOBAL_OPTIONS: UnsafeCell<GlobalOptions> = UnsafeCell::new(GlobalOptions::default());
}

pub(crate) unsafe fn global_options() -> &'static GlobalOptions {
GLOBAL_OPTIONS.with(|global_options| unsafe { &*global_options.get() })
}

/// Options that control all interactions with `aud_io` for the current thread
///
/// # Examples
///
/// ```rust
/// use aud_io::config::{GlobalOptions, apply_global_options};
///
/// // I want to double the allocation limit
/// let global_options =
/// GlobalOptions::new().allocation_limit(GlobalOptions::DEFAULT_ALLOCATION_LIMIT * 2);
/// apply_global_options(global_options);
/// ```
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
#[non_exhaustive]
pub struct GlobalOptions {
pub(crate) allocation_limit: usize,
}

impl GlobalOptions {
/// Default allocation limit for any single allocation
pub const DEFAULT_ALLOCATION_LIMIT: usize = 16 * 1024 * 1024;

/// Creates a new `GlobalOptions`, alias for `Default` implementation
///
/// See also: [`GlobalOptions::default`]
///
/// # Examples
///
/// ```rust
/// use aud_io::config::GlobalOptions;
///
/// let global_options = GlobalOptions::new();
/// ```
#[must_use]
pub const fn new() -> Self {
Self {
allocation_limit: Self::DEFAULT_ALLOCATION_LIMIT,
}
}

/// The maximum number of bytes to allocate for any single allocation
///
/// This is a safety measure to prevent allocating too much memory for a single allocation. If an allocation
/// exceeds this limit, the allocator will return [`AudioError::TooMuchData`](crate::error::AudioError::TooMuchData).
///
/// # Examples
///
/// ```rust
/// use aud_io::config::{GlobalOptions, apply_global_options};
///
/// // I have files with gigantic images, I'll double the allocation limit!
/// let global_options =
/// GlobalOptions::new().allocation_limit(GlobalOptions::DEFAULT_ALLOCATION_LIMIT * 2);
/// apply_global_options(global_options);
/// ```
pub fn allocation_limit(&mut self, allocation_limit: usize) -> Self {
self.allocation_limit = allocation_limit;
*self
}
}

impl Default for GlobalOptions {
/// The default implementation for `GlobalOptions`
///
/// The defaults are as follows:
///
/// ```rust,ignore
/// GlobalOptions {
/// allocation_limit: Self::DEFAULT_ALLOCATION_LIMIT,
/// }
/// ```
fn default() -> Self {
Self::new()
}
}

/// Applies the given `GlobalOptions` to the current thread
///
/// # Examples
///
/// ```rust
/// use aud_io::config::{GlobalOptions, apply_global_options};
///
/// // I want to double the allocation limit
/// let global_options =
/// GlobalOptions::new().allocation_limit(GlobalOptions::DEFAULT_ALLOCATION_LIMIT * 2);
/// apply_global_options(global_options);
/// ```
pub fn apply_global_options(options: GlobalOptions) {
GLOBAL_OPTIONS.with(|global_options| unsafe {
*global_options.get() = options;
});
}
5 changes: 5 additions & 0 deletions aud_io/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod global_options;
pub use global_options::*;

mod parse;
pub use parse::*;
52 changes: 52 additions & 0 deletions aud_io/src/config/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/// The parsing strictness mode
///
/// This can be set with [`Probe::options`](crate::probe::Probe).
///
/// # Examples
///
/// ```rust,no_run
/// use lofty::config::{ParseOptions, ParsingMode};
/// use lofty::probe::Probe;
///
/// # fn main() -> lofty::error::Result<()> {
/// // We only want to read spec-compliant inputs
/// let parsing_options = ParseOptions::new().parsing_mode(ParsingMode::Strict);
/// let tagged_file = Probe::open("foo.mp3")?.options(parsing_options).read()?;
/// # Ok(()) }
/// ```
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Default)]
#[non_exhaustive]
pub enum ParsingMode {
/// Will eagerly error on invalid input
///
/// This mode will eagerly error on any non-spec-compliant input.
///
/// ## Examples of behavior
///
/// * Unable to decode text - The parser will error and the entire input is discarded
/// * Unable to determine the sample rate - The parser will error and the entire input is discarded
Strict,
/// Default mode, less eager to error on recoverably malformed input
///
/// This mode will attempt to fill in any holes where possible in otherwise valid, spec-compliant input.
///
/// NOTE: A readable input does *not* necessarily make it writeable.
///
/// ## Examples of behavior
///
/// * Unable to decode text - If valid otherwise, the field will be replaced by an empty string and the parser moves on
/// * Unable to determine the sample rate - The sample rate will be 0
#[default]
BestAttempt,
/// Least eager to error, may produce invalid/partial output
///
/// This mode will discard any invalid fields, and ignore the majority of non-fatal errors.
///
/// If the input is malformed, the resulting tags may be incomplete, and the properties zeroed.
///
/// ## Examples of behavior
///
/// * Unable to decode text - The entire item is discarded and the parser moves on
/// * Unable to determine the sample rate - The sample rate will be 0
Relaxed,
}
Loading
Loading