fix: escape colons in drawtext text values for ffmpeg 8.1.1 filter parser compatibility

This commit is contained in:
Accusys
2026-05-14 15:55:32 +08:00
parent 6728c2bb90
commit 1f7daf9e8b

View File

@@ -321,7 +321,7 @@ async fn trace_video(
// === DEBUG MODE: text overlay, list all traces in frame range === // === DEBUG MODE: text overlay, list all traces in frame range ===
let start_fn = (start_sec * fps) as i32; 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 // Query all traces with identity names and bbox positions in the visible frame range
let identities_table = schema::table_name("identities"); let identities_table = schema::table_name("identities");
@@ -374,13 +374,13 @@ async fn trace_video(
let bh = _height as i32; let bh = _height as i32;
// Bottom-left info panel (y=h-N positions from bottom up) // 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!( 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={}", "drawtext=text='Frame %{{n}} %{{pts}}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y={}",
frame_offset, frame_offset, fps_str, bh - 30 bh - 30
)); ));
parts.push(format!( 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!( parts.push(format!(
"drawtext=text='{}':fontsize=14:fontcolor=white:box=1:boxcolor=black@0.6:x=10:y={}", file_uuid, bh - 74 "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 { for (tid, frames) in &sorted_traces {
let start = frames.iter().min().unwrap_or(&first_frame); let start = frames.iter().min().unwrap_or(&first_frame);
let identity = trace_identity.get(tid).map(|s| s.as_str()).unwrap_or("unknown"); 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 // 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); 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)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if !result.status.success() { if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr); 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(&filter_file);
let _ = std::fs::remove_file(&tmp); let _ = std::fs::remove_file(&tmp);
return Err(StatusCode::INTERNAL_SERVER_ERROR); return Err(StatusCode::INTERNAL_SERVER_ERROR);