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:
180
detector_segunda_pasada.py
Normal file
180
detector_segunda_pasada.py
Normal file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Segunda pasada: Filtra los mejores clips para reducir a máximo 15 minutos.
|
||||
Prioriza: Rage/insultos > Muertes de aliados > Jugadas épicas
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def score_highlights(transcripcion_json, highlights_json, max_duration=900):
|
||||
"""
|
||||
Analiza los clips existentes y les da un puntaje.
|
||||
Devuelve los mejores clips hasta max_duration segundos.
|
||||
"""
|
||||
logger.info("=== SEGUNDA PASADA - Filtrando mejores momentos ===")
|
||||
|
||||
with open(transcripcion_json, 'r', encoding='utf-8') as f:
|
||||
trans_data = json.load(f)
|
||||
|
||||
with open(highlights_json, 'r') as f:
|
||||
highlights = json.load(f)
|
||||
|
||||
segments = trans_data.get("segments", [])
|
||||
|
||||
# Patrones de alta prioridad para puntuar clips
|
||||
priority_patterns = {
|
||||
"rage_extreme": [ # 100 puntos - MUY buenos clips
|
||||
r'\bputa (madre|mikdre)\b',
|
||||
r'\bretrasados?\b.*\bmentales?\b',
|
||||
r'\bbinguno de (ustedes|vosotros)\b',
|
||||
r'\babsol[úu]to (inter|in[úu]til|retrasado)\b',
|
||||
r'\binfumable\b',
|
||||
r'\basqueroso\w*\b',
|
||||
r'\bbasura\b',
|
||||
],
|
||||
"ally_death": [ # 80 puntos - Tilt triggers
|
||||
r'\b(ha muerto|murio|muri[óo]|falleci[óo]) (un aliado|un teammate|el compa)\b',
|
||||
r'\b(se ha muerto|mur[ií]o) el (top|jungla|support|adc)\b',
|
||||
r'\b(perdemos|perdi|perdiste) al (top|jg|support)\b',
|
||||
],
|
||||
"epic_plays": [ # 70 puntos - Jugadas épicas
|
||||
r'\b(triple|quadra|penta)( kill)?\b',
|
||||
r'\b(pentakill|ace)\b',
|
||||
r'\bbaron\b.*\b(steal|rob[ao])\b',
|
||||
r'\bdrag[oó]n\b.*\b(steal|rob[ao])\b',
|
||||
r'\bnashor\b.*\b(steal|rob[ao])\b',
|
||||
],
|
||||
"insultos": [ # 60 puntos - Insultos varios
|
||||
r'\b(retrasado|imbecil|est[úu]pido|idiota|burro|tonto|mongolo)\b',
|
||||
r'\bcaraj[oó]\b',
|
||||
r'\bhostia\w*\b',
|
||||
r'\bc[áa]gatear\b',
|
||||
],
|
||||
"skills": [ # 40 puntos - Habilidades
|
||||
r'\b(ulti|ultimate|h)\b',
|
||||
r'\bflash\b',
|
||||
r'\bsmite|ignite|exhaust|teleport|heal\b',
|
||||
],
|
||||
}
|
||||
|
||||
# Analizar cada clip y asignar puntaje
|
||||
scored_clips = []
|
||||
|
||||
for start, end in highlights:
|
||||
clip_duration = end - start
|
||||
|
||||
# Buscar segmentos dentro del clip
|
||||
text_in_clip = []
|
||||
for seg in segments:
|
||||
seg_start = seg["start"]
|
||||
seg_end = seg["end"]
|
||||
|
||||
# Si el segmento está dentro o solapa con el clip
|
||||
if not (seg_end < start or seg_start > end):
|
||||
text_in_clip.append(seg["text"])
|
||||
|
||||
# Unir texto del clip
|
||||
clip_text = " ".join(text_in_clip).lower()
|
||||
|
||||
# Calcular puntaje
|
||||
score = 0
|
||||
matched_types = []
|
||||
|
||||
for event_type, patterns in priority_patterns.items():
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, clip_text, re.IGNORECASE):
|
||||
if event_type == "rage_extreme":
|
||||
score += 100
|
||||
elif event_type == "ally_death":
|
||||
score += 80
|
||||
elif event_type == "epic_plays":
|
||||
score += 70
|
||||
elif event_type == "insultos":
|
||||
score += 60
|
||||
elif event_type == "skills":
|
||||
score += 40
|
||||
|
||||
if event_type not in matched_types:
|
||||
matched_types.append(event_type)
|
||||
break
|
||||
|
||||
# Bonus por duración (clips más largos tienen más contexto)
|
||||
if clip_duration > 60:
|
||||
score += 20
|
||||
elif clip_duration > 45:
|
||||
score += 10
|
||||
|
||||
scored_clips.append({
|
||||
"start": start,
|
||||
"end": end,
|
||||
"duration": clip_duration,
|
||||
"score": score,
|
||||
"types": matched_types
|
||||
})
|
||||
|
||||
# Ordenar por puntaje descendente
|
||||
scored_clips.sort(key=lambda x: (-x["score"], x["start"]))
|
||||
|
||||
# Seleccionar clips hasta max_duration
|
||||
selected = []
|
||||
total_duration = 0
|
||||
|
||||
logger.info("\n=== TOP CLIPS SELECCIONADOS ===")
|
||||
logger.info(f"Puntaje | Duración | Tipo | Timestamp")
|
||||
logger.info("-" * 60)
|
||||
|
||||
for clip in scored_clips:
|
||||
if total_duration + clip["duration"] > max_duration:
|
||||
# Si este clip excede el límite, intentar incluirlo si hay espacio
|
||||
remaining = max_duration - total_duration
|
||||
if remaining >= 30: # Solo si hay espacio para al menos 30s
|
||||
# Recortar el clip
|
||||
selected.append((clip["start"], clip["start"] + remaining))
|
||||
total_duration += remaining
|
||||
logger.info(f"{clip['score']:3d}* | {remaining:3d}s | {clip['types']} | {clip['start']}")
|
||||
break
|
||||
|
||||
selected.append((clip["start"], clip["end"]))
|
||||
total_duration += clip["duration"]
|
||||
|
||||
types_str = ", ".join(clip['types'])
|
||||
logger.info(f"{clip['score']:3d} | {int(clip['duration']):3d}s | {types_str} | {clip['start']}")
|
||||
|
||||
# Ordenar selected por timestamp
|
||||
selected.sort()
|
||||
|
||||
logger.info("-" * 60)
|
||||
logger.info(f"Total: {len(selected)} clips, {int(total_duration)}s ({total_duration/60:.1f} min)")
|
||||
|
||||
return selected
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--transcripcion", required=True)
|
||||
parser.add_argument("--highlights", required=True)
|
||||
parser.add_argument("--output", default="highlights_finales.json")
|
||||
parser.add_argument("--max-duration", type=int, default=900, help="Duración máxima en segundos (default: 900s = 15min)")
|
||||
args = parser.parse_args()
|
||||
|
||||
selected = score_highlights(
|
||||
args.transcripcion,
|
||||
args.highlights,
|
||||
args.max_duration
|
||||
)
|
||||
|
||||
# Guardar
|
||||
with open(args.output, 'w') as f:
|
||||
json.dump(selected, f)
|
||||
|
||||
logger.info(f"Guardado en {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user