pub mod codec_id;
pub mod demux;
pub mod ebml;
pub mod ids;
pub mod mux;
use oxideav_container::ContainerRegistry;
pub fn register(reg: &mut ContainerRegistry) {
reg.register_demuxer("matroska", demux::open);
reg.register_muxer("matroska", mux::open);
reg.register_probe("matroska", probe_matroska);
reg.register_demuxer("webm", demux::open);
reg.register_muxer("webm", mux::open_webm);
reg.register_probe("webm", probe_webm);
reg.register_extension("mkv", "matroska");
reg.register_extension("mka", "matroska");
reg.register_extension("mks", "matroska");
reg.register_extension("webm", "webm");
}
const EBML_MAGIC: [u8; 4] = [0x1A, 0x45, 0xDF, 0xA3];
const SCORE_DOCTYPE_MATCH: u8 = 100;
const SCORE_SIGNATURE_ONLY: u8 = 60;
fn probe_matroska(p: &oxideav_container::ProbeData) -> u8 {
match probe_doctype(p.buf) {
DocTypeProbe::Matroska => SCORE_DOCTYPE_MATCH,
DocTypeProbe::Webm => SCORE_SIGNATURE_ONLY,
DocTypeProbe::EbmlOnly => SCORE_SIGNATURE_ONLY,
DocTypeProbe::NotEbml => 0,
}
}
fn probe_webm(p: &oxideav_container::ProbeData) -> u8 {
match probe_doctype(p.buf) {
DocTypeProbe::Webm => SCORE_DOCTYPE_MATCH,
DocTypeProbe::Matroska => 0,
DocTypeProbe::EbmlOnly => 0,
DocTypeProbe::NotEbml => 0,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum DocTypeProbe {
Matroska,
Webm,
EbmlOnly,
NotEbml,
}
fn probe_doctype(buf: &[u8]) -> DocTypeProbe {
if buf.len() < 4 || buf[0..4] != EBML_MAGIC {
return DocTypeProbe::NotEbml;
}
let mut cur = std::io::Cursor::new(&buf[4..]);
let (hdr_size, _) = match ebml::read_vint(&mut cur, false) {
Ok(v) => v,
Err(_) => return DocTypeProbe::EbmlOnly,
};
let hdr_start = 4 + cur.position() as usize;
let hdr_end = hdr_start.saturating_add(hdr_size as usize);
let scan_end = hdr_end.min(buf.len());
let mut pos = hdr_start;
while pos < scan_end {
let mut sub = std::io::Cursor::new(&buf[pos..scan_end]);
let (id, id_len) = match ebml::read_vint(&mut sub, true) {
Ok(v) => v,
Err(_) => return DocTypeProbe::EbmlOnly,
};
let (size, size_len) = match ebml::read_vint(&mut sub, false) {
Ok(v) => v,
Err(_) => return DocTypeProbe::EbmlOnly,
};
let data_start = pos + id_len + size_len;
let data_end = data_start.saturating_add(size as usize);
if data_end > scan_end {
return DocTypeProbe::EbmlOnly;
}
if id == ids::EBML_DOC_TYPE as u64 {
let slice = &buf[data_start..data_end];
let trimmed = slice.split(|&b| b == 0).next().unwrap_or(&[]);
return match trimmed {
b"matroska" => DocTypeProbe::Matroska,
b"webm" => DocTypeProbe::Webm,
_ => DocTypeProbe::EbmlOnly,
};
}
pos = data_end;
}
DocTypeProbe::EbmlOnly
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_ebml_head(doc_type: &str) -> Vec<u8> {
use crate::ebml::{write_element_id, write_vint};
let mut body = Vec::new();
body.extend_from_slice(&write_element_id(ids::EBML_VERSION));
body.extend_from_slice(&write_vint(1, 0));
body.push(0x01);
body.extend_from_slice(&write_element_id(ids::EBML_DOC_TYPE));
body.extend_from_slice(&write_vint(doc_type.len() as u64, 0));
body.extend_from_slice(doc_type.as_bytes());
body.extend_from_slice(&write_element_id(ids::EBML_DOC_TYPE_VERSION));
body.extend_from_slice(&write_vint(1, 0));
body.push(0x02);
body.extend_from_slice(&write_element_id(ids::EBML_DOC_TYPE_READ_VERSION));
body.extend_from_slice(&write_vint(1, 0));
body.push(0x02);
let mut out = Vec::new();
out.extend_from_slice(&write_element_id(ids::EBML_HEADER));
out.extend_from_slice(&write_vint(body.len() as u64, 0));
out.extend_from_slice(&body);
out
}
#[test]
fn probe_doctype_webm() {
let head = synth_ebml_head("webm");
assert_eq!(probe_doctype(&head), DocTypeProbe::Webm);
}
#[test]
fn probe_doctype_matroska() {
let head = synth_ebml_head("matroska");
assert_eq!(probe_doctype(&head), DocTypeProbe::Matroska);
}
#[test]
fn probe_doctype_non_ebml() {
let buf = b"RIFF....WAVEfmt ";
assert_eq!(probe_doctype(buf), DocTypeProbe::NotEbml);
}
#[test]
fn webm_probe_prefers_webm() {
let head = synth_ebml_head("webm");
let p = oxideav_container::ProbeData {
buf: &head,
ext: None,
};
let webm_score = probe_webm(&p);
let mkv_score = probe_matroska(&p);
assert!(
webm_score > mkv_score,
"webm probe ({}) should outrank matroska probe ({}) on DocType=webm",
webm_score,
mkv_score
);
assert_eq!(webm_score, SCORE_DOCTYPE_MATCH);
}
#[test]
fn matroska_probe_prefers_matroska() {
let head = synth_ebml_head("matroska");
let p = oxideav_container::ProbeData {
buf: &head,
ext: None,
};
let webm_score = probe_webm(&p);
let mkv_score = probe_matroska(&p);
assert!(
mkv_score > webm_score,
"matroska probe ({}) should outrank webm probe ({}) on DocType=matroska",
mkv_score,
webm_score
);
assert_eq!(mkv_score, SCORE_DOCTYPE_MATCH);
assert_eq!(webm_score, 0);
}
#[test]
fn registry_extension_mapping() {
let mut reg = ContainerRegistry::new();
register(&mut reg);
assert_eq!(reg.container_for_extension("webm"), Some("webm"));
assert_eq!(reg.container_for_extension("mkv"), Some("matroska"));
assert_eq!(reg.container_for_extension("mka"), Some("matroska"));
assert_eq!(reg.container_for_extension("mks"), Some("matroska"));
}
}