Files
twitch-highlight-detector/multi_game_detector.py
renato97 00180d0b1c Sistema completo de detección de highlights con VLM y análisis de gameplay
- Implementación de detector híbrido (Whisper + Chat + Audio + VLM)
- Sistema de detección de gameplay real vs hablando
- Scene detection con FFmpeg
- Soporte para RTX 3050 y RX 6800 XT
- Guía completa en 6800xt.md para próxima IA
- Scripts de filtrado visual y análisis de contexto
- Pipeline automatizado de generación de videos
2026-02-19 17:38:14 +00:00

241 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""
MULTI-GAME DETECTOR
Detecta múltiples partidas/juegos en el stream y extrae highlights de cada uno.
"""
import json
import numpy as np
from pathlib import Path
def detect_game_boundaries(transcription_file):
"""
Detecta dónde empieza y termina cada juego/partida.
Señales de cambio de juego:
- Cambios grandes en el timeline (>5 min sin actividad)
- Palabras como "victoria", "derrota", "gg", "fin"
- Selección de campeones seguida de gameplay
"""
print("=" * 60)
print("MULTI-GAME DETECTOR")
print("=" * 60)
with open(transcription_file, "r") as f:
trans = json.load(f)
segments = trans["segments"]
# Encontrar cambios de juego
games = []
current_game_start = 0
last_activity = 0
for i, seg in enumerate(segments):
text = seg["text"].lower()
current_time = seg["start"]
# Detectar fin de juego
if any(
word in text
for word in [
"victoria",
"derrota",
"gg wp",
"buena partida",
"fin del juego",
"game over",
"terminamos",
]
):
if current_time - current_game_start > 600: # Mínimo 10 min de juego
games.append(
{
"start": current_game_start,
"end": current_time,
"finish_type": "victoria/derrota",
"text": text[:50],
}
)
current_game_start = current_time
last_activity = current_time
# Detectar gaps grandes (cambio de juego)
if i > 0:
gap = current_time - segments[i - 1]["end"]
if gap > 300: # Gap de 5+ minutos
if current_time - current_game_start > 600:
games.append(
{
"start": current_game_start,
"end": segments[i - 1]["end"],
"finish_type": "gap",
"text": f"Gap de {gap:.0f}s",
}
)
current_game_start = current_time
last_activity = current_time
# Agregar último juego
if segments[-1]["end"] - current_game_start > 300:
games.append(
{
"start": current_game_start,
"end": segments[-1]["end"],
"finish_type": "final",
"text": "Último juego",
}
)
print(f"\nJuegos detectados: {len(games)}")
for i, game in enumerate(games, 1):
mins_start = int(game["start"]) // 60
secs_start = int(game["start"]) % 60
mins_end = int(game["end"]) // 60
secs_end = int(game["end"]) % 60
dur = game["end"] - game["start"]
print(
f"{i}. {mins_start:02d}:{secs_start:02d} - {mins_end:02d}:{secs_end:02d} "
f"({dur // 60}m {dur % 60}s) - {game['finish_type']}"
)
return games
def find_highlights_in_game(game, transcription, chat_data, min_score=6):
"""Encuentra highlights dentro de un juego específico."""
# Patrones de rage/highlights
rage_patterns = [
(r"\bputa\w*", 10, "RAGE"),
(r"\bme mataron\b", 12, "DEATH"),
(r"\bme mori\b", 12, "DEATH"),
(r"\bmierda\b", 8, "RAGE"),
(r"\bjoder\b", 8, "RAGE"),
(r"\bretrasad\w*", 9, "INSULT"),
(r"\bimbecil\b", 9, "INSULT"),
(r"\bla cague\b", 8, "FAIL"),
(r"\bnooo+\b", 6, "FRUSTRATION"),
]
highlights = []
# Buscar en transcripción de este juego
for seg in transcription["segments"]:
if seg["start"] < game["start"] or seg["end"] > game["end"]:
continue
text = seg["text"].lower()
score = 0
reasons = []
for pattern, points, reason in rage_patterns:
import re
if re.search(pattern, text, re.IGNORECASE):
score += points
if reason not in reasons:
reasons.append(reason)
if score >= min_score:
highlights.append(
{
"time": seg["start"],
"score": score,
"text": seg["text"][:60],
"reasons": reasons,
}
)
# Ordenar y tomar top 3 de este juego
highlights.sort(key=lambda x: -x["score"])
return highlights[:3]
def create_game_summary(games, transcription, chat_data):
"""Crea un resumen con highlights de cada juego."""
print("\n" + "=" * 60)
print("RESUMEN POR JUEGO")
print("=" * 60)
all_clips = []
for i, game in enumerate(games, 1):
print(f"\nJuego {i}:")
highlights = find_highlights_in_game(game, transcription, chat_data)
if not highlights:
print(" Sin highlights destacados")
continue
# Tomar el mejor highlight de este juego
best = highlights[0]
# Crear clip extendido (10s antes, 15s después)
clip_start = max(game["start"], best["time"] - 10)
clip_end = min(game["end"], best["time"] + 20)
# Asegurar que no incluya selección de campeones
if clip_start < game["start"] + 30: # Primeros 30s suelen ser selección
clip_start = game["start"] + 30
if clip_end - clip_start >= 15:
all_clips.append(
{
"game": i,
"start": int(clip_start),
"end": int(clip_end),
"score": best["score"],
"text": best["text"],
"reasons": best["reasons"],
}
)
mins = int(clip_start) // 60
secs = int(clip_start) % 60
print(f" {mins:02d}:{secs:02d} - {best['text'][:50]}...")
print(f" Score: {best['score']} - {'/'.join(best['reasons'])}")
# Ordenar clips por tiempo
all_clips.sort(key=lambda x: x["start"])
print(f"\n" + "=" * 60)
print(f"Total clips: {len(all_clips)}")
total_dur = sum(c["end"] - c["start"] for c in all_clips)
print(f"Duración total: {total_dur}s ({total_dur // 60}m {total_dur % 60}s)")
return all_clips
if __name__ == "__main__":
# Detectar juegos
games = detect_game_boundaries("transcripcion_rage.json")
# Cargar datos
with open("transcripcion_rage.json", "r") as f:
trans = json.load(f)
with open("elxokas_chat.json", "r") as f:
chat = json.load(f)
# Crear resumen
clips = create_game_summary(games, trans, chat)
# Guardar
highlights = [[c["start"], c["end"]] for c in clips]
with open("highlights_multi_game.json", "w") as f:
json.dump(highlights, f)
print("\nTimeline final:")
for i, c in enumerate(clips, 1):
mins, secs = divmod(c["start"], 60)
dur = c["end"] - c["start"]
print(
f"{i}. {mins:02d}:{secs:02d} - {dur}s (Juego {c['game']}) [{'/'.join(c['reasons'])}]"
)
print(f"\nGuardado en highlights_multi_game.json")