From 8f013cbdbcd71022ae94541b0b13fe18f0dc8482 Mon Sep 17 00:00:00 2001 From: Accusys Date: Thu, 14 May 2026 15:09:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20trace=20debug=20mode=20=E2=80=94=20show?= =?UTF-8?q?=20all=20traces=20in=20frame=20range=20with=20interpolation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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} --- src/api/media_api.rs | 88 +++++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/src/api/media_api.rs b/src/api/media_api.rs index 6d930de..8f34692 100644 --- a/src/api/media_api.rs +++ b/src/api/media_api.rs @@ -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)> = 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> = HashMap::new(); + let mut trace_identity: HashMap = 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)> = 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 = 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("");