use ez_ffmpeg::error::{Error, OpenInputError, OpenOutputError};
use ez_ffmpeg::{FfmpegContext, Input, Output};
fn fixture() -> String {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("test.mp4");
assert!(path.exists(), "missing repo fixture test.mp4");
path.to_string_lossy().into_owned()
}
fn build_with(input: Input, output: Output) -> ez_ffmpeg::error::Result<FfmpegContext> {
FfmpegContext::builder().input(input).output(output).build()
}
#[test]
fn invalid_input_framerate_fails_build_not_the_setter() {
let input = Input::from(fixture()).set_framerate(0, 1);
let err = build_with(input, Output::from("/tmp/ez_opt_never_written.mp4"))
.err()
.expect("zero framerate must fail build");
assert!(
matches!(&err, Error::OpenInputStream(OpenInputError::InvalidOption(msg))
if msg.contains("set_framerate")),
"unexpected error: {err}"
);
}
#[test]
fn invalid_input_ts_scale_fails_build() {
let input = Input::from(fixture()).set_ts_scale(f64::NAN);
let err = build_with(input, Output::from("/tmp/ez_opt_never_written.mp4"))
.err()
.expect("NaN ts_scale must fail build");
assert!(
matches!(&err, Error::OpenInputStream(OpenInputError::InvalidOption(msg))
if msg.contains("set_ts_scale")),
"unexpected error: {err}"
);
}
#[test]
fn invalid_output_framerate_fails_build() {
let output = Output::from("/tmp/ez_opt_never_written.mp4").set_framerate(30, 0);
let err = build_with(Input::from(fixture()), output)
.err()
.expect("zero denominator must fail build");
assert!(
matches!(&err, Error::OpenOutput(OpenOutputError::InvalidOption(msg))
if msg.contains("set_framerate")),
"unexpected error: {err}"
);
}
#[test]
fn malformed_force_key_frames_spec_fails_build() {
let output = Output::from("/tmp/ez_opt_never_written.mp4")
.set_video_codec("mpeg4")
.set_force_key_frames("0,abc,10");
let err = build_with(Input::from(fixture()), output)
.err()
.expect("malformed spec must fail build");
assert!(
matches!(&err, Error::OpenOutput(OpenOutputError::InvalidOption(_))),
"unexpected error: {err}"
);
}
#[test]
fn unknown_audio_sample_format_name_fails_build() {
let output = Output::from("/tmp/ez_opt_never_written.wav")
.set_audio_codec("pcm_s16le")
.set_audio_sample_fmt("definitely_not_a_format");
let err = build_with(Input::from(fixture()), output)
.err()
.expect("unknown sample format must fail build");
assert!(
matches!(&err, Error::OpenOutput(OpenOutputError::UnknownSampleFormat(name))
if name == "definitely_not_a_format"),
"unexpected error: {err}"
);
}
#[test]
fn sample_format_name_with_interior_nul_fails_as_unknown() {
let output = Output::from("/tmp/ez_opt_never_written.wav")
.set_audio_codec("pcm_s16le")
.set_audio_sample_fmt("s16\0junk");
let err = build_with(Input::from(fixture()), output)
.err()
.expect("NUL-bearing name must fail build");
assert!(
matches!(&err, Error::OpenOutput(OpenOutputError::UnknownSampleFormat(name))
if name == "s16\0junk"),
"unexpected error: {err}"
);
}
#[test]
fn known_audio_sample_format_name_builds() {
let output = Output::from("/tmp/ez_opt_known_fmt.wav")
.set_audio_codec("pcm_s16le")
.set_audio_sample_fmt("s16");
build_with(Input::from(fixture()), output).expect("s16 must resolve");
}
#[test]
fn zero_io_buffer_size_fails_build() {
let input = Input::new_by_read_callback(|_buf: &mut [u8]| 0).set_io_buffer_size(0);
let err = build_with(input, Output::from("/tmp/ez_opt_never_written.mp4"))
.err()
.expect("zero io_buffer_size must fail build");
assert!(
matches!(&err, Error::OpenInputStream(OpenInputError::InvalidOption(msg))
if msg.contains("io_buffer_size")),
"unexpected error: {err}"
);
}
#[test]
fn zero_muxing_queue_caps_fail_build() {
let output = Output::from("/tmp/ez_opt_never_written.mp4").set_max_muxing_queue_size(0);
let err = build_with(Input::from(fixture()), output)
.err()
.expect("zero queue cap must fail build");
assert!(
matches!(&err, Error::OpenOutput(OpenOutputError::InvalidOption(msg))
if msg.contains("max_muxing_queue_size")),
"unexpected error: {err}"
);
let output = Output::from("/tmp/ez_opt_never_written.mp4").set_muxing_queue_data_threshold(0);
let err = build_with(Input::from(fixture()), output)
.err()
.expect("zero data threshold must fail build");
assert!(
matches!(&err, Error::OpenOutput(OpenOutputError::InvalidOption(msg))
if msg.contains("muxing_queue_data_threshold")),
"unexpected error: {err}"
);
}
#[test]
fn unknown_explicit_encoder_is_named() {
let output =
Output::from("/tmp/ez_opt_never_written.mp4").set_video_codec("nonexistent_codec_zz");
let err = build_with(Input::from(fixture()), output)
.err()
.expect("an unknown explicit encoder must fail build");
match &err {
Error::OpenOutput(OpenOutputError::EncoderUnavailable { name }) => {
assert_eq!(name, "nonexistent_codec_zz");
}
other => panic!("expected EncoderUnavailable, got {other}"),
}
let msg = err.to_string();
assert!(
msg.contains("nonexistent_codec_zz") && msg.contains("linked FFmpeg build"),
"error message is not actionable: {msg}"
);
}
#[test]
fn auto_selected_missing_encoder_is_named() {
let has_webp_encoder = ez_ffmpeg::codec::get_encoders()
.iter()
.any(|c| c.desc_name == "webp" || c.codec_name.contains("webp"));
if has_webp_encoder {
eprintln!("skipping: linked FFmpeg provides a webp encoder");
return;
}
let output = Output::from("/tmp/ez_opt_never_written.webp");
let err = build_with(Input::from(fixture()), output)
.err()
.expect("a format whose default codec has no encoder must fail build");
match &err {
Error::OpenOutput(OpenOutputError::EncoderUnavailable { name }) => {
assert!(
name.contains("webp"),
"expected the guessed codec name to mention webp, got '{name}'"
);
}
other => panic!("expected EncoderUnavailable naming the guessed codec, got {other}"),
}
}