Spaces:
Running
Running
| 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) |