1pub mod blocks;
2pub mod decode;
3pub mod lj92;
4
5pub enum MLVError {
6 CorruptFile,
7}
8
9mod util_types {
12 use super::*;
13
14 #[derive(Debug,Clone,Copy,PartialEq,Eq,PartialOrd)]
15 pub struct BlockHeader {
16 pub block_type: BlockTag,
17 pub block_size: u32,
18 pub time_stamp: u64,
19 }
20
21 impl BlockHeader {
22 #[inline]
23 pub const fn from_bytes([a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p]: [u8; 16]) -> Self {
24 Self {
25 block_type: BlockTag([a,b,c,d]),
26 block_size: u32::from_le_bytes([e,f,g,h]),
27 time_stamp: u64::from_le_bytes([i,j,k,l,m,n,o,p]),
28 }
29 }
30 #[inline]
31 pub fn to_bytes(&self, out: &mut [u8]) {
32 out[0..4].copy_from_slice(&self.block_type.0);
33 out[4..8].copy_from_slice(&self.block_size.to_le_bytes());
34 out[8..16].copy_from_slice(&self.time_stamp.to_le_bytes());
35 }
36 }
37
38 #[derive(Clone,Copy,PartialEq,Eq,PartialOrd)]
39 #[repr(transparent)]
40 pub struct BlockTag (pub [u8;4]);
41
42 impl BlockTag {
43 #[inline]
44 pub const fn new(s: &str) -> Option<Self> {
45 let s = s.as_bytes();
46 if s.len() == 4 {
47 Some(Self([s[0],s[1],s[2],s[3]]))
48 } else { return None; }
49 }
50 }
51
52 impl PartialEq<&str> for BlockTag {
53 #[inline]
54 fn eq(&self, s: &&str) -> bool {
55 s.len() == 4 && s.chars().count() == 4 && self.0.iter().zip(s.chars()).all(|(a,b)| *a == b as u8)
56 }
57 }
58
59 impl PartialEq<str> for BlockTag {
60 #[inline] fn eq(&self, s: &str) -> bool { self == s }
61 }
62
63 impl Debug for BlockTag {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 match core::str::from_utf8(&self.0) {
66 Ok(s) => write!(f, "{s}"),
67 Err(_) => write!(f, "[{:#02x},{:#02x},{:#02x},{:#02x}]", self.0[0],self.0[1],self.0[2],self.0[3]),
68 }
69 }
70 }
71
72 #[derive(Default,Debug,Copy,Clone)]
74 pub struct CoreBlocks {
75 pub mlvi: Option<[u8; 52]>,
76 pub rawi: Option<[u8; 180]>,
77 pub wavi: Option<[u8; 32]>,
79 }
89
90 #[derive(Clone,Copy,PartialEq,Eq)]
92 #[repr(transparent)]
93 pub struct FileLocation ([u8;6]);
94
95 impl FileLocation {
96 #[inline]
97 pub fn new(chunk: u8, offset: u64) -> Option<Self> {
98 let [x,y,z,a,b,c,d,e] = offset.to_be_bytes();
99 (x == 0 && y == 0 && z == 0).then_some(Self([chunk,a,b,c,d,e]))
100 }
101 #[inline]
102 pub fn offset(self) -> u64 {
103 let Self([_,a,b,c,d,e]) = self;
104 u64::from_be_bytes([0,0,0,a,b,c,d,e])
105 }
106 #[inline]
107 pub fn chunk(self) -> u8 { self.0[0] }
108 #[inline]
109 pub fn apply_offset(self, offset: i64) -> Option<Self> {
110 Self::new(self.chunk(), u64::try_from((self.offset() as i64).checked_add(offset)?).ok()?)
111 }
112 }
113
114 impl core::fmt::Debug for FileLocation {
115 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116 let (chunk, pos) = (self.chunk(), self.offset());
117 write!(f, "FileLocation {{ chunk: {chunk}, pos: {pos} }}")
118 }
120 }
121}
122
123pub use util_types::*;
124pub mod block_reader;
125
126use std::{io::BufReader, fs::File, path::Path, fmt::Debug};
129
130#[derive(Clone,Copy,Debug,PartialEq)]
131pub struct BlockEntry {
132 pub block: BlockHeader,
133 pub location: FileLocation,
134 }
136
137#[derive(Debug)]
145pub struct MainReader<Reader> {
146 pub core_blocks: CoreBlocks,
147 pub chunk_files: Vec<(Reader, u64)>, pub all_blocks: Vec<BlockEntry>,
149 pub all_audf: Vec<(FileLocation, u64, u64, u32)>,
151 pub all_vidf: Vec<(FileLocation, u64, u64, u32)>,
152}
153
154
155#[cfg(feature = "std")]
156impl MainReader<BufReader<File>>
157{
158 pub fn open_mlv<P: AsRef<Path>>(
159 path: P,
160 max_frames: Option<u32>
161 ) -> Option<Self> {
162 let mut file = File::open(path).ok()?;
165 let mut filesize = file.metadata().unwrap().len();
166 let mut chunk_files_and_lengths = vec![(BufReader::new(file), filesize)];
167
168 let mut reader = Self::empty();
170
171 let mut num_vidf = 0u32;
172
173 for (chunk_index, (file, file_length)) in chunk_files_and_lengths.iter_mut().enumerate() {
175 let result = block_reader::read_blocks::<200, _>(
176 *file_length,
177 block_reader::read_wrapper(file),
178 |block_bytes: &[u8], block_position: u64| {
179 let block_info = BlockHeader::from_bytes(*block_bytes[0..16].first_chunk().unwrap());
180 if block_info.block_type != "NULL" { let location = FileLocation::new(chunk_index as u8, block_position).unwrap();
182 reader.all_blocks.push(
183 BlockEntry { block: block_info, location }
184 );
185 fn try_into<const N: usize>(out: &mut Option<[u8; N]>, data: Option<&[u8]>) {
187 if let Some(data) = data {
188 if out.is_none() && data.len() >= N {
189 *out = Some(core::array::from_fn(|i| data[i]));
190 }
191 }
192 }
193 if block_info.block_type == "MLVI" {
194 try_into(&mut reader.core_blocks.mlvi, Some(block_bytes));
195 } else if block_info.block_type == "RAWI" {
196 try_into(&mut reader.core_blocks.rawi, Some(block_bytes));
197 } else if block_info.block_type == "WAVI" {
198 try_into(&mut reader.core_blocks.wavi, Some(block_bytes));
199 } else if block_info.block_type == "VIDF" {
200 let block_size = block_info.block_size;
201 let frame_data_offset = u32::from_le_bytes(*block_bytes[28..].first_chunk().unwrap());
202 let offset_in_file = block_position + 32 + frame_data_offset as u64;
203 let frame_data_size = (block_size as u32 - (frame_data_offset as u32 + 32)) as u32;
204 reader.all_vidf.push((location, block_info.time_stamp, offset_in_file, frame_data_size)); num_vidf += 1;
206 if let Some(max_frames) = max_frames && max_frames == num_vidf {
207 return true;
208 }
209 } else if block_info.block_type == "AUDF" {
210 let block_size = block_info.block_size;
211 let frame_data_offset = u32::from_le_bytes(*block_bytes[20..].first_chunk().unwrap());
212 let offset_in_file = block_position + 24 + frame_data_offset as u64;
213 let frame_data_size = (block_size as u32 - (frame_data_offset as u32 + 24)) as u32;
214 reader.all_audf.push((location, block_info.time_stamp, offset_in_file, frame_data_size)); }
216 }
217 return true
218 }
219 );
220 println!("Result = {:?}", result);
221 }
222
223 reader.chunk_files = chunk_files_and_lengths;
224
225 reader.all_blocks.sort_unstable_by(|a,b| a.block.time_stamp.cmp(&b.block.time_stamp));
227 reader.all_vidf.sort_unstable_by(|a,b| a.1.cmp(&b.1));
228 reader.all_audf.sort_unstable_by(|a,b| a.1.cmp(&b.1));
229
230 Some(reader)
231 }
232}
233
234pub trait ReadExact {
235 type ReadError;
236 fn read_exact(&mut self, pos: u64, buf: &mut [u8]) -> Result<(), Self::ReadError>;
237}
238
239#[cfg(feature = "std")]
240impl<R: std::io::Read + std::io::Seek> ReadExact for R {
241 type ReadError = std::io::Error;
242 fn read_exact(&mut self, pos: u64, buf: &mut [u8]) -> Result<(), Self::ReadError> {
243 self.seek(std::io::SeekFrom::Start(pos))?;
244 self.read_exact(buf)
245 }
246}
247
248impl<Reader> MainReader<Reader>
249{
250 pub fn width(&self) -> Option<u32> {
251 blocks::get_u16(&self.core_blocks.rawi?, blocks::RAWI.field_offset("xRes")?).map(|x| x as u32)
252 }
253
254 pub fn height(&self) -> Option<u32> {
255 blocks::get_u16(&self.core_blocks.rawi?, blocks::RAWI.field_offset("yRes")?).map(|x| x as u32)
256 }
257
258 pub fn fps(&self) -> Option<(u32, u32)> {
259 let nom = blocks::get_u32(&self.core_blocks.mlvi?, blocks::MLVI.field_offset("sourceFpsNom")?)?;
260 let denom = blocks::get_u32(&self.core_blocks.mlvi?, blocks::MLVI.field_offset("sourceFpsDenom")?)?;
261 Some((nom, denom))
262 }
263
264 pub fn black_level(&self) -> Option<i32> {
265 blocks::get_i32(&self.core_blocks.rawi?, blocks::RAWI.field_offset("black_level")?)
266 }
267
268 pub fn white_level(&self) -> Option<i32> {
269 blocks::get_i32(&self.core_blocks.rawi?, blocks::RAWI.field_offset("white_level")?)
270 }
271
272 pub fn bitdepth(&self) -> Option<i32> {
273 blocks::get_i32(&self.core_blocks.rawi?, blocks::RAWI.field_offset("bits_per_pixel")?)
274 }
275
276 pub fn is_compressed(&self) -> Option<bool> {
277 const MLV_VIDEO_CLASS_FLAG_LJ92: u16 = 0x20;
278 let class = blocks::get_u16(&self.core_blocks.mlvi?, blocks::MLVI.field_offset("videoClass")?);
279 Some(class? & MLV_VIDEO_CLASS_FLAG_LJ92 != 0)
280 }
281
282 pub fn audio_sample_rate(&self) -> Option<u32> {
283 blocks::get_u32(&self.core_blocks.wavi?, blocks::WAVI.field_offset("samplingRate")?)
284 }
285
286 pub fn audio_channels(&self) -> Option<u16> {
287 blocks::get_u16(&self.core_blocks.wavi?, blocks::WAVI.field_offset("channels")?)
288 }
289
290 pub fn audio_bits_per_sample(&self) -> Option<u16> {
291 blocks::get_u16(&self.core_blocks.wavi?, blocks::WAVI.field_offset("bitsPerSample")?)
292 }
293
294 fn empty() -> Self {
295 Self {
296 chunk_files: vec![],
297 core_blocks: CoreBlocks::default(),
298 all_blocks: Vec::new(),
299 all_vidf: vec![],
300 all_audf: vec![],
301 }
302 }
303
304 pub fn print_blocks(&self) {
305 for b in self.all_blocks.iter() {
306 if b.block.block_type != "VIDF" && b.block.block_type != "AUDF" {
307 println!("{:?} : {} bytes", b.block.block_type, b.block.block_size);
308 }
309 }
310 println!("Total blocks: {}", self.all_blocks.len());
311 }
312
313 pub fn num_frames(&self) -> u32 {
314 self.all_vidf.len() as u32
315 }
316
317 pub fn frame_data_location_and_size(&self, idx: u32) -> Option<(FileLocation, u32)> {
318 let (file_location, _timestamp, pos, size) = *self.all_vidf.get(idx as usize)?;
319 Some((FileLocation::new(file_location.chunk(), pos)?, size))
320 }
321
322 pub fn get_frame_payload<'a>(&mut self, idx: u32, mut out: &'a mut [u8]) -> Option<&'a [u8]>
325 where
326 Reader: ReadExact
327 {
328 let (file_location, frame_data_size) = self.frame_data_location_and_size(idx)?;
329 if out.len() < frame_data_size as usize {
330 return None } else {
332 let file = &mut self.chunk_files[file_location.chunk() as usize].0;
333 out = &mut out[0..frame_data_size as usize];
334 let result = file.read_exact(file_location.offset(), &mut out).ok()?;
335 return Some(out)
336 }
337 }
338
339 pub fn decode_frame<'a>(&mut self, idx: u32, output: &'a mut [u16]) -> Option<&'a [u16]>
340 where
341 Reader: ReadExact
342 {
343 let (file_location, frame_data_size) = self.frame_data_location_and_size(idx)?;
344
345 let mut data = Vec::with_capacity(frame_data_size as usize);
347 unsafe { data.set_len(frame_data_size as usize) }
348
349 self.get_frame_payload(idx, &mut data);
350
351 match (self.bitdepth()?, self.is_compressed()?) {
353 (14, false) => decode::decode_packed14(&data, output),
354 (12, false) => decode::decode_packed12(&data, output),
355 (10, false) => decode::decode_packed10(&data, output),
356 (_, true) => {decode::decode_lj92(&data, output);},
357 _ => {}, }
359
360 return Some(&output[..]);
361 }
362
363 pub fn read_audio(&mut self) -> Option<Vec<i16>>
366 where
367 Reader: ReadExact
368 {
369 let mut audio_buffer = vec![];
370 let mut chunk_buffer = vec![];
371 for &(location, timstamp, pos, size) in &self.all_audf {
372 chunk_buffer.clear();
373 chunk_buffer.reserve(size as usize);
374 unsafe { chunk_buffer.set_len(size as usize); }
375 self.chunk_files[location.chunk() as usize].0.read_exact(pos, &mut chunk_buffer).ok()?;
376 for chunk in chunk_buffer.as_chunks().0.iter() {
377 audio_buffer.push(i16::from_le_bytes(*chunk))
378 }
379 }
380 Some(audio_buffer)
381 }
382}