Skip to main content

mlv/
lib.rs

1pub mod blocks;
2pub mod decode;
3pub mod lj92;
4
5pub enum MLVError {
6    CorruptFile,
7}
8
9/************************** Core traits and types ***************************/
10
11mod 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    /* Core blocks providing information about an MLV clip */
73    #[derive(Default,Debug,Copy,Clone)]
74    pub struct CoreBlocks {
75        pub mlvi: Option<[u8; 52]>,
76        pub rawi: Option<[u8; 180]>,
77        // pub rawc: Option<blocks::Rawc>,
78        pub wavi: Option<[u8; 32]>,
79        /* TODO. */
80        // pub idnt: Option<(u64, IDNT)>,
81        // pub diso: Option<(u64, DISO)>,
82        // pub expo: Option<(u64, EXPO)>,
83        // pub rtci: Option<(u64, RTCI)>,
84        // pub lens: Option<(u64, LENS)>,
85        // pub elns: Option<(u64, ELNS)>,
86        // pub wbal: Option<(u64, WBAL)>,
87        // pub styl: Option<(u64, STYL)>,
88    }
89
90    // TODO: simplify this, it was premature optimisation
91    #[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            // f.debug_struct("FileLocation").field("chunk", &chunk).field("offset", &pos).finish()
119        }
120    }
121}
122
123pub use util_types::*;
124pub mod block_reader;
125
126/***************** TOP LEVEL READER IMPLEMENTATION *****************/
127
128use 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    // pub data: Option<Vec<u8>>,
135}
136
137// TODO: simplify the entry to this maybe...
138// pub const BLOCK_MAX_STORE_SIZE: usize = 58;
139// pub struct BlockEntry {
140//     loc: FileLocation,
141//     data: [u8; BLOCK_MAX_STORE_SIZE],
142// }
143
144#[derive(Debug)]
145pub struct MainReader<Reader> {
146    pub core_blocks: CoreBlocks,
147    pub chunk_files: Vec<(Reader, u64)>, /* TODO: maybe don't keep this inside of this object and have it be external!!! */
148    pub all_blocks: Vec<BlockEntry>,
149    /* All VIDF/AUDF blocks (file location of Block, timestamp, data offset, data length) */
150    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        /* TODO: search for all chunks (and limit to 101) */
163        // let mut chunk_files = vec![BlockReader::new(utils::ReadSeekFromStdIo(BufReader::new(File::open(path).ok()?)))?];
164        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        /* Create empty reader/index object */
169        let mut reader = Self::empty();
170
171        let mut num_vidf = 0u32;
172
173        /* TODO: Use rayon par iter maybe?? */
174        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" { /* Skip null blocks */
181                        let location = FileLocation::new(chunk_index as u8, block_position).unwrap();
182                        reader.all_blocks.push(
183                            BlockEntry { block: block_info, location }
184                        );
185                        /* TODO: put this block loading at the end */
186                        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)); // TODO: block
205                            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)); // TODO: block
215                        }
216                    }
217                    return true
218                }
219            );
220            println!("Result = {:?}", result);
221        }
222
223        reader.chunk_files = chunk_files_and_lengths;
224
225        /* Sort by timestamp */
226        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    // TODO: better error handling than just returning option
323    // returns none if out buffer is not big enough
324    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 // output buffer too small
331        } 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        // TODO: allow passing temporary buffer for frame decode
346        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        /*************************** Decode the frame ***************************/
352        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            _ => {}, /* Unsupported format */
358        }
359
360        return Some(&output[..]);
361    }
362
363    /* Intended for 16 bit 44.1khz stereo audio mainly. Returns interleaved stereo I think.
364     * TODO: make this a flatmappable iterator */
365    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}