1use std::error::Error;
4use std::fmt;
5use std::fs::File;
6use std::io;
7use std::path::{Path, PathBuf};
8
9use symphonia::core::codecs::audio::AudioCodecId;
10use symphonia::core::codecs::audio::AudioDecoderOptions;
11use symphonia::core::codecs::audio::well_known::{
12 CODEC_ID_AC3, CODEC_ID_ALAC, CODEC_ID_EAC3, CODEC_ID_MONKEYS_AUDIO, CODEC_ID_MP1, CODEC_ID_MP2,
13 CODEC_ID_OPUS, CODEC_ID_WAVPACK, CODEC_ID_WMA,
14};
15use symphonia::core::errors::Error as SymphoniaError;
16use symphonia::core::formats::probe::Hint;
17use symphonia::core::formats::{FormatOptions, TrackType};
18use symphonia::core::io::{MediaSourceStream, MediaSourceStreamOptions};
19use symphonia::core::meta::MetadataOptions;
20use symphonia::core::units::{Duration, TimeBase};
21
22use super::{Audio, AudioError, SampleRate};
23use crate::units::Seconds;
24
25const CONTAINERS: &str = "WAV, AIFF, FLAC, MP3, AAC (ADTS), MP4 or M4A, Ogg, MKV or WebM";
27
28pub const MIN_SAMPLE_RATE: SampleRate = SampleRate::from_const(8_000);
31pub const MAX_SAMPLE_RATE: SampleRate = SampleRate::from_const(192_000);
34
35pub const MAX_SKIPPED_PACKETS: usize = 4;
41
42#[derive(Clone, Debug, PartialEq)]
44pub struct Decoded {
45 audio: Audio,
46 skipped: Vec<SkippedPacket>,
47}
48
49impl Decoded {
50 #[must_use]
52 pub fn audio(&self) -> &Audio {
53 &self.audio
54 }
55
56 #[must_use]
58 pub fn into_audio(self) -> Audio {
59 self.audio
60 }
61
62 #[must_use]
65 pub fn warnings(&self) -> Vec<String> {
66 match self.audio.channels().len() {
67 count @ 3.. => vec![format!(
68 "the file has {count} channels; the analysis reads the first two and ignores the other {}",
69 count - 2
70 )],
71 _ => Vec::new(),
72 }
73 }
74
75 #[must_use]
78 pub fn skipped(&self) -> &[SkippedPacket] {
79 &self.skipped
80 }
81}
82
83#[derive(Clone, Debug, PartialEq)]
85pub struct SkippedPacket {
86 index: usize,
87 start: Seconds,
88 frames: usize,
89 message: &'static str,
90}
91
92impl SkippedPacket {
93 #[must_use]
95 pub fn index(&self) -> usize {
96 self.index
97 }
98
99 #[must_use]
101 pub fn start(&self) -> Seconds {
102 self.start
103 }
104
105 #[must_use]
107 pub fn frames(&self) -> usize {
108 self.frames
109 }
110
111 #[must_use]
113 pub fn message(&self) -> &'static str {
114 self.message
115 }
116}
117
118impl fmt::Display for SkippedPacket {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(
121 f,
122 "packet {} at {} ({})",
123 self.index, self.start, self.message
124 )
125 }
126}
127
128pub fn decode(path: &Path) -> Result<Decoded, DecodeError> {
143 let file = File::open(path).map_err(|source| DecodeError {
144 path: path.to_path_buf(),
145 kind: Kind::Open(source),
146 })?;
147 decode_source(path, Box::new(file))
148}
149
150pub(crate) fn read_stem(path: &Path) -> Result<Vec<u8>, DecodeError> {
159 use std::io::Read as _;
160 let fail = |kind| DecodeError {
161 path: path.to_path_buf(),
162 kind,
163 };
164 let mut file = File::open(path).map_err(|source| fail(Kind::Open(source)))?;
165 let mut bytes = Vec::new();
166 file.read_to_end(&mut bytes)
167 .map_err(|source| fail(Kind::Read(source)))?;
168 Ok(bytes)
169}
170
171pub(crate) fn decode_bytes(path: &Path, bytes: Vec<u8>) -> Result<Decoded, DecodeError> {
177 decode_source(path, Box::new(io::Cursor::new(bytes)))
178}
179
180fn decode_source(
182 path: &Path,
183 source: Box<dyn symphonia::core::io::MediaSource>,
184) -> Result<Decoded, DecodeError> {
185 let fail = |kind| DecodeError {
186 path: path.to_path_buf(),
187 kind,
188 };
189 let stream = MediaSourceStream::new(source, MediaSourceStreamOptions::default());
190 let mut hint = Hint::new();
191 if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
192 hint.with_extension(extension);
193 }
194 let mut format = symphonia::default::get_probe()
195 .probe(
196 &hint,
197 stream,
198 FormatOptions::default(),
199 MetadataOptions::default(),
200 )
201 .map_err(|error| match error {
202 SymphoniaError::Unsupported(_) => fail(Kind::UnsupportedContainer),
203 other => fail(Kind::from_symphonia(other)),
204 })?;
205 let track = format
206 .default_track(TrackType::Audio)
207 .ok_or_else(|| fail(Kind::NoAudioTrack))?;
208 let track_id = track.id;
209 let time_base = track.time_base;
210 let params = track
211 .codec_params
212 .as_ref()
213 .and_then(|params| params.audio())
214 .ok_or_else(|| fail(Kind::NoAudioTrack))?
215 .clone();
216 let sample_rate = params
217 .sample_rate
218 .and_then(|rate| SampleRate::new(rate).ok())
219 .ok_or_else(|| fail(Kind::NoSampleRate))?;
220 if !(MIN_SAMPLE_RATE..=MAX_SAMPLE_RATE).contains(&sample_rate) {
222 return Err(fail(Kind::RateOutOfRange(sample_rate)));
223 }
224 let mut decoder = symphonia::default::get_codecs()
225 .make_audio_decoder(¶ms, &AudioDecoderOptions::default())
226 .map_err(|error| match error {
227 SymphoniaError::Unsupported(_) => fail(Kind::UnsupportedCodec(params.codec)),
228 other => fail(Kind::from_symphonia(other)),
229 })?;
230
231 let mut collected = Collected::default();
232 let mut planes: Vec<Vec<f64>> = Vec::new();
233 let mut index = 0;
234 while let Some(packet) = format
235 .next_packet()
236 .map_err(|error| fail(Kind::from_symphonia(error)))?
237 {
238 if packet.track_id != track_id {
239 continue;
240 }
241 index += 1;
242 match decoder.decode(&packet) {
243 Ok(buffer) => {
244 buffer.copy_to_vecs_planar::<f64>(&mut planes);
245 collected.append(&planes).map_err(fail)?;
246 }
247 Err(SymphoniaError::DecodeError(message)) => {
248 let frames = packet_frames(packet.dur, time_base, sample_rate);
249 collected
250 .skip(index - 1, frames, message, sample_rate)
251 .map_err(fail)?;
252 }
253 Err(error) => return Err(fail(Kind::from_symphonia(error))),
254 }
255 }
256 let Collected {
257 channels, skipped, ..
258 } = collected;
259 if channels.is_empty() {
260 return Err(fail(Kind::Empty));
261 }
262 let audio = Audio::new(sample_rate, channels).map_err(|error| match error {
263 AudioError::NonFinite { channel, frame } => fail(Kind::NonFinite { channel, frame }),
264 other => fail(Kind::Audio(other)),
265 })?;
266 Ok(Decoded { audio, skipped })
267}
268
269#[derive(Default)]
271struct Collected {
272 channels: Vec<Vec<f64>>,
273 skipped: Vec<SkippedPacket>,
274 leading_silence: usize,
276 last_frames: usize,
279}
280
281impl Collected {
282 fn append(&mut self, planes: &[Vec<f64>]) -> Result<(), Kind> {
283 if self.channels.is_empty() {
284 self.channels
285 .resize(planes.len(), vec![0.0; self.leading_silence]);
286 } else if planes.len() != self.channels.len() {
287 return Err(Kind::ChannelsChanged {
288 from: self.channels.len(),
289 to: planes.len(),
290 });
291 }
292 for (channel, plane) in self.channels.iter_mut().zip(planes) {
293 channel.extend_from_slice(plane);
294 }
295 self.last_frames = planes.first().map_or(0, Vec::len);
296 Ok(())
297 }
298
299 fn skip(
300 &mut self,
301 index: usize,
302 frames: Option<usize>,
303 message: &'static str,
304 rate: SampleRate,
305 ) -> Result<(), Kind> {
306 let frames = frames.unwrap_or(self.last_frames);
307 let done = self.channels.first().map_or(self.leading_silence, Vec::len);
308 self.skipped.push(SkippedPacket {
309 index,
310 start: rate.seconds(done),
311 frames,
312 message,
313 });
314 if self.skipped.len() > MAX_SKIPPED_PACKETS {
315 return Err(Kind::TooManyBadPackets(self.skipped.swap_remove(0)));
316 }
317 if self.channels.is_empty() {
318 self.leading_silence += frames;
319 }
320 for channel in &mut self.channels {
321 channel.resize(channel.len() + frames, 0.0);
322 }
323 Ok(())
324 }
325}
326
327fn packet_frames(dur: Duration, time_base: Option<TimeBase>, rate: SampleRate) -> Option<usize> {
329 let time_base = time_base?;
330 let ticks = u128::from(dur.get()) * u128::from(time_base.numer.get()) * u128::from(rate.get());
331 let denom = u128::from(time_base.denom.get());
332 usize::try_from((ticks + denom / 2) / denom).ok()
333}
334
335#[derive(Debug)]
337pub struct DecodeError {
338 path: PathBuf,
339 kind: Kind,
340}
341
342impl DecodeError {
343 #[must_use]
345 pub fn path(&self) -> &Path {
346 &self.path
347 }
348}
349
350#[derive(Debug)]
351enum Kind {
352 Open(io::Error),
353 Read(io::Error),
354 UnsupportedContainer,
355 UnsupportedCodec(AudioCodecId),
356 NoAudioTrack,
357 NoSampleRate,
358 RateOutOfRange(SampleRate),
359 Malformed(&'static str),
360 Limit(&'static str),
361 ResetRequired,
362 Unexpected,
363 ChannelsChanged { from: usize, to: usize },
364 TooManyBadPackets(SkippedPacket),
365 Empty,
366 NonFinite { channel: usize, frame: usize },
367 Audio(AudioError),
368}
369
370impl Kind {
371 fn from_symphonia(error: SymphoniaError) -> Self {
372 match error {
373 SymphoniaError::IoError(source) => Self::Read(source),
374 SymphoniaError::DecodeError(message) | SymphoniaError::Unsupported(message) => {
375 Self::Malformed(message)
376 }
377 SymphoniaError::LimitError(message) => Self::Limit(message),
378 SymphoniaError::ResetRequired => Self::ResetRequired,
381 _ => Self::Unexpected,
382 }
383 }
384}
385
386fn codec_name(codec: AudioCodecId) -> (&'static str, Option<&'static str>) {
389 match codec {
390 CODEC_ID_MP1 => ("MPEG audio layer I", Some("mp1")),
391 CODEC_ID_MP2 => ("MPEG audio layer II", Some("mp2")),
392 CODEC_ID_ALAC => ("ALAC", Some("alac")),
393 CODEC_ID_OPUS => ("Opus", None),
394 CODEC_ID_AC3 => ("AC-3", None),
395 CODEC_ID_EAC3 => ("E-AC-3", None),
396 CODEC_ID_WMA => ("WMA", None),
397 CODEC_ID_WAVPACK => ("WavPack", None),
398 CODEC_ID_MONKEYS_AUDIO => ("Monkey's Audio", None),
399 _ => ("this", None),
400 }
401}
402
403impl fmt::Display for DecodeError {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 let path = self.path.display();
406 write!(f, "cannot decode {path}: ")?;
407 match &self.kind {
408 Kind::Open(source) => write!(f, "cannot open the file ({source}). Check the path"),
409 Kind::Read(source) => write!(f, "reading failed ({source}). Check the file"),
410 Kind::UnsupportedContainer => write!(
411 f,
412 "the container is not supported. Export the stem as one of: {CONTAINERS}"
413 ),
414 Kind::UnsupportedCodec(codec) => match codec_name(*codec) {
415 (name, Some(feature)) => write!(
416 f,
417 "the {name} codec is not built in. Build with the symphonia Cargo feature `{feature}`, or export the stem as WAV"
418 ),
419 (name, None) => write!(
420 f,
421 "the {name} codec is not supported. Export the stem as WAV or FLAC"
422 ),
423 },
424 Kind::NoAudioTrack => write!(f, "the file has no audio track"),
425 Kind::NoSampleRate => write!(
426 f,
427 "the audio track declares no sample rate. Export the stem again as WAV"
428 ),
429 Kind::RateOutOfRange(rate) => write!(
430 f,
431 "the header declares a sample rate of {rate}, outside the {MIN_SAMPLE_RATE} to {MAX_SAMPLE_RATE} that the analysis reads. Export the stem again at 44.1 or 48 kHz"
432 ),
433 Kind::Malformed(message) => write!(
434 f,
435 "the stream is malformed ({message}). Export the stem again"
436 ),
437 Kind::Limit(message) => write!(f, "a decoder limit was reached ({message})"),
438 Kind::ResetRequired => write!(
439 f,
440 "the stream changes its format midway. Export the stem again as one track"
441 ),
442 Kind::Unexpected => {
443 write!(f, "the decoder failed unexpectedly. Export the stem as WAV")
444 }
445 Kind::ChannelsChanged { from, to } => write!(
446 f,
447 "the channel count changes from {from} to {to} midway. Export the stem again"
448 ),
449 Kind::TooManyBadPackets(first) => write!(
450 f,
451 "more than {MAX_SKIPPED_PACKETS} packets do not decode, the first {first}. Export the stem again"
452 ),
453 Kind::Empty => write!(f, "the audio track holds no samples"),
454 Kind::NonFinite { channel, frame } => write!(
455 f,
456 "the sample at frame {frame} of channel {channel} is NaN or infinite. Export the stem again"
457 ),
458 Kind::Audio(error) => write!(f, "{error}"),
459 }
460 }
461}
462
463impl Error for DecodeError {
464 fn source(&self) -> Option<&(dyn Error + 'static)> {
465 match &self.kind {
466 Kind::Open(source) | Kind::Read(source) => Some(source),
467 Kind::Audio(source) => Some(source),
468 _ => None,
469 }
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 fn rate() -> SampleRate {
478 SampleRate::new(44_100).unwrap()
479 }
480
481 fn error(kind: Kind) -> DecodeError {
482 DecodeError {
483 path: PathBuf::from("bass.wav"),
484 kind,
485 }
486 }
487
488 #[test]
489 fn every_symphonia_error_maps_to_its_kind() {
490 let io = || io::Error::new(io::ErrorKind::UnexpectedEof, "short");
491 assert!(matches!(
492 Kind::from_symphonia(SymphoniaError::IoError(io())),
493 Kind::Read(_)
494 ));
495 assert!(matches!(
496 Kind::from_symphonia(SymphoniaError::DecodeError("bad")),
497 Kind::Malformed("bad")
498 ));
499 assert!(matches!(
500 Kind::from_symphonia(SymphoniaError::Unsupported("odd")),
501 Kind::Malformed("odd")
502 ));
503 assert!(matches!(
504 Kind::from_symphonia(SymphoniaError::LimitError("big")),
505 Kind::Limit("big")
506 ));
507 assert!(matches!(
508 Kind::from_symphonia(SymphoniaError::ResetRequired),
509 Kind::ResetRequired
510 ));
511 assert!(matches!(
512 Kind::from_symphonia(SymphoniaError::SeekError(
513 symphonia::core::errors::SeekErrorKind::Unseekable
514 )),
515 Kind::Unexpected
516 ));
517 }
518
519 #[test]
520 fn every_failure_names_the_file_and_what_failed() {
521 let skipped = SkippedPacket {
522 index: 3,
523 start: Seconds::new(0.5).unwrap(),
524 frames: 1152,
525 message: "bad frame",
526 };
527 let io = || io::Error::other("denied");
528 let cases = [
529 (
530 Kind::Open(io()),
531 "cannot open the file (denied). Check the path",
532 ),
533 (Kind::Read(io()), "reading failed (denied). Check the file"),
534 (Kind::NoAudioTrack, "the file has no audio track"),
535 (
536 Kind::RateOutOfRange(SampleRate::new(1).unwrap()),
537 "the header declares a sample rate of 1 Hz, outside the 8000 Hz to 192000 Hz that the analysis reads. Export the stem again at 44.1 or 48 kHz",
538 ),
539 (
540 Kind::NoSampleRate,
541 "the audio track declares no sample rate. Export the stem again as WAV",
542 ),
543 (
544 Kind::Malformed("bad"),
545 "the stream is malformed (bad). Export the stem again",
546 ),
547 (Kind::Limit("big"), "a decoder limit was reached (big)"),
548 (
549 Kind::ResetRequired,
550 "the stream changes its format midway. Export the stem again as one track",
551 ),
552 (
553 Kind::Unexpected,
554 "the decoder failed unexpectedly. Export the stem as WAV",
555 ),
556 (
557 Kind::ChannelsChanged { from: 2, to: 1 },
558 "the channel count changes from 2 to 1 midway. Export the stem again",
559 ),
560 (
561 Kind::TooManyBadPackets(skipped),
562 "more than 4 packets do not decode, the first packet 3 at 0.5 s (bad frame). Export the stem again",
563 ),
564 (Kind::Empty, "the audio track holds no samples"),
565 (
566 Kind::NonFinite {
567 channel: 1,
568 frame: 7,
569 },
570 "the sample at frame 7 of channel 1 is NaN or infinite. Export the stem again",
571 ),
572 (
573 Kind::Audio(AudioError::NoChannels),
574 "the audio has no channel; it needs at least one",
575 ),
576 (
577 Kind::UnsupportedCodec(CODEC_ID_OPUS),
578 "the Opus codec is not supported. Export the stem as WAV or FLAC",
579 ),
580 (
581 Kind::UnsupportedCodec(CODEC_ID_ALAC),
582 "the ALAC codec is not built in. Build with the symphonia Cargo feature `alac`, or export the stem as WAV",
583 ),
584 ];
585 for (kind, cause) in cases {
586 assert_eq!(
587 error(kind).to_string(),
588 format!("cannot decode bass.wav: {cause}")
589 );
590 }
591 assert!(error(Kind::Open(io())).source().is_some());
592 assert!(error(Kind::Read(io())).source().is_some());
593 assert!(
594 error(Kind::Audio(AudioError::NoChannels))
595 .source()
596 .is_some()
597 );
598 assert!(error(Kind::Empty).source().is_none());
599 assert_eq!(error(Kind::Empty).path(), Path::new("bass.wav"));
600 }
601
602 #[test]
603 fn a_channel_count_that_changes_midway_is_an_error() {
604 let mut collected = Collected::default();
605 collected.append(&[vec![0.1, 0.2], vec![0.3, 0.4]]).unwrap();
606 let error = collected.append(&[vec![0.5]]).unwrap_err();
607 assert!(matches!(error, Kind::ChannelsChanged { from: 2, to: 1 }));
608 }
609
610 #[test]
611 fn a_lost_first_packet_becomes_leading_silence_in_every_channel() {
612 let mut collected = Collected::default();
613 collected.skip(0, Some(3), "bad", rate()).unwrap();
614 collected.append(&[vec![1.0, 2.0], vec![3.0, 4.0]]).unwrap();
615 assert_eq!(
616 collected.channels,
617 [vec![0.0, 0.0, 0.0, 1.0, 2.0], vec![0.0, 0.0, 0.0, 3.0, 4.0]]
618 );
619 assert_eq!(collected.skipped[0].start().get(), 0.0);
620 assert_eq!(collected.skipped[0].frames(), 3);
621 }
622
623 #[test]
624 fn a_lost_packet_of_unknown_length_lasts_as_long_as_the_last_one() {
625 let mut collected = Collected::default();
626 collected.skip(0, None, "bad", rate()).unwrap();
627 assert_eq!(collected.skipped[0].frames(), 0);
628 collected.append(&[vec![1.0, 2.0, 3.0]]).unwrap();
629 collected.skip(2, None, "bad", rate()).unwrap();
630 assert_eq!(collected.channels, [vec![1.0, 2.0, 3.0, 0.0, 0.0, 0.0]]);
631 assert_eq!(collected.skipped[1].frames(), 3);
632 assert_eq!(collected.skipped[1].start().get(), 3.0 / 44_100.0);
633 }
634
635 #[test]
636 fn the_packet_after_the_limit_fails_and_names_the_first() {
637 let mut collected = Collected::default();
638 for index in 0..MAX_SKIPPED_PACKETS {
639 collected.skip(index, Some(10), "bad", rate()).unwrap();
640 }
641 let error = collected
642 .skip(MAX_SKIPPED_PACKETS, Some(10), "bad", rate())
643 .unwrap_err();
644 assert!(matches!(error, Kind::TooManyBadPackets(first) if first.index() == 0));
645 }
646
647 #[test]
648 fn a_packet_length_needs_the_time_base() {
649 let dur = Duration::new(1152);
650 assert_eq!(packet_frames(dur, None, rate()), None);
651 let native = TimeBase::try_from_recip(44_100);
652 assert_eq!(packet_frames(dur, native, rate()), Some(1152));
653 let other = TimeBase::try_from_recip(48_000);
655 assert_eq!(packet_frames(dur, other, rate()), Some(1058));
656 }
657}