- Rust-based digital asset management system - Video analysis: ASR, OCR, YOLO, Face, Pose - RAG capabilities with Qdrant vector database - Multi-database support: PostgreSQL, Redis, MongoDB - Monitoring system with launchd plists - n8n workflow automation integration
85 lines
2.8 KiB
Rust
85 lines
2.8 KiB
Rust
use anyhow::{Context, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::process::Command;
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct ProbeResult {
|
|
pub streams: Vec<StreamInfo>,
|
|
pub format: FormatInfo,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct StreamInfo {
|
|
pub index: u32,
|
|
pub codec_name: Option<String>,
|
|
pub codec_type: Option<String>,
|
|
pub width: Option<u32>,
|
|
pub height: Option<u32>,
|
|
pub r_frame_rate: Option<String>,
|
|
pub duration: Option<String>,
|
|
pub sample_rate: Option<String>,
|
|
pub channels: Option<u32>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct FormatInfo {
|
|
pub filename: Option<String>,
|
|
pub format_name: Option<String>,
|
|
pub duration: Option<String>,
|
|
pub size: Option<String>,
|
|
pub bit_rate: Option<String>,
|
|
}
|
|
|
|
pub fn probe_video(video_path: &str) -> Result<ProbeResult> {
|
|
let output = Command::new("ffprobe")
|
|
.args([
|
|
"-v",
|
|
"quiet",
|
|
"-print_format",
|
|
"json",
|
|
"-show_format",
|
|
"-show_streams",
|
|
video_path,
|
|
])
|
|
.output()
|
|
.context("Failed to run ffprobe")?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
anyhow::bail!("ffprobe failed: {}", stderr);
|
|
}
|
|
|
|
let json_str = String::from_utf8_lossy(&output.stdout);
|
|
let json: serde_json::Value =
|
|
serde_json::from_str(&json_str).context("Failed to parse ffprobe output")?;
|
|
|
|
let streams: Vec<StreamInfo> = json["streams"]
|
|
.as_array()
|
|
.map(|arr| {
|
|
arr.iter()
|
|
.map(|s| StreamInfo {
|
|
index: s["index"].as_u64().unwrap_or(0) as u32,
|
|
codec_name: s["codec_name"].as_str().map(String::from),
|
|
codec_type: s["codec_type"].as_str().map(String::from),
|
|
width: s["width"].as_u64().map(|v| v as u32),
|
|
height: s["height"].as_u64().map(|v| v as u32),
|
|
r_frame_rate: s["r_frame_rate"].as_str().map(String::from),
|
|
duration: s["duration"].as_str().map(String::from),
|
|
sample_rate: s["sample_rate"].as_str().map(String::from),
|
|
channels: s["channels"].as_u64().map(|v| v as u32),
|
|
})
|
|
.collect()
|
|
})
|
|
.unwrap_or_default();
|
|
|
|
let format = FormatInfo {
|
|
filename: json["format"]["filename"].as_str().map(String::from),
|
|
format_name: json["format"]["format_name"].as_str().map(String::from),
|
|
duration: json["format"]["duration"].as_str().map(String::from),
|
|
size: json["format"]["size"].as_str().map(String::from),
|
|
bit_rate: json["format"]["bit_rate"].as_str().map(String::from),
|
|
};
|
|
|
|
Ok(ProbeResult { streams, format })
|
|
}
|