#!/usr/bin/env python3 """ DETECTOR INTELIGENTE DE PRIMERA MUERTE ====================================== METODOLOGÍA: Búsqueda binaria automatizada con OCR 1. Comienza desde un punto conocido (donde hay 0/1) 2. Retrocede en pasos de 30s analizando con Tesseract OCR 3. Cuando encuentra 0/0, hace búsqueda fina cada 2s 4. Encuentra el momento EXACTO del cambio TECNOLOGÍA: Tesseract OCR + OpenCV (GPU para extracción de frames) """ import cv2 import numpy as np import pytesseract import subprocess import os from datetime import timedelta import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s") logger = logging.getLogger(__name__) # Configuración VIDEO_PATH = ( "/home/ren/proyectos/editor/twitch-highlight-detector/stream_2699641307_1080p60.mp4" ) OUTPUT_DIR = "/home/ren/proyectos/editor/twitch-highlight-detector/muertes" # Coordenadas exactas del KDA (1080p) KDA_CROP = {"x": 0, "y": 0, "w": 280, "h": 120} # Esquina superior izquierda def format_time(seconds): """Convierte segundos a HH:MM:SS""" return str(timedelta(seconds=int(seconds))) def extract_frame(video_path, timestamp): """Extrae un frame específico del video""" temp_file = f"/tmp/frame_{int(timestamp * 100)}.png" cmd = [ "ffmpeg", "-y", "-ss", str(timestamp), "-i", video_path, "-vframes", "1", "-vf", f"crop={KDA_CROP['w']}:{KDA_CROP['h']}:{KDA_CROP['x']}:{KDA_CROP['y']},scale=560:240", "-pix_fmt", "rgb24", temp_file, ] try: subprocess.run(cmd, capture_output=True, check=True, timeout=10) return temp_file if os.path.exists(temp_file) else None except: return None def read_kda_tesseract(image_path): """ Lee el KDA usando Tesseract OCR Busca el formato X/Y/Z donde Y es el deaths """ if not os.path.exists(image_path): return None # Cargar imagen img = cv2.imread(image_path) if img is None: return None # Preprocesamiento para mejorar OCR gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Aumentar contraste _, thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # OCR con Tesseract custom_config = r"--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789/" text = pytesseract.image_to_string(thresh, config=custom_config) # Limpiar y buscar formato KDA text = text.strip().replace(" ", "").replace("\n", "") # Buscar formato X/Y/Z import re matches = re.findall(r"(\d+)/(\d+)/(\d+)", text) if matches: kills, deaths, assists = matches[0] return int(kills), int(deaths), int(assists) # Si no encuentra formato completo, buscar números sueltos numbers = re.findall(r"\d+", text) if len(numbers) >= 3: return int(numbers[0]), int(numbers[1]), int(numbers[2]) return None def find_first_death_smart(start_timestamp=4475, step_back=30): """ Búsqueda inteligente hacia atrás Estrategia: 1. Retrocede en pasos grandes (30s) hasta encontrar 0/0 2. Luego busca fina cada 2s entre el último 0/0 y primer 0/1 """ logger.info("=" * 70) logger.info("DETECTOR INTELIGENTE - Búsqueda hacia atrás") logger.info("=" * 70) logger.info(f"Punto de inicio: {format_time(start_timestamp)} (0/1 confirmado)") logger.info(f"Buscando cambio a 0/0 retrocediendo...") logger.info("") current_ts = start_timestamp last_01_ts = start_timestamp found_00 = False # FASE 1: Retroceder en pasos grandes hasta encontrar 0/0 logger.info("FASE 1: Retroceso grueso (pasos de 30s)") logger.info("-" * 70) max_attempts = 20 # Máximo 10 minutos hacia atrás attempt = 0 while attempt < max_attempts: frame_path = extract_frame(VIDEO_PATH, current_ts) if not frame_path: logger.warning(f" [{format_time(current_ts)}] No se pudo extraer frame") current_ts -= step_back attempt += 1 continue kda = read_kda_tesseract(frame_path) if kda: kills, deaths, assists = kda logger.info( f" [{format_time(current_ts)}] KDA: {kills}/{deaths}/{assists}" ) if deaths == 0: logger.info(f" ✓ Encontrado 0/0 en {format_time(current_ts)}") found_00 = True break else: last_01_ts = current_ts else: logger.warning(f" [{format_time(current_ts)}] No se pudo leer KDA") # Limpiar temporal if os.path.exists(frame_path): os.remove(frame_path) current_ts -= step_back attempt += 1 if not found_00: logger.error("No se encontró momento con 0/0") return None # FASE 2: Búsqueda fina entre el último 0/0 y el primer 0/1 logger.info("") logger.info("FASE 2: Búsqueda fina (pasos de 2s)") logger.info("-" * 70) logger.info(f"Buscando entre {format_time(current_ts)} y {format_time(last_01_ts)}") # Retroceder 30s más para asegurar, luego avanzar fino fine_start = current_ts - 30 fine_end = last_01_ts + 5 death_timestamp = None for ts in range(int(fine_start), int(fine_end), 2): # Cada 2 segundos frame_path = extract_frame(VIDEO_PATH, ts) if not frame_path: continue kda = read_kda_tesseract(frame_path) if kda: kills, deaths, assists = kda logger.info(f" [{format_time(ts)}] KDA: {kills}/{deaths}/{assists}") # Detectar cambio de 0 a 1 if deaths >= 1 and death_timestamp is None: death_timestamp = ts logger.info(f" 💀 PRIMERA MUERTE DETECTADA: {format_time(ts)}") break if os.path.exists(frame_path): os.remove(frame_path) return death_timestamp def extract_death_clip(timestamp, output_file): """Extrae clip de la muerte con contexto""" start = max(0, timestamp - 10) duration = 25 # 10s antes + 15s después cmd = [ "ffmpeg", "-y", "-ss", str(start), "-t", str(duration), "-i", VIDEO_PATH, "-c:v", "h264_nvenc", "-preset", "fast", "-rc", "vbr", "-cq", "23", "-r", "60", "-c:a", "copy", output_file, ] try: subprocess.run(cmd, capture_output=True, check=True, timeout=120) return True except: return False def main(): logger.info("\n" + "=" * 70) logger.info("BUSCADOR INTELIGENTE DE PRIMERA MUERTE") logger.info("Tecnología: Tesseract OCR + Retroceso automatizado") logger.info("=" * 70) logger.info("") # Encontrar primera muerte death_ts = find_first_death_smart(start_timestamp=4475) if death_ts: logger.info("") logger.info("=" * 70) logger.info("RESULTADO FINAL") logger.info("=" * 70) logger.info(f"✓ Primera muerte detectada en: {format_time(death_ts)}") logger.info(f" Timestamp exacto: {death_ts} segundos") logger.info("") logger.info("Extrayendo clip final...") # Extraer clip os.makedirs(OUTPUT_DIR, exist_ok=True) output_file = f"{OUTPUT_DIR}/PRIMERA_MUERTE_{int(death_ts)}s.mp4" if extract_death_clip(death_ts, output_file): size_mb = os.path.getsize(output_file) / (1024 * 1024) logger.info(f"✓ Clip guardado: {output_file}") logger.info(f" Tamaño: {size_mb:.1f}MB") logger.info(f" Duración: 25 segundos (contexto completo)") else: logger.error("Error extrayendo clip final") logger.info("") logger.info("=" * 70) logger.info("METODOLOGÍA UTILIZADA:") logger.info("=" * 70) logger.info("1. Tesseract OCR para lectura de KDA") logger.info("2. Retroceso automatizado en pasos de 30s") logger.info("3. Búsqueda fina cada 2s en zona crítica") logger.info("4. Detección de cambio: 0/0 → 0/1") logger.info("=" * 70) else: logger.error("No se pudo determinar la primera muerte") if __name__ == "__main__": main()