import os import subprocess import requests import gradio as gr from moviepy.editor import * from datetime import datetime import tempfile import logging from transformers import pipeline # Configuración inicial logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) PEXELS_API_KEY = os.getenv("PEXELS_API_KEY") # Configurar en Hugging Face # Lista de voces válidas (puedes añadir más) VOICES = [ "es-MX-DaliaNeural", "es-ES-ElviraNeural", "es-AR-ElenaNeural", "en-US-JennyNeural", "fr-FR-DeniseNeural", "de-DE-KatjaNeural", "it-IT-ElsaNeural", "pt-BR-FranciscaNeural", "ja-JP-NanamiNeural" ] # Inicializar el generador de texto try: script_generator = pipeline("text-generation", model="facebook/mbart-large-50") except: logger.warning("No se pudo cargar el modelo de generación de texto") script_generator = None def generar_guion(prompt): """Genera un guion automático usando IA""" if script_generator: try: result = script_generator( f"Genera un guion breve para un video sobre '{prompt}' con 3 puntos principales:", max_length=250, num_return_sequences=1 ) return result[0]['generated_text'] except Exception as e: logger.error(f"Error generando guion: {str(e)}") # Fallback si falla la generación return f"1. Primer punto sobre {prompt}\n2. Segundo punto\n3. Tercer punto" def descargar_video(url, output_path): """Descarga un video y lo guarda localmente""" try: response = requests.get(url, stream=True, timeout=20) response.raise_for_status() with open(output_path, 'wb') as f: for chunk in response.iter_content(chunk_size=1024*1024): # 1MB chunks if chunk: f.write(chunk) return True except Exception as e: logger.error(f"Error descargando video: {str(e)}") return False def crear_video(prompt, custom_script, voz_seleccionada, musica=None): try: # 1. Generar o usar guion guion = custom_script if custom_script else generar_guion(prompt) logger.info(f"Guion: {guion[:100]}...") # 2. Generar voz voz_archivo = "voz.mp3" subprocess.run([ 'edge-tts', '--voice', voz_seleccionada, '--text', guion, '--write-media', voz_archivo ], check=True) # 3. Buscar videos en Pexels headers = {"Authorization": PEXELS_API_KEY} response = requests.get( f"https://api.pexels.com/videos/search?query={prompt[:50]}&per_page=3", headers=headers, timeout=15 ) videos_data = response.json().get("videos", []) if not videos_data: raise Exception("No se encontraron videos en Pexels") # 4. Descargar y preparar clips de video clips = [] for i, video in enumerate(videos_data[:3]): # Seleccionar la mejor calidad de video disponible video_files = sorted( [vf for vf in video['video_files'] if vf.get('width')], key=lambda x: x['width'], reverse=True ) if not video_files: continue video_url = video_files[0]['link'] temp_video_path = f"temp_video_{i}.mp4" if descargar_video(video_url, temp_video_path): clip = VideoFileClip(temp_video_path) # Calcular duración proporcional clip_duration = min(10, clip.duration) # Máximo 10 segundos por clip clips.append(clip.subclip(0, clip_duration)) if not clips: raise Exception("No se pudieron cargar videos válidos") # 5. Procesar audio audio = AudioFileClip(voz_archivo) total_duration = audio.duration if musica: musica_clip = AudioFileClip(musica.name) if musica_clip.duration < total_duration: # Crear loop si la música es más corta looped_music = musica_clip.loop(duration=total_duration) else: looped_music = musica_clip.subclip(0, total_duration) audio = CompositeAudioClip([audio, looped_music.volumex(0.25)]) # 6. Crear video final # Calcular duración por clip clip_durations = [c.duration for c in clips] total_clip_duration = sum(clip_durations) scale_factor = total_duration / total_clip_duration if total_clip_duration > 0 else 1 # Ajustar velocidad de los clips para que coincidan con el audio adjusted_clips = [c.fx(vfx.speedx, scale_factor) for c in clips] final_clip = concatenate_videoclips(adjusted_clips, method="compose") # Aplicar transición suave entre clips final_clip = final_clip.fx(vfx.fadein, 0.5).fx(vfx.fadeout, 0.5) # Ajustar duración exacta final_clip = final_clip.set_duration(total_duration).set_audio(audio) # 7. Guardar video final output_path = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4" final_clip.write_videofile( output_path, codec="libx264", audio_codec="aac", threads=2, preset='fast', fps=24 ) return output_path except Exception as e: logger.error(f"ERROR: {str(e)}") return None finally: # Limpieza de archivos temporales if os.path.exists(voz_archivo): os.remove(voz_archivo) for i in range(3): if os.path.exists(f"temp_video_{i}.mp4"): os.remove(f"temp_video_{i}.mp4") # Interfaz Gradio mejorada with gr.Blocks(theme=gr.themes.Soft(), title="Generador de Videos Profesional") as app: gr.Markdown("# 🎬 GENERADOR DE VIDEOS AUTOMÁTICO") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### Configuración del Video") prompt = gr.Textbox(label="Tema principal", placeholder="Ej: 'Lugares misteriosos de España'") custom_script = gr.TextArea( label="Guion personalizado (opcional)", placeholder="Pega aquí tu propio guion...", lines=5 ) voz = gr.Dropdown( label="Selecciona una voz", choices=VOICES, value="es-ES-ElviraNeural", interactive=True ) musica = gr.File( label="Música de fondo (opcional)", file_types=[".mp3", ".wav"], type="filepath" ) btn = gr.Button("🚀 GENERAR VIDEO", variant="primary", size="lg") with gr.Column(scale=2): output = gr.Video( label="Video Resultante", format="mp4", interactive=False, elem_id="video-player" ) gr.Examples( examples=[ ["Lugares históricos de Roma", "", "it-IT-ElsaNeural", None], ["Tecnologías del futuro", "", "en-US-JennyNeural", None], ["Playas paradisíacas del Caribe", "", "es-MX-DaliaNeural", None] ], inputs=[prompt, custom_script, voz, musica], label="Ejemplos para probar" ) btn.click( fn=crear_video, inputs=[prompt, custom_script, voz, musica], outputs=output ) # CSS para mejorar la visualización app.css = """ #video-player { max-width: 100%; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); } """ if __name__ == "__main__": app.launch(server_name="0.0.0.0", server_port=7860)