INVIDEO_BASIC / app.py
gnosticdev's picture
Update app.py
fa201eb verified
raw
history blame
12 kB
import os
import re
import random
import requests
import gradio as gr
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip
from moviepy.audio.fx.all import audio_loop
import edge_tts
import asyncio
from datetime import datetime
from pathlib import Path
from transformers import pipeline
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
import logging
from typing import List, Optional, Tuple
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Configuración de modelos de IA
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
if not PEXELS_API_KEY:
logger.error("PEXELS_API_KEY no encontrada en variables de entorno")
# Cargamos modelos de IA para análisis semántico
logger.info("Cargando modelos de IA...")
try:
# Modelo para generación de texto
text_generator = pipeline("text-generation", model="facebook/mbart-large-50", device="cpu")
# Modelo para embeddings semánticos (para matching de videos)
semantic_model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')
logger.info("Modelos de IA cargados exitosamente")
except Exception as e:
logger.error(f"Error cargando modelos de IA: {e}")
raise
# Sistema mejorado de búsqueda semántica
def fetch_semantic_videos(query: str, script: str, num_videos: int = 5) -> List[Tuple[str, float]]:
"""Busca videos en Pexels usando matching semántico con el script"""
logger.info(f"Buscando videos semánticos para: '{query}'")
# Generar embedding del script completo
script_embedding = semantic_model.encode(script, convert_to_tensor=True)
headers = {"Authorization": PEXELS_API_KEY}
url = f"https://api.pexels.com/videos/search?query={query}&per_page={num_videos*2}" # Buscamos más para filtrar
try:
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
videos_data = []
for video in response.json().get("videos", []):
# Filtramos por calidad mínima
video_files = [vf for vf in video.get("video_files", [])
if vf.get("width", 0) >= 1280 and vf.get("duration", 0) >= 5]
if video_files:
best_file = max(video_files, key=lambda x: x.get("width", 0))
video_title = video.get("alt", "") or video.get("url", "")
# Calculamos similitud semántica
title_embedding = semantic_model.encode(video_title, convert_to_tensor=True)
similarity = cosine_similarity(
script_embedding.cpu().numpy().reshape(1, -1),
title_embedding.cpu().numpy().reshape(1, -1)
)[0][0]
videos_data.append((best_file["link"], similarity, video_title))
# Ordenamos por relevancia semántica
videos_data.sort(key=lambda x: x[1], reverse=True)
# Filtramos los más relevantes
selected_videos = videos_data[:num_videos]
logger.info(f"Videos encontrados (relevancia):")
for idx, (url, score, title) in enumerate(selected_videos, 1):
logger.info(f"{idx}. {title} (score: {score:.2f})")
return [url for url, _, _ in selected_videos]
except Exception as e:
logger.error(f"Error en búsqueda semántica: {e}")
return []
# Generación de script con contexto mejorado
def generate_script(prompt: str, custom_text: Optional[str] = None) -> str:
"""Genera un script contextualizado con IA"""
if custom_text and custom_text.strip():
return custom_text.strip()
if not prompt or not prompt.strip():
return "Error: Proporciona un tema o guion"
try:
# Prompt mejorado para generación contextual
context_prompt = f"""
Genera un guion detallado para un video sobre '{prompt}'.
El formato debe ser:
1. [Concepto 1]: Descripción breve (15-25 palabras)
2. [Concepto 2]: Descripción breve
...
Incluye detalles visuales entre [] para ayudar a seleccionar imágenes.
Ejemplo: [playa con palmeras] o [ciudad moderna con rascacielos]
"""
generated = text_generator(
context_prompt,
max_length=400,
num_return_sequences=1,
do_sample=True,
temperature=0.7,
top_k=50,
top_p=0.9
)[0]['generated_text']
# Post-procesamiento para limpiar el texto
cleaned = re.sub(r"<.*?>", "", generated) # Remove HTML tags
cleaned = re.sub(r"\n+", "\n", cleaned) # Remove extra newlines
return cleaned.strip()
except Exception as e:
logger.error(f"Error generando script: {e}")
return f"Top 10 sobre {prompt}: [ejemplo 1] Descripción breve..."
# Sistema mejorado de descarga de videos
def download_video_segment(url: str, duration: float, output_path: str) -> bool:
"""Descarga y procesa segmentos de video con manejo robusto"""
temp_path = f"temp_{random.randint(1000,9999)}.mp4"
try:
# Descarga con verificación
with requests.get(url, stream=True, timeout=20) as r:
r.raise_for_status()
with open(temp_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024*1024):
if chunk:
f.write(chunk)
# Procesamiento con controles
with VideoFileClip(temp_path) as clip:
if clip.duration < 2:
raise ValueError("Video demasiado corto")
end_time = min(duration, clip.duration - 0.1)
subclip = clip.subclip(0, end_time)
# Configuración optimizada
subclip.write_videofile(
output_path,
codec="libx264",
audio_codec="aac",
fps=24,
threads=4,
preset='fast',
ffmpeg_params=[
'-max_muxing_queue_size', '1024',
'-crf', '23',
'-movflags', '+faststart'
]
)
return True
except Exception as e:
logger.error(f"Error procesando video: {e}")
return False
finally:
if os.path.exists(temp_path):
os.remove(temp_path)
# Función principal mejorada
def create_contextual_video(prompt: str, custom_text: Optional[str] = None, music_file: Optional[str] = None) -> str:
"""Crea un video con matching semántico entre texto e imágenes"""
# 1. Generación del script
script = generate_script(prompt, custom_text)
logger.info(f"Script generado:\n{script}")
# 2. Búsqueda semántica de videos
search_query = " ".join(extract_keywords(script)) or prompt
video_urls = fetch_semantic_videos(search_query, script)
if not video_urls:
return "Error: No se encontraron videos relevantes. Intenta con otro tema."
# 3. Generación de voz
voice_file = f"voice_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp3"
if not run_async(generate_voice(script, voice_file)):
return "Error: No se pudo generar la narración."
# 4. Procesamiento de videos
output_dir = "output_videos"
os.makedirs(output_dir, exist_ok=True)
output_path = f"{output_dir}/video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
try:
# Descargar y preparar segmentos
clips = []
segment_duration = AudioFileClip(voice_file).duration / len(video_urls)
for idx, url in enumerate(video_urls):
clip_path = f"segment_{idx}.mp4"
if download_video_segment(url, segment_duration, clip_path):
clips.append(VideoFileClip(clip_path))
if not clips:
return "Error: No se pudieron procesar los videos."
# 5. Ensamblaje final
final_video = concatenate_videoclips(clips, method="compose")
audio_clip = AudioFileClip(voice_file)
# Añadir música de fondo si existe
if music_file and os.path.exists(music_file.name):
music = audio_loop(AudioFileClip(music_file.name), duration=audio_clip.duration)
final_audio = CompositeAudioClip([audio_clip, music.volumex(0.2)])
else:
final_audio = audio_clip
final_video = final_video.set_audio(final_audio)
# Renderizado final optimizado
final_video.write_videofile(
output_path,
codec="libx264",
audio_codec="aac",
fps=24,
threads=6,
preset='fast',
bitrate="5000k"
)
return output_path
except Exception as e:
logger.error(f"Error crítico al crear video: {e}")
return f"Error: Fallo en la creación del video - {str(e)}"
finally:
# Limpieza
for clip in clips:
clip.close()
if os.path.exists(voice_file):
os.remove(voice_file)
for i in range(len(video_urls)):
if os.path.exists(f"segment_{i}.mp4"):
os.remove(f"segment_{i}.mp4")
# Interfaz mejorada
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🎬 Generador de Videos con IA Semántica
**Crea videos donde las imágenes coinciden perfectamente con tu texto**
""")
with gr.Row():
with gr.Column(scale=1):
gr.Image("https://i.imgur.com/7X8P5R8.png", label="Ejemplo Visual")
with gr.Accordion("📌 Consejos para mejores resultados", open=False):
gr.Markdown("""
- **Describe tu tema con detalles**: "Playas del Caribe con arena blanca" en vez de solo "playas"
- **Usa sustantivos concretos**: "Animales de la selva amazónica" > "naturaleza"
- **Sé específico**: "Tecnología 2024" > "Avances en inteligencia artificial 2024"
""")
gr.Examples(
examples=[
["Lugares históricos de Europa con arquitectura medieval"],
["Tecnologías emergentes en inteligencia artificial para 2024"],
["Recetas tradicionales mexicanas con ingredientes autóctonos"]
],
inputs=[prompt],
label="Ejemplos de prompts efectivos"
)
with gr.Column(scale=2):
prompt = gr.Textbox(
label="Tema principal del video",
placeholder="Ej: 'Top 5 innovaciones tecnológicas de 2024'",
max_lines=2
)
custom_text = gr.TextArea(
label="O escribe tu propio guion (opcional)",
placeholder="Ej: 1. [Robot humanoide] Avances en robótica...",
lines=6
)
music_file = gr.File(
label="Música de fondo (opcional - MP3)",
type="filepath",
file_types=[".mp3"]
)
submit = gr.Button("🚀 Generar Video", variant="primary")
output = gr.Video(
label="Video Generado",
format="mp4",
interactive=False
)
submit.click(
fn=create_contextual_video,
inputs=[prompt, custom_text, music_file],
outputs=output,
api_name="generate_video"
)
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
debug=True
)