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