use std::path::Path;
use opencv::core::{Mat, Size};
use opencv::prelude::*;
use opencv::videoio::{self, VideoCapture, VideoWriter};
use crate::error::Error;
fn to_backend_error(err: opencv::Error) -> Error {
Error::Backend(err.to_string())
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VideoInfo {
pub width: u32,
pub height: u32,
pub fps: f64,
pub total_frames: Option<u32>,
}
impl VideoInfo {
pub fn from_path(path: &Path) -> crate::Result<Self> {
let capture = VideoCapture::from_file(path, videoio::CAP_ANY).map_err(to_backend_error)?;
if !capture.is_opened().map_err(to_backend_error)? {
return Err(Error::Backend(format!(
"could not open video: {}",
path.display()
)));
}
let width = capture
.get(videoio::CAP_PROP_FRAME_WIDTH)
.map_err(to_backend_error)? as u32;
let height = capture
.get(videoio::CAP_PROP_FRAME_HEIGHT)
.map_err(to_backend_error)? as u32;
let fps = capture
.get(videoio::CAP_PROP_FPS)
.map_err(to_backend_error)?;
let count = capture
.get(videoio::CAP_PROP_FRAME_COUNT)
.map_err(to_backend_error)?;
let total_frames = if count > 0.0 {
Some(count as u32)
} else {
None
};
Ok(Self {
width,
height,
fps,
total_frames,
})
}
}
pub struct FrameIterator {
capture: VideoCapture,
}
impl Iterator for FrameIterator {
type Item = crate::Result<Mat>;
fn next(&mut self) -> Option<Self::Item> {
let mut frame = Mat::default();
match self.capture.read(&mut frame) {
Ok(true) => Some(Ok(frame)),
Ok(false) => None,
Err(e) => Some(Err(to_backend_error(e))),
}
}
}
pub fn frames(path: &Path) -> crate::Result<FrameIterator> {
let capture = VideoCapture::from_file(path, videoio::CAP_ANY).map_err(to_backend_error)?;
if !capture.is_opened().map_err(to_backend_error)? {
return Err(Error::Backend(format!(
"could not open video: {}",
path.display()
)));
}
Ok(FrameIterator { capture })
}
pub struct VideoSink {
writer: VideoWriter,
}
impl VideoSink {
pub fn new(
path: &Path,
info: VideoInfo,
fourcc: (char, char, char, char),
) -> crate::Result<Self> {
let (c1, c2, c3, c4) = fourcc;
let code = VideoWriter::fourcc(c1, c2, c3, c4).map_err(to_backend_error)?;
let size = Size::new(info.width as i32, info.height as i32);
let writer =
VideoWriter::new(path, code, info.fps, size, true).map_err(to_backend_error)?;
if !writer.is_opened().map_err(to_backend_error)? {
return Err(Error::Backend(format!(
"could not open video for writing: {}",
path.display()
)));
}
Ok(Self { writer })
}
pub fn mp4(path: &Path, info: VideoInfo) -> crate::Result<Self> {
Self::new(path, info, ('m', 'p', '4', 'v'))
}
pub fn write_frame(&mut self, frame: &Mat) -> crate::Result<()> {
self.writer.write(frame).map_err(to_backend_error)?;
Ok(())
}
pub fn release(mut self) -> crate::Result<()> {
self.writer.release().map_err(to_backend_error)
}
}