Skip to main content

rff_format/
lib.rs

1//! `rff-format` — the container (muxer/demuxer) abstraction layer (FFmpeg's
2//! `libavformat` core, minus the formats themselves).
3//!
4//! A container is the file wrapper (AVI, MP4, Matroska, ...) that interleaves
5//! one or more elementary streams. This crate defines:
6//! * [`Stream`] — the description of one elementary stream inside a container,
7//! * [`Demuxer`] — reads a container, exposing streams and [`Packet`]s,
8//! * [`Muxer`] — writes streams + packets back into a container,
9//! * [`FormatRegistry`] — what individual format crates register into.
10//!
11//! Concrete formats live in their own crates (`rff-format-avi`, ...).
12
13use std::collections::HashMap;
14use std::io::{Read, Write};
15
16use rff_core::{
17    CodecId, ColorRange, Error, MediaType, Packet, PixelFormat, Rational, Result, SampleFormat,
18};
19
20/// Description of one elementary stream within a container.
21#[derive(Debug, Clone)]
22pub struct Stream {
23    /// Position of this stream within its container.
24    pub index: usize,
25    pub codec_id: CodecId,
26    pub media_type: MediaType,
27    /// Unit of all timestamps on packets of this stream.
28    pub time_base: Rational,
29    // --- Video parameters (zero/ignored for non-video) ---
30    pub width: u32,
31    pub height: u32,
32    /// Raw pixel layout, when known (raw video). Self-describing codecs leave this `None`.
33    pub pixel_format: Option<PixelFormat>,
34    /// Luma/chroma value range. JPEG and PNG sources are always
35    /// [`ColorRange::Full`]; leaving it [`Unspecified`](ColorRange::Unspecified)
36    /// makes containers label the data limited-range, which silently corrupts it.
37    pub color_range: ColorRange,
38    // --- Audio parameters (zero/ignored for non-audio) ---
39    pub sample_rate: u32,
40    pub channels: u16,
41    /// Raw sample layout, when known (PCM). Self-describing codecs leave this `None`.
42    pub sample_format: Option<SampleFormat>,
43    /// Codec-private initialization data (e.g. H.264 SPS/PPS, OpusHead). Empty
44    /// for codecs whose bitstream is fully self-describing.
45    pub extradata: Vec<u8>,
46}
47
48impl Stream {
49    /// Create a stream with sane defaults for the given codec.
50    pub fn new(index: usize, codec_id: CodecId) -> Stream {
51        Stream {
52            index,
53            codec_id,
54            media_type: codec_id.media_type(),
55            time_base: Rational::new(1, 1000),
56            width: 0,
57            height: 0,
58            pixel_format: None,
59            color_range: ColorRange::Unspecified,
60            sample_rate: 0,
61            channels: 0,
62            sample_format: None,
63            extradata: Vec::new(),
64        }
65    }
66}
67
68/// A byte source a demuxer reads from. (`Box<dyn Read + Send>` keeps the trait
69/// object-safe and lets inputs be files, network streams, or in-memory buffers.)
70pub type Input = Box<dyn Read + Send>;
71/// A byte sink a muxer writes to.
72pub type Output = Box<dyn Write + Send>;
73
74/// Reads a container format: parses the header into [`Stream`]s, then yields
75/// [`Packet`]s until the input is exhausted.
76pub trait Demuxer: Send {
77    /// Parse the container header and return its streams.
78    fn read_header(&mut self) -> Result<Vec<Stream>>;
79
80    /// Read the next packet. Returns [`Error::Eof`] at end of input.
81    fn read_packet(&mut self) -> Result<Packet>;
82}
83
84/// Writes a container format: declare streams, write packets, finalize.
85pub trait Muxer: Send {
86    /// Write the container header for the given set of streams.
87    fn write_header(&mut self, streams: &[Stream]) -> Result<()>;
88
89    /// Write one packet (its `stream_index` selects the target stream).
90    fn write_packet(&mut self, packet: &Packet) -> Result<()>;
91
92    /// Finalize the container (indexes, trailers, fixups).
93    fn write_trailer(&mut self) -> Result<()>;
94}
95
96/// Factory opening a demuxer over an input byte source.
97pub type DemuxerFactory = fn(Input) -> Box<dyn Demuxer>;
98/// Factory opening a muxer over an output byte sink.
99pub type MuxerFactory = fn(Output) -> Box<dyn Muxer>;
100/// Content sniffer: scores how strongly a byte prefix looks like this format,
101/// `0` (no match) to `100` (certain) — mirrors FFmpeg's `AVInputFormat::read_probe`.
102pub type ProbeFn = fn(&[u8]) -> i32;
103
104/// Static description of a container format and its read/write support.
105pub struct Format {
106    /// Short name (`avi`, `mp4`, ...), as used by `ffmpeg -f <name>`.
107    pub name: &'static str,
108    pub long_name: &'static str,
109    /// File extensions that imply this format (without the dot).
110    pub extensions: &'static [&'static str],
111    /// Present if this format can be demuxed (read).
112    pub demuxer: Option<DemuxerFactory>,
113    /// Present if this format can be muxed (written).
114    pub muxer: Option<MuxerFactory>,
115    /// Content sniffer for magic-byte detection (independent of the filename).
116    pub probe: Option<ProbeFn>,
117}
118
119impl Format {
120    pub fn can_demux(&self) -> bool {
121        self.demuxer.is_some()
122    }
123    pub fn can_mux(&self) -> bool {
124        self.muxer.is_some()
125    }
126}
127
128/// Holds every container format known to a running engine.
129#[derive(Default)]
130pub struct FormatRegistry {
131    by_name: HashMap<&'static str, Format>,
132}
133
134impl FormatRegistry {
135    pub fn new() -> FormatRegistry {
136        FormatRegistry::default()
137    }
138
139    pub fn register(&mut self, format: Format) {
140        self.by_name.insert(format.name, format);
141    }
142
143    pub fn by_name(&self, name: &str) -> Option<&Format> {
144        self.by_name.get(name)
145    }
146
147    /// Find a format whose extension list contains `ext` (case-insensitive,
148    /// no leading dot). The cheap "guess by filename" path; [`probe`](Self::probe)
149    /// inspects content instead.
150    pub fn by_extension(&self, ext: &str) -> Option<&Format> {
151        let ext = ext.trim_start_matches('.').to_ascii_lowercase();
152        self.by_name
153            .values()
154            .find(|f| f.extensions.iter().any(|e| e.eq_ignore_ascii_case(&ext)))
155    }
156
157    /// Identify a format by sniffing a byte prefix: the registered format whose
158    /// [`probe`](Format::probe) returns the highest positive score wins. Returns
159    /// `None` if nothing recognizes the data.
160    pub fn probe(&self, data: &[u8]) -> Option<&Format> {
161        self.by_name
162            .values()
163            .filter_map(|f| f.probe.map(|p| (p(data), f)))
164            .filter(|(score, _)| *score > 0)
165            .max_by_key(|(score, _)| *score)
166            .map(|(_, f)| f)
167    }
168
169    /// Open a demuxer for the format named `name`.
170    pub fn open_demuxer(&self, name: &str, input: Input) -> Result<Box<dyn Demuxer>> {
171        self.by_name
172            .get(name)
173            .and_then(|f| f.demuxer)
174            .map(|factory| factory(input))
175            .ok_or_else(|| Error::DemuxerNotFound(name.to_string()))
176    }
177
178    /// Open a muxer for the format named `name`.
179    pub fn open_muxer(&self, name: &str, output: Output) -> Result<Box<dyn Muxer>> {
180        self.by_name
181            .get(name)
182            .and_then(|f| f.muxer)
183            .map(|factory| factory(output))
184            .ok_or_else(|| Error::MuxerNotFound(name.to_string()))
185    }
186
187    pub fn iter(&self) -> impl Iterator<Item = &Format> {
188        self.by_name.values()
189    }
190
191    pub fn len(&self) -> usize {
192        self.by_name.len()
193    }
194
195    pub fn is_empty(&self) -> bool {
196        self.by_name.is_empty()
197    }
198}