1use 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 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}