fix: trace debug mode — show all traces in frame range with interpolation

Debug overlay now lists every trace visible in the current frame range,
including interpolated frames (continuous from first to last detection).
Format per trace line:
  Trace {id}: start_frame={n}  Identity={name}
This commit is contained in:
Accusys
2026-05-14 15:09:34 +08:00
parent c51d6f6f2d
commit 8f013cbdbc

View File

@@ -274,7 +274,7 @@ async fn trace_video(
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let (video_path, fps, _width, _height) = row.ok_or(StatusCode::NOT_FOUND)?; let (video_path, fps, _width, _height) = row.ok_or(StatusCode::NOT_FOUND)?;
// Get all detections for this trace_id // Query face detections to find frame range for target trace
let face_table = schema::table_name("face_detections"); let face_table = schema::table_name("face_detections");
let rows: Vec<(i32, i32, i32, i32, i32)> = sqlx::query_as(&format!( let rows: Vec<(i32, i32, i32, i32, i32)> = sqlx::query_as(&format!(
"SELECT frame_number, x, y, width, height FROM {} WHERE file_uuid = $1 AND trace_id = $2 ORDER BY frame_number", "SELECT frame_number, x, y, width, height FROM {} WHERE file_uuid = $1 AND trace_id = $2 ORDER BY frame_number",
@@ -319,19 +319,36 @@ async fn trace_video(
.unwrap()); .unwrap());
} }
// === DEBUG MODE: text overlay without bounding boxes === // === DEBUG MODE: text overlay, list all traces in frame range ===
// Query identity info for this trace let start_fn = (start_sec * fps) as i32;
let end_fn = (start_sec + duration) as i32;
// Query all traces with identity names in the visible frame range
let identities_table = schema::table_name("identities"); let identities_table = schema::table_name("identities");
let identity_name: String = sqlx::query_scalar(&format!( let all_rows: Vec<(i32, i32, Option<String>)> = sqlx::query_as(&format!(
"SELECT COALESCE(i.name, 'unknown') FROM {} fd LEFT JOIN {} i ON i.id = fd.identity_id WHERE fd.file_uuid = $1 AND fd.trace_id = $2 LIMIT 1", "SELECT fd.trace_id, fd.frame_number, i.name \
FROM {} fd \
LEFT JOIN {} i ON fd.identity_id = i.id \
WHERE fd.file_uuid = $1 AND fd.frame_number BETWEEN $2 AND $3 AND fd.trace_id IS NOT NULL \
ORDER BY fd.trace_id, fd.frame_number",
face_table, identities_table face_table, identities_table
)) ))
.bind(&file_uuid).bind(trace_id) .bind(&file_uuid).bind(start_fn).bind(end_fn)
.fetch_optional(state.db.pool()).await .fetch_all(state.db.pool()).await
.unwrap_or(None) .unwrap_or_default();
.unwrap_or_else(|| "unknown".to_string());
// Query cut_id for the first frame // Group frames by trace_id, compute start_frame per trace
use std::collections::HashMap;
let mut trace_frames: HashMap<i32, Vec<i32>> = HashMap::new();
let mut trace_identity: HashMap<i32, String> = HashMap::new();
for (tid, fn_, name_opt) in &all_rows {
trace_frames.entry(*tid).or_default().push(*fn_);
if let Some(name) = name_opt {
trace_identity.entry(*tid).or_insert_with(|| name.clone());
}
}
// Query cut_id for this segment
let cut_table = schema::table_name("cut"); let cut_table = schema::table_name("cut");
let cut_id: i32 = sqlx::query_scalar( let cut_id: i32 = sqlx::query_scalar(
&format!("SELECT scene_number FROM {} WHERE file_uuid = $1 AND start_frame <= $2 AND end_frame >= $2 LIMIT 1", cut_table) &format!("SELECT scene_number FROM {} WHERE file_uuid = $1 AND start_frame <= $2 AND end_frame >= $2 LIMIT 1", cut_table)
@@ -341,28 +358,47 @@ async fn trace_video(
.unwrap_or(None) .unwrap_or(None)
.unwrap_or(0); .unwrap_or(0);
// Sort traces for consistent ordering
let mut sorted_traces: Vec<(i32, &Vec<i32>)> = trace_frames.iter().map(|(k, v)| (*k, v)).collect();
sorted_traces.sort_by_key(|(tid, _)| *tid);
let frame_offset = first_frame as i64 - (padding * fps) as i64; let frame_offset = first_frame as i64 - (padding * fps) as i64;
let trace_start = rows[0].0;
let fps_str = &fps.to_string(); let fps_str = &fps.to_string();
// Static trace info (shown throughout) // Build drawtext entries
let info_block = format!( let mut parts: Vec<String> = Vec::new();
// Static header
parts.push(format!(
"drawtext=text='File UUID: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=12", file_uuid "drawtext=text='File UUID: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=12", file_uuid
); ));
let trace_block = format!( parts.push(format!(
"drawtext=text='Trace {}: start_frame={} Identity: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=34", trace_id, trace_start, identity_name "drawtext=text='Cut: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=34", cut_id
); ));
let cut_block = format!( parts.push(format!(
"drawtext=text='Cut: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=56", cut_id "drawtext=text='Frame: %{{eif:n+{}:d}} Time: %{{eif:(n+{})*100/{}:d}}s':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=56",
);
// Per-frame info (frame number, time)
let frame_info = format!(
"drawtext=text='Frame: %{{eif:n+{}:d}} Time: %{{eif:(n+{})*100/{}:d}}s':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=78",
frame_offset, frame_offset, fps_str frame_offset, frame_offset, fps_str
); ));
let filter_text = format!("{}, {}, {}, {}", info_block, trace_block, cut_block, frame_info); // Per-trace entries: show trace_id, start_frame, identity name
// Position starts at y=78, increments by 22 per trace
let mut y_pos = 78;
for (tid, frames) in &sorted_traces {
let start = frames.iter().min().unwrap_or(&first_frame);
let identity = trace_identity.get(tid).map(|s| s.as_str()).unwrap_or("unknown");
let label = format!("Trace {}: start={} {}", tid, start, identity);
// Continuous range (interpolated): visible from first to last frame
let enable = format!("between(n,{},{})", frames[0] as i64 - frame_offset, frames[frames.len() - 1] as i64 - frame_offset);
parts.push(format!(
"drawtext=text='{}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y={}:enable='{}'",
label, y_pos, enable
));
y_pos += 22;
}
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::Uuid::new_v4()));
let _ = std::fs::write(&filter_file, &filter_text); let _ = std::fs::write(&filter_file, &filter_text);
let filter_path = filter_file.to_str().unwrap_or(""); let filter_path = filter_file.to_str().unwrap_or("");