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:
217
pipeline_dos_pasadas.py
Normal file
217
pipeline_dos_pasadas.py
Normal file
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Workflow de dos pasadas para highlights:
|
||||
1. 360p (rápido) → Previsualización → Confirmación usuario
|
||||
2. 1080p (calidad) → Video final
|
||||
"""
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def download_video(video_id, quality="360p", output="video.mp4"):
|
||||
"""Descarga video con streamlink"""
|
||||
logger.info(f"Descargando video en {quality}...")
|
||||
|
||||
# Mapeo de calidad
|
||||
quality_map = {
|
||||
"360p": "360p,480p,best",
|
||||
"1080p": "1080p,720p,best"
|
||||
}
|
||||
|
||||
cmd = [
|
||||
"streamlink",
|
||||
f"https://www.twitch.tv/videos/{video_id}",
|
||||
quality_map[quality],
|
||||
"-o", output
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True)
|
||||
if result.returncode != 0:
|
||||
logger.error(f"Error descargando video: {result.stderr.decode()}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def download_chat(video_id, output="chat.json"):
|
||||
"""Descarga chat con TwitchDownloaderCLI"""
|
||||
logger.info(f"Descargando chat...")
|
||||
|
||||
cmd = ["dotnet", "/tmp/TDC_output/TwitchDownloaderCLI.dll",
|
||||
"chatdownload", "--id", video_id, "-o", output]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True)
|
||||
return result.returncode == 0
|
||||
|
||||
def detect_highlights(video, chat, output="highlights.json",
|
||||
threshold=0.8, min_duration=5):
|
||||
"""Detecta highlights con GPU"""
|
||||
logger.info(f"Detectando highlights (threshold={threshold}, min_duration={min_duration})...")
|
||||
|
||||
cmd = [
|
||||
"python3", "detector_gpu.py",
|
||||
"--video", video,
|
||||
"--chat", chat,
|
||||
"--output", output,
|
||||
"--threshold", str(threshold),
|
||||
"--min-duration", str(min_duration),
|
||||
"--device", "cuda"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
|
||||
# Cargar resultados
|
||||
with open(output, 'r') as f:
|
||||
highlights = json.load(f)
|
||||
|
||||
return highlights
|
||||
|
||||
def generate_summary(video, highlights, output, padding=5):
|
||||
"""Genera video resumen"""
|
||||
logger.info(f"Generando video resumen ({len(highlights)} clips)...")
|
||||
|
||||
cmd = [
|
||||
"python3", "generate_video.py",
|
||||
"--video", video,
|
||||
"--highlights", highlights,
|
||||
"--output", output,
|
||||
"--padding", str(padding)
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
return result.returncode == 0
|
||||
|
||||
def format_timestamp(seconds):
|
||||
"""Formatea segundos a HH:MM:SS"""
|
||||
hours = seconds // 3600
|
||||
minutes = (seconds % 3600) // 60
|
||||
secs = seconds % 60
|
||||
return f"{hours:02d}:{minutes:02d}:{secs:02d}"
|
||||
|
||||
def show_highlights(highlights):
|
||||
"""Muestra resumen de highlights"""
|
||||
total_duration = sum(e - s for s, e in highlights)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("HIGHLIGHTS DETECTADOS".center(60))
|
||||
print("=" * 60)
|
||||
print(f"Total: {len(highlights)} clips")
|
||||
print(f"Duración total: {total_duration}s ({total_duration/60:.1f} minutos)")
|
||||
print("-" * 60)
|
||||
|
||||
for i, (start, end) in enumerate(highlights[:20], 1):
|
||||
duration = end - start
|
||||
print(f"{i:2d}. {format_timestamp(start)} - {format_timestamp(end)} ({duration}s)")
|
||||
|
||||
if len(highlights) > 20:
|
||||
print(f"... y {len(highlights) - 20} más")
|
||||
|
||||
print("=" * 60)
|
||||
|
||||
def confirm_action():
|
||||
"""Pide confirmación al usuario"""
|
||||
response = input("\n¿Generar versión en 1080p? (s/n): ").strip().lower()
|
||||
return response in ['s', 'si', 'y', 'yes']
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--video-id", required=True)
|
||||
parser.add_argument("--threshold", type=float, default=0.8)
|
||||
parser.add_argument("--min-duration", type=int, default=8)
|
||||
parser.add_argument("--skip-360p", action="store_true")
|
||||
parser.add_argument("--force-1080p", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
video_id = args.video_id
|
||||
|
||||
if args.force_1080p:
|
||||
# Directo a 1080p
|
||||
logger.info("Modo: Generar directamente en 1080p")
|
||||
video_file = f"stream_{video_id}_1080p.mp4"
|
||||
chat_file = f"chat_{video_id}.json"
|
||||
highlights_file = f"highlights_{video_id}.json"
|
||||
output_file = f"resumen_{video_id}_1080p.mp4"
|
||||
|
||||
if not download_video(video_id, "1080p", video_file):
|
||||
return 1
|
||||
|
||||
if not download_chat(video_id, chat_file):
|
||||
return 1
|
||||
|
||||
highlights = detect_highlights(video_file, chat_file, highlights_file,
|
||||
args.threshold, args.min_duration)
|
||||
show_highlights(highlights)
|
||||
|
||||
generate_summary(video_file, highlights_file, output_file)
|
||||
print(f"\n✅ Video final: {output_file}")
|
||||
|
||||
elif not args.skip_360p:
|
||||
# PASADA 1: 360p (previsualización rápida)
|
||||
logger.info("PASADA 1: Procesando en 360p para previsualización")
|
||||
|
||||
video_360 = f"stream_{video_id}_360p.mp4"
|
||||
chat_file = f"chat_{video_id}.json"
|
||||
highlights_file = f"highlights_{video_id}.json"
|
||||
output_360 = f"preview_{video_id}_360p.mp4"
|
||||
|
||||
# Descargar 360p
|
||||
if not download_video(video_id, "360p", video_360):
|
||||
return 1
|
||||
|
||||
# Descargar chat
|
||||
if not download_chat(video_id, chat_file):
|
||||
return 1
|
||||
|
||||
# Detectar highlights
|
||||
highlights = detect_highlights(video_360, chat_file, highlights_file,
|
||||
args.threshold, args.min_duration)
|
||||
|
||||
if not highlights:
|
||||
print("❌ No se detectaron highlights. Intenta con threshold más bajo.")
|
||||
return 1
|
||||
|
||||
# Mostrar resultados
|
||||
show_highlights(highlights)
|
||||
|
||||
# Generar previsualización 360p
|
||||
generate_summary(video_360, highlights_file, output_360)
|
||||
print(f"\n📺 Previsualización: {output_360}")
|
||||
|
||||
# Confirmar para 1080p
|
||||
if confirm_action():
|
||||
# PASADA 2: 1080p (calidad final)
|
||||
logger.info("PASADA 2: Procesando en 1080p para calidad final")
|
||||
|
||||
video_1080 = f"stream_{video_id}_1080p.mp4"
|
||||
output_1080 = f"resumen_{video_id}_1080p.mp4"
|
||||
|
||||
print(f"\n⏳ Descargando video en 1080p...")
|
||||
if not download_video(video_id, "1080p", video_1080):
|
||||
print("❌ Error descargando en 1080p. Puedes usar el video 360p.")
|
||||
return 1
|
||||
|
||||
# Reusar highlights ya detectados
|
||||
generate_summary(video_1080, highlights_file, output_1080)
|
||||
|
||||
print(f"\n✅ Video final 1080p: {output_1080}")
|
||||
print(f"📺 Previsualización 360p: {output_360}")
|
||||
print(f"📊 Highlights: {highlights_file}")
|
||||
else:
|
||||
print("\n✅ Proceso cancelado. Puedes usar:")
|
||||
print(f" - Video 360p: {video_360}")
|
||||
print(f" - Previsualización: {output_360}")
|
||||
print(f" - Highlights JSON: {highlights_file}")
|
||||
|
||||
else:
|
||||
print("Usa --force-1080p para generar directamente en 1080p")
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user