Skip to main content

fmf/
reader.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::{fs::File, io::Read, path::Path};
5
6use byteorder::{LittleEndian, ReadBytesExt};
7
8use chrono::{DateTime, Utc};
9use formats::PixFmt;
10use strand_dynamic_frame::DynamicFrameOwned;
11
12use crate::{FMFError, FMFResult, pixel_formats};
13
14const TIMESTAMP_SIZE: usize = 8;
15
16fn open_buffered<P: AsRef<Path>>(p: &P) -> std::io::Result<std::io::BufReader<File>> {
17    Ok(std::io::BufReader::new(File::open(p.as_ref())?))
18}
19
20pub struct FMFReader {
21    // We cannot Seek because the gzip Decoder does not implement that.
22    f: Box<dyn Read>,
23    pixel_format: PixFmt,
24    height: u32,
25    width: u32,
26    image_data_size: usize,
27    // In theory, a corrupt file could have more frames than indicated by the
28    // `n_frames` field in the header, but we assume the file is OK.
29    n_frames: usize,
30    count: usize,
31    file_pos: usize,
32    did_error: bool,
33}
34
35impl FMFReader {
36    pub fn new<P: AsRef<Path>>(path: P) -> FMFResult<FMFReader> {
37        let extension = path.as_ref().extension().and_then(|x| x.to_str());
38        let mut f: Box<dyn Read> = if extension == Some("gz") {
39            let gz_fd = open_buffered(&path).map_err(|e| FMFError::IoPath {
40                source: e,
41                path: path.as_ref().display().to_string(),
42            })?;
43            let decoder = libflate::gzip::Decoder::new(gz_fd)?;
44            Box::new(decoder)
45        } else {
46            Box::new(open_buffered(&path).map_err(|e| FMFError::IoPath {
47                source: e,
48                path: path.as_ref().display().to_string(),
49            })?)
50        };
51
52        // version
53        let mut pos = 0;
54        let version = f.read_u32::<LittleEndian>()?;
55        pos += 4;
56        if version != 3 {
57            return Err(FMFError::UnimplementedVersion(version));
58        }
59
60        // format
61        let expected_format_len = f.read_u32::<LittleEndian>()? as usize;
62        pos += 4;
63        let mut format: Vec<u8> = vec![0; expected_format_len];
64        let actual_format_len = f.read(&mut format)?;
65        pos += actual_format_len;
66        if expected_format_len != actual_format_len {
67            return Err(FMFError::PrematureFileEnd);
68        }
69        let pixel_format = pixel_formats::get_pixel_format(&format)?;
70
71        let _bpp = f.read_u32::<LittleEndian>()?;
72        pos += 4;
73        let height = f.read_u32::<LittleEndian>()?;
74        pos += 4;
75        let width = f.read_u32::<LittleEndian>()?;
76        pos += 4;
77        let chunksize: usize = f.read_u64::<LittleEndian>()?.try_into().unwrap();
78        assert!(chunksize > TIMESTAMP_SIZE);
79        let image_data_size = chunksize - TIMESTAMP_SIZE;
80        pos += 8;
81        let n_frames = f.read_u64::<LittleEndian>()?.try_into().unwrap();
82        pos += 8;
83        let count = 0;
84
85        Ok(Self {
86            f,
87            pixel_format,
88            height,
89            width,
90            image_data_size,
91            n_frames,
92            count,
93            file_pos: pos,
94            did_error: false,
95        })
96    }
97
98    #[inline]
99    pub fn width(&self) -> u32 {
100        self.width
101    }
102
103    #[inline]
104    pub fn height(&self) -> u32 {
105        self.height
106    }
107
108    #[inline]
109    pub fn format(&self) -> PixFmt {
110        self.pixel_format
111    }
112
113    pub fn file_pos(&self) -> usize {
114        self.file_pos
115    }
116
117    /// Return the number of frames indicated in the header.
118    pub fn n_frames(&self) -> usize {
119        self.n_frames
120    }
121
122    fn next_frame(&mut self) -> FMFResult<(DynamicFrameOwned, DateTime<Utc>)> {
123        // Private function to actually read next frame.
124        if self.count >= self.n_frames {
125            return Err(FMFError::ReadingPastEnd);
126        }
127
128        let mut timestamp_data: Vec<u8> = vec![0; TIMESTAMP_SIZE];
129        self.f.read_exact(&mut timestamp_data)?;
130        self.file_pos += TIMESTAMP_SIZE;
131
132        let mut image_data: Vec<u8> = vec![0; self.image_data_size];
133        self.f.read_exact(&mut image_data)?;
134        self.file_pos += self.image_data_size;
135
136        let timestamp_f64 = timestamp_data.as_slice().read_f64::<LittleEndian>()?;
137        let dt = strand_datetime_conversion::f64_to_datetime(timestamp_f64);
138
139        let width = self.width;
140        let height = self.height;
141        let pixel_format = self.pixel_format;
142        let bpp = self.pixel_format.bits_per_pixel() as u32;
143        let stride = (width * bpp) / 8;
144        self.count += 1;
145
146        if let Some(dframe) =
147            DynamicFrameOwned::from_buf(width, height, stride as usize, image_data, pixel_format)
148        {
149            Ok((dframe, dt))
150        } else {
151            Err(FMFError::UnexpectedSize)
152        }
153    }
154}
155
156impl Iterator for FMFReader {
157    type Item = FMFResult<(DynamicFrameOwned, DateTime<Utc>)>;
158    fn next(&mut self) -> Option<Self::Item> {
159        if self.did_error {
160            // Encountered error. Do not read more.
161            return None;
162        }
163
164        if self.count >= self.n_frames {
165            // Done reading all frames. Do not read more.
166            return None;
167        }
168
169        let frame = self.next_frame();
170        if frame.is_err() {
171            self.did_error = true;
172        }
173        Some(frame)
174    }
175}