Skip to main content

audiobox/
types.rs

1use crate::{Error, Result};
2use std::{collections::BTreeMap, str::FromStr};
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub enum AudioFormat {
6    Wav,
7    Aiff,
8    Caf,
9    Au,
10    Flac,
11    Mp3,
12    /// Ogg Vorbis I, decode only.
13    Vorbis,
14    /// Ogg Opus, decode only, with 48 kHz output.
15    Opus,
16    Raw,
17}
18impl FromStr for AudioFormat {
19    type Err = Error;
20    fn from_str(s: &str) -> Result<Self> {
21        match s.to_ascii_lowercase().as_str() {
22            "wav" | "wave" => Ok(Self::Wav),
23            "aiff" | "aif" | "aifc" => Ok(Self::Aiff),
24            "caf" => Ok(Self::Caf),
25            "au" | "snd" => Ok(Self::Au),
26            "flac" => Ok(Self::Flac),
27            "mp3" => Ok(Self::Mp3),
28            "ogg" | "oga" | "vorbis" => Ok(Self::Vorbis),
29            "opus" => Ok(Self::Opus),
30            "raw" | "pcm" => Ok(Self::Raw),
31            _ => Err(Error::new(
32                crate::ErrorCode::UnsupportedFormat,
33                format!("Unsupported format: {s}"),
34            )),
35        }
36    }
37}
38
39#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
40pub enum SampleFormat {
41    U8,
42    S8,
43    #[default]
44    S16,
45    S24,
46    S32,
47    F32,
48    F64,
49    Alaw,
50    Ulaw,
51}
52impl SampleFormat {
53    pub fn bytes(self) -> usize {
54        match self {
55            Self::U8 | Self::S8 | Self::Alaw | Self::Ulaw => 1,
56            Self::S16 => 2,
57            Self::S24 => 3,
58            Self::S32 | Self::F32 => 4,
59            Self::F64 => 8,
60        }
61    }
62    pub fn bits(self) -> u16 {
63        (self.bytes() * 8) as u16
64    }
65}
66
67/// Optional noise at final linear integer PCM quantization.
68#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
69pub enum Dither {
70    /// Round and clamp without added noise; preserves the existing encoded bytes.
71    #[default]
72    None,
73    /// Add triangular noise in (-1, 1) destination LSBs before rounding and clamping.
74    /// Each channel has its own deterministic stream, restarted for each export.
75    /// Supported for U8/S8/S16/S24/S32 PCM, including FLAC. Float, G.711 and MP3
76    /// output reject this option with [`crate::ErrorCode::InvalidArgument`].
77    Tpdf { seed: u64 },
78}
79
80#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
81pub enum Endian {
82    #[default]
83    Little,
84    Big,
85}
86
87/// Allocation and duration limits checked before allocating decoded PCM.
88#[derive(Clone, Debug)]
89pub struct Limits {
90    pub max_decoded_bytes: usize,
91    pub max_duration_seconds: f64,
92    pub max_channels: usize,
93    pub min_sample_rate: u32,
94    pub max_sample_rate: u32,
95    pub max_frames: usize,
96}
97impl Default for Limits {
98    fn default() -> Self {
99        Self {
100            max_decoded_bytes: 512 * 1024 * 1024,
101            max_duration_seconds: 21600.0,
102            max_channels: 64,
103            min_sample_rate: 1000,
104            max_sample_rate: 768000,
105            max_frames: u32::MAX as usize,
106        }
107    }
108}
109impl Limits {
110    pub fn check(&self, frames: usize, channels: usize, rate: u32) -> Result<()> {
111        if !self.max_duration_seconds.is_finite() || self.max_duration_seconds < 0.0 {
112            return Err(Error::invalid(
113                "Duration limit must be finite and non-negative",
114            ));
115        }
116        if rate == 0 || rate < self.min_sample_rate || rate > self.max_sample_rate {
117            return Err(Error::limit("Sample rate exceeds decode limits"));
118        }
119        if channels == 0 || channels > self.max_channels {
120            return Err(Error::limit("Channel count exceeds decode limits"));
121        }
122        if frames > self.max_frames {
123            return Err(Error::limit("Frame count exceeds decode limits"));
124        }
125        if frames as f64 / rate as f64 > self.max_duration_seconds {
126            return Err(Error::limit("Duration exceeds decode limits"));
127        }
128        let bytes = frames
129            .checked_mul(channels)
130            .and_then(|v| v.checked_mul(4))
131            .ok_or_else(|| Error::limit("Decoded size overflow"))?;
132        if bytes > self.max_decoded_bytes {
133            return Err(Error::limit("Decoded bytes exceed decode limits"));
134        }
135        Ok(())
136    }
137    /// Makes room for `next` samples in one channel of whole-file output, which the
138    /// caller has checked against these limits. Capacity grows by half at a time,
139    /// fallibly, up to the limits and `cap`, a length the stream cannot exceed: exact
140    /// growth per frame can copy all prior samples on every frame.
141    pub(crate) fn reserve_output(
142        &self,
143        output: &mut Vec<f32>,
144        next: usize,
145        channels: usize,
146        rate: u32,
147        cap: usize,
148        message: &'static str,
149    ) -> Result<()> {
150        if next <= output.capacity() {
151            return Ok(());
152        }
153        let ceiling = self
154            .max_frames
155            .min(self.max_decoded_bytes / channels / size_of::<f32>())
156            .min((self.max_duration_seconds * rate as f64).floor() as usize)
157            .min(cap);
158        let grown = output.capacity().saturating_add(output.capacity() / 2);
159        let target = next.max(grown.min(ceiling));
160        // Retain the caller's check at floating-point duration boundaries even if the
161        // speculative target cannot be used.
162        let target = if self.check(target, channels, rate).is_ok() {
163            target
164        } else {
165            next
166        };
167        output
168            .try_reserve_exact(target - output.len())
169            .map_err(|_| Error::limit(message))
170    }
171}
172
173pub type AudioMetadata = BTreeMap<String, String>;
174#[derive(Clone, Debug, Default)]
175pub struct DecodeInfo {
176    pub format: Option<AudioFormat>,
177    pub sample_format: Option<SampleFormat>,
178    pub metadata: AudioMetadata,
179    /// Structured publishing tags and the first supported front cover.
180    pub tags: crate::Tags,
181}
182#[derive(Clone, Debug, Default)]
183pub struct DecodeOptions {
184    pub format: Option<AudioFormat>,
185    pub limits: Limits,
186}
187#[derive(Clone, Debug)]
188pub struct EncodeOptions {
189    pub sample_format: SampleFormat,
190    pub endian: Endian,
191    /// Noise added once at the destination integer quantizer. Off by default.
192    pub dither: Dither,
193    pub metadata: AudioMetadata,
194    /// Structured publishing tags, supported by WAV, FLAC and MP3. Native map keys
195    /// remain available through `metadata`; supplying the same field twice is an error.
196    pub tags: crate::Tags,
197    /// Optional WAVEFORMATEXTENSIBLE speaker mask for PCM/float WAV. Its set-bit
198    /// count must equal the audio channels; only the standard low 18 bits are valid.
199    /// Other output formats reject it. None preserves the existing WAV encoding.
200    pub wav_channel_mask: Option<u32>,
201    /// MP3 bitrate in kbit/s.
202    pub bitrate: u32,
203    /// FLAC compression level, 0 through 8.
204    pub compression_level: u8,
205}
206impl Default for EncodeOptions {
207    fn default() -> Self {
208        Self {
209            sample_format: SampleFormat::S16,
210            endian: Endian::Little,
211            dither: Dither::None,
212            metadata: AudioMetadata::new(),
213            tags: crate::Tags::default(),
214            bitrate: 192,
215            wav_channel_mask: None,
216            compression_level: 5,
217        }
218    }
219}
220
221/// Seconds, an exact frame position, or a parsed timestamp. Negative positions count from the end.
222#[derive(Clone, Copy, Debug)]
223pub enum TimePosition {
224    Seconds(f64),
225    Frames(i64),
226}
227impl From<f64> for TimePosition {
228    fn from(value: f64) -> Self {
229        Self::Seconds(value)
230    }
231}
232impl FromStr for TimePosition {
233    type Err = Error;
234    fn from_str(value: &str) -> Result<Self> {
235        let text = value.trim().to_ascii_lowercase();
236        let parse = |s: &str| {
237            s.trim()
238                .parse::<f64>()
239                .map_err(|_| Error::invalid("Invalid time position"))
240        };
241        let seconds = if let Some(s) = text.strip_suffix("ms") {
242            parse(s)? / 1000.0
243        } else if let Some(s) = text
244            .strip_suffix("seconds")
245            .or_else(|| text.strip_suffix("second"))
246            .or_else(|| text.strip_suffix("sec"))
247            .or_else(|| text.strip_suffix('s'))
248        {
249            parse(s)?
250        } else if text.contains(':') {
251            let parts: Vec<_> = text.split(':').collect();
252            if parts.len() > 3 {
253                return Err(Error::invalid("Too many timestamp components"));
254            }
255            let mut total = 0.0;
256            for part in parts {
257                let n = parse(part)?;
258                if n < 0.0 {
259                    return Err(Error::invalid("Negative timestamp component"));
260                }
261                total = total * 60.0 + n;
262            }
263            total
264        } else {
265            parse(&text)?
266        };
267        if !seconds.is_finite() {
268            return Err(Error::invalid("Time must be finite"));
269        }
270        Ok(Self::Seconds(seconds))
271    }
272}
273impl TimePosition {
274    pub(crate) fn index(self, rate: u32, frames: usize) -> Result<usize> {
275        let n = match self {
276            Self::Frames(n) => n as f64,
277            Self::Seconds(s) => s * rate as f64,
278        };
279        if !n.is_finite() {
280            return Err(Error::invalid("Time must be finite"));
281        }
282        let n = n.round();
283        Ok((if n < 0.0 { frames as f64 + n } else { n }).clamp(0.0, frames as f64) as usize)
284    }
285    pub(crate) fn duration(self, rate: u32) -> Result<usize> {
286        let n = match self {
287            Self::Frames(n) => n as f64,
288            Self::Seconds(s) => s * rate as f64,
289        };
290        if !n.is_finite() || n < 0.0 || n > usize::MAX as f64 {
291            return Err(Error::invalid("Invalid duration"));
292        }
293        Ok(n.round() as usize)
294    }
295}