Files
twitch-highlight-detector/generate_final_video.py
renato97 4cd1d475fe Sesión 19 Feb: OCR intentos, MCP op.gg, timestamps manuales, video final muertes
- Agregado intentos.md con registro de todos los fallos
- Actualizado contexto.md con sesión de noche
- MCP op.gg instalado (no funcionó - 0 matches)
- OCR con Tesseract y EasyOCR (falló - texto muy pequeño)
- Video final generado: HIGHLIGHTS_MUERTES_COMPLETO.mp4
- Juegos separados: JUEGO_1/2/3_COMPLETO.mp4
- 10 muertes secuenciales: 0/1→0/10
- Scripts de extracción automática con timestamps
2026-02-19 23:29:55 +00:00

227 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""
Generador de video final CORREGIDO - 30fps
Crea highlights con las muertes detectadas por OCR-GPU
"""
import json
import os
import subprocess
from datetime import timedelta
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
logger = logging.getLogger(__name__)
def format_time(seconds):
return str(timedelta(seconds=int(seconds)))
def extract_clip_correct(video_path, start_sec, end_sec, output_file):
"""Extrae clip manteniendo 30fps original"""
duration = end_sec - start_sec
cmd = [
"ffmpeg",
"-y",
"-ss",
str(start_sec),
"-t",
str(duration),
"-i",
video_path,
"-c:v",
"libx264", # Re-encodear para asegurar consistencia
"-preset",
"fast",
"-crf",
"23",
"-r",
"30", # Forzar 30fps
"-pix_fmt",
"yuv420p",
"-c:a",
"aac",
"-b:a",
"128k",
output_file,
]
try:
subprocess.run(cmd, capture_output=True, check=True, timeout=60)
return True
except Exception as e:
logger.error(f"Error extrayendo clip: {e}")
return False
def group_nearby_deaths(deaths, min_gap=30):
"""Agrupa muertes que están cercanas para evitar clips repetidos"""
if not deaths:
return []
# Ordenar por timestamp
sorted_deaths = sorted(deaths, key=lambda x: x.get("timestamp", 0))
groups = []
current_group = [sorted_deaths[0]]
for death in sorted_deaths[1:]:
if death.get("timestamp", 0) - current_group[-1].get("timestamp", 0) < min_gap:
# Muerte cercana, agregar al grupo
current_group.append(death)
else:
# Muerte lejana, cerrar grupo y empezar nuevo
groups.append(current_group)
current_group = [death]
# Agregar último grupo
if current_group:
groups.append(current_group)
return groups
def create_final_video(video_path, deaths, output_file="HIGHLIGHTS_FINAL_30FPS.mp4"):
"""Crea video final concatenando clips de muertes"""
logger.info("=" * 70)
logger.info("GENERANDO VIDEO FINAL - 30 FPS")
logger.info("=" * 70)
os.makedirs("clips_final", exist_ok=True)
# Agrupar muertes cercanas
death_groups = group_nearby_deaths(deaths, min_gap=30)
logger.info(f"Detectadas {len(deaths)} muertes en {len(death_groups)} grupos")
# Extraer cada grupo como clip
clip_files = []
for i, group in enumerate(death_groups[:10], 1): # Máximo 10 clips
# Calcular rango del grupo
timestamps = [d.get("timestamp", 0) for d in group]
group_start = min(timestamps)
group_end = max(timestamps)
# Calcular timestamps del clip
clip_start = max(0, group_start - 10) # 10s antes del primero
clip_end = group_end + 15 # 15s después del último
# Asegurar duración mínima de 20 segundos
if clip_end - clip_start < 20:
clip_end = clip_start + 20
clip_file = f"clips_final/group_{i:02d}_{int(group_start)}.mp4"
death_nums = ", ".join([str(d.get("death_number", "?")) for d in group])
logger.info(
f"[{i}/{len(death_groups)}] Extrayendo grupo {i} (muertes: {death_nums})"
)
logger.info(f" Rango: {format_time(clip_start)} - {format_time(clip_end)}")
if extract_clip_correct(video_path, clip_start, clip_end, clip_file):
clip_files.append(clip_file)
logger.info(f" ✓ Clip extraído: {clip_file}")
if not clip_files:
logger.error("No se pudieron extraer clips")
return None
# Crear archivo de concatenación
concat_file = "/tmp/concat_final.txt"
with open(concat_file, "w") as f:
for clip in clip_files:
f.write(f"file '{os.path.abspath(clip)}'\n")
# Concatenar todo
logger.info("\nConcatenando clips...")
cmd = [
"ffmpeg",
"-y",
"-f",
"concat",
"-safe",
"0",
"-i",
concat_file,
"-c:v",
"libx264",
"-preset",
"medium",
"-crf",
"20",
"-r",
"30", # Forzar 30fps en salida
"-pix_fmt",
"yuv420p",
"-c:a",
"aac",
"-b:a",
"128k",
output_file,
]
try:
subprocess.run(cmd, capture_output=True, check=True, timeout=120)
logger.info(f"✓ Video final creado: {output_file}")
# Verificar
check = subprocess.run(
[
"ffprobe",
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=r_frame_rate",
"-of",
"default=noprint_wrappers=1:nokey=1",
output_file,
],
capture_output=True,
text=True,
)
logger.info(f" FPS del video: {check.stdout.strip()}")
return output_file
except Exception as e:
logger.error(f"Error creando video final: {e}")
return None
def main():
# Usar video 1080p60
video_path = "/home/ren/proyectos/editor/twitch-highlight-detector/stream_2699641307_1080p60.mp4"
# Cargar muertes detectadas (1080p60)
deaths_file = (
"/home/ren/proyectos/editor/twitch-highlight-detector/deaths_1080p60_final.json"
)
if not os.path.exists(deaths_file):
logger.error(f"No existe: {deaths_file}")
logger.info("Ejecuta primero: python3 detect_deaths_ocr_gpu.py")
return
with open(deaths_file, "r") as f:
data = json.load(f)
deaths = data.get("deaths", [])
logger.info(f"Cargadas {len(deaths)} muertes detectadas")
# Crear video
final_video = create_final_video(video_path, deaths)
if final_video:
logger.info("\n" + "=" * 70)
logger.info("✓ VIDEO FINAL GENERADO CORRECTAMENTE")
logger.info(f" Archivo: {final_video}")
logger.info(" FPS: 30")
logger.info("=" * 70)
if __name__ == "__main__":
main()