use crate::format::{MediaType, PixelFormat, SampleFormat};
use crate::options::CodecOptions;
use crate::rational::Rational;
use crate::time::TimeBase;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CodecId(pub String);
impl CodecId {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for CodecId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::fmt::Display for CodecId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum CodecTag {
Fourcc([u8; 4]),
WaveFormat(u16),
Mp4ObjectType(u8),
Matroska(String),
}
impl CodecTag {
pub fn fourcc(raw: &[u8; 4]) -> Self {
let mut out = [0u8; 4];
for i in 0..4 {
out[i] = raw[i].to_ascii_uppercase();
}
Self::Fourcc(out)
}
pub fn wave_format(tag: u16) -> Self {
Self::WaveFormat(tag)
}
pub fn mp4_object_type(oti: u8) -> Self {
Self::Mp4ObjectType(oti)
}
pub fn matroska(id: impl Into<String>) -> Self {
Self::Matroska(id.into())
}
}
impl std::fmt::Display for CodecTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fourcc(fcc) => {
if fcc.iter().all(|b| b.is_ascii_graphic() || *b == b' ') {
write!(f, "fourcc({})", std::str::from_utf8(fcc).unwrap_or("????"))
} else {
write!(
f,
"fourcc(0x{:02X}{:02X}{:02X}{:02X})",
fcc[0], fcc[1], fcc[2], fcc[3]
)
}
}
Self::WaveFormat(t) => write!(f, "wFormatTag(0x{t:04X})"),
Self::Mp4ObjectType(o) => write!(f, "mp4_oti(0x{o:02X})"),
Self::Matroska(s) => write!(f, "matroska({s})"),
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct ProbeContext<'a> {
pub tag: &'a CodecTag,
pub header: Option<&'a [u8]>,
pub packet: Option<&'a [u8]>,
pub bits_per_sample: Option<u16>,
pub channels: Option<u16>,
pub sample_rate: Option<u32>,
pub width: Option<u32>,
pub height: Option<u32>,
}
impl<'a> ProbeContext<'a> {
pub fn new(tag: &'a CodecTag) -> Self {
Self {
tag,
header: None,
packet: None,
bits_per_sample: None,
channels: None,
sample_rate: None,
width: None,
height: None,
}
}
pub fn header(mut self, h: &'a [u8]) -> Self {
self.header = Some(h);
self
}
pub fn packet(mut self, p: &'a [u8]) -> Self {
self.packet = Some(p);
self
}
pub fn bits(mut self, n: u16) -> Self {
self.bits_per_sample = Some(n);
self
}
pub fn channels(mut self, n: u16) -> Self {
self.channels = Some(n);
self
}
pub fn sample_rate(mut self, n: u32) -> Self {
self.sample_rate = Some(n);
self
}
pub fn width(mut self, n: u32) -> Self {
self.width = Some(n);
self
}
pub fn height(mut self, n: u32) -> Self {
self.height = Some(n);
self
}
}
pub type Confidence = f32;
pub type ProbeFn = fn(&ProbeContext) -> Confidence;
pub trait CodecResolver: Sync {
fn resolve_tag(&self, ctx: &ProbeContext) -> Option<CodecId>;
}
#[derive(Default, Clone, Copy)]
pub struct NullCodecResolver;
impl CodecResolver for NullCodecResolver {
fn resolve_tag(&self, _ctx: &ProbeContext) -> Option<CodecId> {
None
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CodecParameters {
pub codec_id: CodecId,
pub media_type: MediaType,
pub sample_rate: Option<u32>,
pub channels: Option<u16>,
pub sample_format: Option<SampleFormat>,
pub width: Option<u32>,
pub height: Option<u32>,
pub pixel_format: Option<PixelFormat>,
pub frame_rate: Option<Rational>,
pub extradata: Vec<u8>,
pub bit_rate: Option<u64>,
pub options: CodecOptions,
}
impl CodecParameters {
pub fn audio(codec_id: CodecId) -> Self {
Self {
codec_id,
media_type: MediaType::Audio,
sample_rate: None,
channels: None,
sample_format: None,
width: None,
height: None,
pixel_format: None,
frame_rate: None,
extradata: Vec::new(),
bit_rate: None,
options: CodecOptions::default(),
}
}
pub fn matches_core(&self, other: &CodecParameters) -> bool {
self.codec_id == other.codec_id
&& self.sample_rate == other.sample_rate
&& self.channels == other.channels
&& self.sample_format == other.sample_format
&& self.width == other.width
&& self.height == other.height
&& self.pixel_format == other.pixel_format
}
pub fn video(codec_id: CodecId) -> Self {
Self {
codec_id,
media_type: MediaType::Video,
sample_rate: None,
channels: None,
sample_format: None,
width: None,
height: None,
pixel_format: None,
frame_rate: None,
extradata: Vec::new(),
bit_rate: None,
options: CodecOptions::default(),
}
}
pub fn subtitle(codec_id: CodecId) -> Self {
Self {
codec_id,
media_type: MediaType::Subtitle,
sample_rate: None,
channels: None,
sample_format: None,
width: None,
height: None,
pixel_format: None,
frame_rate: None,
extradata: Vec::new(),
bit_rate: None,
options: CodecOptions::default(),
}
}
pub fn data(codec_id: CodecId) -> Self {
Self {
codec_id,
media_type: MediaType::Data,
sample_rate: None,
channels: None,
sample_format: None,
width: None,
height: None,
pixel_format: None,
frame_rate: None,
extradata: Vec::new(),
bit_rate: None,
options: CodecOptions::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct StreamInfo {
pub index: u32,
pub time_base: TimeBase,
pub duration: Option<i64>,
pub start_time: Option<i64>,
pub params: CodecParameters,
}
#[cfg(test)]
mod codec_tag_tests {
use super::*;
#[test]
fn fourcc_uppercases_on_construction() {
let t = CodecTag::fourcc(b"div3");
assert_eq!(t, CodecTag::Fourcc(*b"DIV3"));
let t2 = CodecTag::fourcc(b"MP42");
assert_eq!(t2, CodecTag::Fourcc(*b"MP42"));
let t3 = CodecTag::fourcc(&[0xFF, b'a', 0x00, b'1']);
assert_eq!(t3, CodecTag::Fourcc([0xFF, b'A', 0x00, b'1']));
}
#[test]
fn fourcc_equality_case_insensitive_via_ctor() {
assert_eq!(CodecTag::fourcc(b"xvid"), CodecTag::fourcc(b"XVID"));
assert_eq!(CodecTag::fourcc(b"DiV3"), CodecTag::fourcc(b"div3"));
}
#[test]
fn display_printable_fourcc() {
assert_eq!(CodecTag::fourcc(b"XVID").to_string(), "fourcc(XVID)");
}
#[test]
fn display_non_printable_fourcc_as_hex() {
let t = CodecTag::Fourcc([0x00, 0x00, 0x00, 0x01]);
assert_eq!(t.to_string(), "fourcc(0x00000001)");
}
#[test]
fn display_wave_format() {
assert_eq!(
CodecTag::wave_format(0x0055).to_string(),
"wFormatTag(0x0055)"
);
}
#[test]
fn display_mp4_oti() {
assert_eq!(CodecTag::mp4_object_type(0x40).to_string(), "mp4_oti(0x40)");
}
#[test]
fn display_matroska() {
assert_eq!(
CodecTag::matroska("V_MPEG4/ISO/AVC").to_string(),
"matroska(V_MPEG4/ISO/AVC)",
);
}
#[test]
fn null_resolver_resolves_nothing() {
let r = NullCodecResolver;
let xvid = CodecTag::fourcc(b"XVID");
assert!(r.resolve_tag(&ProbeContext::new(&xvid)).is_none());
let wf = CodecTag::wave_format(0x0055);
assert!(r.resolve_tag(&ProbeContext::new(&wf)).is_none());
}
#[test]
fn probe_context_builder_fills_hints() {
let tag = CodecTag::wave_format(0x0001);
let ctx = ProbeContext::new(&tag)
.bits(24)
.channels(2)
.sample_rate(48_000)
.header(&[1, 2, 3])
.packet(&[4, 5]);
assert_eq!(ctx.bits_per_sample, Some(24));
assert_eq!(ctx.channels, Some(2));
assert_eq!(ctx.sample_rate, Some(48_000));
assert_eq!(ctx.header.unwrap(), &[1, 2, 3]);
assert_eq!(ctx.packet.unwrap(), &[4, 5]);
}
}