- 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
197 lines
6.5 KiB
Python
197 lines
6.5 KiB
Python
#!/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()
|