feat: all video endpoints support mode=normal|debug + audio=on|off

This commit is contained in:
Accusys
2026-05-14 19:04:42 +08:00
parent d2bc7c0e2d
commit 189bec929a

View File

@@ -7,9 +7,18 @@ use axum::{
Router,
};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use uuid::Uuid;
use crate::core::db::{schema, PostgresDb};
/// Shared video query params: mode=normal|debug, audio=on|off
fn parse_video_params(params: &std::collections::HashMap<String, String>) -> (String, String) {
let mode = params.get("mode").map(|s| s.as_str()).unwrap_or("normal").to_string();
let audio = params.get("audio").map(|s| s.as_str()).unwrap_or("on").to_string();
(mode, audio)
}
static FFMPEG: Lazy<String> = Lazy::new(|| {
std::env::var("MOMENTRY_FFMPEG").unwrap_or_else(|_| {
let full = "/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg";
@@ -124,6 +133,8 @@ struct BboxParams {
end_time: Option<f64>,
face_uuid: Option<String>,
duration: Option<f64>,
mode: Option<String>,
audio: Option<String>,
}
/// Resolve (start_frame, end_frame) from dual input.
@@ -218,37 +229,28 @@ async fn bbox_overlay_video(
}
}
let vf = if parts.is_empty() {
let bbox_mode = p.mode.as_deref().unwrap_or("normal");
let bbox_audio = p.audio.as_deref().unwrap_or("on");
let vf = if parts.is_empty() || bbox_mode == "normal" {
"null".to_string()
} else {
parts.join(",")
};
let tmp = std::env::temp_dir().join(format!("bbox_{}.mp4", uuid::Uuid::new_v4()));
let tmp = std::env::temp_dir().join(format!("bbox_{}.mp4", Uuid::new_v4()));
let tmp_str = tmp.to_str().unwrap_or("").to_string();
let status = ffmpeg_cmd()
.args([
"-ss",
&start_sec.to_string(),
"-i",
&video_path,
"-t",
&duration.to_string(),
"-vf",
&vf,
"-c:v",
"libx264",
"-preset",
"ultrafast",
"-crf",
"28",
"-an",
"-movflags",
"+faststart",
"-y",
&tmp_str,
])
.status()
let ss = start_sec.to_string();
let dur = duration.to_string();
let mut bbox_args = vec!["-ss", &ss, "-i", &video_path, "-t", &dur];
if vf != "null" {
bbox_args.extend_from_slice(&["-vf", &vf, "-c:v", "libx264", "-preset", "ultrafast", "-crf", "28"]);
} else {
bbox_args.extend_from_slice(&["-c", "copy"]);
}
if bbox_audio == "off" { bbox_args.push("-an"); }
bbox_args.extend_from_slice(&["-movflags", "+faststart", "-y", &tmp_str]);
let status = ffmpeg_cmd().args(&bbox_args).status()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if !status.success() {
let _ = std::fs::remove_file(&tmp);
@@ -285,7 +287,7 @@ async fn trace_video(
) -> Result<impl IntoResponse, StatusCode> {
use axum::http::header;
let mode = params.get("mode").map(|s| s.as_str()).unwrap_or("normal");
let (mode, audio) = parse_video_params(&params);
let videos_table = schema::table_name("videos");
let row: Option<(String, f64, i32, i32)> = sqlx::query_as(&format!(
@@ -324,12 +326,14 @@ async fn trace_video(
// === NORMAL MODE: raw video without overlays ===
if mode == "normal" {
let tmp = std::env::temp_dir().join(format!("trace_{}.mp4", uuid::Uuid::new_v4()));
let tmp = std::env::temp_dir().join(format!("trace_{}.mp4", Uuid::new_v4()));
let tmp_str = tmp.to_str().unwrap_or("").to_string();
let result = ffmpeg_cmd()
.args(["-ss", &seek.to_string(), "-i", &video_path, "-t", &duration.to_string(),
"-c", "copy", "-y", &tmp_str])
.output()
let sk = seek.to_string();
let du = duration.to_string();
let mut cmd_args = vec!["-ss", &sk, "-i", &video_path, "-t", &du, "-c", "copy"];
if audio == "off" { cmd_args.push("-an"); }
cmd_args.extend_from_slice(&["-y", &tmp_str]);
let result = ffmpeg_cmd().args(&cmd_args).output()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if !result.status.success() {
return Err(StatusCode::INTERNAL_SERVER_ERROR);
@@ -362,7 +366,6 @@ async fn trace_video(
.unwrap_or_default();
// Group frames by trace_id, compute start_frame per trace; collect bbox per frame
use std::collections::HashMap;
let mut trace_frames: HashMap<i32, Vec<i32>> = HashMap::new();
let mut trace_identity: HashMap<i32, String> = HashMap::new();
let mut bbox_per_frame: HashMap<(i32, i32), (i32, i32, i32, i32)> = HashMap::new(); // (tid, fn) -> (x, y, w, h)
@@ -453,18 +456,20 @@ async fn trace_video(
}
let filter_text = parts.join(",");
let filter_file = std::env::temp_dir().join(format!("vf_{}.txt", uuid::Uuid::new_v4()));
let filter_file = std::env::temp_dir().join(format!("vf_{}.txt", Uuid::new_v4()));
let _ = std::fs::write(&filter_file, &filter_text);
let filter_path = filter_file.to_str().unwrap_or("");
let tmp = std::env::temp_dir().join(format!("trace_{}.mp4", uuid::Uuid::new_v4()));
let tmp = std::env::temp_dir().join(format!("trace_{}.mp4", Uuid::new_v4()));
let tmp_str = tmp.to_str().unwrap_or("").to_string();
let result = ffmpeg_cmd()
.args(["-ss", &seek.to_string(), "-i", &video_path, "-t", &duration.to_string(),
"-/filter_complex", &filter_path,
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "28",
"-c:a", "aac", "-movflags", "+faststart", "-y", &tmp_str])
.output()
let sk = seek.to_string();
let du = duration.to_string();
let mut debug_args = vec!["-ss", &sk, "-i", &video_path, "-t", &du,
"-/filter_complex", &filter_path,
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "28"];
if audio == "on" { debug_args.extend_from_slice(&["-c:a", "aac"]); }
debug_args.extend_from_slice(&["-movflags", "+faststart", "-y", &tmp_str]);
let result = ffmpeg_cmd().args(&debug_args).output()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
@@ -493,6 +498,8 @@ async fn stream_video(
) -> Result<impl IntoResponse, StatusCode> {
use tokio::io::{AsyncReadExt, AsyncSeekExt};
let (_mode, audio) = parse_video_params(&params);
let videos_table = schema::table_name("videos");
let row: Option<(String,)> = sqlx::query_as(&format!(
"SELECT file_path FROM {} WHERE file_uuid = $1",
@@ -537,12 +544,14 @@ async fn stream_video(
return Err(StatusCode::BAD_REQUEST);
}
let tmp = std::env::temp_dir().join(format!("chunk_{}.mp4", uuid::Uuid::new_v4()));
let tmp = std::env::temp_dir().join(format!("chunk_{}.mp4", Uuid::new_v4()));
let tmp_str = tmp.to_str().unwrap_or("").to_string();
let status = ffmpeg_cmd()
.args(["-ss", &start_sec.to_string(), "-i", &file_path, "-t", &dur.to_string(),
"-c", "copy", "-movflags", "+faststart", "-y", &tmp_str])
.status()
let ss = start_sec.to_string();
let d = dur.to_string();
let mut chunk_args = vec!["-ss", &ss, "-i", &file_path, "-t", &d, "-c", "copy"];
if audio == "off" { chunk_args.push("-an"); }
chunk_args.extend_from_slice(&["-movflags", "+faststart", "-y", &tmp_str]);
let status = ffmpeg_cmd().args(&chunk_args).status()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if !status.success() {
let _ = std::fs::remove_file(&tmp);