Spaces:
Sleeping
Sleeping
import os | |
import subprocess | |
import requests | |
import gradio as gr | |
from moviepy.editor import * | |
from datetime import datetime | |
import logging | |
import re | |
import torch | |
from transformers import GPT2LMHeadModel, GPT2Tokenizer | |
import warnings | |
# Configuración inicial | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# Suprimir warnings específicos de transformers | |
warnings.filterwarnings("ignore", category=UserWarning, module="transformers") | |
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY") | |
# Lista de voces válidas | |
VOICES = [ | |
"es-MX-DaliaNeural", "es-ES-ElviraNeural", "es-AR-ElenaNeural", | |
"es-MX-JorgeNeural", "es-ES-AlvaroNeural", "es-AR-TomasNeural", | |
"en-US-JennyNeural", "fr-FR-DeniseNeural", "de-DE-KatjaNeural", | |
"it-IT-ElsaNeural", "pt-BR-FranciscaNeural", "ja-JP-NanamiNeural", | |
"en-GB-SoniaNeural", "es-CL-CatalinaNeural", "es-CO-GonzaloNeural" | |
] | |
# Cargar modelo y tokenizador de GPT-2 en español | |
try: | |
tokenizer = GPT2Tokenizer.from_pretrained("datificate/gpt2-small-spanish") | |
model = GPT2LMHeadModel.from_pretrained("datificate/gpt2-small-spanish") | |
logger.info("Modelo GPT-2 en español cargado correctamente") | |
except Exception as e: | |
logger.error(f"Error cargando el modelo: {str(e)}") | |
model = None | |
tokenizer = None | |
def generar_guion_largo(tema, custom_script=None): | |
"""Genera un texto largo sobre el tema usando GPT-2 con configuración correcta""" | |
if custom_script: | |
return custom_script | |
if model is None or tokenizer is None: | |
return f"Texto generado automáticamente sobre {tema}. " * 50 | |
try: | |
# Prompt directo como solicitaste | |
prompt = f"Escribe un texto largo y detallado sobre {tema}" | |
inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True) | |
# Generar texto con configuración corregida | |
outputs = model.generate( | |
inputs.input_ids, | |
max_length=800, | |
do_sample=True, | |
temperature=0.9, | |
top_k=50, | |
top_p=0.95, | |
num_return_sequences=1, | |
pad_token_id=tokenizer.eos_token_id, | |
# early_stopping=True # Eliminado para evitar warnings | |
) | |
guion = tokenizer.decode(outputs[0], skip_special_tokens=True) | |
# Limpiar texto | |
guion = re.sub(r'<.*?>', '', guion) | |
guion = re.sub(r'\n+', '\n', guion) | |
guion = re.sub(r'\s+', ' ', guion).strip() | |
logger.info(f"Guion generado: {len(guion)} caracteres") | |
return guion | |
except Exception as e: | |
logger.error(f"Error generando guion: {str(e)}") | |
return f"Texto generado automáticamente sobre {tema}. " * 50 | |
def buscar_videos_pexels(tema, num_videos=4): | |
"""Busca videos en Pexels usando el tema directamente""" | |
try: | |
headers = {"Authorization": PEXELS_API_KEY} | |
logger.info(f"Buscando videos para: {tema}") | |
response = requests.get( | |
f"https://api.pexels.com/videos/search?query={tema}&per_page={num_videos}", | |
headers=headers, | |
timeout=15 | |
) | |
if response.status_code != 200: | |
return [] | |
return response.json().get("videos", [])[:num_videos] | |
except Exception as e: | |
logger.error(f"Error buscando videos: {str(e)}") | |
return [] | |
def descargar_video(url, output_path): | |
"""Descarga un video de manera eficiente""" | |
try: | |
with requests.get(url, stream=True, timeout=30) as r: | |
r.raise_for_status() | |
with open(output_path, 'wb') as f: | |
for chunk in r.iter_content(chunk_size=8192): | |
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 guion largo | |
guion = generar_guion_largo(prompt, custom_script) | |
# 2. Generar narración | |
voz_archivo = "voz.mp3" | |
subprocess.run([ | |
'edge-tts', | |
'--voice', voz_seleccionada, | |
'--text', guion, | |
'--write-media', voz_archivo | |
], check=True) | |
# 3. Procesar audio principal | |
audio = AudioFileClip(voz_archivo) | |
duracion_total = audio.duration | |
# 4. Buscar videos relevantes | |
videos_data = buscar_videos_pexels(prompt) | |
if not videos_data: | |
logger.warning("No se encontraron videos. Usando videos genéricos...") | |
videos_data = buscar_videos_pexels("nature") | |
# 5. Descargar y preparar videos | |
clips = [] | |
for i, video in enumerate(videos_data): | |
try: | |
# Seleccionar la mejor calidad disponible | |
if 'video_files' not in video or not video['video_files']: | |
continue | |
video_file = max( | |
video['video_files'], | |
key=lambda x: x.get('width', 0) * x.get('height', 0) | |
) | |
video_url = video_file['link'] | |
temp_path = f"temp_video_{i}.mp4" | |
if descargar_video(video_url, temp_path): | |
clip = VideoFileClip(temp_path) | |
# Calcular duración proporcional para cada clip | |
duracion_clip = duracion_total / len(videos_data) | |
# Ajustar clip a la duración requerida | |
if clip.duration < duracion_clip: | |
clip = clip.loop(duration=duracion_clip) | |
else: | |
clip = clip.subclip(0, duracion_clip) | |
clips.append(clip) | |
except Exception as e: | |
logger.error(f"Error procesando video {i}: {str(e)}") | |
if not clips: | |
# Crear video de fondo negro si no hay videos | |
clip = ColorClip(size=(1280, 720), color=(0, 0, 0), duration=duracion_total) | |
clips = [clip] | |
# 6. Combinar videos | |
final_clip = concatenate_videoclips(clips, method="compose") | |
final_clip = final_clip.set_audio(audio) | |
# 7. Aplicar música de fondo si existe | |
if musica: | |
try: | |
musica_clip = AudioFileClip(musica.name) | |
if musica_clip.duration < duracion_total: | |
musica_clip = musica_clip.loop(duration=duracion_total) | |
else: | |
musica_clip = musica_clip.subclip(0, duracion_total) | |
audio_final = CompositeAudioClip([ | |
audio.volumex(1.0), | |
musica_clip.volumex(0.25) | |
]) | |
final_clip = final_clip.set_audio(audio_final) | |
except Exception as e: | |
logger.error(f"Error procesando música: {str(e)}") | |
# 8. Exportar video final | |
output_path = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4" | |
final_clip.write_videofile( | |
output_path, | |
fps=24, | |
codec="libx264", | |
audio_codec="aac", | |
threads=4, | |
preset='fast' | |
) | |
logger.info(f"Video generado exitosamente: {output_path}") | |
return output_path | |
except Exception as e: | |
logger.error(f"ERROR: {str(e)}") | |
return None | |
finally: | |
# Limpieza garantizada | |
if os.path.exists(voz_archivo): | |
os.remove(voz_archivo) | |
for i in range(5): | |
temp_file = f"temp_video_{i}.mp4" | |
if os.path.exists(temp_file): | |
os.remove(temp_file) | |
# Interfaz mejorada | |
with gr.Blocks(title="Generador de Videos", theme=gr.themes.Soft()) as app: | |
gr.Markdown("# 🎬 GENERADOR AUTOMÁTICO DE VIDEOS") | |
with gr.Row(): | |
with gr.Column(): | |
prompt = gr.Textbox( | |
label="Tema del video", | |
placeholder="Ej: 'La historia de la piratería en el Caribe'", | |
max_lines=1 | |
) | |
custom_script = gr.TextArea( | |
label="Guion personalizado (opcional)", | |
placeholder="Pega tu guion completo aquí...", | |
lines=8, | |
max_lines=20 | |
) | |
voz = gr.Dropdown( | |
label="Voz Narradora", | |
choices=VOICES, | |
value=VOICES[0], | |
interactive=True | |
) | |
musica = gr.File( | |
label="Música de fondo (opcional)", | |
file_types=["audio"], | |
type="filepath" | |
) | |
btn = gr.Button("Generar Video", variant="primary") | |
with gr.Column(): | |
output = gr.Video( | |
label="Video Resultado", | |
format="mp4", | |
interactive=False | |
) | |
btn.click( | |
fn=crear_video, | |
inputs=[prompt, custom_script, voz, musica], | |
outputs=output | |
) | |
if __name__ == "__main__": | |
app.launch(server_name="0.0.0.0", server_port=7860) |