zoomvtools 2.0.0

Video motion vector analysis utilities in pure Rust
Documentation
#![allow(clippy::unwrap_used, reason = "allow in test files")]

use std::num::{NonZeroU8, NonZeroUsize};

use crate::{
    analysis::MVAnalysisData,
    filters::{
        analyse::{Analyse, AnalyseOptions, SuperClipInfo},
        recalculate::{Recalculate, RecalculateOptions},
    },
    frame::{Frame, FramePlane},
    params::{DivideMode, MVPlaneSet, MotionFlags, SearchType, Subpel},
    video::{ColorFamily, Resolution, SampleType, VideoFormat, VideoInfo},
};

fn yuv420p8_super_info() -> (VideoInfo, SuperClipInfo) {
    let format = VideoFormat {
        color_family: ColorFamily::Yuv,
        sample_type: SampleType::Integer,
        bits_per_sample: NonZeroU8::new(8).unwrap(),
        bytes_per_sample: NonZeroU8::new(1).unwrap(),
        sub_sampling_w: 1,
        sub_sampling_h: 1,
    };
    let info = VideoInfo {
        format,
        resolution: Resolution {
            width: 672,
            height: 2750,
        },
        num_frames: 10,
    };
    let super_info = SuperClipInfo {
        height: NonZeroUsize::new(480).unwrap(),
        hpad: 16,
        vpad: 16,
        pel: Subpel::Half,
        mode_yuv: MVPlaneSet::YUVPLANES,
        levels: 8,
    };
    (info, super_info)
}

fn gray8_super_info() -> (VideoInfo, SuperClipInfo) {
    let format = VideoFormat {
        color_family: ColorFamily::Gray,
        sample_type: SampleType::Integer,
        bits_per_sample: NonZeroU8::new(8).unwrap(),
        bytes_per_sample: NonZeroU8::new(1).unwrap(),
        sub_sampling_w: 0,
        sub_sampling_h: 0,
    };
    let info = VideoInfo {
        format,
        resolution: Resolution {
            width: 672,
            height: 2750,
        },
        num_frames: 10,
    };
    let super_info = SuperClipInfo {
        height: NonZeroUsize::new(480).unwrap(),
        hpad: 16,
        vpad: 16,
        pel: Subpel::Half,
        mode_yuv: MVPlaneSet::YPLANE,
        levels: 8,
    };
    (info, super_info)
}

fn sample_vectors_data(
    x_ratio_uv: NonZeroU8,
    y_ratio_uv: NonZeroU8,
    motion_flags: MotionFlags,
) -> MVAnalysisData {
    MVAnalysisData {
        blk_size_x: NonZeroUsize::new(8).unwrap(),
        blk_size_y: NonZeroUsize::new(8).unwrap(),
        pel: Subpel::Half,
        level_count: 1,
        delta_frame: 1,
        is_backward: false,
        motion_flags,
        width: NonZeroUsize::new(640).unwrap(),
        height: NonZeroUsize::new(480).unwrap(),
        overlap_x: 0,
        overlap_y: 0,
        blk_x: NonZeroUsize::new(80).unwrap(),
        blk_y: NonZeroUsize::new(60).unwrap(),
        bits_per_sample: NonZeroU8::new(8).unwrap(),
        y_ratio_uv,
        x_ratio_uv,
        h_padding: 16,
        v_padding: 16,
    }
}

fn tiny_super_frame() -> Frame<u8> {
    let y = FramePlane::new(
        vec![0u8; 672 * 2750].into_boxed_slice(),
        NonZeroUsize::new(672).unwrap(),
        NonZeroUsize::new(2750).unwrap(),
        NonZeroUsize::new(672).unwrap(),
    )
    .unwrap();
    let u = FramePlane::new(
        vec![0u8; 336 * 1375].into_boxed_slice(),
        NonZeroUsize::new(336).unwrap(),
        NonZeroUsize::new(1375).unwrap(),
        NonZeroUsize::new(336).unwrap(),
    )
    .unwrap();
    let v = FramePlane::new(
        vec![0u8; 336 * 1375].into_boxed_slice(),
        NonZeroUsize::new(336).unwrap(),
        NonZeroUsize::new(1375).unwrap(),
        NonZeroUsize::new(336).unwrap(),
    )
    .unwrap();
    Frame::new([Some(y), Some(u), Some(v)])
}

#[test]
fn recalculate_new_defaults_match_vapoursynth_filter_defaults() {
    let (info, super_info) = yuv420p8_super_info();

    let recalculate = Recalculate::new(
        info,
        super_info,
        sample_vectors_data(
            NonZeroU8::new(2).unwrap(),
            NonZeroU8::new(2).unwrap(),
            MotionFlags::USE_CHROMA_MOTION,
        ),
        RecalculateOptions::default(),
    )
    .unwrap();

    assert_eq!(recalculate.search_type(), SearchType::Hex2);
    assert_eq!(recalculate.search_param(), 2);
    assert_eq!(recalculate.lambda(), 1000);
    assert_eq!(recalculate.penalty_new(), 50);
    assert_eq!(recalculate.dct_mode(), crate::params::DctMode::Spatial);
    assert_eq!(recalculate.divide_extra(), DivideMode::None);
    assert!(recalculate.smooth());
    assert!(recalculate.meander());
    assert!(!recalculate.fields());

    let data = recalculate.analysis_data();
    assert_eq!(data.blk_size_x.get(), 8);
    assert_eq!(data.blk_size_y.get(), 8);
    assert_eq!(data.motion_flags, MotionFlags::USE_CHROMA_MOTION);
    assert_eq!(data.width.get(), 640);
    assert_eq!(data.height.get(), 480);
    assert!(recalculate.analysis_data_divided().is_none());
}

#[test]
fn recalculate_new_rejects_gray_when_chroma_requested() {
    let (info, super_info) = gray8_super_info();

    let err = Recalculate::new(
        info,
        super_info,
        sample_vectors_data(
            NonZeroU8::new(1).unwrap(),
            NonZeroU8::new(1).unwrap(),
            MotionFlags::empty(),
        ),
        RecalculateOptions {
            chroma: Some(true),
            ..RecalculateOptions::default()
        },
    )
    .unwrap_err();

    assert_eq!(
        err.to_string(),
        "Recalculate: super clip does not contain needed colour data."
    );
}

#[test]
fn recalculate_frame_with_divide_emits_divided_analysis_data() {
    let (info, super_info) = yuv420p8_super_info();
    let analyse = Analyse::new(info, super_info, AnalyseOptions::default()).unwrap();
    let recalculate = Recalculate::new(
        info,
        super_info,
        analyse.analysis_data(),
        RecalculateOptions {
            divide_extra: Some(DivideMode::Original),
            ..RecalculateOptions::default()
        },
    )
    .unwrap();
    let src = tiny_super_frame();
    let reference = tiny_super_frame();
    let src_view = src.as_view().unwrap();
    let reference_view = reference.as_view().unwrap();
    let analysed = analyse
        .analyse_frame(
            1,
            &src_view,
            Some(&reference_view),
            Some(false),
            Some(false),
        )
        .unwrap();

    let output = recalculate
        .recalculate_frame(
            &src_view,
            Some(&reference_view),
            &analysed.vectors.block_data,
            Some(false),
            Some(false),
        )
        .unwrap();

    assert_eq!(
        output.analysis_data,
        recalculate.analysis_data_divided().unwrap()
    );
}

#[test]
fn recalculate_frame_with_borrowed_views_returns_valid_vectors() {
    let (info, super_info) = yuv420p8_super_info();
    let analyse = Analyse::new(info, super_info, AnalyseOptions::default()).unwrap();
    let recalculate = Recalculate::new(
        info,
        super_info,
        analyse.analysis_data(),
        RecalculateOptions::default(),
    )
    .unwrap();
    let src = tiny_super_frame();
    let reference = tiny_super_frame();
    let src_view = src.as_view().unwrap();
    let reference_view = reference.as_view().unwrap();
    let analysed = analyse
        .analyse_frame(
            1,
            &src_view,
            Some(&reference_view),
            Some(false),
            Some(false),
        )
        .unwrap();

    let output = recalculate
        .recalculate_frame(
            &src_view,
            Some(&reference_view),
            &analysed.vectors.block_data,
            Some(false),
            Some(false),
        )
        .unwrap();

    assert_eq!(output.analysis_data, recalculate.analysis_data());
    assert!(output.vectors.validity);
}

#[test]
fn recalculate_frame_without_reference_returns_default_vectors() {
    let (info, super_info) = yuv420p8_super_info();
    let analyse = Analyse::new(info, super_info, AnalyseOptions::default()).unwrap();
    let recalculate = Recalculate::new(
        info,
        super_info,
        analyse.analysis_data(),
        RecalculateOptions::default(),
    )
    .unwrap();
    let src = tiny_super_frame();
    let reference = tiny_super_frame();
    let src_view = src.as_view().unwrap();
    let reference_view = reference.as_view().unwrap();
    let analysed = analyse
        .analyse_frame(
            1,
            &src_view,
            Some(&reference_view),
            Some(false),
            Some(false),
        )
        .unwrap();

    let output = recalculate
        .recalculate_frame(
            &src_view,
            None,
            &analysed.vectors.block_data,
            Some(false),
            None,
        )
        .unwrap();

    assert_eq!(output.analysis_data, recalculate.analysis_data());
    assert!(!output.vectors.validity);
}