#![allow(clippy::unwrap_used, unsafe_code)]
mod fixtures;
use ff_encode::{
H264Options, H264Preset, H264Profile, H264Tune, VideoCodec, VideoCodecOptions, VideoEncoder,
};
use ff_format::codec::VideoCodec as FmtVideoCodec;
use fixtures::{
FileGuard, assert_valid_output_file, create_black_frame, get_file_size, test_output_path,
};
fn is_libx264_available() -> bool {
if !cfg!(feature = "gpl") {
return false;
}
let name = b"libx264\0";
unsafe { ff_sys::avcodec::find_encoder_by_name(name.as_ptr() as *const i8).is_some() }
}
#[test]
fn h264_high_profile_level41_should_produce_valid_output() {
if !is_libx264_available() {
println!("Skipping: libx264 not available");
return;
}
let output_path = test_output_path("h264_opts_high_level41_1080p.mp4");
let _guard = FileGuard::new(output_path.clone());
let opts = VideoCodecOptions::H264(H264Options {
profile: H264Profile::High,
level: Some(41),
preset: Some(H264Preset::Ultrafast),
..H264Options::default()
});
let result = VideoEncoder::create(&output_path)
.video(1920, 1080, 30.0)
.video_codec(VideoCodec::H264)
.codec_options(opts)
.build();
let mut encoder = match result {
Ok(enc) => enc,
Err(e) => {
println!("Skipping: H.264 encoder unavailable: {e}");
return;
}
};
for _ in 0..30 {
let frame = create_black_frame(1920, 1080);
encoder
.push_video(&frame)
.expect("Failed to push video frame");
}
encoder.finish().expect("Failed to finish encoding");
assert_valid_output_file(&output_path);
let file_size = get_file_size(&output_path);
assert!(file_size > 1000, "Output too small: {file_size} bytes");
let info = ff_probe::open(&output_path).expect("Failed to probe output");
let video = info.primary_video().expect("No video stream in output");
assert_eq!(
video.codec(),
FmtVideoCodec::H264,
"Expected H.264 codec in output, got {:?}",
video.codec()
);
println!(
"H264 High@4.1 1080p: codec={} size={file_size} bytes",
video.codec_name()
);
}
#[test]
fn h264_veryslow_preset_should_produce_output_no_larger_than_ultrafast() {
if !is_libx264_available() {
println!("Skipping: libx264 not available");
return;
}
let veryslow_path = test_output_path("h264_opts_veryslow.mp4");
let _guard_vs = FileGuard::new(veryslow_path.clone());
let result = VideoEncoder::create(&veryslow_path)
.video(320, 240, 30.0)
.video_codec(VideoCodec::H264)
.codec_options(VideoCodecOptions::H264(H264Options {
preset: Some(H264Preset::Veryslow),
..H264Options::default()
}))
.build();
let mut enc_vs = match result {
Ok(enc) => enc,
Err(e) => {
println!("Skipping: H.264 encoder unavailable: {e}");
return;
}
};
for _ in 0..10 {
enc_vs
.push_video(&create_black_frame(320, 240))
.expect("push failed");
}
enc_vs.finish().expect("finish failed");
let veryslow_size = get_file_size(&veryslow_path);
let ultrafast_path = test_output_path("h264_opts_ultrafast.mp4");
let _guard_uf = FileGuard::new(ultrafast_path.clone());
let mut enc_uf = VideoEncoder::create(&ultrafast_path)
.video(320, 240, 30.0)
.video_codec(VideoCodec::H264)
.codec_options(VideoCodecOptions::H264(H264Options {
preset: Some(H264Preset::Ultrafast),
..H264Options::default()
}))
.build()
.expect("ultrafast should succeed after veryslow succeeded");
for _ in 0..10 {
enc_uf
.push_video(&create_black_frame(320, 240))
.expect("push failed");
}
enc_uf.finish().expect("finish failed");
let ultrafast_size = get_file_size(&ultrafast_path);
println!("veryslow={veryslow_size} bytes ultrafast={ultrafast_size} bytes");
assert!(
veryslow_size <= ultrafast_size,
"veryslow ({veryslow_size} B) should be no larger than ultrafast ({ultrafast_size} B)"
);
}
#[test]
fn h264_tune_grain_should_be_accepted_without_error() {
if !is_libx264_available() {
println!("Skipping: libx264 not available");
return;
}
let output_path = test_output_path("h264_opts_tune_grain.mp4");
let _guard = FileGuard::new(output_path.clone());
let opts = VideoCodecOptions::H264(H264Options {
preset: Some(H264Preset::Ultrafast),
tune: Some(H264Tune::Grain),
..H264Options::default()
});
let result = VideoEncoder::create(&output_path)
.video(320, 240, 30.0)
.video_codec(VideoCodec::H264)
.codec_options(opts)
.build();
let mut encoder = match result {
Ok(enc) => enc,
Err(e) => {
println!("Skipping: H.264 encoder unavailable: {e}");
return;
}
};
for _ in 0..5 {
encoder
.push_video(&create_black_frame(320, 240))
.expect("push_video failed");
}
encoder.finish().expect("finish failed");
assert_valid_output_file(&output_path);
println!("H264 tune=grain accepted without error");
}
#[test]
fn h264_invalid_level_should_not_panic() {
if !is_libx264_available() {
println!("Skipping: libx264 not available");
return;
}
let output_path = test_output_path("h264_opts_invalid_level.mp4");
let _guard = FileGuard::new(output_path.clone());
let opts = VideoCodecOptions::H264(H264Options {
level: Some(999),
preset: Some(H264Preset::Ultrafast),
..H264Options::default()
});
let build_result = VideoEncoder::create(&output_path)
.video(320, 240, 30.0)
.video_codec(VideoCodec::H264)
.codec_options(opts)
.build();
match build_result {
Ok(mut encoder) => {
for _ in 0..5 {
let _ = encoder.push_video(&create_black_frame(320, 240));
}
let _ = encoder.finish();
println!("H264 level=999: encoder accepted (clamped or ignored)");
}
Err(e) => {
println!("H264 level=999: build() returned error (expected): {e}");
}
}
}