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 | |
| import nltk | |
| from nltk.tokenize import sent_tokenize | |
| import numpy as np | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| import re | |
| # Configuraci贸n inicial | |
| nltk.download('punkt', quiet=True) | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| PEXELS_API_KEY = os.getenv("PEXELS_API_KEY") | |
| # Lista de voces v谩lidas | |
| 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="gpt2", # Modelo m谩s flexible | |
| device=0 if torch.cuda.is_available() else -1 | |
| ) | |
| except: | |
| logger.warning("No se pudo cargar el modelo de generaci贸n de texto") | |
| script_generator = None | |
| def generar_guion(prompt): | |
| """Genera un guion natural y extenso basado en el prompt""" | |
| if script_generator: | |
| try: | |
| result = script_generator( | |
| f"Genera un texto detallado y bien estructurado sobre '{prompt}' para un video de YouTube:", | |
| max_length=500, # Texto m谩s largo | |
| temperature=0.9, # M谩s creatividad | |
| num_return_sequences=1 | |
| ) | |
| guion = result[0]['generated_text'] | |
| # Limpiar el guion generado | |
| guion = re.sub(r'<.*?>', '', guion) | |
| guion = re.sub(r'\n+', '\n', guion) | |
| return guion.strip() | |
| except Exception as e: | |
| logger.error(f"Error generando guion: {str(e)}") | |
| # Fallback natural | |
| return f"En este video exploraremos en profundidad el tema de {prompt}. " \ | |
| "Analizaremos diversos aspectos y perspectivas para ofrecer una visi贸n completa. " \ | |
| "Veremos c贸mo este tema se relaciona con nuestra vida cotidiana y su impacto en la sociedad actual." | |
| def extraer_palabras_clave(texto, n=7): | |
| """Extrae palabras clave relevantes usando TF-IDF""" | |
| # Preprocesamiento del texto | |
| texto = re.sub(r'[^\w\s]', '', texto.lower()) | |
| # Tokenizar en oraciones | |
| oraciones = sent_tokenize(texto) | |
| # Crear matriz TF-IDF | |
| vectorizer = TfidfVectorizer( | |
| stop_words=['el', 'la', 'los', 'las', 'de', 'en', 'y', 'que', 'un', 'una', 'con', 'para'], | |
| max_features=500 | |
| ) | |
| X = vectorizer.fit_transform(oraciones) | |
| # Obtener palabras con mayor puntuaci贸n TF-IDF | |
| suma_scores = np.asarray(X.sum(axis=0)).ravel() | |
| indices = np.argsort(suma_scores)[::-1][:n] | |
| palabras = vectorizer.get_feature_names_out() | |
| return [palabras[i] for i in indices] | |
| def buscar_videos_pexels(palabras_clave, num_videos=3): | |
| """Busca videos en Pexels usando palabras clave con enfoque en relevancia""" | |
| try: | |
| headers = {"Authorization": PEXELS_API_KEY} | |
| query = "+".join(palabras_clave[:3]) # Usar las 3 palabras m谩s relevantes | |
| logger.info(f"Buscando videos con palabras clave: {query}") | |
| response = requests.get( | |
| f"https://api.pexels.com/videos/search?query={query}&per_page={num_videos}", | |
| headers=headers, | |
| timeout=15 | |
| ) | |
| videos = response.json().get("videos", []) | |
| # Filtrar videos de alta calidad | |
| return sorted( | |
| videos, | |
| key=lambda x: x.get('duration', 0), | |
| reverse=True | |
| )[: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=25) 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 natural | |
| guion = custom_script if custom_script else generar_guion(prompt) | |
| logger.info(f"Guion generado ({len(guion)} caracteres)") | |
| # 2. Extraer palabras clave del guion completo | |
| palabras_clave = extraer_palabras_clave(guion) | |
| logger.info(f"Palabras clave extra铆das: {', '.join(palabras_clave)}") | |
| # 3. Buscar videos relevantes usando IA | |
| videos_data = buscar_videos_pexels(palabras_clave) | |
| if not videos_data: | |
| raise Exception("No se encontraron videos relevantes. Usando backup...") | |
| # 4. Generar narraci贸n | |
| voz_archivo = "voz.mp3" | |
| subprocess.run([ | |
| 'edge-tts', | |
| '--voice', voz_seleccionada, | |
| '--text', guion, | |
| '--write-media', voz_archivo | |
| ], check=True) | |
| # 5. Procesar audio | |
| audio = AudioFileClip(voz_archivo) | |
| duracion_total = audio.duration | |
| # 6. Descargar y preparar videos | |
| clips = [] | |
| for i, video in enumerate(videos_data): | |
| # Seleccionar la mejor calidad | |
| 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) | |
| # Ajustar duraci贸n proporcional | |
| duracion_clip = min(duracion_total / len(videos_data), clip.duration) | |
| clips.append(clip.subclip(0, duracion_clip)) | |
| # 7. Combinar videos | |
| video_final = concatenate_videoclips(clips) | |
| video_final = video_final.set_audio(audio) | |
| # 8. Exportar | |
| output_path = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4" | |
| video_final.write_videofile( | |
| output_path, | |
| fps=24, | |
| codec="libx264", | |
| audio_codec="aac", | |
| threads=2 | |
| ) | |
| return output_path | |
| except Exception as e: | |
| logger.error(f"ERROR: {str(e)}") | |
| return None | |
| finally: | |
| # Limpieza | |
| if os.path.exists(voz_archivo): | |
| os.remove(voz_archivo) | |
| for i in range(3): | |
| temp_file = f"temp_video_{i}.mp4" | |
| if os.path.exists(temp_file): | |
| os.remove(temp_file) | |
| # Interfaz simplificada y funcional | |
| with gr.Blocks(title="Generador de Videos") as app: | |
| gr.Markdown("# 馃帴 Generador Autom谩tico de Videos") | |
| with gr.Row(): | |
| prompt = gr.Textbox(label="Tema del video", placeholder="Ej: Exploraci贸n espacial") | |
| voz = gr.Dropdown(label="Voz Narradora", choices=VOICES, value=VOICES[0]) | |
| btn = gr.Button("Generar Video", variant="primary") | |
| output = gr.Video(label="Resultado", format="mp4") | |
| btn.click( | |
| fn=crear_video, | |
| inputs=[prompt, gr.Textbox(visible=False), voz, gr.File(visible=False)], | |
| outputs=output | |
| ) | |
| if __name__ == "__main__": | |
| app.launch(server_name="0.0.0.0", server_port=7860) |