INVIDEO_BASIC / app.py
gnosticdev's picture
Update app.py
b82e6a6 verified
raw
history blame
8.45 kB
import os
import asyncio
import logging
import tempfile
import time
import shutil
from datetime import datetime, timedelta
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip, ColorClip, TextClip, CompositeVideoClip
import edge_tts
import gradio as gr
# Configuración avanzada de logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("video_generator.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Directorio temporal personalizado con limpieza automática
TEMP_DIR = "temp_media"
os.makedirs(TEMP_DIR, exist_ok=True)
def clean_old_files():
"""Elimina archivos temporales con más de 24 horas"""
now = time.time()
cutoff = now - (24 * 3600)
for filename in os.listdir(TEMP_DIR):
file_path = os.path.join(TEMP_DIR, filename)
if os.path.isfile(file_path):
file_time = os.path.getmtime(file_path)
if file_time < cutoff:
try:
os.remove(file_path)
logger.info(f"Eliminado archivo antiguo: {filename}")
except Exception as e:
logger.error(f"Error al eliminar {filename}: {e}")
async def text_to_speech(text, voice="es-ES-ElviraNeural"):
"""Convierte texto a voz y guarda en archivo temporal"""
clean_old_files()
output_path = os.path.join(TEMP_DIR, f"tts_{datetime.now().strftime('%Y%m%d%H%M%S')}.mp3")
try:
logger.info(f"Generando TTS para texto de {len(text)} caracteres")
communicate = edge_tts.Communicate(text, voice)
await communicate.save(output_path)
return output_path
except Exception as e:
logger.error(f"Error en TTS: {e}")
raise
def create_audio_loop(audio_path, target_duration):
"""Crea un loop de audio hasta alcanzar la duración objetivo"""
try:
audio = AudioFileClip(audio_path)
if audio.duration >= target_duration:
return audio.subclip(0, target_duration)
loops_needed = int(target_duration // audio.duration) + 1
clips = [audio] * loops_needed
looped_audio = concatenate_audioclips(clips).subclip(0, target_duration)
return looped_audio
except Exception as e:
logger.error(f"Error al crear loop de audio: {e}")
raise
def create_video_with_text(text, duration, size=(1280, 720)):
"""Crea un video simple con texto centrado"""
try:
# Fondo del video
bg_clip = ColorClip(size, color=(30, 30, 30), duration=duration)
# Texto con ajuste automático de tamaño
text_clip = TextClip(
text,
fontsize=28,
color='white',
font='Arial-Bold',
size=(size[0]-100, size[1]-100),
method='caption',
align='center'
).set_position('center').set_duration(duration)
return CompositeVideoClip([bg_clip, text_clip])
except Exception as e:
logger.error(f"Error al crear video con texto: {e}")
raise
async def generate_video_content(text, background_music=None, use_tts=True):
"""Función principal que genera el contenido del video"""
try:
clean_old_files()
# 1. Procesar audio principal
if use_tts:
voice_path = await text_to_speech(text)
main_audio = AudioFileClip(voice_path)
else:
# Si no usamos TTS, creamos un audio silencioso de la duración estimada
estimated_duration = max(5, len(text.split()) / 3) # Estimación basada en palabras
main_audio = AudioFileClip(lambda t: 0, duration=estimated_duration)
duration = main_audio.duration
# 2. Procesar música de fondo
final_audio = main_audio
if background_music:
try:
bg_music_loop = create_audio_loop(background_music, duration).volumex(0.2)
final_audio = CompositeAudioClip([bg_music_loop, main_audio])
except Exception as e:
logger.error(f"Error al procesar música de fondo, continuando sin ella: {e}")
# 3. Crear video
video_clip = create_video_with_text(text, duration)
video_clip = video_clip.set_audio(final_audio)
# 4. Guardar resultado
output_path = os.path.join(TEMP_DIR, f"video_{datetime.now().strftime('%Y%m%d%H%M%S')}.mp4")
video_clip.write_videofile(
output_path,
fps=24,
threads=4,
codec='libx264',
audio_codec='aac',
preset='fast',
logger=None
)
return output_path
except Exception as e:
logger.error(f"Error en generate_video_content: {e}")
raise
finally:
# Cerrar todos los clips para liberar recursos
if 'main_audio' in locals():
main_audio.close()
if 'bg_music_loop' in locals():
bg_music_loop.close()
if 'video_clip' in locals():
video_clip.close()
# Interfaz Gradio mejorada
with gr.Blocks(title="Generador de Videos Avanzado", theme="soft") as app:
gr.Markdown("""
# 🎥 Generador de Videos Automático
Crea videos con voz sintetizada y música de fondo
""")
with gr.Tab("Configuración Principal"):
with gr.Row():
with gr.Column():
text_input = gr.Textbox(
label="Texto del Video",
placeholder="Escribe aquí el contenido de tu video...",
lines=5,
max_lines=20
)
with gr.Accordion("Opciones Avanzadas", open=False):
use_tts = gr.Checkbox(
label="Usar Texto a Voz (TTS)",
value=True
)
voice_selector = gr.Dropdown(
label="Voz TTS",
choices=["es-ES-ElviraNeural", "es-MX-DaliaNeural", "es-US-AlonsoNeural"],
value="es-ES-ElviraNeural",
visible=True
)
bg_music = gr.Audio(
label="Música de Fondo",
type="filepath",
sources=["upload"],
format="mp3"
)
generate_btn = gr.Button("Generar Video", variant="primary")
with gr.Column():
video_output = gr.Video(
label="Video Resultante",
format="mp4",
interactive=False
)
status_output = gr.Textbox(
label="Estado",
interactive=False
)
# Lógica para mostrar/ocultar selector de voz
use_tts.change(
fn=lambda x: gr.Dropdown(visible=x),
inputs=use_tts,
outputs=voice_selector
)
# Función principal de generación
def generate_video(text, use_tts, voice, bg_music):
try:
if not text.strip():
raise ValueError("Por favor ingresa un texto para el video")
# Limpieza inicial
clean_old_files()
# Generación del video
video_path = asyncio.run(
generate_video_content(
text=text,
use_tts=use_tts,
background_music=bg_music
)
)
return video_path, "✅ Video generado con éxito"
except Exception as e:
logger.error(f"Error en la generación: {str(e)}")
return None, f"❌ Error: {str(e)}"
generate_btn.click(
fn=generate_video,
inputs=[text_input, use_tts, voice_selector, bg_music],
outputs=[video_output, status_output]
)
if __name__ == "__main__":
# Limpieza inicial de archivos antiguos
clean_old_files()
# Configuración del servidor
app.launch(
server_name="0.0.0.0",
server_port=7860,
show_error=True,
share=False,
favicon_path=None
)