pub mod aac;
pub mod av1;
pub mod avc;
pub mod hvc;
use std::collections::HashMap;
use std::io::{Read, Seek, Write};
use rff_core::{
CodecId, ColorRange, Dictionary, Error, MediaType, Packet, PixelFormat, Rational, Result,
SampleFormat,
};
#[derive(Debug, Clone)]
pub struct Stream {
pub index: usize,
pub codec_id: CodecId,
pub media_type: MediaType,
pub time_base: Rational,
pub width: u32,
pub height: u32,
pub pixel_format: Option<PixelFormat>,
pub color_range: ColorRange,
pub sample_rate: u32,
pub channels: u16,
pub sample_format: Option<SampleFormat>,
pub extradata: Vec<u8>,
pub duration: Option<i64>,
pub nb_frames: Option<u64>,
}
impl Stream {
pub fn new(index: usize, codec_id: CodecId) -> Stream {
Stream {
index,
codec_id,
media_type: codec_id.media_type(),
time_base: Rational::new(1, 1000),
width: 0,
height: 0,
pixel_format: None,
color_range: ColorRange::Unspecified,
sample_rate: 0,
channels: 0,
sample_format: None,
extradata: Vec::new(),
duration: None,
nb_frames: None,
}
}
}
pub type Input = Box<dyn Read + Send>;
pub trait ReadSeek: Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}
pub type SeekInput = Box<dyn ReadSeek + Send>;
pub type Output = Box<dyn Write + Send>;
pub trait WriteSeek: Write + Seek {}
impl<T: Write + Seek> WriteSeek for T {}
pub type SeekOutput = Box<dyn WriteSeek + Send>;
pub trait Demuxer: Send {
fn read_header(&mut self) -> Result<Vec<Stream>>;
fn read_packet(&mut self) -> Result<Packet>;
}
pub trait Muxer: Send {
fn set_metadata(&mut self, _metadata: &Dictionary) {}
fn write_header(&mut self, streams: &[Stream]) -> Result<()>;
fn write_packet(&mut self, packet: &Packet) -> Result<()>;
fn write_trailer(&mut self) -> Result<()>;
}
pub type DemuxerFactory = fn(Input) -> Box<dyn Demuxer>;
pub type SeekableDemuxerFactory = fn(SeekInput) -> Box<dyn Demuxer>;
pub type MuxerFactory = fn(Output) -> Box<dyn Muxer>;
pub type SeekableMuxerFactory = fn(SeekOutput) -> Box<dyn Muxer>;
pub type PathMuxerFactory = fn(&std::path::Path, &Dictionary) -> Result<Box<dyn Muxer>>;
pub type ProbeFn = fn(&[u8]) -> i32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MuxCaps {
pub codecs: &'static [CodecId],
pub max_streams: Option<usize>,
pub still_image: bool,
}
impl MuxCaps {
pub const NONE: MuxCaps = MuxCaps {
codecs: &[],
max_streams: Some(0),
still_image: false,
};
pub const fn container(codecs: &'static [CodecId]) -> MuxCaps {
MuxCaps {
codecs,
max_streams: None,
still_image: false,
}
}
pub const fn single(codecs: &'static [CodecId]) -> MuxCaps {
MuxCaps {
codecs,
max_streams: Some(1),
still_image: false,
}
}
pub const fn image(self) -> MuxCaps {
MuxCaps {
codecs: self.codecs,
max_streams: self.max_streams,
still_image: true,
}
}
pub fn accepts(&self, id: CodecId) -> bool {
self.codecs.contains(&id)
}
pub fn accepts_media(&self, media: MediaType) -> bool {
self.codecs.iter().any(|c| c.media_type() == media)
}
pub fn codecs_for(&self, media: MediaType) -> impl Iterator<Item = CodecId> + '_ {
self.codecs
.iter()
.copied()
.filter(move |c| c.media_type() == media)
}
}
pub struct Format {
pub name: &'static str,
pub long_name: &'static str,
pub extensions: &'static [&'static str],
pub demuxer: Option<DemuxerFactory>,
pub muxer: Option<MuxerFactory>,
pub muxer_path: Option<PathMuxerFactory>,
pub probe: Option<ProbeFn>,
pub mux_caps: MuxCaps,
}
impl Format {
pub fn can_demux(&self) -> bool {
self.demuxer.is_some()
}
pub fn can_mux(&self) -> bool {
self.muxer.is_some() || self.muxer_path.is_some()
}
}
#[derive(Default)]
pub struct FormatRegistry {
by_name: HashMap<&'static str, Format>,
seekable: HashMap<&'static str, SeekableDemuxerFactory>,
seekable_muxers: HashMap<&'static str, SeekableMuxerFactory>,
}
impl FormatRegistry {
pub fn new() -> FormatRegistry {
FormatRegistry::default()
}
pub fn register(&mut self, format: Format) {
self.by_name.insert(format.name, format);
}
pub fn register_seekable_demuxer(
&mut self,
name: &'static str,
factory: SeekableDemuxerFactory,
) {
self.seekable.insert(name, factory);
}
pub fn by_name(&self, name: &str) -> Option<&Format> {
self.by_name.get(name)
}
pub fn by_extension(&self, ext: &str) -> Option<&Format> {
let ext = ext.trim_start_matches('.').to_ascii_lowercase();
self.by_name
.values()
.find(|f| f.extensions.iter().any(|e| e.eq_ignore_ascii_case(&ext)))
}
pub fn probe(&self, data: &[u8]) -> Option<&Format> {
self.by_name
.values()
.filter_map(|f| f.probe.map(|p| (p(data), f)))
.filter(|(score, _)| *score > 0)
.max_by_key(|(score, _)| *score)
.map(|(_, f)| f)
}
pub fn open_demuxer(&self, name: &str, input: Input) -> Result<Box<dyn Demuxer>> {
self.by_name
.get(name)
.and_then(|f| f.demuxer)
.map(|factory| factory(input))
.ok_or_else(|| Error::DemuxerNotFound(name.to_string()))
}
pub fn open_demuxer_seekable(&self, name: &str, input: SeekInput) -> Result<Box<dyn Demuxer>> {
if !self.by_name.get(name).is_some_and(Format::can_demux) {
return Err(Error::DemuxerNotFound(name.to_string()));
}
match self.seekable.get(name) {
Some(factory) => Ok(factory(input)),
None => self.open_demuxer(name, input),
}
}
pub fn register_seekable_muxer(&mut self, name: &'static str, factory: SeekableMuxerFactory) {
self.seekable_muxers.insert(name, factory);
}
pub fn open_muxer_seekable(&self, name: &str, output: SeekOutput) -> Result<Box<dyn Muxer>> {
if !self.by_name.get(name).is_some_and(|f| f.muxer.is_some()) {
return Err(Error::MuxerNotFound(name.to_string()));
}
match self.seekable_muxers.get(name) {
Some(factory) => Ok(factory(output)),
None => self.open_muxer(name, output),
}
}
pub fn open_muxer(&self, name: &str, output: Output) -> Result<Box<dyn Muxer>> {
self.by_name
.get(name)
.and_then(|f| f.muxer)
.map(|factory| factory(output))
.ok_or_else(|| Error::MuxerNotFound(name.to_string()))
}
pub fn iter(&self) -> impl Iterator<Item = &Format> {
self.by_name.values()
}
pub fn len(&self) -> usize {
self.by_name.len()
}
pub fn is_empty(&self) -> bool {
self.by_name.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
struct Tagged(usize);
impl Demuxer for Tagged {
fn read_header(&mut self) -> Result<Vec<Stream>> {
Ok((0..self.0).map(|i| Stream::new(i, CodecId::None)).collect())
}
fn read_packet(&mut self) -> Result<rff_core::Packet> {
Err(Error::Eof)
}
}
fn format(name: &'static str) -> Format {
Format {
name,
long_name: name,
extensions: &[],
demuxer: Some(|_| Box::new(Tagged(1))),
muxer: None,
muxer_path: None,
probe: None,
mux_caps: MuxCaps::NONE,
}
}
fn streams(mut d: Box<dyn Demuxer>) -> usize {
d.read_header().unwrap().len()
}
#[test]
fn seekable_open_prefers_the_seekable_factory_and_falls_back() {
let mut r = FormatRegistry::new();
r.register(format("plain"));
r.register(format("seeky"));
r.register_seekable_demuxer("seeky", |_| Box::new(Tagged(2)));
let input = || -> SeekInput { Box::new(Cursor::new(vec![0u8; 4])) };
assert_eq!(
streams(r.open_demuxer_seekable("seeky", input()).unwrap()),
2
);
assert_eq!(
streams(r.open_demuxer_seekable("plain", input()).unwrap()),
1
);
assert_eq!(
streams(
r.open_demuxer("seeky", Box::new(Cursor::new(vec![])))
.unwrap()
),
1
);
assert!(matches!(
r.open_demuxer_seekable("missing", input()),
Err(Error::DemuxerNotFound(_))
));
}
struct TaggedMux(&'static str);
impl Muxer for TaggedMux {
fn write_header(&mut self, _: &[Stream]) -> Result<()> {
Ok(())
}
fn write_packet(&mut self, _: &rff_core::Packet) -> Result<()> {
Ok(())
}
fn write_trailer(&mut self) -> Result<()> {
Err(Error::invalid(self.0))
}
}
fn tag(mut m: Box<dyn Muxer>) -> String {
m.write_trailer().unwrap_err().to_string()
}
#[test]
fn seekable_muxer_open_prefers_the_seekable_factory_and_falls_back() {
let mut r = FormatRegistry::new();
let with_muxer = |name| Format {
muxer: Some(|_| Box::new(TaggedMux("plain"))),
..format(name)
};
r.register(with_muxer("plain"));
r.register(with_muxer("seeky"));
r.register(format("demux_only"));
r.register_seekable_muxer("seeky", |_| Box::new(TaggedMux("seekable")));
let output = || -> SeekOutput { Box::new(Cursor::new(Vec::new())) };
assert!(tag(r.open_muxer_seekable("seeky", output()).unwrap()).contains("seekable"));
assert!(tag(r.open_muxer_seekable("plain", output()).unwrap()).contains("plain"));
assert!(tag(r.open_muxer("seeky", Box::new(Vec::new())).unwrap()).contains("plain"));
assert!(matches!(
r.open_muxer_seekable("demux_only", output()),
Err(Error::MuxerNotFound(_))
));
assert!(matches!(
r.open_muxer_seekable("missing", output()),
Err(Error::MuxerNotFound(_))
));
}
}