Skip to main content

oxide_browser/
video_format.rs

1//! Magic-byte sniffing and MIME mapping for guest video containers.
2
3/// Unknown or not recognized as a supported Oxide video format.
4pub const VIDEO_FORMAT_UNKNOWN: u32 = 0;
5/// MP4 / ISO BMFF (`ftyp`).
6pub const VIDEO_FORMAT_MP4: u32 = 1;
7/// WebM / Matroska (EBML).
8pub const VIDEO_FORMAT_WEBM: u32 = 2;
9/// AV1 bitstream (often in MP4 or WebM; hint only).
10pub const VIDEO_FORMAT_AV1: u32 = 3;
11
12/// `Accept` header for [`super::capabilities`] URL fetches (progressive + adaptive).
13pub const VIDEO_HTTP_ACCEPT: &str = "video/mp4,video/webm,video/quicktime,video/x-matroska,application/vnd.apple.mpegurl,application/x-mpegURL,audio/mpegurl,video/*;q=0.9,*/*;q=0.1";
14
15/// File suffix for temp files when saving bytes (`.mp4`, `.webm`, …).
16pub fn suffix_for_format(code: u32) -> &'static str {
17    match code {
18        VIDEO_FORMAT_WEBM => ".webm",
19        VIDEO_FORMAT_AV1 => ".mp4",
20        VIDEO_FORMAT_MP4 => ".mp4",
21        _ => ".bin",
22    }
23}
24
25/// Inspect leading bytes to guess container (does not validate full file).
26pub fn sniff_video_format(data: &[u8]) -> u32 {
27    if data.len() < 12 {
28        return VIDEO_FORMAT_UNKNOWN;
29    }
30    // ISO BMFF: size + "ftyp" at offset 4, or "ftyp" at 0 in some files
31    if data.len() >= 8 && &data[4..8] == b"ftyp" {
32        return VIDEO_FORMAT_MP4;
33    }
34    if data.len() >= 12 && data[0..4] == [0x00, 0x00, 0x00, 0x1c] && &data[4..8] == b"ftyp" {
35        return VIDEO_FORMAT_MP4;
36    }
37    // EBML / WebM / Matroska
38    if data.len() >= 4 && data[0] == 0x1a && data[1] == 0x45 && data[2] == 0xdf && data[3] == 0xa3 {
39        return VIDEO_FORMAT_WEBM;
40    }
41    VIDEO_FORMAT_UNKNOWN
42}
43
44/// Map `Content-Type` to a `VIDEO_FORMAT_*` constant (for example [`VIDEO_FORMAT_MP4`]).
45pub fn mime_to_video_format(mime: &str) -> u32 {
46    let s = mime
47        .split(';')
48        .next()
49        .unwrap_or(mime)
50        .trim()
51        .to_ascii_lowercase();
52    match s.as_str() {
53        "video/mp4" | "video/quicktime" | "application/mp4" => VIDEO_FORMAT_MP4,
54        "video/webm" | "video/x-matroska" => VIDEO_FORMAT_WEBM,
55        "video/av1" => VIDEO_FORMAT_AV1,
56        "application/vnd.apple.mpegurl" | "application/x-mpegURL" | "audio/mpegurl" => {
57            // HLS manifest
58            VIDEO_FORMAT_UNKNOWN
59        }
60        _ => VIDEO_FORMAT_UNKNOWN,
61    }
62}
63
64/// MIME types that usually indicate an HTML/JSON error body rather than media.
65pub fn is_likely_non_video_document(mime: &str) -> bool {
66    let s = mime
67        .split(';')
68        .next()
69        .unwrap_or(mime)
70        .trim()
71        .to_ascii_lowercase();
72    s.starts_with("text/html")
73        || s.starts_with("text/plain")
74        || s == "application/json"
75        || s.starts_with("text/")
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn sniff_mp4_ftyp() {
84        let mut b = vec![0u8; 12];
85        b[4..8].copy_from_slice(b"ftyp");
86        assert_eq!(sniff_video_format(&b), VIDEO_FORMAT_MP4);
87    }
88
89    #[test]
90    fn sniff_webm_ebml() {
91        let b = [
92            0x1a, 0x45, 0xdf, 0xa3, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
93        ];
94        assert_eq!(sniff_video_format(&b), VIDEO_FORMAT_WEBM);
95    }
96}