feat: Phase 1 handover - schema migration, correction mechanism, API fixes
Schema changes: dev.chunks->dev.chunk, remove old_chunk_id/chunk_index Correction: asr-1.json format, generate/apply scripts API: 37/37 endpoints fixed and tested Docs: HANDOVER_V2.0.md for M4
This commit is contained in:
@@ -13,14 +13,20 @@ use crate::core::db::{schema, PostgresDb};
|
||||
static FFMPEG: Lazy<String> = Lazy::new(|| {
|
||||
std::env::var("MOMENTRY_FFMPEG").unwrap_or_else(|_| {
|
||||
let full = "/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg";
|
||||
if std::path::Path::new(full).exists() { full.to_string() } else { "ffmpeg".to_string() }
|
||||
if std::path::Path::new(full).exists() {
|
||||
full.to_string()
|
||||
} else {
|
||||
"ffmpeg".to_string()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
fn ffmpeg_cmd() -> std::process::Command {
|
||||
let mut cmd = std::process::Command::new(&*FFMPEG);
|
||||
let full_lib = "/opt/homebrew/opt/ffmpeg-full/lib";
|
||||
if std::path::Path::new(full_lib).exists() { cmd.env("DYLD_LIBRARY_PATH", full_lib); }
|
||||
if std::path::Path::new(full_lib).exists() {
|
||||
cmd.env("DYLD_LIBRARY_PATH", full_lib);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
@@ -293,20 +299,32 @@ async fn trace_video(
|
||||
let first_frame = rows[0].0;
|
||||
let last_frame = rows[rows.len() - 1].0;
|
||||
let start_sec = first_frame as f64 / fps;
|
||||
let padding = params.get("padding").and_then(|s| s.parse().ok()).unwrap_or(2.0);
|
||||
let padding = params
|
||||
.get("padding")
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(2.0);
|
||||
let duration = (last_frame - first_frame) as f64 / fps + padding * 2.0;
|
||||
let seek = (start_sec - padding).max(0.0);
|
||||
|
||||
// Build filters: bbox+drawtext (1 filter + 1 drawtext per detection)
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
for (i, (frame, x, y, w, h)) in rows.iter().enumerate() {
|
||||
let next_frame = if i + 1 < rows.len() { rows[i + 1].0 } else { last_frame + (padding * fps) as i32 };
|
||||
let next_frame = if i + 1 < rows.len() {
|
||||
rows[i + 1].0
|
||||
} else {
|
||||
last_frame + (padding * fps) as i32
|
||||
};
|
||||
let start_offset = frame - first_frame + (padding * fps) as i32;
|
||||
let end_offset = next_frame - first_frame + (padding * fps) as i32;
|
||||
// Bbox
|
||||
parts.push(format!(
|
||||
"drawbox=x={}:y={}:w={}:h={}:color=red@0.8:thickness=8:enable='between(n,{},{})'",
|
||||
x, y, w, h, start_offset, end_offset - 1
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
start_offset,
|
||||
end_offset - 1
|
||||
));
|
||||
// Text label (drawtext, 1 filter vs ~175 bitmap drawboxes)
|
||||
parts.push(format!(
|
||||
@@ -325,14 +343,31 @@ async fn trace_video(
|
||||
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",
|
||||
"-an", "-movflags", "+faststart", "-y", &tmp_str,
|
||||
"-ss",
|
||||
&seek.to_string(),
|
||||
"-i",
|
||||
&video_path,
|
||||
"-t",
|
||||
&duration.to_string(),
|
||||
"-/filter_complex",
|
||||
&filter_path,
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-preset",
|
||||
"ultrafast",
|
||||
"-crf",
|
||||
"28",
|
||||
"-an",
|
||||
"-movflags",
|
||||
"+faststart",
|
||||
"-y",
|
||||
&tmp_str,
|
||||
])
|
||||
.output()
|
||||
.map_err(|e| { tracing::error!("ffmpeg spawn: {}", e); StatusCode::INTERNAL_SERVER_ERROR })?;
|
||||
.map_err(|e| {
|
||||
tracing::error!("ffmpeg spawn: {}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
if !result.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&result.stderr);
|
||||
tracing::error!("ffmpeg failed: {}", &stderr[..stderr.len().min(300)]);
|
||||
|
||||
Reference in New Issue
Block a user