use std::io::{BufRead, BufReader}; use std::path::Path; use std::process::{Command, Stdio}; use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; use anyhow::{Context, Result}; use crate::rules::RuleSet; #[derive(Debug)] pub struct DecodeOutput { pub lines: Vec, pub early_stop: bool, } pub fn run_decode(path: &Path, ffmpeg_path: &str, ruleset: &RuleSet) -> Result { let mut child = Command::new(ffmpeg_path) .arg("-v") .arg("error") .arg("-i") .arg(path) .arg("-f") .arg("null") .arg("-") .stderr(Stdio::piped()) .stdout(Stdio::null()) .spawn() .with_context(|| format!("Failed to run ffmpeg decode for {}", path.display()))?; let stderr = child.stderr.take().context("Failed to capture ffmpeg stderr")?; let reader = BufReader::new(stderr); let early_stop = Arc::new(AtomicBool::new(false)); let early_stop_flag = early_stop.clone(); let mut lines = Vec::new(); for line in reader.lines() { let line = line.unwrap_or_default(); if line.is_empty() { continue; } lines.push(line.clone()); if should_stop(&line, ruleset) { early_stop_flag.store(true, Ordering::SeqCst); let _ = child.kill(); break; } } let _ = child.wait(); Ok(DecodeOutput { lines, early_stop: early_stop.load(Ordering::SeqCst), }) } fn should_stop(line: &str, ruleset: &RuleSet) -> bool { for rule in &ruleset.rules { if !rule.rule.stop_scan { continue; } if rule.patterns.iter().any(|re| re.is_match(line)) { return true; } } false }