gnosticdev's picture
Update app.py
7d5ce2e verified
raw
history blame
7.02 kB
import math
import tempfile
import logging
from PIL import Image
import os
# PATCH PARA PILLOW 10+ (cr铆tico)
Image.ANTIALIAS = Image.Resampling.LANCZOS # Parche antes de importar MoviePy
from moviepy.editor import (
VideoFileClip, AudioFileClip, ImageClip,
concatenate_videoclips, CompositeVideoClip, CompositeAudioClip
)
import edge_tts
import gradio as gr
import asyncio
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"
# Validar existencia de archivos obligatorios
for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK]:
if not os.path.exists(file):
logging.error(f"Falta archivo necesario: {file}")
raise FileNotFoundError(f"Falta archivo necesario: {file}")
def cortar_video(video_path, metodo="inteligente", duracion=10):
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
# Simulaci贸n b谩sica de cortes autom谩ticos (puedes mejorar esto con VAD)
clips = []
ultimo_corte = 0
for i in range(1, math.ceil(video.duration)):
if i % 5 == 0: # Simulaci贸n de pausas
clips.append(video.subclip(ultimo_corte, i))
ultimo_corte = i
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, clips_duracion):
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)
# Ajustar TTS a duraci贸n de clips
if tts_audio.duration < clips_duracion:
tts_audio = tts_audio.loop(duration=clips_duracion)
else:
tts_audio = tts_audio.subclip(0, clips_duracion)
# Mezclar con m煤sica de fondo
bg_music = AudioSegment.from_mp3(MUSIC_BG)
if len(bg_music) < clips_duracion * 1000:
bg_music = bg_music * math.ceil(clips_duracion * 1000 / len(bg_music))
bg_music = bg_music[:clips_duracion * 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)
logging.info("Audio procesado correctamente.")
return CompositeAudioClip([bg_audio, tts_audio.volumex(0.9)])
except Exception as e:
logging.error(f"Error al procesar audio: {e}")
raise
def agregar_transiciones(clips):
try:
logging.info("Agregando transiciones...")
fx_audio = AudioFileClip(FX_SOUND).set_duration(2.5)
transicion = ImageClip(WATERMARK).set_duration(2.5)
# Redimensionar la transici贸n (ahora compatible)
transicion = transicion.resize(height=clips[0].h).set_position(("center", 0.1))
clips_con_fx = []
for i, clip in enumerate(clips):
# Agregar watermark
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
):
temp_files = []
try:
logging.info("Iniciando procesamiento de video...")
# Procesar video principal
clips = cortar_video(video_input, metodo_corte, duracion_corte)
video_editado = agregar_transiciones(clips)
# Agregar intro/outro
intro = VideoFileClip(INTRO_VIDEO)
outro = VideoFileClip(OUTRO_VIDEO)
video_final = concatenate_videoclips([intro, video_editado, outro])
# Procesar audio
audio_final = await procesar_audio(texto_tts, voz_seleccionada, video_editado.duration)
# Combinar y renderizar
video_final = video_final.set_audio(audio_final)
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
video_final.write_videofile(tmp.name, codec="libx264", fps=24)
logging.info("Video procesado y guardado temporalmente.")
temp_files.append(tmp.name)
return tmp.name
except Exception as e:
logging.error(f"Error durante el procesamiento: {e}")
raise
finally:
# Eliminar archivos temporales
for file in temp_files:
try:
if os.path.exists(file):
os.remove(file)
logging.info(f"Archivo temporal eliminado: {file}")
except Exception as e:
logging.warning(f"No se pudo eliminar el archivo temporal {file}: {e}")
# Interfaz Gradio
with gr.Blocks() as demo:
gr.Markdown("# Video Editor IA")
with gr.Tab("Principal"):
video_input = gr.Video(label="Subir video")
texto_tts = gr.Textbox(label="Texto para TTS", lines=3)
voz_seleccionada = gr.Dropdown(
label="Seleccionar voz",
choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"]
)
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()