feat: all video endpoints support mode=normal|debug + audio=on|off
This commit is contained in:
@@ -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(¶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<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(¶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);
|
||||
|
||||
Reference in New Issue
Block a user