Skip to main content

alpha2_extensions/
sequence.rs

1//! Still images played in order at a fixed rate: PNGs, or OpenEXR images.
2//!
3//! The project lists each frame's locator; the host resolves their bytes.
4//! The source keeps the resolver and reads a frame when it shows it: what a
5//! sequence holds is the frames it decoded lately, within a byte budget,
6//! never its files. Every frame must be the first frame's format and size;
7//! a gap in the numbering is shown as the sequence's policy says. OpenEXR
8//! frames all take the color the first declares, or the project names.
9
10use std::collections::VecDeque;
11use std::sync::Arc;
12
13use alpha2_model::{LinearPrimaries, MissingFrames};
14use alpha2_time::{FrameRate, MediaTime};
15
16use crate::api::{
17    Diagnostic, ExtensionError, Format, FramePixels, LinearColor, ProbeReport, SharedResolver,
18    Timing, VisualFrame, VisualSource, premultiply,
19};
20use crate::budget::{DEFAULT_FRAME_BUDGET, check_canvas};
21use crate::raster::decode_png;
22
23/// Opens an image sequence: `frames` in play order (`None` for a gap in the
24/// numbering), at `rate`, their bytes from `resolver` as they are shown;
25/// OpenEXR frames in `primaries` when given. Only the first frame is read
26/// here.
27///
28/// # Errors
29/// Why the first frame cannot be read, or the settings are incoherent.
30pub fn open_sequence(
31    frames: &[Option<String>],
32    rate: FrameRate,
33    missing: MissingFrames,
34    primaries: Option<LinearPrimaries>,
35    resolver: SharedResolver,
36) -> Result<Box<dyn VisualSource>, ExtensionError> {
37    open_within(
38        frames,
39        rate,
40        missing,
41        primaries,
42        resolver,
43        DEFAULT_FRAME_BUDGET,
44    )
45}
46
47/// [`open_sequence`], keeping at most `budget` decoded bytes (and always
48/// the frame last shown).
49pub(crate) fn open_within(
50    frames: &[Option<String>],
51    rate: FrameRate,
52    missing: MissingFrames,
53    primaries: Option<LinearPrimaries>,
54    resolver: SharedResolver,
55    budget: usize,
56) -> Result<Box<dyn VisualSource>, ExtensionError> {
57    let first = frames
58        .first()
59        .and_then(Option::as_ref)
60        .ok_or_else(|| ExtensionError::Malformed("the sequence's first frame is missing".into()))?;
61    if missing == MissingFrames::Reject && frames.iter().any(Option::is_none) {
62        return Err(ExtensionError::Mismatch(
63            "the sequence has a gap and refuses missing frames".into(),
64        ));
65    }
66    let first = decode(&resolver.bytes(first)?, None, primaries)?;
67    if first.color.is_none() && primaries.is_some() {
68        return Err(ExtensionError::Mismatch(
69            "primaries apply to OpenEXR frames, and these are PNGs".into(),
70        ));
71    }
72    check_canvas(first.width, first.height)?;
73    let report = ProbeReport {
74        format: first.format,
75        width: first.width,
76        height: first.height,
77        timing: Timing::Rate {
78            rate,
79            frames: frames.len(),
80        },
81        loop_count: None,
82        items: Vec::new(),
83        item: None,
84        color: first.color.clone(),
85        diagnostics: first.diagnostics.clone(),
86    };
87    let mut source = SequenceSource {
88        frames: frames.to_vec(),
89        resolver,
90        missing,
91        report,
92        kept: VecDeque::new(),
93        kept_bytes: 0,
94        budget,
95    };
96    source.keep(0, Arc::new(first));
97    Ok(Box::new(source))
98}
99
100/// One decoded frame.
101struct Decoded {
102    format: Format,
103    width: u32,
104    height: u32,
105    pixels: FramePixels,
106    /// For an OpenEXR frame, the color it was read in.
107    color: Option<LinearColor>,
108    diagnostics: Vec<Diagnostic>,
109}
110
111impl Decoded {
112    /// The bytes its pixels take.
113    fn bytes(&self) -> usize {
114        match &self.pixels {
115            FramePixels::Rgba8(pixels) => pixels.len(),
116            FramePixels::LinearF16(pixels) => pixels.len() * 2,
117        }
118    }
119}
120
121/// Decodes a PNG or OpenEXR frame; an OpenEXR frame in `color` when given
122/// (the sequence's), else in `primaries` or what it declares.
123fn decode(
124    bytes: &[u8],
125    color: Option<&LinearColor>,
126    primaries: Option<LinearPrimaries>,
127) -> Result<Decoded, ExtensionError> {
128    #[cfg(feature = "exr")]
129    if crate::exr::is_exr(bytes) {
130        let color = color
131            .cloned()
132            .or_else(|| primaries.map(crate::exr::project_color));
133        let image = crate::exr::decode_exr(bytes, color)?;
134        return Ok(Decoded {
135            format: Format::ExrSequence,
136            width: image.width,
137            height: image.height,
138            pixels: FramePixels::LinearF16(image.pixels.into()),
139            color: Some(image.color),
140            diagnostics: image.diagnostics,
141        });
142    }
143    let _ = (color, primaries);
144    if bytes.starts_with(&[0x76, 0x2f, 0x31, 0x01]) {
145        return Err(ExtensionError::Unsupported(
146            "this build reads no OpenEXR frames".into(),
147        ));
148    }
149    let (width, height, mut pixels) = decode_png(bytes)?;
150    premultiply(&mut pixels);
151    Ok(Decoded {
152        format: Format::PngSequence,
153        width,
154        height,
155        pixels: FramePixels::Rgba8(pixels.into()),
156        color: None,
157        diagnostics: Vec::new(),
158    })
159}
160
161/// An open image sequence, decoding frames as they are shown.
162struct SequenceSource {
163    /// Each frame's locator; `None` for a gap.
164    frames: Vec<Option<String>>,
165    resolver: SharedResolver,
166    missing: MissingFrames,
167    report: ProbeReport,
168    /// Recently decoded frames by index; newest last.
169    kept: VecDeque<(usize, Arc<Decoded>)>,
170    /// The bytes `kept` holds.
171    kept_bytes: usize,
172    /// Decoded bytes kept between requests; the newest frame is kept
173    /// whatever its size.
174    budget: usize,
175}
176
177impl SequenceSource {
178    /// The frame shown at `time`: its index, its start and end, and the index
179    /// whose picture it shows (a held gap shows the nearest present frame
180    /// before it).
181    fn shown(&self, time: MediaTime) -> (usize, MediaTime, Option<MediaTime>, usize) {
182        let (index, start, end) = self.report.timing.frame_at(time);
183        let shown = match self.missing {
184            MissingFrames::Hold => (0..=index)
185                .rev()
186                .find(|at| self.frames[*at].is_some())
187                .unwrap_or(0),
188            MissingFrames::Blank | MissingFrames::Reject => index,
189        };
190        (index, start, end, shown)
191    }
192
193    fn keep(&mut self, index: usize, decoded: Arc<Decoded>) {
194        self.kept_bytes += decoded.bytes();
195        self.kept.push_back((index, decoded));
196        while self.kept.len() > 1 && self.kept_bytes > self.budget {
197            if let Some((_, oldest)) = self.kept.pop_front() {
198                self.kept_bytes -= oldest.bytes();
199            }
200        }
201    }
202
203    fn decoded(&mut self, index: usize) -> Result<Arc<Decoded>, ExtensionError> {
204        if let Some((_, decoded)) = self.kept.iter().find(|(kept, _)| *kept == index) {
205            return Ok(Arc::clone(decoded));
206        }
207        let (width, height) = (self.report.width, self.report.height);
208        let decoded = if let Some(locator) = &self.frames[index] {
209            let decoded = decode(
210                &self.resolver.bytes(locator)?,
211                self.report.color.as_ref(),
212                None,
213            )?;
214            if decoded.format != self.report.format {
215                return Err(ExtensionError::Mismatch(format!(
216                    "frame {index} is not in the first frame's format"
217                )));
218            }
219            if (decoded.width, decoded.height) != (width, height) {
220                return Err(ExtensionError::Mismatch(format!(
221                    "frame {index} is {}x{}; the sequence is {width}x{height}",
222                    decoded.width, decoded.height
223                )));
224            }
225            decoded
226        } else {
227            let samples = width as usize * height as usize * 4;
228            Decoded {
229                format: self.report.format,
230                width,
231                height,
232                pixels: if self.report.color.is_some() {
233                    FramePixels::LinearF16(vec![0; samples].into())
234                } else {
235                    FramePixels::Rgba8(vec![0; samples].into())
236                },
237                color: self.report.color.clone(),
238                diagnostics: Vec::new(),
239            }
240        };
241        let decoded = Arc::new(decoded);
242        self.keep(index, Arc::clone(&decoded));
243        Ok(decoded)
244    }
245}
246
247impl VisualSource for SequenceSource {
248    fn report(&self) -> &ProbeReport {
249        &self.report
250    }
251
252    fn render(&mut self, time: MediaTime, _fit: (u32, u32)) -> Result<VisualFrame, ExtensionError> {
253        let (index, start, end, shown) = self.shown(time);
254        let decoded = self.decoded(shown)?;
255        Ok(VisualFrame {
256            width: self.report.width,
257            height: self.report.height,
258            pixels: decoded.pixels.clone(),
259            index,
260            start,
261            end,
262            diagnostics: decoded.diagnostics.clone(),
263        })
264    }
265
266    fn needs(&self, time: MediaTime) -> Vec<String> {
267        let (.., shown) = self.shown(time);
268        if self.kept.iter().any(|(kept, _)| *kept == shown) {
269            return Vec::new();
270        }
271        self.frames[shown].iter().cloned().collect()
272    }
273
274    fn resident_bytes(&self) -> usize {
275        self.kept_bytes
276    }
277}