use crate::common::errors::*;
use crate::downloader::youtube;
use gif;
use image::io::Reader as ImageReader;
use image::{DynamicImage, ImageBuffer};
use opencv::{imgproc, prelude::*, videoio::VideoCapture};
use std::fs::File;
use std::path::Path;
use url::Url;
pub enum FrameIterator {
Image(Option<DynamicImage>),
Video(VideoCapture),
AnimatedGif {
frames: Vec<DynamicImage>,
current_frame: usize,
},
}
impl Iterator for FrameIterator {
type Item = DynamicImage;
fn next(&mut self) -> Option<Self::Item> {
match self {
FrameIterator::Image(ref mut img) => img.take(),
FrameIterator::Video(ref mut video) => capture_video_frame(video),
FrameIterator::AnimatedGif {
ref frames,
ref mut current_frame,
} => {
let frame = frames.get(*current_frame).cloned();
*current_frame = (*current_frame + 1) % frames.len();
frame
}
}
}
}
fn mat_to_dynamic_image(mat: &Mat) -> Option<DynamicImage> {
let mut rgb_mat = Mat::default();
if imgproc::cvt_color(&mat, &mut rgb_mat, imgproc::COLOR_BGR2RGB, 0).is_ok() {
if let Ok(_elem_size) = rgb_mat.elem_size() {
if let Ok(size) = rgb_mat.size() {
let reshaped_mat = rgb_mat.reshape(1, size.width * size.height).ok()?;
let data_vec: Vec<u8> = reshaped_mat
.data_typed::<u8>()
.expect("Unexpected invalid data")
.to_vec();
if let Some(img_buf) = ImageBuffer::<image::Rgb<u8>, _>::from_raw(
size.width as u32,
size.height as u32,
data_vec,
) {
return Some(DynamicImage::ImageRgb8(img_buf));
}
}
}
}
None
}
fn capture_video_frame(video: &mut VideoCapture) -> Option<DynamicImage> {
let mut frame = Mat::default();
if video.read(&mut frame).unwrap_or(false) && !frame.empty() {
mat_to_dynamic_image(&frame)
} else {
None
}
}
fn open_image(path: &Path) -> Result<FrameIterator, MyError> {
let img = ImageReader::open(path)?.decode().map_err(|e| {
MyError::Application(format!("{error}: {e:?}", error = ERROR_DECODING_IMAGE))
})?;
Ok(FrameIterator::Image(Some(img)))
}
fn open_video(path: &Path) -> Result<FrameIterator, MyError> {
let video = VideoCapture::from_file(
path.to_str().expect(ERROR_OPENING_VIDEO),
opencv::videoio::CAP_ANY,
)?;
if video.is_opened()? {
Ok(FrameIterator::Video(video))
} else {
Err(MyError::Application(ERROR_OPENING_VIDEO.to_string()))
}
}
fn open_gif(path: &Path) -> Result<FrameIterator, MyError> {
let file = File::open(path)
.map_err(|e| MyError::Application(format!("{error}: {e:?}", error = ERROR_OPENING_GIF)))?;
let mut options = gif::DecodeOptions::new();
options.set_color_output(gif::ColorOutput::RGBA);
let mut decoder = options.read_info(file).map_err(|e| {
MyError::Application(format!("{error}: {e:?}", error = ERROR_READING_GIF_HEADER))
})?;
let mut frames = Vec::new();
while let Ok(Some(frame)) = decoder.read_next_frame() {
let buffer = frame.buffer.clone();
if let Some(image) = image::RgbaImage::from_raw(
decoder.width() as u32,
decoder.height() as u32,
buffer.to_vec(),
) {
frames.push(DynamicImage::ImageRgba8(image));
} else {
}
}
Ok(FrameIterator::AnimatedGif {
frames,
current_frame: 0,
})
}
pub fn open_media<P: AsRef<Path>>(path: P) -> Result<FrameIterator, MyError> {
let path = path.as_ref();
let ext = path.extension().and_then(std::ffi::OsStr::to_str);
if let Ok(url) = Url::parse(path.to_str().unwrap_or("")) {
if let Some(domain) = url.domain() {
if domain.ends_with("youtube.com") || domain.ends_with("youtu.be") {
let video = youtube::download_video(path.to_str().unwrap_or(""))?;
return open_video(&video);
}
}
}
match ext {
Some("png") | Some("bmp") | Some("ico") | Some("tif") | Some("tiff") | Some("jpg")
| Some("jpeg") => open_image(path),
Some("mp4") | Some("avi") | Some("webm") | Some("mkv") | Some("mov") | Some("flv")
| Some("ogg") => open_video(path),
Some("gif") => open_gif(path),
_ => open_video(path), }
}