#[cfg(feature = "vapoursynth")]
use std::collections::HashMap;
use std::fs::File;
use std::io::{stdin, BufReader, Read};
use std::path::Path;
use v_frame::frame::Frame;
use v_frame::pixel::{ChromaSampling, Pixel};
#[cfg(feature = "vapoursynth")]
use vapoursynth::node::Node;
#[cfg(feature = "vapoursynth")]
use vapoursynth::prelude::Environment;
mod error;
mod helpers {
#[cfg(feature = "ffmpeg")]
pub(crate) mod ffmpeg;
#[cfg(feature = "vapoursynth")]
pub(crate) mod vapoursynth;
pub(crate) mod y4m;
}
mod util;
#[cfg(feature = "ffmpeg")]
pub use crate::helpers::ffmpeg::FfmpegDecoder;
#[cfg(feature = "vapoursynth")]
pub use crate::helpers::vapoursynth::ModifyNode;
#[cfg(feature = "vapoursynth")]
pub use crate::helpers::vapoursynth::VapoursynthDecoder;
#[cfg(feature = "vapoursynth")]
use crate::helpers::vapoursynth::{VariableName, VariableValue};
pub use error::DecoderError;
pub use num_rational::Rational32;
pub use v_frame;
pub use y4m::Decoder as Y4mDecoder;
const Y4M_EXTENSIONS: &[&str] = &["y4m", "yuv"];
#[derive(Debug, Clone, Copy)]
pub struct VideoDetails {
pub width: usize,
pub height: usize,
pub bit_depth: usize,
pub chroma_sampling: ChromaSampling,
pub frame_rate: Rational32,
pub total_frames: Option<usize>,
}
#[cfg(test)]
impl Default for VideoDetails {
#[inline]
fn default() -> Self {
VideoDetails {
width: 640,
height: 480,
bit_depth: 8,
chroma_sampling: ChromaSampling::Cs420,
frame_rate: Rational32::new(30, 1),
total_frames: None,
}
}
}
pub struct Decoder {
decoder: DecoderImpl,
video_details: VideoDetails,
frames_read: usize,
}
impl Decoder {
#[inline]
#[allow(unreachable_code)]
#[allow(clippy::needless_return)]
pub fn from_file<P: AsRef<Path>>(input: P) -> Result<Decoder, DecoderError> {
let ext = input
.as_ref()
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_ascii_lowercase());
if let Some(ext) = ext.as_deref() {
if Y4M_EXTENSIONS.contains(&ext) {
let reader =
BufReader::new(File::open(input).map_err(|e| DecoderError::FileReadError {
cause: e.to_string(),
})?);
let decoder = DecoderImpl::Y4m(
y4m::decode(Box::new(reader) as Box<dyn Read>).map_err(|e| match e {
y4m::Error::EOF => DecoderError::EndOfFile,
_ => DecoderError::GenericDecodeError {
cause: e.to_string(),
},
})?,
);
let video_details = decoder.video_details()?;
return Ok(Decoder {
decoder,
video_details,
frames_read: 0,
});
}
#[cfg(feature = "vapoursynth")]
if ext == "vpy" {
let decoder =
DecoderImpl::Vapoursynth(VapoursynthDecoder::from_file(input, HashMap::new())?);
let video_details = decoder.video_details()?;
return Ok(Decoder {
decoder,
video_details,
frames_read: 0,
});
}
}
#[cfg(feature = "ffmpeg")]
{
let decoder = DecoderImpl::Ffmpeg(FfmpegDecoder::new(input)?);
let video_details = decoder.video_details()?;
return Ok(Decoder {
decoder,
video_details,
frames_read: 0,
});
}
#[cfg(feature = "vapoursynth")]
{
use crate::util::escape_python_string;
let script = format!(
r#"
import vapoursynth as vs
core = vs.core
clip = core.ffms2.Source("{}")
clip.set_output()
"#,
escape_python_string(
&std::path::absolute(input)
.map_err(|e| DecoderError::FileReadError {
cause: e.to_string()
})?
.to_string_lossy()
)
);
let decoder =
DecoderImpl::Vapoursynth(VapoursynthDecoder::from_script(&script, HashMap::new())?);
let video_details = decoder.video_details()?;
return Ok(Decoder {
decoder,
video_details,
frames_read: 0,
});
}
Err(DecoderError::NoDecoder)
}
#[inline]
#[cfg(feature = "vapoursynth")]
pub fn from_script(
script: &str,
variables: HashMap<VariableName, VariableValue>,
) -> Result<Decoder, DecoderError> {
let dec = VapoursynthDecoder::from_script(script, variables)?;
let decoder = DecoderImpl::Vapoursynth(dec);
let video_details = decoder.video_details()?;
Ok(Decoder {
decoder,
video_details,
frames_read: 0,
})
}
#[inline]
pub fn from_stdin() -> Result<Decoder, DecoderError> {
let reader = BufReader::new(stdin());
let decoder = DecoderImpl::Y4m(y4m::decode(Box::new(reader) as Box<dyn Read>).map_err(
|e| match e {
y4m::Error::EOF => DecoderError::EndOfFile,
_ => DecoderError::GenericDecodeError {
cause: e.to_string(),
},
},
)?);
let video_details: VideoDetails = decoder.video_details()?;
Ok(Decoder {
decoder,
video_details,
frames_read: 0,
})
}
#[inline]
pub fn from_decoder_impl(decoder_impl: DecoderImpl) -> Result<Decoder, DecoderError> {
let video_details = decoder_impl.video_details()?;
Ok(Decoder {
decoder: decoder_impl,
video_details,
frames_read: 0,
})
}
#[inline]
pub fn get_video_details(&self) -> &VideoDetails {
&self.video_details
}
#[inline]
pub fn read_video_frame<T: Pixel>(&mut self) -> Result<Frame<T>, DecoderError> {
let result = self.decoder.read_video_frame(
&self.video_details,
#[cfg(any(feature = "ffmpeg", feature = "vapoursynth"))]
self.frames_read,
);
if result.is_ok() {
self.frames_read += 1;
}
result
}
#[inline]
#[cfg(feature = "vapoursynth")]
pub fn get_video_frame<T: Pixel>(
&mut self,
frame_index: usize,
) -> Result<Frame<T>, DecoderError> {
self.decoder.get_video_frame(
#[cfg(feature = "vapoursynth")]
&self.video_details,
#[cfg(feature = "vapoursynth")]
frame_index,
)
}
#[inline]
#[cfg(feature = "vapoursynth")]
pub fn get_vapoursynth_env(&mut self) -> Result<&mut Environment, DecoderError> {
match self.decoder {
DecoderImpl::Vapoursynth(ref mut dec) => Ok(dec.get_env()),
_ => Err(DecoderError::UnsupportedDecoder),
}
}
#[inline]
#[cfg(feature = "vapoursynth")]
pub fn get_vapoursynth_node(&self) -> Result<Node, DecoderError> {
match self.decoder {
DecoderImpl::Vapoursynth(ref dec) => Ok(dec.get_output_node()),
_ => Err(DecoderError::UnsupportedDecoder),
}
}
}
pub enum DecoderImpl {
Y4m(Y4mDecoder<Box<dyn Read>>),
#[cfg(feature = "vapoursynth")]
Vapoursynth(VapoursynthDecoder),
#[cfg(feature = "ffmpeg")]
Ffmpeg(FfmpegDecoder),
}
impl DecoderImpl {
pub(crate) fn video_details(&self) -> Result<VideoDetails, DecoderError> {
match self {
Self::Y4m(dec) => Ok(helpers::y4m::get_video_details(dec)),
#[cfg(feature = "vapoursynth")]
Self::Vapoursynth(dec) => dec.get_video_details(),
#[cfg(feature = "ffmpeg")]
Self::Ffmpeg(dec) => Ok(dec.video_details),
}
}
pub(crate) fn read_video_frame<T: Pixel>(
&mut self,
cfg: &VideoDetails,
#[cfg(any(feature = "ffmpeg", feature = "vapoursynth"))] frame_index: usize,
) -> Result<Frame<T>, DecoderError> {
match self {
Self::Y4m(dec) => helpers::y4m::read_video_frame::<Box<dyn Read>, T>(dec, cfg),
#[cfg(feature = "vapoursynth")]
Self::Vapoursynth(dec) => dec.read_video_frame::<T>(cfg, frame_index),
#[cfg(feature = "ffmpeg")]
Self::Ffmpeg(dec) => dec.read_video_frame::<T>(frame_index),
}
}
#[cfg(feature = "vapoursynth")]
pub(crate) fn get_video_frame<T: Pixel>(
&mut self,
cfg: &VideoDetails,
frame_index: usize,
) -> Result<Frame<T>, DecoderError> {
match self {
Self::Y4m(_) => {
Err(DecoderError::UnsupportedDecoder)
}
#[cfg(feature = "vapoursynth")]
Self::Vapoursynth(dec) => dec.read_video_frame::<T>(cfg, frame_index),
#[cfg(feature = "ffmpeg")]
Self::Ffmpeg(_) => Err(DecoderError::UnsupportedDecoder),
}
}
}