Skip to main content

frame_source/
fmf_source.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{FrameData, FrameDataSource, ImageData, Result, Timestamp};
5use fmf::reader::FMFReader;
6use std::path::Path;
7
8struct FmfSourceIter {
9    rdr: FMFReader,
10    frame0_time_utc: chrono::DateTime<chrono::Utc>,
11    idx: usize,
12}
13impl FmfSourceIter {
14    fn new(parent: &FmfSource) -> Result<Self> {
15        let mut rdr = FMFReader::new(&parent.filename)?;
16        let frame0_time_utc = parent.frame0_time_utc;
17        for _ in 0..parent.skip_frames {
18            rdr.next();
19        }
20        Ok(Self {
21            rdr,
22            frame0_time_utc,
23            idx: 0,
24        })
25    }
26}
27impl Iterator for FmfSourceIter {
28    type Item = Result<FrameData>;
29    fn next(&mut self) -> Option<Self::Item> {
30        let pos_start = self.rdr.file_pos();
31        self.rdr.next().map(|fmf_result| match fmf_result {
32            Ok((frame, frame_time_utc)) => {
33                let pos_end = self.rdr.file_pos();
34                let buf_len = pos_end - pos_start;
35                let timestamp = frame_time_utc - self.frame0_time_utc;
36                let timestamp = Timestamp::Duration(timestamp.to_std()?);
37                let idx = self.idx;
38                self.idx += 1;
39                Ok(FrameData {
40                    image: ImageData::Decoded(frame),
41                    timestamp,
42                    buf_len,
43                    idx,
44                    poc: None,
45                })
46            }
47            Err(e) => Err(e.into()),
48        })
49    }
50}
51
52// Because of the need to create an iterator over the frames an arbitrary number
53// of times but the inability of `FMFReader` to seek (due to underlying
54// potential use of a .gz file reader which does not support seeking), we store
55// the filename and repeatedly reopen the file as necessary. An an optimization,
56// the opened reader and its last read frame could be kept in a cache. This
57// would reduce the number of re-openings.
58pub struct FmfSource {
59    filename: std::path::PathBuf,
60    width: u32,
61    height: u32,
62    frame0_time_utc: chrono::DateTime<chrono::Utc>,
63    frame0_time: chrono::DateTime<chrono::FixedOffset>,
64    skip_frames: usize,
65}
66
67impl FrameDataSource for FmfSource {
68    fn width(&self) -> u32 {
69        self.width
70    }
71    fn height(&self) -> u32 {
72        self.height
73    }
74    fn frame0_time(&self) -> Option<chrono::DateTime<chrono::FixedOffset>> {
75        Some(self.frame0_time)
76    }
77    fn skip_n_frames(&mut self, n_frames: usize) -> Result<()> {
78        if n_frames == 0 {
79            return Ok(());
80        }
81        let mut rdr = FMFReader::new(&self.filename)?;
82
83        let mut frame_timestamp = None;
84        for _ in 0..n_frames {
85            frame_timestamp = rdr.next()
86        }
87
88        let frame_timestamp = frame_timestamp
89            .map(|f| f.map_err(Into::into))
90            .unwrap_or_else(|| Err(crate::Error::FmfWithNotEnoughData))?;
91
92        let (_frame, frame_time_utc) = frame_timestamp;
93        let duration = frame_time_utc - self.frame0_time_utc;
94        let frame_time = self.frame0_time + duration;
95
96        self.skip_frames = n_frames;
97        self.frame0_time = frame_time;
98        self.frame0_time_utc = frame_time_utc;
99        Ok(())
100    }
101    fn average_framerate(&self) -> Option<f64> {
102        None
103    }
104    fn estimate_luminance_range(&mut self) -> Result<(u16, u16)> {
105        // FMF reader does not support seek because we may read .gz files.
106        Err(crate::Error::UnsupportedForEsimatingLuminangeRange)
107    }
108    fn decode_order_iter(&mut self) -> Box<dyn Iterator<Item = Result<FrameData>>> {
109        Box::new(FmfSourceIter::new(self).unwrap())
110    }
111    fn timestamp_source(&self) -> &str {
112        "FMF frame metadata"
113    }
114    fn has_timestamps(&self) -> bool {
115        true
116    }
117}
118
119impl FmfSource {
120    fn new<P: AsRef<std::path::Path>>(filename: P) -> Result<Self> {
121        let filename = filename.as_ref().to_path_buf();
122        let mut rdr = FMFReader::new(&filename)?;
123        let width = rdr.width();
124        let height = rdr.height();
125        let frame_timestamp0 = rdr
126            .next()
127            .map(|f| f.map_err(crate::Error::from))
128            .unwrap_or_else(|| Err(crate::Error::FmfWithNotEnoughData))?;
129
130        let (_frame0, frame0_time_utc) = frame_timestamp0;
131        let frame0_time = mkv_strand_reader::infer_timezone(&frame0_time_utc, filename.to_str())?;
132
133        Ok(Self {
134            filename,
135            width,
136            height,
137            frame0_time_utc,
138            frame0_time,
139            skip_frames: 0,
140        })
141    }
142}
143
144pub fn from_path<P: AsRef<Path>>(path: P) -> Result<FmfSource> {
145    let filename = path.as_ref();
146    FmfSource::new(filename)
147}