#![allow(clippy::unwrap_used)]
use ff_decode::{DecodeError, VideoDecoder};
use std::time::Duration;
fn test_video_path() -> std::path::PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
std::path::PathBuf::from(format!("{manifest_dir}/../../assets/video/gameplay.mp4"))
}
#[test]
fn extract_frame_should_return_frame_at_timestamp() {
let path = test_video_path();
if !path.exists() {
println!("Skipping: test video not found at {}", path.display());
return;
}
let mut decoder = match VideoDecoder::open(&path).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: VideoDecoder::build failed ({e})");
return;
}
};
let target = Duration::from_secs(1);
let frame = match decoder.extract_frame(target) {
Ok(f) => f,
Err(e) => {
println!("Skipping: extract_frame failed ({e})");
return;
}
};
let pts = frame.timestamp().as_duration();
assert!(pts >= target, "expected PTS >= {target:?}, got {pts:?}");
let window = Duration::from_secs(2);
assert!(
pts <= target + window,
"expected PTS within {window:?} of target, got pts={pts:?} target={target:?}"
);
}
#[test]
fn extract_frame_beyond_duration_should_return_no_frame_at_timestamp() {
let path = test_video_path();
if !path.exists() {
println!("Skipping: test video not found at {}", path.display());
return;
}
let mut decoder = match VideoDecoder::open(&path).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: VideoDecoder::build failed ({e})");
return;
}
};
let beyond = Duration::from_secs(999_999);
let result = decoder.extract_frame(beyond);
assert!(
matches!(result, Err(DecodeError::NoFrameAtTimestamp { .. })),
"expected NoFrameAtTimestamp for timestamp beyond duration, got {result:?}"
);
}
#[test]
fn extract_frame_at_zero_should_return_first_frame() {
let path = test_video_path();
if !path.exists() {
println!("Skipping: test video not found at {}", path.display());
return;
}
let mut decoder = match VideoDecoder::open(&path).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: VideoDecoder::build failed ({e})");
return;
}
};
let frame = match decoder.extract_frame(Duration::ZERO) {
Ok(f) => f,
Err(e) => {
println!("Skipping: extract_frame(0) failed ({e})");
return;
}
};
let pts = frame.timestamp().as_duration();
let window = Duration::from_secs(1);
assert!(
pts <= window,
"expected first frame within {window:?} of t=0, got pts={pts:?}"
);
}
#[test]
fn extract_frame_multiple_calls_should_each_seek_correctly() {
let path = test_video_path();
if !path.exists() {
println!("Skipping: test video not found at {}", path.display());
return;
}
let mut decoder = match VideoDecoder::open(&path).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: VideoDecoder::build failed ({e})");
return;
}
};
let t2 = Duration::from_secs(2);
let t1 = Duration::from_secs(1);
let frame2 = match decoder.extract_frame(t2) {
Ok(f) => f,
Err(e) => {
println!("Skipping: extract_frame(t2) failed ({e})");
return;
}
};
let frame1 = match decoder.extract_frame(t1) {
Ok(f) => f,
Err(e) => {
println!("Skipping: extract_frame(t1) failed ({e})");
return;
}
};
assert!(
frame2.timestamp().as_duration() >= t2,
"frame2 PTS should be >= t2"
);
assert!(
frame1.timestamp().as_duration() >= t1,
"frame1 PTS should be >= t1"
);
}