import os import requests import asyncio import tempfile import logging from datetime import datetime import gradio as gr from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip import edge_tts from transformers import pipeline import torch logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # --- CONFIG --- PEXELS_API_KEY = os.getenv("PEXELS_API_KEY") # Modelo de texto en español MODEL_NAME = "DeepESP/gpt2-spanish" # Inicializar generador de texto device = 0 if torch.cuda.is_available() else -1 generator = pipeline("text-generation", model=MODEL_NAME, device=device) async def listar_voces(): try: return await edge_tts.list_voices() except Exception as e: logger.error(f"Error obteniendo voces: {e}") return [{'ShortName': 'es-ES-ElviraNeural', 'Name': 'Elvira', 'Gender': 'Female'}] VOCES = asyncio.run(listar_voces()) VOICE_NAMES = [f"{v['Name']} ({v['Gender']})" for v in VOCES] def generar_guion(prompt): prompt_text = f"Escribe un guion profesional para un vídeo de YouTube sobre '{prompt}':\n" try: out = generator(prompt_text, max_new_tokens=300, temperature=0.7, truncate=True, num_return_sequences=1) texto = out[0]['generated_text'] return texto except Exception as e: logger.error(f"Error generando guion: {e}") return f"Error generando guion: {e}" def buscar_videos(prompt, max_videos=3): try: url = f"https://api.pexels.com/videos/search?query={prompt}&per_page={max_videos}" resp = requests.get(url, headers={"Authorization": PEXELS_API_KEY}, timeout=10) return resp.json().get("videos", []) except Exception as e: logger.error(f"Error en Pexels: {e}") return [] async def crear_video(prompt, voz_index, musica_path=None): texto = generar_guion(prompt) voz_short = VOCES[voz_index]['ShortName'] audio_file = "tts.mp3" await edge_tts.Communicate(texto, voz_short).save(audio_file) tts_audio = AudioFileClip(audio_file) dur = tts_audio.duration # Música opcional if musica_path: music = AudioFileClip(musica_path).volumex(0.2).subclip(0, dur) audio_comp = CompositeAudioClip([music, tts_audio]) else: audio_comp = tts_audio videos = buscar_videos(prompt) if not videos: return None clips = [] for v in videos: url = v['video_files'][0]['link'] r = requests.get(url, stream=True, timeout=15) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") [tmp.write(c) for c in r.iter_content(1024*1024)] tmp.close() clip = VideoFileClip(tmp.name).subclip(0, min(dur/len(videos), v['duration'])) clips.append(clip) final = concatenate_videoclips(clips).set_audio(audio_comp) out_name = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4" final.write_videofile(out_name, codec="libx264", audio_codec="aac", fps=24) # Cleanup tts_audio.close() for c in clips: c.close() os.remove(c.filename) os.remove(audio_file) return out_name def run_crear(prompt, voz_name, muz_file): try: idx = VOICE_NAMES.index(voz_name) except: idx = 0 return asyncio.run(crear_video(prompt, idx, muz_file.name if muz_file else None)) with gr.Blocks() as app: tema = gr.Textbox(label="Tema para guion y video") voz = gr.Dropdown(choices=VOICE_NAMES, value=VOICE_NAMES[0], label="Voz TTS") muz = gr.File(label="Música fondo opcional (mp3/wav)", file_types=[".mp3", ".wav"]) btn = gr.Button("Crear video") vid_out = gr.Video(label="Video generado") btn.click(fn=run_crear, inputs=[tema, voz, muz], outputs=vid_out) if __name__ == "__main__": app.launch()