#![cfg(feature = "async")]
use std::path::Path;
use image::DynamicImage;
use tokio_stream::StreamExt;
use unbundle::{ExtractOptions, FrameRange, FrameStream, MediaFile};
const SAMPLE_VIDEO: &str = "tests/fixtures/sample_video.mp4";
const AUDIO_ONLY: &str = "tests/fixtures/sample_audio_only.mp4";
fn skip_unless(path: &str) -> bool {
if !Path::new(path).exists() {
eprintln!("Skipping: fixture {path} not found");
return true;
}
false
}
#[tokio::test]
async fn stream_first_five_frames() {
if skip_unless(SAMPLE_VIDEO) {
return;
}
let mut unbundler = MediaFile::open(SAMPLE_VIDEO).unwrap();
let mut stream: FrameStream = unbundler
.video()
.frame_stream(
FrameRange::Specific(vec![0, 1, 2, 3, 4]),
ExtractOptions::new(),
)
.unwrap();
let mut count = 0u64;
while let Some(result) = stream.next().await {
let (frame_number, image): (u64, DynamicImage) = result.unwrap();
assert!(image.width() > 0);
assert!(image.height() > 0);
assert!(frame_number <= 4, "unexpected frame number {frame_number}");
count += 1;
}
assert_eq!(count, 5, "expected 5 frames from stream");
}
#[tokio::test]
async fn stream_non_consecutive_frames() {
if skip_unless(SAMPLE_VIDEO) {
return;
}
let mut unbundler = MediaFile::open(SAMPLE_VIDEO).unwrap();
let mut stream = unbundler
.video()
.frame_stream(FrameRange::Specific(vec![0, 30, 60]), ExtractOptions::new())
.unwrap();
let mut frames = Vec::new();
while let Some(result) = stream.next().await {
let (_, image): (u64, DynamicImage) = result.unwrap();
frames.push(image);
}
assert_eq!(frames.len(), 3);
}
#[tokio::test]
async fn stream_range() {
if skip_unless(SAMPLE_VIDEO) {
return;
}
let mut unbundler = MediaFile::open(SAMPLE_VIDEO).unwrap();
let mut stream = unbundler
.video()
.frame_stream(FrameRange::Range(10, 14), ExtractOptions::new())
.unwrap();
let mut count = 0u64;
while let Some(result) = stream.next().await {
let _ = result.unwrap();
count += 1;
}
assert_eq!(count, 5, "Range(10, 14) should yield 5 frames");
}
#[tokio::test]
async fn stream_no_video_stream_error() {
if skip_unless(AUDIO_ONLY) {
return;
}
let mut unbundler = MediaFile::open(AUDIO_ONLY).unwrap();
let result = unbundler
.video()
.frame_stream(FrameRange::Range(0, 0), ExtractOptions::new());
assert!(result.is_err(), "should error on audio-only file");
}
#[tokio::test]
async fn audio_future_extracts_wav() {
if skip_unless(SAMPLE_VIDEO) {
return;
}
let mut unbundler = MediaFile::open(SAMPLE_VIDEO).unwrap();
let audio_bytes = unbundler
.audio()
.extract_async(unbundle::AudioFormat::Wav, ExtractOptions::new())
.unwrap()
.await
.unwrap();
assert!(!audio_bytes.is_empty(), "expected non-empty WAV data");
assert_eq!(
&audio_bytes[..4],
b"RIFF",
"WAV output should start with RIFF header",
);
}
#[tokio::test]
async fn stream_from_open_url_source_input() {
if skip_unless(SAMPLE_VIDEO) {
return;
}
let mut unbundler = MediaFile::open_url(SAMPLE_VIDEO).unwrap();
let mut stream = unbundler
.video()
.frame_stream(FrameRange::Range(0, 2), ExtractOptions::new())
.unwrap();
let mut count = 0u64;
while let Some(result) = stream.next().await {
let (_frame_number, image): (u64, DynamicImage) = result.unwrap();
assert!(image.width() > 0);
assert!(image.height() > 0);
count += 1;
}
assert_eq!(count, 3, "expected three frames from URL-opened source");
}
#[tokio::test]
async fn audio_future_from_open_url_source_input() {
if skip_unless(SAMPLE_VIDEO) {
return;
}
let mut unbundler = MediaFile::open_url(SAMPLE_VIDEO).unwrap();
let audio_bytes = unbundler
.audio()
.extract_async(unbundle::AudioFormat::Wav, ExtractOptions::new())
.unwrap()
.await
.unwrap();
assert!(!audio_bytes.is_empty(), "expected non-empty WAV data");
assert_eq!(&audio_bytes[..4], b"RIFF");
}