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::{
    filters::analyse::{Analyse, AnalyseOptions, SuperClipInfo},
    frame::{Frame, FramePlane},
    params::{DctMode, DivideMode, MVPlaneSet, MotionFlags, PenaltyScaling, 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)
}

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

    let analyse = Analyse::new(info, super_info, AnalyseOptions::default()).unwrap();

    assert_eq!(analyse.search_type(), SearchType::Hex2);
    assert_eq!(analyse.search_type_coarse(), SearchType::Exhaustive);
    assert_eq!(analyse.search_param(), 2);
    assert_eq!(analyse.pel_search(), 2);
    assert_eq!(analyse.penalty_level(), PenaltyScaling::Linear);
    assert!(analyse.global());
    assert_eq!(analyse.penalty_new(), 50);
    assert_eq!(analyse.penalty_zero(), 50);
    assert_eq!(analyse.penalty_global(), 0);
    assert_eq!(analyse.dct_mode(), DctMode::Spatial);
    assert_eq!(analyse.divide_extra(), DivideMode::None);
    assert_eq!(analyse.bad_range(), 24);
    assert!(analyse.meander());
    assert!(!analyse.try_many());
    assert!(!analyse.fields());
    assert_eq!(analyse.tff(), None);
    assert_eq!(analyse.lambda(), 1000);
    assert_eq!(analyse.lambda_sad(), 1200);
    assert_eq!(analyse.bad_sad(), 10_000);

    let data = analyse.analysis_data();
    assert_eq!(data.blk_size_x.get(), 8);
    assert_eq!(data.blk_size_y.get(), 8);
    assert_eq!(data.pel, Subpel::Half);
    assert_eq!(data.delta_frame, 1);
    assert!(!data.is_backward);
    assert_eq!(data.motion_flags, MotionFlags::USE_CHROMA_MOTION);
    assert_eq!(data.width.get(), 640);
    assert_eq!(data.height.get(), 480);
    assert_eq!(data.h_padding, 16);
    assert_eq!(data.v_padding, 16);
    assert!(analyse.analysis_data_divided().is_none());
}

#[test]
fn analyse_new_rejects_invalid_block_size() {
    let (info, super_info) = yuv420p8_super_info();
    let options = AnalyseOptions {
        blk_size_x: Some(3),
        blk_size_y: Some(3),
        ..AnalyseOptions::default()
    };

    let err = Analyse::new(info, super_info, options).unwrap_err();

    assert!(err.to_string().contains("block size must be"));
}

#[test]
fn analyse_new_divide_builds_divided_analysis_data() {
    let (info, super_info) = yuv420p8_super_info();
    let options = AnalyseOptions {
        divide_extra: Some(DivideMode::Original),
        ..AnalyseOptions::default()
    };

    let analyse = Analyse::new(info, super_info, options).unwrap();
    let divided = analyse.analysis_data_divided().unwrap();

    assert_eq!(divided.blk_size_x.get(), 4);
    assert_eq!(divided.blk_size_y.get(), 4);
    assert_eq!(divided.blk_x.get(), analyse.analysis_data().blk_x.get() * 2);
    assert_eq!(divided.blk_y.get(), analyse.analysis_data().blk_y.get() * 2);
    assert_eq!(divided.level_count, analyse.analysis_data().level_count + 1);
}

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 reference_frame_number_matches_forward_and_backward_modes() {
    let (info, super_info) = yuv420p8_super_info();
    let forward = Analyse::new(info, super_info, AnalyseOptions::default()).unwrap();
    let backward = Analyse::new(
        info,
        super_info,
        AnalyseOptions {
            is_backward: true,
            ..AnalyseOptions::default()
        },
    )
    .unwrap();

    assert_eq!(forward.reference_frame_number(0), None);
    assert_eq!(forward.reference_frame_number(1), Some(0));
    assert_eq!(backward.reference_frame_number(8), Some(9));
    assert_eq!(backward.reference_frame_number(9), None);
}

#[test]
fn analyse_frame_without_reference_returns_default_vectors_and_analysis_data() {
    let (info, super_info) = yuv420p8_super_info();
    let analyse = Analyse::new(info, super_info, AnalyseOptions::default()).unwrap();
    let frame = tiny_super_frame();
    let view = frame.as_view().unwrap();

    let output = analyse.analyse_frame(0, &view, None, None, None).unwrap();

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

#[test]
fn analyse_frame_with_reference_returns_valid_vectors() {
    let (info, super_info) = yuv420p8_super_info();
    let analyse = Analyse::new(info, super_info, AnalyseOptions::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 output = analyse
        .analyse_frame(
            1,
            &src_view,
            Some(&reference_view),
            Some(false),
            Some(false),
        )
        .unwrap();

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