use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FfmpegStatus {
Unknown,
Available,
Missing,
}
const UNKNOWN: u8 = 0;
const AVAILABLE: u8 = 1;
const MISSING: u8 = 2;
static LAST_RESULT: AtomicU8 = AtomicU8::new(UNKNOWN);
static CHECK_RUNNING: AtomicBool = AtomicBool::new(false);
const PROBE_TIMEOUT: Duration = Duration::from_secs(5);
pub fn probe() -> bool {
let child = Command::new("ffmpeg")
.arg("-version")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
let Ok(mut child) = child else {
return false;
};
let deadline = Instant::now() + PROBE_TIMEOUT;
loop {
match child.try_wait() {
Ok(Some(status)) => return status.success(),
Ok(None) if Instant::now() < deadline => thread::sleep(Duration::from_millis(20)),
_ => {
let _ = child.kill();
let _ = child.wait();
return false;
}
}
}
}
pub fn refresh_in_background() {
if CHECK_RUNNING.swap(true, Ordering::SeqCst) {
return;
}
let spawned = thread::Builder::new()
.name("ffmpeg-probe".into())
.spawn(|| {
let result = if probe() { AVAILABLE } else { MISSING };
LAST_RESULT.store(result, Ordering::SeqCst);
CHECK_RUNNING.store(false, Ordering::SeqCst);
});
if spawned.is_err() {
CHECK_RUNNING.store(false, Ordering::SeqCst);
}
}
pub fn status() -> FfmpegStatus {
match LAST_RESULT.load(Ordering::SeqCst) {
AVAILABLE => FfmpegStatus::Available,
MISSING => FfmpegStatus::Missing,
_ => FfmpegStatus::Unknown,
}
}