gnosticdev's picture
Update app.py
02e97a3 verified
raw
history blame
8.49 kB
import math
import tempfile
import logging
import os
import asyncio
import time
from threading import Timer
from moviepy.editor import (
VideoFileClip, AudioFileClip, ImageClip,
concatenate_videoclips, CompositeVideoClip, CompositeAudioClip
)
import edge_tts
import gradio as gr
from pydub import AudioSegment
# Configuraci贸n de Logs
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
# CONSTANTES DE ARCHIVOS
INTRO_VIDEO = "introvideo.mp4"
OUTRO_VIDEO = "outrovideo.mp4"
MUSIC_BG = "musicafondo.mp3"
FX_SOUND = "fxsound.mp3"
WATERMARK = "watermark.png"
EJEMPLO_VIDEO = "ejemplo.mp4" # Video de ejemplo en el root
OUTPUT_DIR = "output_videos" # Carpeta para videos finales
# Validar existencia de archivos obligatorios
for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK, EJEMPLO_VIDEO]:
if not os.path.exists(file):
logging.error(f"Falta archivo necesario: {file}")
raise FileNotFoundError(f"Falta archivo necesario: {file}")
# Crear carpeta de salida si no existe
os.makedirs(OUTPUT_DIR, exist_ok=True)
def eliminar_archivo_tiempo(ruta, delay=1800):
"""Elimina un archivo despu茅s de 'delay' segundos (30 minutos por defecto)."""
def eliminar():
try:
if os.path.exists(ruta):
os.remove(ruta)
logging.info(f"Archivo eliminado por timer: {ruta}")
except Exception as e:
logging.error(f"Error al eliminar {ruta}: {e}")
Timer(delay, eliminar).start()
def cortar_video(video_path, metodo="inteligente", duracion=10):
"""Corta el video en clips seg煤n el m茅todo especificado."""
try:
logging.info("Iniciando corte de video...")
video = VideoFileClip(video_path)
if metodo == "manual":
clips = [video.subclip(i * duracion, (i + 1) * duracion)
for i in range(math.ceil(video.duration / duracion))]
logging.info(f"Video cortado en {len(clips)} clips manuales.")
return clips
# M茅todo "inteligente" simplificado (cortes cada 5 segundos)
clips = []
ultimo_corte = 0
for i in range(1, math.ceil(video.duration)):
if i % 5 == 0:
clips.append(video.subclip(ultimo_corte, i))
ultimo_corte = i
if ultimo_corte < video.duration:
clips.append(video.subclip(ultimo_corte, video.duration))
logging.info(f"Video cortado en {len(clips)} clips autom谩ticos.")
return clips
except Exception as e:
logging.error(f"Error al cortar video: {e}")
raise
async def procesar_audio(texto, voz, duracion_total, duracion_intro, duracion_video_editado):
"""Genera el TTS y mezcla con la m煤sica de fondo."""
temp_files = []
try:
logging.info("Generando TTS y mezclando audio...")
communicate = edge_tts.Communicate(texto, voz)
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
await communicate.save(tmp.name)
tts_audio = AudioFileClip(tmp.name)
temp_files.append(tmp.name)
# Ajustar duraci贸n del TTS
if tts_audio.duration > duracion_video_editado:
tts_audio = tts_audio.subclip(0, duracion_video_editado)
# Preparar m煤sica de fondo
bg_music = AudioSegment.from_mp3(MUSIC_BG)
if len(bg_music) < duracion_total * 1000:
bg_music = bg_music * math.ceil(duracion_total * 1000 / len(bg_music))
bg_music = bg_music[:duracion_total * 1000].fade_out(3000)
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
bg_music.export(tmp.name, format="mp3")
bg_audio = AudioFileClip(tmp.name).volumex(0.10)
temp_files.append(tmp.name)
# Combinar audios
audio_final = CompositeAudioClip([
bg_audio,
tts_audio.volumex(0.9).set_start(duracion_intro)
])
logging.info("Audio procesado correctamente.")
return audio_final
except Exception as e:
logging.error(f"Error al procesar audio: {e}")
raise
finally:
# Eliminar archivos temporales de audio inmediatamente
for file in temp_files:
try:
if os.path.exists(file):
os.remove(file)
logging.info(f"Archivo temporal de audio eliminado: {file}")
except Exception as e:
logging.warning(f"No se pudo eliminar el archivo temporal {file}: {e}")
def agregar_transiciones(clips):
"""Agrega transiciones con watermark y sonido FX entre clips."""
try:
logging.info("Agregando transiciones...")
fx_audio = AudioFileClip(FX_SOUND).set_duration(2.5)
transicion = ImageClip(WATERMARK).set_duration(2.5).resize(height=clips[0].h).set_position(("center", 0.1))
clips_con_fx = []
for i, clip in enumerate(clips):
clip_watermarked = CompositeVideoClip([clip, transicion])
clips_con_fx.append(clip_watermarked)
if i < len(clips) - 1:
clips_con_fx.append(
CompositeVideoClip([transicion.set_position("center")]).set_audio(fx_audio)
)
logging.info("Transiciones agregadas correctamente.")
return concatenate_videoclips(clips_con_fx)
except Exception as e:
logging.error(f"Error al agregar transiciones: {e}")
raise
async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte):
"""Procesa el video completo con intro, cortes, TTS y outro."""
try:
logging.info("Iniciando procesamiento de video...")
# Cortar y editar video
clips = cortar_video(video_input, metodo_corte, duracion_corte)
video_editado = agregar_transiciones(clips)
# Cargar intro y outro
intro = VideoFileClip(INTRO_VIDEO)
outro = VideoFileClip(OUTRO_VIDEO)
# Calcular duraciones
duracion_intro = intro.duration
duracion_video_editado = video_editado.duration
duracion_total = duracion_intro + duracion_video_editado + outro.duration
# Concatenar clips
video_final = concatenate_videoclips([intro, video_editado, outro])
# Procesar audio
audio_final = await procesar_audio(texto_tts, voz_seleccionada, duracion_total, duracion_intro, duracion_video_editado)
# Renderizar video final
video_final = video_final.set_audio(audio_final)
timestamp = int(time.time())
output_path = os.path.join(OUTPUT_DIR, f"video_{timestamp}.mp4")
video_final.write_videofile(output_path, codec="libx264", fps=24)
# Programar eliminaci贸n del video en 30 minutos
eliminar_archivo_tiempo(output_path, delay=1800)
logging.info(f"Video guardado temporalmente en: {output_path}")
return output_path
except Exception as e:
logging.error(f"Error durante el procesamiento: {e}")
raise
# Interfaz Gradio con ejemplo de uso
with gr.Blocks() as demo:
gr.Markdown("# Video Editor IA")
with gr.Tab("Principal"):
video_input = gr.Video(label="Subir video", value=EJEMPLO_VIDEO) # Video de ejemplo
texto_tts = gr.Textbox(
label="Texto para TTS",
lines=3,
value="hola esto es una prueba suscribete al canal ya empieza el video" # Texto de ejemplo
)
voz_seleccionada = gr.Dropdown(
label="Seleccionar voz",
choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"],
value="es-ES-AlvaroNeural"
)
procesar_btn = gr.Button("Generar Video")
video_output = gr.Video(label="Resultado")
with gr.Tab("Ajustes"):
metodo_corte = gr.Radio(
["inteligente", "manual"],
label="M茅todo de cortes",
value="inteligente"
)
duracion_corte = gr.Slider(
1, 60, 10,
label="Segundos por corte (solo manual)"
)
procesar_btn.click(
procesar_video,
inputs=[video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte],
outputs=video_output
)
if __name__ == "__main__":
logging.info("Iniciando aplicaci贸n Gradio...")
demo.queue().launch()