use std::{
fs,
io::{Read, Seek},
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize};
use symphonia::core::{
formats::{FormatOptions, TrackType, probe::Hint},
io::MediaSourceStream,
meta::MetadataOptions,
};
use thiserror::Error;
mod codec;
pub use codec::*;
mod format;
pub use format::*;
mod sample_format;
pub use sample_format::*;
mod stream;
pub use stream::*;
#[derive(Debug, Error)]
pub enum ContainerError {
#[error("IoError: {0}")]
Io(#[from] std::io::Error),
#[error("Codec Error: {0}")]
Codec(#[from] CodecError),
#[error("Symphonia Error: {0}")]
Symphonia(#[from] symphonia::core::errors::Error),
#[error("File '{0}' has no tracks/streams")]
NoStream(PathBuf),
#[error("File '{0}' has an unsupported container")]
InvalidSource(PathBuf),
#[error("'{0:?}' is an unsupported container")]
UnsupportedContainer(Format),
#[error("Couldn't find sample rate")]
NoSampleRate,
#[error("Couldn't find channel count'")]
NoChannelCount,
#[error("Couldn't find an audio stream")]
NoAudioStream,
#[error("The track is unplayable")]
Unplayable,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct MediaContainer {
pub(crate) format: Format,
pub(crate) stream: Stream,
}
impl MediaContainer {
#[must_use]
pub fn format(&self) -> Format {
self.format
}
#[must_use]
pub fn extension(&self) -> &'static str {
let format = self.format;
let codec = self.stream.codec_params.codec;
match format {
Format::Flac if codec.is_flac() => "flac",
Format::Mp3 if codec.is_mp3() => "mp3",
Format::Ogg if codec.is_vorbis() => "ogg",
Format::Ogg if codec.is_opus() => "opus",
Format::Ogg if codec.is_flac() => "oga",
Format::Wav if codec.is_pcm() => "wav",
Format::Aiff if codec.is_pcm() => "aiff",
Format::Ape if codec.is_ape() => "ape",
_ => panic!("Invalid format/codec pairing: {format:?} + {codec:?}"),
}
}
#[must_use]
pub fn mime_type(&self) -> &'static str {
let format = self.format;
let codec = self.stream.codec_params.codec;
match format {
Format::Flac if codec.is_flac() => "audio/flac",
Format::Mp3 if codec.is_mp3() => "audio/mpeg",
Format::Ogg if codec.is_vorbis() => "audio/ogg",
Format::Ogg if codec.is_opus() => "audio/opus",
Format::Ogg if codec.is_flac() => "audio/oga",
Format::Wav if codec.is_pcm() => "audio/wav",
Format::Aiff if codec.is_pcm() => "audio/aiff",
Format::Ape if codec.is_ape() => "audio/x-ape",
_ => panic!("Invalid format/codec pairing: {format:?} + {codec:?}"),
}
}
#[must_use]
pub fn stream(&self) -> &Stream {
&self.stream
}
}
pub fn extract_from_file(path: impl AsRef<Path>) -> Result<MediaContainer, ContainerError> {
let path = path.as_ref();
let mut file = fs::File::open(path)?;
let mut buf = [0_u8; 16];
file.read_exact(&mut buf)?;
file.seek(std::io::SeekFrom::Start(0))?;
let container_format =
Format::from_buf(&buf).ok_or_else(|| ContainerError::InvalidSource(path.to_path_buf()))?;
let mss = MediaSourceStream::new(Box::new(file), Default::default());
let fmt_opts = FormatOptions::default();
let metadata_opts = MetadataOptions::default();
let probe =
symphonia::default::get_probe().probe(&Hint::new(), mss, fmt_opts, metadata_opts)?;
let track = probe
.default_track(TrackType::Audio)
.or_else(|| probe.first_track(TrackType::Audio))
.ok_or(ContainerError::NoAudioStream)?;
let stream = Stream::try_from(track)?;
let container = MediaContainer {
format: container_format,
stream,
};
Ok(container)
}