use ez_ffmpeg::{FfmpegContext, Input, Output};
fn tmp_path(name: &str) -> String {
let dir = std::env::temp_dir().join(format!("ez_ffmpeg_build_side_{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir.join(name).to_string_lossy().into_owned()
}
#[test]
fn build_does_not_truncate_an_existing_output_file() {
let out = tmp_path("existing.mp4");
let sentinel = b"PRE-EXISTING CONTENT that build() must leave intact".to_vec();
std::fs::write(&out, &sentinel).unwrap();
let ctx = FfmpegContext::builder()
.input(Input::from("color=c=red:s=64x64:r=15:d=0.2").set_format("lavfi"))
.output(Output::from(out.as_str()).set_video_codec("mpeg4"))
.build()
.expect("valid config must build");
let after = std::fs::read(&out).expect("output file must still exist after build()");
assert_eq!(
after, sentinel,
"build() truncated/overwrote the output file; build() must be side-effect-free"
);
drop(ctx);
let after_drop = std::fs::read(&out).expect("output file must still exist after drop");
assert_eq!(
after_drop, sentinel,
"dropping an unstarted context truncated the output file"
);
}
fn run_to_result(ctx: FfmpegContext, secs: u64) -> ez_ffmpeg::error::Result<()> {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let _ = tx.send(ctx.start().and_then(|scheduler| scheduler.wait()));
});
match rx.recv_timeout(std::time::Duration::from_secs(secs)) {
Ok(result) => result,
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
panic!("job did not finish within {secs}s (hang)")
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
panic!("wait() thread panicked before reporting")
}
}
}
#[test]
fn opening_an_output_in_a_missing_directory_fails_at_run_time() {
let base = std::env::temp_dir().join(format!("ez_ffmpeg_build_side_{}", std::process::id()));
std::fs::create_dir_all(&base).unwrap();
let parent = base.join("no_such_subdir");
let _ = std::fs::remove_dir_all(&parent);
assert!(
!parent.exists(),
"the output's parent dir must be absent for this test"
);
let missing = parent.join("out.mp4");
let missing_str = missing.to_string_lossy().into_owned();
let ctx = FfmpegContext::builder()
.input(Input::from("color=c=red:s=64x64:r=15:d=0.2").set_format("lavfi"))
.output(Output::from(missing_str.as_str()).set_video_codec("mpeg4"))
.build()
.expect("build must succeed even when the output path cannot be opened");
let result = run_to_result(ctx, 30);
assert!(
matches!(result, Err(ez_ffmpeg::error::Error::OpenOutput(_))),
"a job whose output cannot be opened must fail with OpenOutput at run time: {result:?}"
);
}
#[test]
fn streamless_ffmetadata_output_is_created_at_run_time() {
let out = tmp_path("meta_created.ffmeta");
let _ = std::fs::remove_file(&out);
let ctx = FfmpegContext::builder()
.output(Output::from(out.as_str()).set_format("ffmetadata"))
.build()
.expect("streamless ffmetadata build");
run_to_result(ctx, 30).expect("streamless job runs to completion");
assert!(
std::path::Path::new(&out).exists(),
"a file-backed streamless output must be created at run time"
);
}
#[test]
fn streamless_output_open_failure_surfaces_at_run_time() {
let base = std::env::temp_dir().join(format!("ez_ffmpeg_build_side_{}", std::process::id()));
std::fs::create_dir_all(&base).unwrap();
let parent = base.join("no_such_subdir_meta");
let _ = std::fs::remove_dir_all(&parent);
assert!(!parent.exists());
let missing = parent.join("out.ffmeta");
let ctx = FfmpegContext::builder()
.output(Output::from(missing.to_string_lossy().as_ref()).set_format("ffmetadata"))
.build()
.expect("build succeeds; the streamless open is deferred to run time");
let result = run_to_result(ctx, 30);
assert!(
matches!(result, Err(ez_ffmpeg::error::Error::OpenOutput(_))),
"a streamless output whose parent is missing must fail with OpenOutput: {result:?}"
);
}