import math import tempfile import logging import os import asyncio 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 # 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}") 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 como fallback) clips = [] ultimo_corte = 0 for i in range(1, math.ceil(video.duration)): if i % 5 == 0: # Corte cada 5 segundos (puedes mejorar esto con VAD más adelante) 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.""" 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 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) # 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 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.""" temp_files = [] 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) with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp: video_final.write_videofile(tmp.name, codec="libx264", fps=24) temp_files.append(tmp.name) logging.info("Video procesado y guardado temporalmente.") return tmp.name except Exception as e: logging.error(f"Error durante el procesamiento: {e}") raise finally: 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 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()