feat: Initial v0.9 release with API Key authentication

## v0.9.20260325_144654

### Features
- API Key Authentication System
- Job Worker System
- V2 Backup Versioning

### Bug Fixes
- get_processor_results_by_job column mapping

Co-authored-by: OpenCode
This commit is contained in:
accusys
2026-03-25 14:52:51 +08:00
parent 47e86b696f
commit 383201cacd
193 changed files with 40268 additions and 422 deletions

84
src/core/probe/ffprobe.rs Normal file
View File

@@ -0,0 +1,84 @@
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 })
}