Skip to main content

oxideav_container/
lib.rs

1//! Container traits (demuxer + muxer) and a registry.
2
3pub mod registry;
4
5use oxideav_core::{Packet, Result, StreamInfo};
6use std::io::{Read, Seek, Write};
7
8/// Reads a container and emits packets per stream.
9pub trait Demuxer: Send {
10    /// Name of the container format (e.g., `"wav"`).
11    fn format_name(&self) -> &str;
12
13    /// Streams in this container. Stable across the lifetime of the demuxer.
14    fn streams(&self) -> &[StreamInfo];
15
16    /// Read the next packet from any stream. Returns `Error::Eof` at end.
17    fn next_packet(&mut self) -> Result<Packet>;
18
19    /// Hint that only the listed stream indices will be consumed by the
20    /// pipeline. Demuxers that can efficiently skip inactive streams at
21    /// the container level (e.g., MKV cluster-aware, MP4 trak-aware)
22    /// should override this. The default is a no-op — the pipeline
23    /// drops unwanted packets on the floor.
24    fn set_active_streams(&mut self, _indices: &[u32]) {}
25
26    /// Seek to the nearest keyframe at or before `pts` (in the given
27    /// stream's time base). Returns the actual timestamp seeked to, or
28    /// `Error::Unsupported` if this demuxer can't seek.
29    fn seek_to(&mut self, _stream_index: u32, _pts: i64) -> Result<i64> {
30        Err(oxideav_core::Error::unsupported(
31            "this demuxer does not support seeking",
32        ))
33    }
34
35    /// Container-level metadata as ordered (key, value) pairs.
36    /// Keys follow a loose convention borrowed from Vorbis comments:
37    /// `title`, `artist`, `album`, `comment`, `date`, `sample_name:<n>`,
38    /// `channels`, `n_patterns`, etc. Demuxers that carry no metadata
39    /// return an empty slice (the default).
40    fn metadata(&self) -> &[(String, String)] {
41        &[]
42    }
43    /// Container-level duration, if known. Default is `None` — callers
44    /// may fall back to the longest per-stream duration. Expressed as
45    /// microseconds for portability; convert to seconds at the edge.
46    fn duration_micros(&self) -> Option<i64> {
47        None
48    }
49
50    /// Attached pictures (cover art, artist photos, ...) embedded in
51    /// the container. Returns an empty slice (the default) when the
52    /// container carries none or doesn't support them. Containers that
53    /// do — ID3v2 on MP3, `METADATA_BLOCK_PICTURE` on FLAC, `covr`
54    /// atoms on MP4, etc. — override this to expose the images.
55    fn attached_pictures(&self) -> &[oxideav_core::AttachedPicture] {
56        &[]
57    }
58}
59
60/// Writes packets into a container.
61pub trait Muxer: Send {
62    fn format_name(&self) -> &str;
63
64    /// Write the container header. Must be called after stream configuration
65    /// and before the first `write_packet`.
66    fn write_header(&mut self) -> Result<()>;
67
68    fn write_packet(&mut self, packet: &Packet) -> Result<()>;
69
70    /// Finalize the file (write index, patch in total sizes, etc.).
71    fn write_trailer(&mut self) -> Result<()>;
72}
73
74/// Factory that tries to open a stream as a particular container format.
75///
76/// Implementations should read the minimum needed to confirm the format and
77/// return `Error::InvalidData` if the stream is not in this format.
78pub type OpenDemuxerFn = fn(input: Box<dyn ReadSeek>) -> Result<Box<dyn Demuxer>>;
79
80/// Factory that creates a muxer for a set of streams.
81pub type OpenMuxerFn =
82    fn(output: Box<dyn WriteSeek>, streams: &[StreamInfo]) -> Result<Box<dyn Muxer>>;
83
84/// Information passed to a content-based [`ProbeFn`].
85///
86/// `buf` holds the first few KB of the input — enough to recognise the
87/// magic bytes of any container we know about. `ext` carries the file
88/// extension as a hint (lowercase, no leading dot); some containers
89/// (raw MP3 with no ID3v2, headerless tracker formats) need it to break
90/// ties with otherwise weak signatures.
91pub struct ProbeData<'a> {
92    pub buf: &'a [u8],
93    pub ext: Option<&'a str>,
94}
95
96/// Confidence score returned by a [`ProbeFn`]. `0` means no match.
97/// Higher means more certain. Conventional values:
98///
99/// * `100` – unambiguous magic bytes at a known offset
100/// * `75`  – signature match corroborated by file extension
101/// * `50`  – signature match without extension corroboration
102/// * `25`  – extension match only (no content signature available)
103pub type ProbeScore = u8;
104
105/// Maximum probe score (alias for `100`).
106pub const MAX_PROBE_SCORE: ProbeScore = 100;
107/// Default score returned when only the file extension matches.
108pub const PROBE_SCORE_EXTENSION: ProbeScore = 25;
109
110/// Content-based format detection function.
111///
112/// Returns a [`ProbeScore`] in `0..=100`. Implementations should be
113/// pure (no I/O, no allocation beyond the stack) and fast — they may
114/// be invoked once per registered demuxer on every input file.
115pub type ProbeFn = fn(probe: &ProbeData) -> ProbeScore;
116
117/// Convenience trait bundle for seekable readers.
118pub trait ReadSeek: Read + Seek + Send {}
119impl<T: Read + Seek + Send> ReadSeek for T {}
120
121/// Convenience trait bundle for seekable writers.
122pub trait WriteSeek: Write + Seek + Send {}
123impl<T: Write + Seek + Send> WriteSeek for T {}
124
125pub use registry::ContainerRegistry;
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use oxideav_core::Error;
131
132    struct DummyDemuxer;
133
134    impl Demuxer for DummyDemuxer {
135        fn format_name(&self) -> &str {
136            "dummy"
137        }
138        fn streams(&self) -> &[StreamInfo] {
139            &[]
140        }
141        fn next_packet(&mut self) -> Result<Packet> {
142            Err(Error::Eof)
143        }
144    }
145
146    #[test]
147    fn default_seek_to_is_unsupported() {
148        let mut d = DummyDemuxer;
149        match d.seek_to(0, 0) {
150            Err(Error::Unsupported(_)) => {}
151            other => panic!(
152                "expected default seek_to to return Unsupported, got {:?}",
153                other
154            ),
155        }
156    }
157}