#!/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())