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}