use aurora_core::{AuroraResult, Pipeline, Value};
use std::process::Command;
fn find_tool(name: &str) -> bool {
Command::new("which")
.arg(name)
.output()
.ok()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn check_tool(name: &str) -> AuroraResult<()> {
if !find_tool(name) {
return Err(aurora_core::AuroraError::CommandNotFound(
format!("{} is not installed", name)
));
}
Ok(())
}
pub fn media_info(input: &str) -> AuroraResult<Pipeline> {
check_tool("ffprobe")?;
let output = Command::new("ffprobe")
.args(["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams"])
.arg(input)
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("failed to run ffprobe: {}", e)
))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(aurora_core::AuroraError::ModuleError(
format!("ffprobe failed: {}", stderr)
));
}
let raw = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&raw)
.map_err(|e| aurora_core::AuroraError::ParseError(
format!("failed to parse ffprobe output: {}", e)
))?;
let mut headers = vec![
"index".into(),
"codec".into(),
"type".into(),
"width".into(),
"height".into(),
"bitrate".into(),
];
let mut rows: Vec<Vec<Value>> = Vec::new();
if let Some(streams) = parsed.get("streams").and_then(|v| v.as_array()) {
for s in streams {
let idx = s.get("index").and_then(|v| v.as_i64()).unwrap_or(0);
let codec = s.get("codec_name").and_then(|v| v.as_str()).unwrap_or("");
let kind = s.get("codec_type").and_then(|v| v.as_str()).unwrap_or("");
let w = s.get("width").and_then(|v| v.as_i64()).unwrap_or(0);
let h = s.get("height").and_then(|v| v.as_i64()).unwrap_or(0);
let br = s.get("bit_rate").and_then(|v| v.as_str()).unwrap_or("");
rows.push(vec![
Value::Int(idx),
Value::String(codec.into()),
Value::String(kind.into()),
Value::Int(w),
Value::Int(h),
Value::String(br.into()),
]);
}
}
if let Some(format) = parsed.get("format") {
headers.push("format_name".into());
headers.push("duration".into());
headers.push("size".into());
if let Some(row) = rows.first_mut() {
let fmt_name = format.get("format_name").and_then(|v| v.as_str()).unwrap_or("");
let dur = format.get("duration").and_then(|v| v.as_str()).unwrap_or("");
let size = format.get("size").and_then(|v| v.as_str()).unwrap_or("");
row.push(Value::String(fmt_name.into()));
row.push(Value::String(dur.into()));
row.push(Value::String(size.into()));
} else {
let fmt_name = format.get("format_name").and_then(|v| v.as_str()).unwrap_or("");
let dur = format.get("duration").and_then(|v| v.as_str()).unwrap_or("");
let size = format.get("size").and_then(|v| v.as_str()).unwrap_or("");
rows.push(vec![
Value::Null, Value::Null, Value::Null, Value::Null, Value::Null, Value::Null,
Value::String(fmt_name.into()),
Value::String(dur.into()),
Value::String(size.into()),
]);
}
}
Ok(Pipeline::table(headers, rows))
}
pub fn media_convert(input: &str, output: &str) -> AuroraResult<Pipeline> {
check_tool("ffmpeg")?;
let result = Command::new("ffmpeg")
.args(["-i", input])
.arg(output)
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("failed to run ffmpeg: {}", e)
))?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
return Err(aurora_core::AuroraError::ModuleError(
format!("ffmpeg conversion failed: {}", stderr)
));
}
Ok(Pipeline::table(
vec!["action".into(), "input".into(), "output".into(), "status".into()],
vec![vec![
Value::String("convert".into()),
Value::String(input.into()),
Value::String(output.into()),
Value::String("ok".into()),
]],
))
}
pub fn media_stream(input: &str) -> AuroraResult<Pipeline> {
media_info(input)
}
pub fn media_extract(input: &str, output: &str) -> AuroraResult<Pipeline> {
check_tool("ffmpeg")?;
let result = Command::new("ffmpeg")
.args(["-i", input])
.arg(output)
.output()
.map_err(|e| aurora_core::AuroraError::ModuleError(
format!("failed to run ffmpeg: {}", e)
))?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
return Err(aurora_core::AuroraError::ModuleError(
format!("ffmpeg extraction failed: {}", stderr)
));
}
Ok(Pipeline::table(
vec!["action".into(), "input".into(), "output".into(), "status".into()],
vec![vec![
Value::String("extract".into()),
Value::String(input.into()),
Value::String(output.into()),
Value::String("ok".into()),
]],
))
}