#![allow(dead_code)]
use std::path::Path;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use mpv_engine::{Engine, ExportOptions, ExportedFrame};
pub fn video_engine() -> Option<Engine> {
match Engine::video().property("ao", "null").build() {
Ok(e) => Some(e),
Err(e) => {
eprintln!("skipping: mpv engine unavailable: {e}");
None
}
}
}
pub fn generate_red_over_black(target: &Path) -> Option<()> {
let status = std::process::Command::new("ffmpeg")
.args([
"-y",
"-f",
"lavfi",
"-i",
"color=red:size=64x32:rate=10",
"-vf",
"pad=64:64:0:0:black",
"-t",
"1",
"-c:v",
"rawvideo",
"-pix_fmt",
"yuv420p",
])
.arg(target)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
match status {
Ok(s) if s.success() => Some(()),
_ => {
eprintln!("skipping: ffmpeg unavailable or failed");
None
}
}
}
pub fn engine_with_frame() -> Option<(Engine, ExportedFrame, mpsc::Receiver<()>)> {
let engine = video_engine()?;
let dir = tempfile::tempdir().expect("tempdir");
let clip = dir.path().join("red_over_black.nut");
generate_red_over_black(&clip)?;
let (tx, rx) = mpsc::channel();
if let Err(e) = engine.attach_exported_render(ExportOptions::new(64, 64), move || {
let _ = tx.send(());
}) {
eprintln!("skipping: exported render unavailable: {e}");
return None;
}
engine
.load_when_ready(clip.to_str().expect("utf-8 temp path"))
.expect("load");
let deadline = Instant::now() + Duration::from_secs(15);
let (mut signals, mut blank) = (0u32, 0u32);
loop {
if Instant::now() >= deadline {
panic!(
"no exported frame with content published within 15s \
({signals} publish signal(s), {blank} blank frame(s))"
);
}
if rx.recv_timeout(Duration::from_millis(200)).is_ok() {
signals += 1;
}
if let Some(frame) = engine.acquire_frame().expect("acquire") {
let has_content = frame
.copy_pixels()
.chunks_exact(4)
.any(|px| px[0] | px[1] | px[2] != 0);
if has_content {
return Some((engine, frame, rx));
}
blank += 1;
}
}
}