- 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
207 lines
6.0 KiB
Python
207 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Detector de MUERTES Y AUTO-CRÍTICA:
|
|
Encuentra momentos donde el streamer muere o se critica por jugar mal.
|
|
"""
|
|
import json
|
|
import logging
|
|
import re
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def detect_death_and_failure_moments(transcripcion_json, min_duration=12, max_duration=35, top=30):
|
|
"""
|
|
Detecta momentos de muerte o auto-crítica.
|
|
"""
|
|
logger.info("=== Buscando MUERTES Y AUTO-CRÍTICA ===")
|
|
|
|
with open(transcripcion_json, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
segments = data.get("segments", [])
|
|
|
|
# Patrones de muerte
|
|
death_patterns = [
|
|
r'\bme han (matado|kill|limeado|pegado)\b',
|
|
r'\b(me caigo|me muero|estoy muerto|muero|mor[íi])\b',
|
|
r'\bme (matan|mate|kill|destruyen)\b',
|
|
r'\bhaz (kill|muerte|limpieza)\b',
|
|
r'\b(tf|trade)\b', # trading deaths
|
|
r'\bhe (muerto|matado)\b',
|
|
r'\b(me llevan|me cargan|me comen)\b',
|
|
r'\bfallec[íi]\b',
|
|
r'\bdefunc[ií]on\b',
|
|
|
|
# Sonidos de muerte/grito
|
|
r'\burgh?\b',
|
|
r'\baggh?\b',
|
|
r'\bargh?\b',
|
|
r'\b(ah|oh|ugh) (no|puta|mierda|dios)\b',
|
|
r'\bno+[.,!]+\b',
|
|
r'\bjoder\b',
|
|
r'\bputa madre\b',
|
|
r'\bputa\b',
|
|
r'\bmierda\b',
|
|
|
|
# Frases de muerte
|
|
r'\bestoy (muerto|perdido|acabado)\b',
|
|
r'\bno puedo\b',
|
|
r'\bimposible\b',
|
|
r'\bme (acaban|terminaron)\b',
|
|
]
|
|
|
|
# Patrones de auto-crítica ("jugué muy mal")
|
|
failure_patterns = [
|
|
r'\b(la )?cagu[ée]\b',
|
|
r'\b(jugu[ée]|he jugado) (mal|p[ée]simamente|horrible)\b',
|
|
r'\b(qu[ée] (mal|p[ée]simo|terrible)|error|fail)\b',
|
|
r'\b(lo hice|la hice) mal\b',
|
|
r'\bputa (mala|terrible|fatal)\b',
|
|
r'\bno (me sali[óo]|funcion[óo]|lo logr[ée])\b',
|
|
r'\b(es)tupidez\b',
|
|
r'\bimbecilidad\b',
|
|
r'\bburrada\b',
|
|
r'\bputada\b',
|
|
r'\b(desastroso|catastr[óo]fico)\b',
|
|
r'\b(qu[ée] pena|verg[üu]enza)\b',
|
|
r'\b(he fallado|fall[ée])\b',
|
|
r'\bperd[íi]\b',
|
|
r'\bno deb[ií] (haber|hacer)\b',
|
|
r'\bcagad[oa]\b',
|
|
|
|
# Más patrones de fallo
|
|
r'\bmal (jugu|he|estoy)\b',
|
|
r'\bterrible\b',
|
|
r'\bhorrible\b',
|
|
r'\bfatal\b',
|
|
r'\b(pesimo|p[ée]simo)\b',
|
|
r'\bno (sé|pude|pude)\b',
|
|
r'\b(dif[íi]cil|imposible)\b',
|
|
r'\bperd[íi] (el tiempo|la oportunidad|el flash|la fight)\b',
|
|
r'\berror (m[íi]o|grave)\b',
|
|
]
|
|
|
|
# Analizar cada segmento
|
|
moments = []
|
|
for i, seg in enumerate(segments):
|
|
text = seg["text"].lower()
|
|
start = seg["start"]
|
|
end = seg["end"]
|
|
|
|
score = 0
|
|
type_ = None
|
|
|
|
# Buscar patrones de muerte
|
|
for pattern in death_patterns:
|
|
if re.search(pattern, text, re.IGNORECASE):
|
|
score += 20
|
|
type_ = "muerte"
|
|
break
|
|
|
|
# Buscar patrones de auto-crítica
|
|
for pattern in failure_patterns:
|
|
if re.search(pattern, text, re.IGNORECASE):
|
|
score += 15
|
|
if not type_:
|
|
type_ = "fallo"
|
|
break
|
|
|
|
if score > 0:
|
|
moments.append({
|
|
"start": start,
|
|
"end": end,
|
|
"score": score,
|
|
"text": text.strip(),
|
|
"type": type_
|
|
})
|
|
|
|
if not moments:
|
|
logger.warning("No se encontraron momentos de muerte/fallo")
|
|
return []
|
|
|
|
# Ordenar por score y timestamp
|
|
moments.sort(key=lambda x: (-x["score"], x["start"]))
|
|
|
|
# Agrupar en intervalos sin solapamiento
|
|
intervals = []
|
|
for moment in moments:
|
|
start = int(moment["start"])
|
|
end = int(moment["end"])
|
|
|
|
# Extender a duración mínima si es muy corto
|
|
duration = max(min_duration, min(end - start, max_duration))
|
|
end = start + duration
|
|
|
|
# Verificar solapamiento con intervalos existentes
|
|
overlaps = False
|
|
for s, e in intervals:
|
|
if not (end < s or start > e): # Hay solapamiento
|
|
overlaps = True
|
|
break
|
|
|
|
if not overlaps:
|
|
intervals.append((start, end))
|
|
if len(intervals) >= top:
|
|
break
|
|
|
|
# Ordenar por timestamp final
|
|
intervals.sort()
|
|
|
|
logger.info(f"Momentos detectados: {len(intervals)}")
|
|
|
|
return intervals, moments
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--transcripcion", required=True)
|
|
parser.add_argument("--output", default="highlights_muertes.json")
|
|
parser.add_argument("--top", type=int, default=30)
|
|
parser.add_argument("--min-duration", type=int, default=12)
|
|
parser.add_argument("--max-duration", type=int, default=35)
|
|
args = parser.parse_args()
|
|
|
|
intervals, moments = detect_death_and_failure_moments(
|
|
args.transcripcion,
|
|
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"MOMENTOS DE MUERTE Y AUTO-CRÍTICA".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"{'-'*70}")
|
|
|
|
for i, (start, end) in enumerate(intervals, 1):
|
|
duration = end - start
|
|
h = start // 3600
|
|
m = (start % 3600) // 60
|
|
sec = start % 60
|
|
|
|
# Buscar el texto correspondiente
|
|
for moment in moments:
|
|
if abs(moment["start"] - start) < 5:
|
|
type_icon = "💀" if moment["type"] == "muerte" else "❌"
|
|
text_preview = moment["text"][:55].replace('\n', ' ')
|
|
print(f"{i:2d}. {h:02d}:{m:02d}:{sec:02d} - {duration}s {type_icon} - {text_preview}...")
|
|
break
|
|
|
|
print(f"{'='*70}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|