ff-filter 0.14.1

Video and audio filter graph operations - the Rust way
Documentation
//! Integration tests for v0.14.0 video effect methods on `FilterGraph`.
//!
//! Each test pushes a synthetic frame through FFmpeg and verifies either a
//! measurable pixel change or — where FFmpeg buffering prevents immediate
//! output — that the graph built and accepted a frame without error.

mod fixtures;
use fixtures::yuv420p_frame;

use ff_filter::{FilterError, FilterGraph, LensProfile};

// ── motion_blur ───────────────────────────────────────────────────────────────

#[test]
fn motion_blur_should_accept_video_frame_without_error() {
    let mut graph = match FilterGraph::builder().build() {
        Ok(g) => g,
        Err(e) => {
            println!("Skipping: graph build failed: {e}");
            return;
        }
    };
    if let Err(e) = graph.motion_blur(180.0, 2) {
        println!("Skipping: motion_blur setup failed: {e}");
        return;
    }
    let frame = yuv420p_frame(64, 64, 100, 128, 128);
    match graph.push_video(0, &frame) {
        Ok(()) => {}
        Err(FilterError::BuildFailed) => {
            println!("Skipping: tblend not available in this FFmpeg build");
            return;
        }
        Err(e) => panic!("push_video failed unexpectedly: {e}"),
    }
    // tblend requires two frames to produce output; a second push is valid
    let frame2 = yuv420p_frame(64, 64, 150, 128, 128);
    let _ = graph.push_video(0, &frame2);
    // pull may return None (still buffering) — either outcome is acceptable
    let _ = graph.pull_video();
}

// ── film_grain ────────────────────────────────────────────────────────────────

#[test]
fn film_grain_should_produce_output_frame_with_preserved_dimensions() {
    let mut graph = match FilterGraph::builder().build() {
        Ok(g) => g,
        Err(e) => {
            println!("Skipping: graph build failed: {e}");
            return;
        }
    };
    graph.film_grain(50.0, 20.0);
    let frame = yuv420p_frame(32, 32, 128, 128, 128);
    match graph.push_video(0, &frame) {
        Ok(()) => {}
        Err(FilterError::BuildFailed) => {
            println!("Skipping: noise filter not available in this FFmpeg build");
            return;
        }
        Err(e) => panic!("push_video failed unexpectedly: {e}"),
    }
    match graph.pull_video() {
        Ok(Some(out)) => {
            assert_eq!(out.width(), 32, "film_grain must preserve frame width");
            assert_eq!(out.height(), 32, "film_grain must preserve frame height");
        }
        Ok(None) => println!("Note: film_grain buffered (no output yet — acceptable)"),
        Err(e) => println!("Note: pull_video returned: {e}"),
    }
}

// ── lens_correction ───────────────────────────────────────────────────────────

#[test]
fn lens_correction_should_preserve_frame_dimensions_on_output() {
    let mut graph = match FilterGraph::builder().build() {
        Ok(g) => g,
        Err(e) => {
            println!("Skipping: graph build failed: {e}");
            return;
        }
    };
    if let Err(e) = graph.lens_correction(-0.1, 0.0) {
        println!("Skipping: lens_correction setup failed: {e}");
        return;
    }
    let frame = yuv420p_frame(64, 64, 128, 128, 128);
    match graph.push_video(0, &frame) {
        Ok(()) => {}
        Err(FilterError::BuildFailed) => {
            println!("Skipping: lenscorrection not available in this FFmpeg build");
            return;
        }
        Err(e) => panic!("push_video failed unexpectedly: {e}"),
    }
    match graph.pull_video() {
        Ok(Some(out)) => {
            assert_eq!(out.width(), 64, "lens_correction must preserve width");
            assert_eq!(out.height(), 64, "lens_correction must preserve height");
        }
        Ok(None) => println!("Note: lenscorrection buffered (no output yet)"),
        Err(e) => println!("Note: pull_video returned: {e}"),
    }
}

// ── fix_chromatic_aberration ──────────────────────────────────────────────────

#[test]
fn chromatic_aberration_correction_should_preserve_frame_dimensions() {
    let mut graph = match FilterGraph::builder().build() {
        Ok(g) => g,
        Err(e) => {
            println!("Skipping: graph build failed: {e}");
            return;
        }
    };
    if let Err(e) = graph.fix_chromatic_aberration(1.002, 0.998) {
        println!("Skipping: fix_chromatic_aberration setup failed: {e}");
        return;
    }
    let frame = yuv420p_frame(64, 64, 128, 128, 128);
    match graph.push_video(0, &frame) {
        Ok(()) => {}
        Err(FilterError::BuildFailed) => {
            println!("Skipping: rgbashift not available in this FFmpeg build");
            return;
        }
        Err(e) => panic!("push_video failed unexpectedly: {e}"),
    }
    match graph.pull_video() {
        Ok(Some(out)) => {
            assert_eq!(out.width(), 64, "chromatic correction must preserve width");
            assert_eq!(
                out.height(),
                64,
                "chromatic correction must preserve height"
            );
        }
        Ok(None) => println!("Note: rgbashift buffered (no output yet)"),
        Err(e) => println!("Note: pull_video returned: {e}"),
    }
}

// ── glow / bloom ──────────────────────────────────────────────────────────────

#[test]
fn glow_effect_should_produce_output_with_preserved_dimensions() {
    let mut graph = match FilterGraph::builder().build() {
        Ok(g) => g,
        Err(e) => {
            println!("Skipping: graph build failed: {e}");
            return;
        }
    };
    graph.glow(0.8, 5.0, 0.5);
    let frame = yuv420p_frame(64, 64, 200, 128, 128);
    match graph.push_video(0, &frame) {
        Ok(()) => {}
        Err(FilterError::BuildFailed) => {
            println!("Skipping: glow filter chain not available in this FFmpeg build");
            return;
        }
        Err(e) => panic!("push_video failed unexpectedly: {e}"),
    }
    match graph.pull_video() {
        Ok(Some(out)) => {
            assert_eq!(out.width(), 64, "glow must preserve width");
            assert_eq!(out.height(), 64, "glow must preserve height");
        }
        Ok(None) => println!("Note: glow buffered (no output yet)"),
        Err(e) => println!("Note: pull_video returned: {e}"),
    }
}

// ── lens_profile ──────────────────────────────────────────────────────────────

#[test]
fn lens_profile_custom_identity_should_process_frame_without_error() {
    let mut graph = match FilterGraph::builder().build() {
        Ok(g) => g,
        Err(e) => {
            println!("Skipping: graph build failed: {e}");
            return;
        }
    };
    // Custom(k1=0, k2=0, scale=1.0) is an identity correction
    graph.lens_profile(LensProfile::Custom {
        k1: 0.0,
        k2: 0.0,
        scale: 1.0,
    });
    let frame = yuv420p_frame(32, 32, 100, 128, 128);
    match graph.push_video(0, &frame) {
        Ok(()) => {}
        Err(FilterError::BuildFailed) => {
            println!("Skipping: lenscorrection not available in this FFmpeg build");
        }
        Err(e) => panic!("push_video failed unexpectedly: {e}"),
    }
}