unbundle 5.2.0

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

use std::{path::Path, time::Duration};

use unbundle::{AudioFormat, MediaFile};

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

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

    let mut unbundler = MediaFile::open(path).expect("Failed to open test video");
    let audio_bytes = unbundler
        .audio()
        .extract(AudioFormat::Wav)
        .expect("Failed to extract WAV audio");

    assert!(!audio_bytes.is_empty(), "Expected non-empty WAV audio data");
    // WAV files start with RIFF header.
    assert_eq!(
        &audio_bytes[..4],
        b"RIFF",
        "Expected RIFF header in WAV output"
    );
}

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

    let mut unbundler = MediaFile::open(path).expect("Failed to open test video");
    let audio_bytes = unbundler
        .audio()
        .extract_range(
            Duration::from_secs(1),
            Duration::from_secs(3),
            AudioFormat::Wav,
        )
        .expect("Failed to extract audio range");

    assert!(
        !audio_bytes.is_empty(),
        "Expected non-empty audio range data"
    );
}

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

    let output = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let output_path = output.path().with_extension("wav");

    let mut unbundler = MediaFile::open(path).expect("Failed to open test video");
    unbundler
        .audio()
        .save(&output_path, AudioFormat::Wav)
        .expect("Failed to save audio to file");

    let written_bytes = std::fs::read(&output_path).expect("Failed to read output file");
    assert!(
        !written_bytes.is_empty(),
        "Expected non-empty output WAV file"
    );
    assert_eq!(&written_bytes[..4], b"RIFF");

    // Clean up.
    let _ = std::fs::remove_file(&output_path);
}

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

    let output = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let output_path = output.path().with_extension("wav");

    let mut unbundler = MediaFile::open(path).expect("Failed to open test video");
    unbundler
        .audio()
        .save_range(
            &output_path,
            Duration::from_secs(1),
            Duration::from_secs(3),
            AudioFormat::Wav,
        )
        .expect("Failed to save audio range to file");

    let written_bytes = std::fs::read(&output_path).expect("Failed to read output file");
    assert!(!written_bytes.is_empty());

    let _ = std::fs::remove_file(&output_path);
}

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

    let mut unbundler = MediaFile::open(path).expect("Failed to open video-only file");
    let result = unbundler.audio().extract(AudioFormat::Wav);

    assert!(result.is_err(), "Expected NoAudioStream error");
}

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

    let mut unbundler = MediaFile::open(path).expect("Failed to open audio-only file");
    assert!(unbundler.metadata().audio.is_some());
    assert!(unbundler.metadata().video.is_none());

    let audio_bytes = unbundler
        .audio()
        .extract(AudioFormat::Wav)
        .expect("Failed to extract audio from audio-only file");
    assert!(!audio_bytes.is_empty());
}

#[test]
fn audio_metadata_is_populated() {
    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");

    assert!(
        audio_metadata.sample_rate > 0,
        "Expected positive sample rate"
    );
    assert!(audio_metadata.channels > 0, "Expected at least 1 channel");
    assert!(
        !audio_metadata.codec.is_empty(),
        "Expected non-empty codec name"
    );
}