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)?;
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 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",
@@ -319,19 +319,36 @@ async fn trace_video(
.unwrap());
}
// === DEBUG MODE: text overlay without bounding boxes ===
// Query identity info for this trace
// === DEBUG MODE: text overlay, list all traces in frame range ===
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 identity_name: String = sqlx::query_scalar(&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",
let all_rows: Vec<(i32, i32, Option<String>)> = sqlx::query_as(&format!(
"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
))
.bind(&file_uuid).bind(trace_id)
.fetch_optional(state.db.pool()).await
.unwrap_or(None)
.unwrap_or_else(|| "unknown".to_string());
.bind(&file_uuid).bind(start_fn).bind(end_fn)
.fetch_all(state.db.pool()).await
.unwrap_or_default();
// 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_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)
@@ -341,28 +358,47 @@ async fn trace_video(
.unwrap_or(None)
.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 trace_start = rows[0].0;
let fps_str = &fps.to_string();
// Static trace info (shown throughout)
let info_block = format!(
// Build drawtext entries
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
);
let trace_block = 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
);
let cut_block = format!(
"drawtext=text='Cut: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=56", cut_id
);
// 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",
));
parts.push(format!(
"drawtext=text='Cut: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y=34", cut_id
));
parts.push(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=56",
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 _ = std::fs::write(&filter_file, &filter_text);
let filter_path = filter_file.to_str().unwrap_or("");