From 1f7daf9e8b5fe4d207605f8dff544759509ee677 Mon Sep 17 00:00:00 2001 From: Accusys Date: Thu, 14 May 2026 15:55:32 +0800 Subject: [PATCH] fix: escape colons in drawtext text values for ffmpeg 8.1.1 filter parser compatibility --- src/api/media_api.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/api/media_api.rs b/src/api/media_api.rs index b707958..f8d7f21 100644 --- a/src/api/media_api.rs +++ b/src/api/media_api.rs @@ -321,7 +321,7 @@ async fn trace_video( // === 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; + let end_fn = ((start_sec + duration) * fps) as i32; // Query all traces with identity names and bbox positions in the visible frame range let identities_table = schema::table_name("identities"); @@ -374,13 +374,13 @@ async fn trace_video( let bh = _height as i32; // Bottom-left info panel (y=h-N positions from bottom up) - // Frame/time at the very bottom + // Frame/time (colons must be escaped as \: for ffmpeg filter parser) 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={}", - frame_offset, frame_offset, fps_str, bh - 30 + "drawtext=text='Frame %{{n}} %{{pts}}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y={}", + bh - 30 )); parts.push(format!( - "drawtext=text='Cut: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y={}", cut_id, bh - 52 + "drawtext=text='Cut\\: {}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y={}", cut_id, bh - 52 )); parts.push(format!( "drawtext=text='{}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y={}", file_uuid, bh - 74 @@ -392,7 +392,7 @@ async fn trace_video( 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); + 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); @@ -446,7 +446,8 @@ async fn trace_video( .map_err(|_| 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)]); + let _ = std::fs::write("/tmp/ffmpeg_last_error.txt", stderr.as_bytes()); + tracing::error!("ffmpeg failed ({} bytes), see /tmp/ffmpeg_last_error.txt", stderr.len()); let _ = std::fs::remove_file(&filter_file); let _ = std::fs::remove_file(&tmp); return Err(StatusCode::INTERNAL_SERVER_ERROR);