From 189bec929a5c38b1d2b213272aebd9281eba1e11 Mon Sep 17 00:00:00 2001 From: Accusys Date: Thu, 14 May 2026 19:04:42 +0800 Subject: [PATCH] feat: all video endpoints support mode=normal|debug + audio=on|off --- src/api/media_api.rs | 99 ++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/src/api/media_api.rs b/src/api/media_api.rs index 07d4602..ddec271 100644 --- a/src/api/media_api.rs +++ b/src/api/media_api.rs @@ -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) { + 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 = 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, face_uuid: Option, duration: Option, + mode: Option, + audio: Option, } /// 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 { use axum::http::header; - let mode = params.get("mode").map(|s| s.as_str()).unwrap_or("normal"); + let (mode, audio) = parse_video_params(¶ms); 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> = HashMap::new(); let mut trace_identity: HashMap = 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 { use tokio::io::{AsyncReadExt, AsyncSeekExt}; + let (_mode, audio) = parse_video_params(¶ms); + 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);