Skip to main content

siglus_assets/
omv.rs

1//! OMV (Ogg/Theora wrapper) parser.
2//!
3//! Parser for the OMV container format.
4//!
5//! OMV stores a small fixed metadata block followed by internal seek tables and
6//! finally the embedded raw Ogg bitstream (Theora/Vorbis). For playback we only
7//! need the display size, frame time, and the offset of the first `OggS` page.
8
9use anyhow::{bail, Context, Result};
10use std::fs::File;
11use std::io::Read;
12use std::path::Path;
13
14pub const OMV_THEORA_TYPE_RGB: u32 = 0;
15pub const OMV_THEORA_TYPE_RGBA: u32 = 1;
16pub const OMV_THEORA_TYPE_YUV: u32 = 2;
17
18#[derive(Debug, Clone)]
19pub struct OmvHeader {
20    pub header_size: u32,
21    pub version: u32,
22    pub theora_type: u32,
23    pub display_width: u32,
24    pub display_height: u32,
25    pub frame_time_us: u32,
26    pub max_data_size: u32,
27    pub page_count_hint: u32,
28    pub packet_count_hint: u32,
29}
30
31#[derive(Debug, Clone, Copy)]
32pub struct OmvTheoraPage {
33    pub ofs: u32,
34    pub time: u32,
35    pub key: u8,
36    pub reserved: [u8; 3],
37}
38
39#[derive(Debug, Clone, Copy)]
40pub struct OmvTheoraPacket {
41    pub ofs: u32,
42    pub time: u32,
43}
44
45#[derive(Debug, Clone)]
46pub struct OmvFile {
47    pub header: OmvHeader,
48    pub pages: Vec<OmvTheoraPage>,
49    pub packets: Vec<OmvTheoraPacket>,
50    pub ogg_data_offset: u64,
51}
52
53impl OmvFile {
54    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
55        let bytes = std::fs::read(&path)
56            .with_context(|| format!("open OMV: {}", path.as_ref().display()))?;
57        let header = read_header(&bytes)?;
58        let ogg_data_offset = find_ogg_offset(&bytes)?;
59        Ok(Self {
60            header,
61            pages: Vec::new(),
62            packets: Vec::new(),
63            ogg_data_offset,
64        })
65    }
66
67    /// Read the embedded Ogg bitstream (Theora) as bytes.
68    pub fn read_embedded_ogg(path: impl AsRef<Path>) -> Result<Vec<u8>> {
69        let bytes = std::fs::read(&path)
70            .with_context(|| format!("open OMV: {}", path.as_ref().display()))?;
71        let ogg_data_offset = find_ogg_offset(&bytes)? as usize;
72        Ok(bytes[ogg_data_offset..].to_vec())
73    }
74}
75
76fn read_header(buf: &[u8]) -> Result<OmvHeader> {
77    if buf.len() < 0x58 {
78        bail!("OMV header too small");
79    }
80
81    let header_size = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
82    let version = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
83    let theora_type = u32::from_le_bytes([buf[0x28], buf[0x29], buf[0x2a], buf[0x2b]]);
84    let display_width = u32::from_le_bytes([buf[0x2c], buf[0x2d], buf[0x2e], buf[0x2f]]);
85    let display_height = u32::from_le_bytes([buf[0x30], buf[0x31], buf[0x32], buf[0x33]]);
86    let frame_time_us = u32::from_le_bytes([buf[0x3c], buf[0x3d], buf[0x3e], buf[0x3f]]);
87    let max_data_size = u32::from_le_bytes([buf[0x40], buf[0x41], buf[0x42], buf[0x43]]);
88    let page_count_hint = u32::from_le_bytes([buf[0x4c], buf[0x4d], buf[0x4e], buf[0x4f]]);
89    let packet_count_hint = u32::from_le_bytes([buf[0x50], buf[0x51], buf[0x52], buf[0x53]]);
90
91    if header_size < 0x58 {
92        bail!("invalid OMV header size: {header_size:#x}");
93    }
94    if theora_type > OMV_THEORA_TYPE_YUV {
95        bail!("invalid OMV theora type: {theora_type}");
96    }
97    if display_width == 0 || display_height == 0 {
98        bail!(
99            "invalid OMV display size: {}x{}",
100            display_width,
101            display_height
102        );
103    }
104
105    Ok(OmvHeader {
106        header_size,
107        version,
108        theora_type,
109        display_width,
110        display_height,
111        frame_time_us,
112        max_data_size,
113        page_count_hint,
114        packet_count_hint,
115    })
116}
117
118fn find_ogg_offset(buf: &[u8]) -> Result<u64> {
119    let needle = b"OggS";
120    let pos = buf
121        .windows(needle.len())
122        .position(|w| w == needle)
123        .ok_or_else(|| anyhow::anyhow!("OggS not found in OMV payload"))?;
124    Ok(pos as u64)
125}