#[cfg(feature = "native")]
mod adts;
mod ebml;
#[cfg(feature = "native")]
mod flac;
mod matroska;
mod metadata;
#[cfg(feature = "native")]
mod mp3;
#[cfg(feature = "native")]
mod mp4;
pub(crate) mod mpeg_audio;
#[cfg(feature = "native")]
mod ogg;
#[cfg(feature = "native")]
mod ts;
pub(crate) mod tags;
#[cfg(feature = "native")]
mod wav;
#[cfg(feature = "native")]
pub(crate) mod window;
use std::io::SeekFrom;
use std::time::Duration;
#[cfg(feature = "native")]
pub use adts::AdtsDemuxer;
#[cfg(feature = "native")]
pub use flac::FlacDemuxer;
pub use matroska::MatroskaDemuxer;
pub use metadata::{Metadata, Picture};
#[cfg(feature = "native")]
pub(crate) use matroska::split_xiph_lacing;
#[cfg(feature = "native")]
pub use mp3::Mp3Demuxer;
#[cfg(feature = "native")]
pub use mp4::Mp4Demuxer;
#[cfg(feature = "native")]
pub use ogg::OggDemuxer;
#[cfg(feature = "native")]
pub use ts::{TsDemuxer, VideoParams};
#[cfg(feature = "hls")]
pub(crate) use ts::is_ts as ts_is_ts;
#[cfg(feature = "native")]
pub use wav::WavDemuxer;
use crate::Result;
use crate::source::MediaSource;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum StreamKind {
Video,
Audio,
Other,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Codec {
Av1,
Vp8,
Vp9,
H264,
Hevc,
ProRes,
Opus,
Vorbis,
Aac,
Mp3,
Ac3,
Eac3,
Flac,
Alac,
Pcm(PcmFormat),
Other(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PcmFormat {
pub bits: u16,
pub float: bool,
pub big_endian: bool,
pub signed: bool,
}
impl PcmFormat {
pub const fn int(bits: u16, big_endian: bool, signed: bool) -> Self {
Self { bits, float: false, big_endian, signed }
}
pub const fn float(bits: u16, big_endian: bool) -> Self {
Self { bits, float: true, big_endian, signed: true }
}
}
impl Codec {
pub fn from_matroska_id(id: &str) -> Codec {
match id {
"V_AV1" => Codec::Av1,
"V_VP8" => Codec::Vp8,
"V_VP9" => Codec::Vp9,
"V_MPEG4/ISO/AVC" => Codec::H264,
"V_MPEGH/ISO/HEVC" => Codec::Hevc,
"V_PRORES" => Codec::ProRes,
"A_OPUS" => Codec::Opus,
"A_VORBIS" => Codec::Vorbis,
"A_MPEG/L3" => Codec::Mp3,
"A_AC3" => Codec::Ac3,
"A_EAC3" => Codec::Eac3,
"A_FLAC" => Codec::Flac,
"A_ALAC" => Codec::Alac,
"A_PCM/INT/LIT" => Codec::Pcm(PcmFormat::int(0, false, true)),
"A_PCM/INT/BIG" => Codec::Pcm(PcmFormat::int(0, true, true)),
"A_PCM/FLOAT/IEEE" => Codec::Pcm(PcmFormat::float(0, false)),
id if id.starts_with("A_AAC") => Codec::Aac,
other => Codec::Other(other.to_owned()),
}
}
pub fn from_mp4_codec_string(s: &str) -> Codec {
let fourcc = s.split('.').next().unwrap_or(s);
match fourcc {
"av01" => Codec::Av1,
"vp08" => Codec::Vp8,
"vp09" => Codec::Vp9,
"avc1" | "avc3" => Codec::H264,
"hvc1" | "hev1" => Codec::Hevc,
"apco" | "apcs" | "apcn" | "apch" | "ap4h" | "ap4x" => Codec::ProRes,
"Opus" | "opus" => Codec::Opus,
"mp4a" => Codec::Aac,
"ac-3" => Codec::Ac3,
"ec-3" => Codec::Eac3,
"fLaC" => Codec::Flac,
"alac" => Codec::Alac,
_ => Codec::Other(s.to_owned()),
}
}
}
impl std::fmt::Display for Codec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Codec::Other(s) => f.write_str(s),
Codec::Pcm(p) => {
let kind = if p.float { "f" } else if p.signed { "s" } else { "u" };
write!(f, "PCM {kind}{}{}", p.bits, if p.big_endian { "be" } else { "le" })
}
known => write!(f, "{known:?}"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StreamInfo {
pub id: u32,
pub kind: StreamKind,
pub codec: Codec,
pub width: u32,
pub height: u32,
pub duration: Option<Duration>,
pub extradata: Option<Vec<u8>>,
pub default: bool,
pub sample_rate: u32,
pub channels: u16,
pub codec_delay: Duration,
pub seek_preroll: Duration,
pub end_trim: Option<Duration>,
pub color_matrix: Option<u8>,
pub full_range: Option<bool>,
}
impl StreamInfo {
pub fn new(id: u32, kind: StreamKind, codec: Codec) -> Self {
Self {
id,
kind,
codec,
width: 0,
height: 0,
duration: None,
extradata: None,
default: true,
sample_rate: 0,
channels: 0,
codec_delay: Duration::ZERO,
seek_preroll: Duration::ZERO,
end_trim: None,
color_matrix: None,
full_range: None,
}
}
}
#[derive(Clone, Debug)]
pub struct Packet {
pub stream: u32,
pub pts: Duration,
pub keyframe: bool,
pub data: Vec<u8>,
pub generation: u64,
}
pub trait Demuxer: Send {
fn streams(&self) -> &[StreamInfo];
fn next_packet(&mut self) -> Result<Option<Packet>>;
fn seek(&mut self, target: Duration) -> Result<Duration>;
fn metadata(&self) -> Option<&Metadata> {
None
}
fn take_stream_update(&mut self) -> Option<StreamInfo> {
None
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ContainerFormat {
Matroska,
Mp4,
Mp3,
Adts,
Flac,
Wav,
Ogg,
MpegTs,
}
const PROBE_WINDOW: usize = 64 * 1024;
pub fn probe(src: &mut dyn MediaSource) -> Result<Option<ContainerFormat>> {
let found = probe_content(src);
src.seek(SeekFrom::Start(0))?;
found
}
fn probe_content(src: &mut dyn MediaSource) -> Result<Option<ContainerFormat>> {
let head = read_at(src, 0, 12)?;
if head.starts_with(&[0x1A, 0x45, 0xDF, 0xA3]) {
return Ok(Some(ContainerFormat::Matroska));
}
#[cfg(feature = "native")]
if ts::is_ts(&read_at(src, 0, 4 + 3 * 192)?) {
return Ok(Some(ContainerFormat::MpegTs));
}
if head.len() >= 8 && matches!(&head[4..8], b"ftyp" | b"moov" | b"styp" | b"wide" | b"mdat" | b"free") {
return Ok(Some(ContainerFormat::Mp4));
}
let mut start = 0u64;
for _ in 0..4 {
match tags::id3::id3v2_len(&read_at(src, start, 10)?) {
Some(len) => start += len,
None => break,
}
}
let head = if start == 0 { head } else { read_at(src, start, 12)? };
if head.starts_with(b"fLaC") {
return Ok(Some(ContainerFormat::Flac));
}
if head.len() >= 12 && head.starts_with(b"RIFF") && &head[8..12] == b"WAVE" {
return Ok(Some(ContainerFormat::Wav));
}
if head.starts_with(b"OggS") && start == 0 {
return Ok(Some(ContainerFormat::Ogg));
}
let window = read_at(src, start, PROBE_WINDOW)?;
let mp3 = mpeg_audio::find_chain::<mpeg_audio::MpegHeader>(&window, 3, None).map(|(i, _)| i);
let adts = mpeg_audio::find_chain::<mpeg_audio::AdtsHeader>(&window, 3, None).map(|(i, _)| i);
Ok(match (mp3, adts) {
(Some(m), Some(a)) if a < m => Some(ContainerFormat::Adts),
(Some(_), _) => Some(ContainerFormat::Mp3),
(None, Some(_)) => Some(ContainerFormat::Adts),
(None, None) => None,
})
}
fn read_at(src: &mut dyn MediaSource, pos: u64, len: usize) -> Result<Vec<u8>> {
src.seek(SeekFrom::Start(pos))?;
let mut buf = vec![0u8; len];
let mut filled = 0;
while filled < len {
let n = src.read(&mut buf[filled..])?;
if n == 0 {
break;
}
filled += n;
}
buf.truncate(filled);
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::source::FileSource;
fn probe_path(p: &str) -> Option<ContainerFormat> {
let mut src = FileSource::open(p).unwrap();
probe(&mut src).unwrap()
}
#[test]
fn probes_by_magic_bytes() {
assert_eq!(probe_path("tests/fixtures/av1.webm"), Some(ContainerFormat::Matroska));
assert_eq!(probe_path("tests/fixtures/av1.mp4"), Some(ContainerFormat::Mp4));
assert_eq!(probe_path("tests/fixtures/prores_hq.mov"), Some(ContainerFormat::Mp4));
assert_eq!(probe_path("tests/fixtures/not_video.bin"), None);
}
#[test]
fn maps_codec_ids() {
assert_eq!(Codec::from_matroska_id("V_AV1"), Codec::Av1);
assert_eq!(Codec::from_matroska_id("A_AAC/MPEG4/LC"), Codec::Aac);
assert_eq!(Codec::from_mp4_codec_string("av01.0.00M.08"), Codec::Av1);
assert_eq!(Codec::from_mp4_codec_string("avc1.64001f"), Codec::H264);
assert_eq!(Codec::from_mp4_codec_string("xyz1"), Codec::Other("xyz1".into()));
assert_eq!(Codec::from_matroska_id("V_PRORES"), Codec::ProRes);
assert_eq!(Codec::from_matroska_id("A_MPEG/L3"), Codec::Mp3);
assert_eq!(Codec::from_matroska_id("A_AC3"), Codec::Ac3);
assert_eq!(Codec::from_matroska_id("A_EAC3"), Codec::Eac3);
assert_eq!(Codec::from_matroska_id("A_FLAC"), Codec::Flac);
assert_eq!(Codec::from_matroska_id("A_ALAC"), Codec::Alac);
assert_eq!(Codec::from_mp4_codec_string("ac-3"), Codec::Ac3);
assert_eq!(Codec::from_mp4_codec_string("ec-3"), Codec::Eac3);
assert_eq!(Codec::from_mp4_codec_string("fLaC"), Codec::Flac);
assert_eq!(Codec::from_mp4_codec_string("alac"), Codec::Alac);
assert_eq!(Codec::from_mp4_codec_string("ap4h"), Codec::ProRes);
assert_eq!(Codec::Vp9.to_string(), "Vp9");
}
}