gnosticdev's picture
Update app.py
d80bdda verified
raw
history blame
6.34 kB
import os
import math
import tempfile
import logging
from PIL import Image
from pydub import AudioSegment
from moviepy.editor import (
VideoFileClip, AudioFileClip, ImageClip,
concatenate_videoclips, CompositeVideoClip
)
import edge_tts
import gradio as gr
import asyncio
# PATCH PARA PILLOW 10+
Image.ANTIALIAS = Image.Resampling.LANCZOS
# CONFIGURACI脫N
INTRO_VIDEO = "introvideo.mp4" # Duraci贸n: 3s
OUTRO_VIDEO = "outrovideo.mp4" # Duraci贸n: 3s
MUSIC_BG = "musicafondo.mp3"
FX_SOUND = "fxsound.mp3"
WATERMARK = "watermark.png"
# Validar archivos
for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK]:
if not os.path.exists(file):
raise FileNotFoundError(f"Falta: {file}")
async def procesar_audio(texto, voz):
try:
communicate = edge_tts.Communicate(texto, voz)
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_tts:
await communicate.save(tmp_tts.name)
tts_audio = AudioSegment.from_wav(tmp_tts.name)
# Calcular duraci贸n total (intro + contenido + outro)
intro = VideoFileClip(INTRO_VIDEO).duration
outro = VideoFileClip(OUTRO_VIDEO).duration
duracion_total = intro + outro + VideoFileClip(video_input).duration
# Preparar m煤sica de fondo en loop
bg_music = AudioSegment.from_mp3(MUSIC_BG) - 10 # 10% volumen
repeticiones = math.ceil(duracion_total * 1000 / len(bg_music))
bg_music_loop = bg_music * repeticiones
bg_music_final = bg_music_loop[:duracion_total*1000].fade_out(3000)
# Combinar TTS (despu茅s de la intro) con m煤sica
audio_final = bg_music_final.overlay(tts_audio, position=intro*1000)
# Exportar como MP3 con par谩metros robustos
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_mix:
audio_final.export(tmp_mix.name, format="mp3", parameters=["-ac", "2", "-ar", "44100", "-b:a", "320k"])
return tmp_mix.name
except Exception as e:
logging.error(f"Error procesando audio: {e}")
raise
def cortar_video(video_path, metodo="inteligente", duracion=10):
try:
video = VideoFileClip(video_path)
if metodo == "manual":
return [
video.subclip(i*duracion, (i+1)*duracion)
for i in range(math.ceil(video.duration/duracion))
]
# Simulaci贸n de cortes autom谩ticos
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
return clips if clips else [video]
except Exception as e:
logging.error(f"Error cortando video: {e}")
raise
def agregar_transiciones(clips):
try:
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):
# Agregar watermark
clip_watermarked = CompositeVideoClip([clip, transicion])
clips_con_fx.append(clip_watermarked)
# Agregar transici贸n entre clips
if i < len(clips)-1:
clips_con_fx.append(
CompositeVideoClip([transicion.set_position("center")])
.set_audio(fx_audio)
)
return concatenate_videoclips(clips_con_fx)
except Exception as e:
logging.error(f"Error en transiciones: {e}")
raise
async def procesar_video(
video_input,
texto_tts,
voz_seleccionada,
metodo_corte,
duracion_corte
):
temp_files = []
try:
# 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])
# Generar audio (m煤sica en loop + TTS despu茅s de intro)
audio_mix_path = await procesar_audio(texto_tts, voz_seleccionada, video_input)
# Combinar video y audio
video_final = video_final.set_audio(AudioFileClip(audio_mix_path))
# Renderizar
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_out:
video_final.write_videofile(
tmp_out.name,
codec="libx264",
audio_codec="aac",
fps=24
)
temp_files.append(tmp_out.name)
return tmp_out.name
except Exception as e:
logging.error(f"Error general: {e}")
raise
finally:
# Eliminar archivos temporales
for file in temp_files:
try:
os.remove(file)
except:
pass
# Interfaz Gradio
with gr.Blocks() as demo:
gr.Markdown("# Video Editor IA")
gr.Markdown("""
**Ejemplo de timeline generado:**
- 0s-3s: Intro (video + m煤sica)
- 3s-23s: Contenido editado (video + m煤sica + TTS)
- 23s-26s: Outro (video + m煤sica con fade-out)
""")
with gr.Tab("Principal"):
video_input = gr.Video(label="Subir video (ej: 20s de duraci贸n)")
texto_tts = gr.Textbox(label="Texto para TTS", lines=3)
voz_seleccionada = gr.Dropdown(
label="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 corte",
value="inteligente"
)
duracion_corte = gr.Slider(1, 60, 10, label="Duraci贸n 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__":
demo.queue().launch()