1extern crate byteorder;
5extern crate chrono;
6extern crate machine_vision_formats as formats;
7extern crate strand_dynamic_frame;
8
9extern crate strand_datetime_conversion;
10
11use std::f64;
12use std::io::{Seek, SeekFrom, Write};
13
14use byteorder::{LittleEndian, WriteBytesExt};
15use formats::{ImageStride, PixFmt, PixelFormat};
16
17pub type FMFResult<M> = std::result::Result<M, FMFError>;
18
19pub(crate) mod pixel_formats;
20
21#[derive(thiserror::Error, Debug)]
22pub enum FMFError {
23 #[error("unexpected size")]
24 UnexpectedSize,
25 #[error("unexpected pixel_format {0} (expected {1})")]
26 UnexpectedEncoding(PixFmt, PixFmt),
27 #[error("unimplemented pixel_format {0}")]
28 UnimplementedPixelFormat(PixFmt),
29
30 #[error("Unimplemented FMF file version {0}. Only FMF v3 files supported.")]
31 UnimplementedVersion(u32),
32 #[error("premature file end")]
33 PrematureFileEnd,
34 #[error("unknown format {0}")]
35 UnknownFormat(String),
36 #[error("inconsistent state")]
37 InconsistentState,
38 #[error("already closed")]
39 AlreadyClosed,
40
41 #[error("reading past the end of the file")]
42 ReadingPastEnd,
43
44 #[error("{source}")]
45 Io {
46 #[from]
47 source: std::io::Error,
48 },
49
50 #[error("From {path}: {source}")]
51 IoPath {
52 path: String,
53 #[source]
54 source: std::io::Error,
55 },
56
57 #[error("{0}")]
58 Cell(std::cell::BorrowMutError),
59}
60
61impl From<std::cell::BorrowMutError> for FMFError {
62 fn from(orig: std::cell::BorrowMutError) -> FMFError {
63 FMFError::Cell(orig)
64 }
65}
66
67pub mod reader;
68pub use crate::reader::FMFReader;
69
70pub struct FMFWriter<F: Write + Seek> {
76 state: WriterState<F>,
77}
78
79enum WriterState<F: Write + Seek> {
80 FileOpened(F),
81 Writing(FMFWriterInner<F>),
82 InconsistentState,
83}
84
85struct FMFWriterInner<F: Write + Seek> {
86 f: Option<F>,
87 w: u32,
88 h: u32,
89 e: PixFmt,
90 row_bytes: usize,
91 n_frames_pos_bytes: u64,
92 n_frames: u64,
93}
94
95impl<F: Write + Seek> FMFWriter<F> {
109 pub fn new(f: F) -> FMFResult<Self> {
111 Ok(Self {
112 state: WriterState::FileOpened(f),
113 })
114 }
115
116 pub fn write<TZ, FMT>(
118 &mut self,
119 frame: &dyn ImageStride<FMT>,
120 dtl: chrono::DateTime<TZ>,
121 ) -> FMFResult<()>
122 where
123 TZ: chrono::TimeZone,
124 FMT: PixelFormat,
125 {
126 let timestamp = strand_datetime_conversion::datetime_to_f64(&dtl);
127
128 let state = std::mem::replace(&mut self.state, WriterState::InconsistentState);
131 let new_state = match state {
132 WriterState::FileOpened(f) => {
133 let inner = FMFWriterInner::new(
134 f,
135 frame.width(),
136 frame.height(),
137 machine_vision_formats::pixel_format::pixfmt::<FMT>().unwrap(),
138 )?;
139 WriterState::Writing(inner)
140 }
141 WriterState::Writing(inner) => WriterState::Writing(inner),
142 WriterState::InconsistentState => return Err(FMFError::InconsistentState),
143 };
144 self.state = new_state;
145
146 match self.state {
147 WriterState::Writing(ref mut inner) => {
148 if (frame.width() != inner.w)
149 || (frame.height() != inner.h)
150 || (inner.row_bytes > frame.stride())
151 {
152 return Err(FMFError::UnexpectedSize);
153 }
154 if machine_vision_formats::pixel_format::pixfmt::<FMT>().unwrap() != inner.e {
155 return Err(FMFError::UnexpectedEncoding(
156 machine_vision_formats::pixel_format::pixfmt::<FMT>().unwrap(),
157 inner.e,
158 ));
159 }
160 inner.write_inner(frame, timestamp)?;
161 }
162 _ => {
163 return Err(FMFError::InconsistentState);
164 }
165 }
166 Ok(())
167 }
168
169 pub fn close(self) -> FMFResult<F> {
174 match self.state {
175 WriterState::FileOpened(f) => Ok(f),
176 WriterState::Writing(mut inner) => inner.close(),
177 WriterState::InconsistentState => Err(FMFError::InconsistentState),
178 }
179 }
180}
181
182impl<F: Write + Seek> FMFWriterInner<F> {
183 fn new(mut f: F, w: u32, h: u32, pixel_format: PixFmt) -> FMFResult<Self> {
184 let format = pixel_formats::get_format(pixel_format)?;
185
186 let bytes_per_pixel = pixel_format.bits_per_pixel() / 8;
187
188 let row_bytes = w as usize * bytes_per_pixel as usize;
189 let chunksize = row_bytes * h as usize + 8;
190
191 let mut pos = 0;
192 f.write_u32::<LittleEndian>(3)?;
193 pos += 4; f.write_u32::<LittleEndian>(format.len() as u32)?;
195 pos += 4;
196 f.write_all(&format)?;
197 pos += format.len();
198 f.write_u32::<LittleEndian>(bytes_per_pixel as u32 * 8)?;
199 pos += 4;
200 f.write_u32::<LittleEndian>(h)?;
201 pos += 4;
202 f.write_u32::<LittleEndian>(w)?;
203 pos += 4;
204 f.write_u64::<LittleEndian>(chunksize as u64)?;
205 pos += 8;
206 f.write_u64::<LittleEndian>(0)?; let f = Some(f);
209
210 Ok(Self {
211 f,
212 w,
213 h,
214 e: pixel_format,
215 row_bytes,
216 n_frames_pos_bytes: pos as u64,
217 n_frames: 0,
218 })
219 }
220
221 fn write_inner<FMT>(&mut self, frame: &dyn ImageStride<FMT>, timestamp: f64) -> FMFResult<()>
222 where
223 FMT: PixelFormat,
224 {
225 let self_f = match self.f {
226 Some(ref mut f) => f,
227 None => {
228 return Err(FMFError::AlreadyClosed);
229 }
230 };
231 self_f.write_f64::<LittleEndian>(timestamp)?;
232
233 let image_data = frame.image_data();
238 let e = machine_vision_formats::pixel_format::pixfmt::<FMT>().unwrap();
239 let bpp = e.bits_per_pixel();
240 let n_bytes_per_row = self.w as usize * (bpp / 8) as usize;
241 let mut ptr = 0;
242 for _ in 0..self.h {
243 let end = ptr + n_bytes_per_row;
244 let row_buf = &image_data[ptr..end];
245 self_f.write_all(row_buf)?;
246 ptr += frame.stride();
247 }
248
249 self.n_frames += 1;
250
251 Ok(())
252 }
253
254 fn close(&mut self) -> FMFResult<F> {
259 let opt_f = self.f.take();
260
261 let mut self_f = match opt_f {
262 Some(f) => f,
263 None => {
264 return Err(FMFError::AlreadyClosed);
265 }
266 };
267
268 self_f.seek(SeekFrom::Start(self.n_frames_pos_bytes))?;
270 self_f.write_u64::<LittleEndian>(self.n_frames)?;
271 self_f.flush()?;
272 Ok(self_f)
273 }
274}
275
276impl<F: Write + Seek> Drop for FMFWriterInner<F> {
278 fn drop(&mut self) {
279 match self.close() {
281 Ok(_f) => {}
282 Err(_e) => {} }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::FMFWriter;
290
291 use machine_vision_formats::{owned::OImage, pixel_format::Mono8};
292
293 fn zeros(w: u32, h: u32) -> OImage<Mono8> {
294 let image_data = vec![0; (w * h) as usize];
295
296 OImage::new(w, h, w.try_into().unwrap(), image_data).unwrap()
297 }
298
299 #[test]
300 fn test_writing() {
301 let w = 320;
302 let h = 240;
303 let f = std::io::Cursor::new(Vec::new());
304 let mut writer = FMFWriter::new(f).unwrap();
306
307 let f1 = zeros(w, h);
309 let dt = chrono::DateTime::from_timestamp(61, 0).unwrap();
310
311 writer.write(&f1, dt).unwrap();
312
313 let f2 = zeros(w, h);
314 writer.write(&f2, dt).unwrap();
315
316 let f3 = zeros(w, h);
317 writer.write(&f3, dt).unwrap();
318
319 let f = writer.close().unwrap();
320
321 let buf = f.into_inner();
323
324 let expected = [3, 0, 0, 0, 5, 0, 0, 0, 77, 79]; assert_eq!(&buf[0..10], expected);
326 }
327}