use std::path::{Path, PathBuf};
use ff_format::VideoFrame;
use crate::error::DecodeError;
use crate::image::decoder_inner::ImageDecoderInner;
#[derive(Debug)]
pub struct ImageDecoderBuilder {
path: PathBuf,
}
impl ImageDecoderBuilder {
pub(crate) fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn build(self) -> Result<ImageDecoder, DecodeError> {
if !self.path.exists() {
return Err(DecodeError::FileNotFound {
path: self.path.clone(),
});
}
let inner = ImageDecoderInner::new(&self.path)?;
let width = inner.width();
let height = inner.height();
Ok(ImageDecoder {
inner: Some(inner),
width,
height,
})
}
}
pub struct ImageDecoder {
inner: Option<ImageDecoderInner>,
width: u32,
height: u32,
}
impl ImageDecoder {
pub fn open(path: impl AsRef<Path>) -> ImageDecoderBuilder {
ImageDecoderBuilder::new(path.as_ref().to_path_buf())
}
#[must_use]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
pub fn height(&self) -> u32 {
self.height
}
pub fn decode_one(&mut self) -> Result<Option<VideoFrame>, DecodeError> {
let Some(inner) = self.inner.take() else {
return Ok(None);
};
Ok(Some(inner.decode()?))
}
pub fn frames(&mut self) -> impl Iterator<Item = Result<VideoFrame, DecodeError>> + '_ {
ImageFrameIterator { decoder: self }
}
pub fn decode(mut self) -> Result<VideoFrame, DecodeError> {
self.decode_one()?.ok_or_else(|| DecodeError::Ffmpeg {
code: 0,
message: "Image already decoded".to_string(),
})
}
}
pub(crate) struct ImageFrameIterator<'a> {
decoder: &'a mut ImageDecoder,
}
impl Iterator for ImageFrameIterator<'_> {
type Item = Result<VideoFrame, DecodeError>;
fn next(&mut self) -> Option<Self::Item> {
match self.decoder.decode_one() {
Ok(Some(frame)) => Some(Ok(frame)),
Ok(None) => None,
Err(e) => Some(Err(e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn open_nonexistent_file_should_return_file_not_found() {
let result = ImageDecoder::open("nonexistent_image_12345.png").build();
assert!(result.is_err());
assert!(matches!(result, Err(DecodeError::FileNotFound { .. })));
}
#[test]
fn builder_new_should_store_path() {
let builder = ImageDecoderBuilder::new(PathBuf::from("photo.png"));
assert_eq!(builder.path, PathBuf::from("photo.png"));
}
}