fix: support videos with no audio or no faces

1. trace_done now checks for 'no_faces' status in face_traced.json
   - Videos with no detected faces now complete correctly
   - Previously stuck because trace_count=0 returned false

2. ASRX fallback to ASR segments includes start_frame/end_frame
   - Added _convert_asr_segments_to_asrx helper function
   - TKG can now process fallback segments correctly

This allows processing of:
- Videos with no audio track (ASR: no_audio_track)
- Videos with no faces (face_traced.json: no_faces)
This commit is contained in:
Accusys
2026-07-06 15:54:14 +08:00
parent 27660f48e4
commit dd63dbff9b
2 changed files with 47 additions and 2 deletions

View File

@@ -110,6 +110,39 @@ def _shared_audio_setup(video_path):
return tmp_dir, video_path return tmp_dir, video_path
def _convert_asr_segments_to_asrx(asr_segments, output_path):
"""Convert ASR segments to ASRX format with frame information"""
fps = 30.0
base_name = os.path.basename(output_path)
uuid_part = base_name.split(".")[0]
probe_path = os.path.join(os.path.dirname(output_path),
f"{uuid_part}.probe.json")
if os.path.exists(probe_path):
try:
with open(probe_path) as pf:
probe_data = json.load(pf)
if "fps" in probe_data:
fps = float(probe_data["fps"])
except Exception:
pass
segments = []
for s in asr_segments:
start_time = s.get("start", s.get("start_time", 0))
end_time = s.get("end", s.get("end_time", 0))
start_frame = int(start_time * fps)
end_frame = int(end_time * fps)
segments.append({
"start_time": start_time,
"end_time": end_time,
"start_frame": start_frame,
"end_frame": end_frame,
"text": s.get("text", ""),
"speaker_id": "SPEAKER_0",
})
return segments
def _convert_result(result, output_path): def _convert_result(result, output_path):
"""Stage 3: 將 SelfASRXFixed result 轉為 Rust-expected format""" """Stage 3: 將 SelfASRXFixed result 轉為 Rust-expected format"""
fps = 30.0 fps = 30.0
@@ -231,11 +264,12 @@ def process_asrx(video_path: str, output_path: str, uuid: str = "",
# Fallback to ASR segments if ASRX fails but ASR has content # Fallback to ASR segments if ASRX fails but ASR has content
if asr_segments: if asr_segments:
print(f"[ASRX] Resume error, falling back to {len(asr_segments)} ASR segments", file=sys.stderr) print(f"[ASRX] Resume error, falling back to {len(asr_segments)} ASR segments", file=sys.stderr)
segments = _convert_asr_segments_to_asrx(asr_segments, output_path)
output_result = { output_result = {
"status": "has_transcript", "status": "has_transcript",
"language": None, "language": None,
"segments": [{"start_time": s["start"], "end_time": s["end"], "text": s["text"], "speaker_id": "SPEAKER_0"} for s in asr_segments], "segments": segments,
"segment_count": len(asr_segments), "segment_count": len(segments),
"fallback_from_asr": True, "fallback_from_asr": True,
} }
else: else:

View File

@@ -1201,6 +1201,17 @@ impl JobWorker {
if std::path::Path::new(&traced_path).exists() { if std::path::Path::new(&traced_path).exists() {
if let Ok(content) = std::fs::read_to_string(&traced_path) { if let Ok(content) = std::fs::read_to_string(&traced_path) {
if let Ok(traced_data) = serde_json::from_str::<serde_json::Value>(&content) { if let Ok(traced_data) = serde_json::from_str::<serde_json::Value>(&content) {
// Check if status is no_faces (valid completion with no faces)
if let Some(status) = traced_data.get("status").and_then(|s| s.as_str()) {
if status == "no_faces" {
tracing::info!(
"[Ingestion] No faces detected for {} - trace_done=true",
uuid
);
return true;
}
}
if let Some(traces) = traced_data.get("traces") { if let Some(traces) = traced_data.get("traces") {
// traces can be an object (dictionary) or array // traces can be an object (dictionary) or array
let trace_count = if traces.is_object() { let trace_count = if traces.is_object() {