aurora-modules 0.1.0

Git, filesystem, system, network, archive, docker, unix, crypto, calculator, QR, color, timer, notes, clipboard, and text processing modules
Documentation
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()),
        ]],
    ))
}