unbundle 5.2.0

Unbundle media files - extract still frames, audio tracks, and subtitles from video files
Documentation
//! Metadata integration tests.
//!
//! These tests require the fixture files generated by
//! `tests/fixtures/generate_fixtures.sh` (or `.bat` on Windows).

use std::path::Path;

use unbundle::MediaFile;

fn sample_video_path() -> &'static str {
    "tests/fixtures/sample_video.mp4"
}

#[test]
fn metadata_has_video_stream() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let metadata = unbundler.metadata();

    assert!(metadata.video.is_some(), "Expected video metadata");
}

#[test]
fn metadata_has_audio_stream() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let metadata = unbundler.metadata();

    assert!(metadata.audio.is_some(), "Expected audio metadata");
}

#[test]
fn video_metadata_dimensions() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let video_metadata = unbundler
        .metadata()
        .video
        .as_ref()
        .expect("No video metadata");

    // The test fixture is 640x480.
    assert_eq!(video_metadata.width, 640);
    assert_eq!(video_metadata.height, 480);
}

#[test]
fn video_metadata_frame_rate() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let video_metadata = unbundler
        .metadata()
        .video
        .as_ref()
        .expect("No video metadata");

    // The test fixture is 30 fps.
    assert!(
        (video_metadata.frames_per_second - 30.0).abs() < 1.0,
        "Expected ~30 fps, got {}",
        video_metadata.frames_per_second,
    );
}

#[test]
fn video_metadata_frame_count() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let video_metadata = unbundler
        .metadata()
        .video
        .as_ref()
        .expect("No video metadata");

    // 5 seconds at 30 fps = ~150 frames (±10 for encoding variance).
    assert!(
        video_metadata.frame_count >= 140 && video_metadata.frame_count <= 160,
        "Expected ~150 frames, got {}",
        video_metadata.frame_count,
    );
}

#[test]
fn metadata_duration() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let duration = unbundler.metadata().duration;

    // The test fixture is 5 seconds.
    let duration_seconds = duration.as_secs_f64();
    assert!(
        duration_seconds >= 4.5 && duration_seconds <= 5.5,
        "Expected ~5s duration, got {duration_seconds}s",
    );
}

#[test]
fn metadata_format_name() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let format = &unbundler.metadata().format;

    // MP4 container format name in FFmpeg.
    assert!(
        format.contains("mov") || format.contains("mp4") || format.contains("m4a"),
        "Expected MP4-family format, got '{format}'",
    );
}

#[test]
fn audio_metadata_sample_rate() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let audio_metadata = unbundler
        .metadata()
        .audio
        .as_ref()
        .expect("No audio metadata");

    // The test fixture has 44100 Hz audio.
    assert_eq!(
        audio_metadata.sample_rate, 44100,
        "Expected 44100 Hz sample rate",
    );
}

#[test]
fn audio_metadata_channels() {
    let path = sample_video_path();
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open test video");
    let audio_metadata = unbundler
        .metadata()
        .audio
        .as_ref()
        .expect("No audio metadata");

    // The test fixture has stereo audio.
    assert_eq!(audio_metadata.channels, 2, "Expected 2 channels (stereo)");
}

#[test]
fn audio_only_file_has_no_video_metadata() {
    let path = "tests/fixtures/sample_audio_only.mp4";
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open audio-only file");
    let metadata = unbundler.metadata();

    assert!(metadata.video.is_none(), "Expected no video metadata");
    assert!(metadata.audio.is_some(), "Expected audio metadata");
}

#[test]
fn video_only_file_has_no_audio_metadata() {
    let path = "tests/fixtures/sample_video_only.mp4";
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open video-only file");
    let metadata = unbundler.metadata();

    assert!(metadata.video.is_some(), "Expected video metadata");
    assert!(metadata.audio.is_none(), "Expected no audio metadata");
}

#[test]
fn open_nonexistent_file_returns_error() {
    let result = MediaFile::open("nonexistent_file.mp4");
    assert!(result.is_err(), "Expected error for nonexistent file");
}

#[test]
fn open_url_accepts_path_input() {
    let path = Path::new(sample_video_path());
    if !path.exists() {
        return;
    }

    let unbundler =
        MediaFile::open_url(sample_video_path()).expect("Failed to open source via open_url");
    let metadata = unbundler.metadata();

    assert!(metadata.video.is_some(), "Expected video metadata");
    assert!(metadata.audio.is_some(), "Expected audio metadata");
    assert!(
        metadata.duration.as_secs_f64() > 0.0,
        "Expected non-zero duration"
    );
}

#[test]
fn mkv_format_works() {
    let path = "tests/fixtures/sample_video.mkv";
    if !Path::new(path).exists() {
        return;
    }

    let unbundler = MediaFile::open(path).expect("Failed to open MKV file");
    let metadata = unbundler.metadata();

    assert!(metadata.video.is_some(), "Expected video stream in MKV");
    assert!(metadata.audio.is_some(), "Expected audio stream in MKV");
    assert!(
        metadata.format.contains("matroska") || metadata.format.contains("webm"),
        "Expected matroska format, got '{}'",
        metadata.format,
    );
}