from __future__ import annotations import tempfile import time from pathlib import Path from typing import Any from .asl.mediapipe_utils import draw_holistic_landmarks def create_debug_overlay_video(video_path: str | Path, result: dict[str, Any]) -> str: cv2 = _load_cv2() mp, holistic = _load_holistic() path = Path(video_path) cap = cv2.VideoCapture(str(path)) if not cap.isOpened(): raise ValueError(f"Could not open video for debug overlay: {path}") width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 640) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 480) fps = float(cap.get(cv2.CAP_PROP_FPS) or 24.0) if fps <= 0: fps = 24.0 output_path = Path(tempfile.gettempdir()) / f"signspeak_debug_{int(time.time() * 1000)}.mp4" writer = cv2.VideoWriter( str(output_path), cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height), ) if not writer.isOpened(): cap.release() raise RuntimeError(f"Could not create debug overlay video: {output_path}") asl = result.get("asl", {}) emotion = result.get("emotion", {}) intent = result.get("intent_input", {}) glosses = intent.get("detected_glosses") or asl.get("gloss_sequence") or [] gloss_text = " ".join(str(gloss) for gloss in glosses) if glosses else "NO ASL WORDS DETECTED" emotion_text = str(emotion.get("dominant_emotion", "unknown")).upper() top_prediction = asl.get("top_prediction") or "none" confidence = float(asl.get("confidence", 0.0) or 0.0) threshold = float(asl.get("confidence_threshold", 0.0) or 0.0) windows_used = int(asl.get("windows_used", 0) or 0) status_text = ( f"ASL {asl.get('status', 'unknown')} | top {top_prediction} " f"{confidence:.2f}/{threshold:.2f} | windows {windows_used} | EMOTION {emotion.get('status', 'unknown')}" ) try: while True: ok, frame = cap.read() if not ok or frame is None: break if mp is not None and holistic is not None: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = holistic.process(frame_rgb) draw_holistic_landmarks(mp, frame, results) _draw_overlay(cv2, frame, gloss_text, emotion_text, status_text) writer.write(frame) finally: cap.release() if holistic is not None: holistic.close() writer.release() return str(output_path) def _draw_overlay(cv2, frame, gloss_text: str, emotion_text: str, status_text: str) -> None: height, width = frame.shape[:2] pad = 14 panel_height = 112 cv2.rectangle(frame, (0, 0), (width, panel_height), (8, 11, 16), -1) cv2.rectangle(frame, (0, panel_height - 2), (width, panel_height), (45, 212, 191), -1) _put_text(cv2, frame, "DETECTED ASL", (pad, 28), 0.52, (203, 213, 225), 1) _put_text(cv2, frame, gloss_text, (pad, 64), 0.86, (248, 250, 252), 2) _put_text(cv2, frame, f"EMOTION: {emotion_text}", (pad, 96), 0.58, (245, 158, 11), 2) status_size = cv2.getTextSize(status_text, cv2.FONT_HERSHEY_SIMPLEX, 0.46, 1)[0] x = max(pad, width - status_size[0] - pad) _put_text(cv2, frame, status_text, (x, height - 18), 0.46, (203, 213, 225), 1) def _put_text(cv2, frame, text: str, origin: tuple[int, int], scale: float, color: tuple[int, int, int], thickness: int) -> None: cv2.putText( frame, text[:90], origin, cv2.FONT_HERSHEY_SIMPLEX, scale, color, thickness, cv2.LINE_AA, ) def _load_cv2(): try: import cv2 return cv2 except Exception as exc: raise RuntimeError("OpenCV is required for debug overlay video generation.") from exc def _load_holistic(): try: import mediapipe as mp return ( mp, mp.solutions.holistic.Holistic( min_detection_confidence=0.5, min_tracking_confidence=0.5, ), ) except Exception: return None, None