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:
196
detector_minimax.py
Normal file
196
detector_minimax.py
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Detector de highlights usando minimax API (OpenAI compatible).
|
||||
Analiza la transcripción de Whisper para encontrar momentos interesantes.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Importar OpenAI SDK
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError:
|
||||
print("Instalando openai...")
|
||||
import subprocess
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "openai", "--break-system-packages", "-q"])
|
||||
from openai import OpenAI
|
||||
|
||||
|
||||
def detect_with_minimax(transcripcion_json, output_json="highlights_minimax.json"):
|
||||
"""
|
||||
Carga la transcripción y usa minimax para encontrar highlights.
|
||||
"""
|
||||
# Obtener credenciales de variables de entorno (OpenAI compatible)
|
||||
base_url = os.environ.get("OPENAI_BASE_URL", "https://api.minimax.io/v1")
|
||||
api_key = os.environ.get("OPENAI_API_KEY")
|
||||
|
||||
if not api_key:
|
||||
logger.error("Se necesita OPENAI_API_KEY")
|
||||
return None
|
||||
|
||||
logger.info(f"Usando endpoint: {base_url}")
|
||||
|
||||
logger.info(f"Cargando transcripción de {transcripcion_json}...")
|
||||
|
||||
with open(transcripcion_json, 'r', encoding='utf-8') as f:
|
||||
transcripcion_data = json.load(f)
|
||||
|
||||
# Crear un resumen estructurado para la IA
|
||||
segments = transcripcion_data.get("segments", [])
|
||||
|
||||
# Crear transcripción con timestamps
|
||||
transcript_lines = []
|
||||
for seg in segments[:800]: # Limitar segmentos
|
||||
start = int(seg["start"])
|
||||
end = int(seg["end"])
|
||||
text = seg["text"].strip()
|
||||
if len(text) > 3: # Filtrar textos muy cortos
|
||||
mins = start // 60
|
||||
secs = start % 60
|
||||
timestamp = f"[{mins:02d}:{secs:02d}]"
|
||||
transcript_lines.append(f"{timestamp} {text}")
|
||||
|
||||
full_text = "\n".join(transcript_lines)
|
||||
|
||||
logger.info(f"Enviando a minimax ({len(full_text)} caracteres)...")
|
||||
|
||||
# Crear cliente OpenAI con endpoint de minimax
|
||||
client = OpenAI(
|
||||
base_url=base_url,
|
||||
api_key=api_key
|
||||
)
|
||||
|
||||
prompt = f"""Eres un experto editor de highlights de gaming (Twitch/YouTube). Tu especialidad es identificar MOMENTOS ÉPICOS y VIRALES.
|
||||
|
||||
TRANSCRIPCIÓN DEL STREAM:
|
||||
{full_text}
|
||||
|
||||
TU ÚNICA MISIÓN:
|
||||
Encuentra 20-30 CLIPS CORTOS (15-30 segundos cada uno) que sean VIRALICOS.
|
||||
|
||||
SOLO busca estos tipos de momentos:
|
||||
1. **JUGADAS ÉPICAS**: Multi-kills, clutches, jugadas increíbles, moments de gran habilidad
|
||||
2. **RISAS/GRACIAS**: Momentos donde el streamer se ríe a carcajadas, algo gracioso pasa
|
||||
3. **REACCIONES ÉPICAS**: Gritos de emoción, sorpresa extrema, momentos de "¡NO LO PUEDO CREER!"
|
||||
|
||||
LO QUE DEBES EVITAR ABSOLUTAMENTE:
|
||||
❌ Quejas/rage sobre el juego (insultos, frustración)
|
||||
❌ Hablar de cargar partidas, esperar, problemas técnicos
|
||||
❌ Conversaciones normales/aburridas
|
||||
❌ Análisis estratégicos aburridos
|
||||
❌ Saludos, intros, despedidas
|
||||
❌ Leer chat o spam
|
||||
|
||||
REGLAS CRÍTICAS:
|
||||
- Cada clip debe durar 15-30 segundos MÁXIMO
|
||||
- Cada clip debe tener una "recompensa" inmediata (risa, emoción, jugada épica)
|
||||
- Prioriza CLARIDAD sobre cantidad: es mejor 10 clips geniales que 30 clips regulares
|
||||
- Busca PATRONES específicos: "¡!", risas ("jajaja", "jeje"), gritos ("¡PUTA!", "¡QUE!", "¡NO!")
|
||||
|
||||
FORMATO DE RESPUESTA (solo JSON válido):
|
||||
{{
|
||||
"highlights": [
|
||||
{{"start": 123, "end": 144, "reason": "razón muy breve"}},
|
||||
{{"start": 456, "end": 477, "reason": "razón muy breve"}}
|
||||
]
|
||||
}}
|
||||
|
||||
Timestamps en SEGUNDOS del video."""
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="MiniMax-M2.5", # Modelo de minimax
|
||||
messages=[
|
||||
{"role": "system", "content": "Eres un experto editor de contenido de Twitch que identifica momentos memorables."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=4096
|
||||
)
|
||||
|
||||
content = response.choices[0].message.content
|
||||
|
||||
# Buscar JSON en la respuesta
|
||||
import re
|
||||
json_match = re.search(r'\{[\s\S]*\}', content)
|
||||
if json_match:
|
||||
result = json.loads(json_match.group())
|
||||
else:
|
||||
logger.error("No se encontró JSON válido en la respuesta")
|
||||
logger.debug(f"Respuesta: {content}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error llamando API minimax: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
if result and "highlights" in result:
|
||||
highlights = result["highlights"]
|
||||
|
||||
# Validar y filtrar highlights
|
||||
valid_intervals = []
|
||||
for h in highlights:
|
||||
start = int(h["start"])
|
||||
end = int(h["end"])
|
||||
duration = end - start
|
||||
# Filtrar: duración entre 12 y 45 segundos (clips muy cortos)
|
||||
if 12 <= duration <= 45:
|
||||
valid_intervals.append({
|
||||
"start": start,
|
||||
"end": end,
|
||||
"reason": h.get("reason", "N/A")
|
||||
})
|
||||
|
||||
# Convertir a formato de intervalos
|
||||
intervals = [[h["start"], h["end"]] for h in valid_intervals]
|
||||
|
||||
# Guardar con detalles
|
||||
with open(output_json, 'w') as f:
|
||||
json.dump({"intervals": intervals, "details": valid_intervals}, f, indent=2)
|
||||
|
||||
logger.info(f"Guardado en {output_json}")
|
||||
|
||||
# Imprimir resumen
|
||||
print(f"\n{'='*70}")
|
||||
print(f"HIGHLIGHTS DETECTADOS POR minimax".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, h in enumerate(valid_intervals, 1):
|
||||
start = h["start"]
|
||||
end = h["end"]
|
||||
duration = end - start
|
||||
hours = start // 3600
|
||||
mins = (start % 3600) // 60
|
||||
secs = start % 60
|
||||
reason = h["reason"]
|
||||
print(f"{i:2d}. {hours:02d}:{mins:02d}:{secs:02d} - {duration}s - {reason}")
|
||||
|
||||
print(f"{'='*70}")
|
||||
|
||||
return intervals
|
||||
else:
|
||||
logger.error("No se pudo obtener highlights de minimax")
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--transcripcion", required=True, help="Archivo JSON de transcripción de Whisper")
|
||||
parser.add_argument("--output", default="highlights_minimax.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
detect_with_minimax(args.transcripcion, args.output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user