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:
@@ -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("");
|
||||
|
||||
Reference in New Issue
Block a user