- 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
214 lines
6.1 KiB
Python
214 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
RAGE & FAIL DETECTOR
|
|
Prioriza momentos de muerte, rage, insultos y fails.
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
def detect_rage_highlights(
|
|
transcription_file, chat_file, skip_intro=455, max_duration=8237
|
|
):
|
|
"""Detecta momentos de rage, muerte y fails."""
|
|
|
|
print("=" * 60)
|
|
print("RAGE & FAIL DETECTOR")
|
|
print("=" * 60)
|
|
|
|
# Cargar transcripción
|
|
with open(transcription_file, "r") as f:
|
|
trans = json.load(f)
|
|
|
|
# Cargar chat
|
|
with open(chat_file, "r") as f:
|
|
chat_data = json.load(f)
|
|
|
|
# Diccionario de rage completo
|
|
rage_patterns = {
|
|
"extreme_rage": [
|
|
r"\bputa\b",
|
|
r"\bmadre\b",
|
|
r"\bretrasad\w*",
|
|
r"\bimbecil\b",
|
|
r"\bestupid\w*",
|
|
r"\bidiota\b",
|
|
r"\bmierda\b",
|
|
r"\bbasura\b",
|
|
r"\binutil\b",
|
|
r"\bmongol\w*",
|
|
r"\bcancer\b",
|
|
r"\bmaricon\b",
|
|
],
|
|
"death": [
|
|
r"\bme mataron\b",
|
|
r"\bme mori\b",
|
|
r"\bmuerto\b",
|
|
r"\bme matan\b",
|
|
r"\bmatenme\b",
|
|
r"\bfeed\w*",
|
|
r"\bfeeding\b",
|
|
r"\bme destrozaron\b",
|
|
r"\bme comieron\b",
|
|
r"\bme cargaron\b",
|
|
],
|
|
"fail": [
|
|
r"\bla cague\b",
|
|
r"\bla lie\b",
|
|
r"\berror\b",
|
|
r"\bfail\b",
|
|
r"\bperdon\b",
|
|
r"\bperdón\b",
|
|
r"\blo siento\b",
|
|
r"\bmala mia\b",
|
|
r"\bfalle\b",
|
|
r"\bfall[eé]\b",
|
|
r"\bno puede ser\b",
|
|
r"\bcomo\?\b",
|
|
],
|
|
"team_rage": [
|
|
r"\bequipo\b.*\b(mierda|basura|malos)\b",
|
|
r"\bteam\b.*\b(trash|bad)\b",
|
|
r"\breport\w*",
|
|
r"\btroll\w*",
|
|
r"\binting\b",
|
|
r"\bjugadores\b.*\bmalos\b",
|
|
],
|
|
"frustration": [
|
|
r"\b(nooo|noo|no no no)\b",
|
|
r"\bpor que\b",
|
|
r"\bporque\b",
|
|
r"\ben serio\b",
|
|
r"\bno me jodas\b",
|
|
r"\bque (haces|hace)\b",
|
|
r"\bomg\b",
|
|
r"\bdios\b",
|
|
r"\bhostia\b",
|
|
r"\bjoder\b",
|
|
],
|
|
}
|
|
|
|
# Analizar cada segmento
|
|
rage_moments = []
|
|
|
|
for seg in trans.get("segments", []):
|
|
if seg["start"] < skip_intro:
|
|
continue
|
|
|
|
text = seg["text"].lower()
|
|
score = 0
|
|
reasons = []
|
|
|
|
for category, patterns in rage_patterns.items():
|
|
for pattern in patterns:
|
|
if re.search(pattern, text, re.IGNORECASE):
|
|
if category == "extreme_rage":
|
|
score += 15
|
|
if "EXTREME" not in reasons:
|
|
reasons.append("EXTREME")
|
|
elif category == "death":
|
|
score += 12
|
|
if "DEATH" not in reasons:
|
|
reasons.append("DEATH")
|
|
elif category == "team_rage":
|
|
score += 10
|
|
if "TEAM_RAGE" not in reasons:
|
|
reasons.append("TEAM_RAGE")
|
|
elif category == "fail":
|
|
score += 8
|
|
if "FAIL" not in reasons:
|
|
reasons.append("FAIL")
|
|
else:
|
|
score += 5
|
|
if "FRUSTRATION" not in reasons:
|
|
reasons.append("FRUSTRATION")
|
|
break
|
|
|
|
if score >= 5: # Mínimo score significativo
|
|
rage_moments.append(
|
|
{
|
|
"start": seg["start"],
|
|
"end": seg["end"],
|
|
"score": score,
|
|
"text": seg["text"][:70],
|
|
"reasons": reasons,
|
|
}
|
|
)
|
|
|
|
print(f"\nMomentos de rage detectados: {len(rage_moments)}")
|
|
|
|
# Ordenar por score
|
|
rage_moments.sort(key=lambda x: -x["score"])
|
|
|
|
# Crear clips extendidos
|
|
clips = []
|
|
for moment in rage_moments[:25]: # Top 25
|
|
start = max(skip_intro, int(moment["start"]) - 10)
|
|
end = min(max_duration, int(moment["end"]) + 20)
|
|
|
|
if end - start >= 12:
|
|
clips.append(
|
|
{
|
|
"start": start,
|
|
"end": end,
|
|
"score": moment["score"],
|
|
"reasons": moment["reasons"],
|
|
"text": moment["text"],
|
|
}
|
|
)
|
|
|
|
# Eliminar solapamientos
|
|
clips.sort(key=lambda x: x["start"])
|
|
filtered = []
|
|
|
|
for clip in clips:
|
|
if not filtered:
|
|
filtered.append(clip)
|
|
else:
|
|
last = filtered[-1]
|
|
if clip["start"] <= last["end"] + 3:
|
|
# Fusionar
|
|
last["end"] = max(last["end"], clip["end"])
|
|
last["score"] = max(last["score"], clip["score"])
|
|
last["reasons"] = list(set(last["reasons"] + clip["reasons"]))
|
|
else:
|
|
filtered.append(clip)
|
|
|
|
# Tomar top 15
|
|
filtered.sort(key=lambda x: -x["score"])
|
|
final = filtered[:15]
|
|
final.sort(key=lambda x: x["start"])
|
|
|
|
print(f"\nClips sin solapar: {len(final)}")
|
|
print(f"\nTop momentos RAGE:")
|
|
for i, clip in enumerate(final, 1):
|
|
mins = int(clip["start"]) // 60
|
|
secs = int(clip["start"]) % 60
|
|
dur = clip["end"] - clip["start"]
|
|
print(
|
|
f"{i:2d}. {mins:02d}:{secs:02d} - {dur}s [Score: {clip['score']:2d}] "
|
|
f"{'/'.join(clip['reasons'])}"
|
|
)
|
|
|
|
total_dur = sum(c["end"] - c["start"] for c in final)
|
|
print(
|
|
f"\nTotal: {len(final)} clips, {total_dur}s ({total_dur // 60}m {total_dur % 60}s)"
|
|
)
|
|
|
|
return [[c["start"], c["end"]] for c in final]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
highlights = detect_rage_highlights(
|
|
"transcripcion_medium.json", "elxokas_chat.json"
|
|
)
|
|
|
|
with open("HIGHLIGHTS_RAGE.json", "w") as f:
|
|
json.dump(highlights, f)
|
|
|
|
print(f"\nGuardado en HIGHLIGHTS_RAGE.json")
|