zoomvtools 2.0.0

Video motion vector analysis utilities in pure Rust
Documentation
#[cfg(test)]
mod tests;

use anyhow::{Result, ensure};

use crate::{
    analysis::MVAnalysisData,
    fake::group_of_planes::FakeGroupOfPlanes,
    frame::{FramePlanesMut, FrameView, PlaneSizeTuple},
    video::VideoInfo,
};

use super::flow_inter::{FlowInterExtraVectors, FlowInterpolationBase, FlowInterpolationMode};

/// Options used to construct [`FlowFPS`].
#[derive(Debug, Clone, Copy)]
pub struct FlowFPSOptions {
    /// Mask mode in the MVTools-compatible `0..=2` range.
    pub mask_mode: u8,
    /// Motion-length scaling factor used for occlusion masks.
    pub ml: f64,
}

/// Frame-rate conversion filter built on motion interpolation.
pub struct FlowFPS {
    base: FlowInterpolationBase,
    mask_mode: u8,
}

impl FlowFPS {
    /// Builds a motion-interpolated FPS filter from validated clip and vector metadata.
    ///
    /// # Errors
    /// Returns an error if the clip format, vector metadata, or options are invalid.
    #[inline]
    pub fn new(
        info: VideoInfo,
        backward_data: MVAnalysisData,
        forward_data: MVAnalysisData,
        options: FlowFPSOptions,
    ) -> Result<Self> {
        ensure!(
            (0..=2).contains(&options.mask_mode),
            "FlowFPS: mask must be 0, 1, or 2."
        );
        ensure!(options.ml > 0.0, "FlowFPS: ml must be greater than 0.");

        Ok(Self {
            base: FlowInterpolationBase::new(
                info,
                backward_data,
                forward_data,
                options.ml,
                "FlowFPS",
            )?,
            mask_mode: options.mask_mode,
        })
    }

    /// Renders one interpolated frame into `output`.
    ///
    /// `time256` uses the MVTools 8.8 fixed-point time domain.
    ///
    /// # Errors
    /// Returns an error if the frames, vectors, optional extra vectors, or output buffers do not
    /// match the filter configuration.
    #[inline]
    pub fn render_frame<T: crate::util::Pixel>(
        &self,
        backward: &FrameView<'_, T>,
        forward: &FrameView<'_, T>,
        output: &mut FramePlanesMut<'_, T>,
        output_pitch: PlaneSizeTuple,
        backward_vectors: &FakeGroupOfPlanes,
        forward_vectors: &FakeGroupOfPlanes,
        extra_vectors: Option<FlowInterExtraVectors<'_>>,
        time256: i32,
    ) -> Result<()> {
        ensure!(
            self.mask_mode == 2 || extra_vectors.is_none(),
            "FlowFPS: extra vectors require mask mode 2."
        );

        let mode = match (self.mask_mode, extra_vectors.is_some()) {
            (0, _) => FlowInterpolationMode::Simple,
            (1, _) => FlowInterpolationMode::Regular,
            (2, true) => FlowInterpolationMode::Extra,
            (2, false) => FlowInterpolationMode::Simple,
            _ => unreachable!("mask_mode validated in constructor"),
        };

        self.base.render_frame(
            mode,
            time256,
            backward,
            forward,
            output,
            output_pitch,
            backward_vectors,
            forward_vectors,
            extra_vectors,
        )
    }

    /// Linearly blends `current` and `future` using the supplied 8.8 fixed-point time.
    ///
    /// # Errors
    /// Returns an error if the frames or output buffers do not match the filter configuration.
    #[inline]
    pub fn blend_frame<T: crate::util::Pixel>(
        &self,
        current: &FrameView<'_, T>,
        future: &FrameView<'_, T>,
        output: &mut FramePlanesMut<'_, T>,
        output_pitch: PlaneSizeTuple,
        time256: i32,
    ) -> Result<()> {
        self.base
            .blend_frame(time256, current, future, output, output_pitch)
    }
}