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:
Accusys
2026-05-11 07:03:22 +08:00
parent ef894a44ad
commit 39ba5ddf76
147 changed files with 19843 additions and 3053 deletions

View File

@@ -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)]);