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
This commit is contained in:
186
detector_gameplay.py
Normal file
186
detector_gameplay.py
Normal file
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Detector de GAMEPLAY ACTIVO:
|
||||
Busca momentos donde está jugando, no solo hablando.
|
||||
Filtra intros y momentos de solo hablar.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import numpy as np
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def detect_active_gameplay(chat_data, transcripcion_json, intro_skip=90, min_duration=20, max_duration=45, top=30):
|
||||
"""
|
||||
Detecta momentos de gameplay activo (hablando + jugando).
|
||||
"""
|
||||
logger.info("=== Buscando GAMEPLAY ACTIVO ===")
|
||||
|
||||
with open(transcripcion_json, 'r', encoding='utf-8') as f:
|
||||
trans_data = json.load(f)
|
||||
|
||||
segments = trans_data.get("segments", [])
|
||||
|
||||
# Palabras de GAMEPLAY (está jugando)
|
||||
gameplay_keywords = [
|
||||
r'\b(champion|campe[oó]n|ult[i|í]|habilidad|spell|q|w|e|r)\b',
|
||||
r'\b(kill|muert[e|é]|mate|muero|fui|mat[aá])\b',
|
||||
r'\b(fight|pelea|fight|team|equip|jungla|top|mid|adc|support)\b',
|
||||
r'\b(lane|linia|mina|drag[oó]n|baron|nashor|torre|inhib)\b',
|
||||
r'\b(ult|ultimate|flash|ignite|exhaust|teleport|heal)\b',
|
||||
r'\b(gank|roam|invade|invasi[oó])\b',
|
||||
r'\b(ward|vision|control|map|objetivo)\b',
|
||||
r'\b(damage|daño|dps|burst|poke)\b',
|
||||
r'\b(lethality|letalidad|crit|cr[i|í]tico)\b',
|
||||
r'\b(arma|item|build|objeto|poder|stats)\b',
|
||||
r'\b(level|nivel|exp|gold|oro|farm|cs)\b',
|
||||
r'\b(esquiv|evade|dodge|block|bloqueo)\b',
|
||||
r'\b(engage|pelear|inici|all[i|í]n|surrender|rindase)\b',
|
||||
]
|
||||
|
||||
# Palabras de SOLO HABLAR (excluir)
|
||||
talking_only_keywords = [
|
||||
r'\b(hola|buenas|buenas tardes|buenas noches|adi[oó]s)\b',
|
||||
r'\b(gracias|thank|agradezco)\b',
|
||||
r'\b(playlist|música|canci[oó]n|song)\b',
|
||||
r'\b(intro|presento|presentaci[oó]n|inicio)\b',
|
||||
r'\b(despedida|adi[oó]s|nos vemos|chao)\b',
|
||||
r'\b(merch|tienda|store|donar|donaci[oó]n)\b',
|
||||
r'\b(rrs|redes sociales|twitter|instagram|discord)\b',
|
||||
r'\b giveaway|sorteo|regalo\b',
|
||||
]
|
||||
|
||||
# Analizar segmentos
|
||||
gameplay_moments = []
|
||||
|
||||
for i, seg in enumerate(segments):
|
||||
start = seg["start"]
|
||||
end = seg["end"]
|
||||
text = seg["text"]
|
||||
text_lower = text.lower()
|
||||
|
||||
# Saltar intro
|
||||
if start < intro_skip:
|
||||
continue
|
||||
|
||||
# Verificar que NO sea solo hablar
|
||||
is_talking_only = False
|
||||
for pattern in talking_only_keywords:
|
||||
if re.search(pattern, text_lower):
|
||||
is_talking_only = True
|
||||
break
|
||||
|
||||
if is_talking_only:
|
||||
continue
|
||||
|
||||
# Verificar que tenga palabras de gameplay
|
||||
gameplay_score = 0
|
||||
for pattern in gameplay_keywords:
|
||||
matches = len(re.findall(pattern, text_lower))
|
||||
gameplay_score += matches
|
||||
|
||||
if gameplay_score > 0:
|
||||
gameplay_moments.append({
|
||||
"start": start,
|
||||
"end": end,
|
||||
"score": gameplay_score,
|
||||
"text": text.strip()[:80]
|
||||
})
|
||||
|
||||
if not gameplay_moments:
|
||||
logger.warning("No se encontraron momentos de gameplay")
|
||||
return []
|
||||
|
||||
# Ordenar por score
|
||||
gameplay_moments.sort(key=lambda x: -x["score"])
|
||||
|
||||
# Agrupar en intervalos sin solapamiento
|
||||
intervals = []
|
||||
for moment in gameplay_moments:
|
||||
start = int(moment["start"])
|
||||
end = int(moment["end"])
|
||||
|
||||
# Duración del clip
|
||||
duration = max(min_duration, min(end - start, max_duration))
|
||||
|
||||
# Extender el final para capturar la acción
|
||||
end = start + duration + 5 # +5s padding
|
||||
|
||||
# Verificar solapamiento
|
||||
overlaps = False
|
||||
for s, e in intervals:
|
||||
if not (end < s or start > e):
|
||||
overlaps = True
|
||||
break
|
||||
|
||||
if not overlaps:
|
||||
intervals.append((start, int(end)))
|
||||
if len(intervals) >= top:
|
||||
break
|
||||
|
||||
intervals.sort()
|
||||
|
||||
logger.info(f"Moments de gameplay detectados: {len(intervals)}")
|
||||
|
||||
return intervals, gameplay_moments
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--chat", required=True)
|
||||
parser.add_argument("--transcripcion", required=True)
|
||||
parser.add_argument("--output", default="highlights_gameplay.json")
|
||||
parser.add_argument("--top", type=int, default=30)
|
||||
parser.add_argument("--intro-skip", type=int, default=90)
|
||||
parser.add_argument("--min-duration", type=int, default=20)
|
||||
parser.add_argument("--max-duration", type=int, default=45)
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.chat, 'r') as f:
|
||||
chat_data = json.load(f)
|
||||
|
||||
intervals, moments = detect_active_gameplay(
|
||||
chat_data,
|
||||
args.transcripcion,
|
||||
args.intro_skip,
|
||||
args.min_duration,
|
||||
args.max_duration,
|
||||
args.top
|
||||
)
|
||||
|
||||
# Guardar
|
||||
with open(args.output, 'w') as f:
|
||||
json.dump(intervals, f)
|
||||
|
||||
logger.info(f"Guardado en {args.output}")
|
||||
|
||||
# Imprimir resumen
|
||||
print(f"\n{'='*70}")
|
||||
print(f"GAMEPLAY ACTIVO - MOMENTOS JUGANDO".center(70))
|
||||
print(f"{'='*70}")
|
||||
print(f"Total: {len(intervals)} clips")
|
||||
print(f"Duración total: {sum(e-s for s,e in intervals)}s ({sum(e-s for s,e in intervals)/60:.1f} min)")
|
||||
print(f"Intro excluida: primeros {args.intro_skip}s")
|
||||
print(f"{'-'*70}")
|
||||
|
||||
for i, (start, end) in enumerate(intervals, 1):
|
||||
duration = end - start
|
||||
h = start // 3600
|
||||
m = (start % 3600) // 60
|
||||
sec = start % 60
|
||||
|
||||
for moment in moments:
|
||||
if abs(moment["start"] - start) < 10:
|
||||
text_preview = moment["text"][:60].replace('\n', ' ')
|
||||
print(f"{i:2d}. {h:02d}:{m:02d}:{sec:02d} - {duration}s - {text_preview}...")
|
||||
break
|
||||
|
||||
print(f"{'='*70}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user