gnosticdev commited on
Commit
1088ad0
verified
1 Parent(s): ee5aeee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -110
app.py CHANGED
@@ -1,184 +1,187 @@
1
- import os
2
  import math
3
  import tempfile
4
  import logging
5
- from PIL import Image
6
- from pydub import AudioSegment
7
  from moviepy.editor import (
8
  VideoFileClip, AudioFileClip, ImageClip,
9
- concatenate_videoclips, CompositeVideoClip
10
  )
11
  import edge_tts
12
  import gradio as gr
13
- import asyncio
14
 
15
- # PATCH PARA PILLOW 10+
16
- Image.ANTIALIAS = Image.Resampling.LANCZOS
17
 
18
- # CONFIGURACI脫N
19
- INTRO_VIDEO = "introvideo.mp4" # Duraci贸n: 3s
20
- OUTRO_VIDEO = "outrovideo.mp4" # Duraci贸n: 3s
21
  MUSIC_BG = "musicafondo.mp3"
22
  FX_SOUND = "fxsound.mp3"
23
  WATERMARK = "watermark.png"
 
24
 
25
- # Validar archivos
26
- for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK]:
27
  if not os.path.exists(file):
28
- raise FileNotFoundError(f"Falta: {file}")
29
-
30
- async def procesar_audio(texto, voz):
31
- try:
32
- communicate = edge_tts.Communicate(texto, voz)
33
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_tts:
34
- await communicate.save(tmp_tts.name)
35
- tts_audio = AudioSegment.from_wav(tmp_tts.name)
36
-
37
- # Calcular duraci贸n total (intro + contenido + outro)
38
- intro = VideoFileClip(INTRO_VIDEO).duration
39
- outro = VideoFileClip(OUTRO_VIDEO).duration
40
-
41
- # Obtener duraci贸n del video principal
42
- video_principal = VideoFileClip("ejemplo.mp4")
43
- duracion_total = intro + outro + video_principal.duration
44
-
45
- # Preparar m煤sica de fondo en loop
46
- bg_music = AudioSegment.from_mp3(MUSIC_BG) - 10 # 10% volumen
47
- repeticiones = math.ceil(duracion_total * 1000 / len(bg_music))
48
- bg_music_loop = bg_music * repeticiones
49
- bg_music_final = bg_music_loop[:duracion_total*1000].fade_out(3000)
50
-
51
- # Combinar TTS (despu茅s de la intro) con m煤sica
52
- audio_final = bg_music_final.overlay(tts_audio, position=intro*1000)
53
-
54
- # Exportar como MP3 con par谩metros robustos
55
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_mix:
56
- audio_final.export(tmp_mix.name, format="mp3", parameters=["-ac", "2", "-ar", "44100", "-b:a", "320k"])
57
- return tmp_mix.name
58
- except Exception as e:
59
- logging.error(f"Error procesando audio: {e}")
60
- raise
61
 
62
  def cortar_video(video_path, metodo="inteligente", duracion=10):
 
63
  try:
 
64
  video = VideoFileClip(video_path)
 
65
  if metodo == "manual":
66
- return [
67
- video.subclip(i*duracion, (i+1)*duracion)
68
- for i in range(math.ceil(video.duration/duracion))
69
- ]
70
 
71
- # Simulaci贸n de cortes autom谩ticos
72
  clips = []
73
  ultimo_corte = 0
74
  for i in range(1, math.ceil(video.duration)):
75
- if i % 5 == 0: # Simulaci贸n de pausas
76
  clips.append(video.subclip(ultimo_corte, i))
77
  ultimo_corte = i
78
- return clips if clips else [video]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  except Exception as e:
80
- logging.error(f"Error cortando video: {e}")
81
  raise
82
 
83
  def agregar_transiciones(clips):
 
84
  try:
 
85
  fx_audio = AudioFileClip(FX_SOUND).set_duration(2.5)
86
  transicion = ImageClip(WATERMARK).set_duration(2.5).resize(height=clips[0].h).set_position(("center", 0.1))
87
 
88
  clips_con_fx = []
89
  for i, clip in enumerate(clips):
90
- # Agregar watermark
91
  clip_watermarked = CompositeVideoClip([clip, transicion])
92
  clips_con_fx.append(clip_watermarked)
93
-
94
- # Agregar transici贸n entre clips
95
- if i < len(clips)-1:
96
  clips_con_fx.append(
97
- CompositeVideoClip([transicion.set_position("center")])
98
- .set_audio(fx_audio)
99
  )
 
100
  return concatenate_videoclips(clips_con_fx)
101
  except Exception as e:
102
- logging.error(f"Error en transiciones: {e}")
103
  raise
104
 
105
- async def procesar_video(
106
- video_input,
107
- texto_tts,
108
- voz_seleccionada,
109
- metodo_corte,
110
- duracion_corte
111
- ):
112
  temp_files = []
113
  try:
114
- # Procesar video principal
 
115
  clips = cortar_video(video_input, metodo_corte, duracion_corte)
116
  video_editado = agregar_transiciones(clips)
117
 
118
- # Agregar intro/outro
119
  intro = VideoFileClip(INTRO_VIDEO)
120
  outro = VideoFileClip(OUTRO_VIDEO)
121
- video_final = concatenate_videoclips([intro, video_editado, outro])
122
 
123
- # Generar audio (m煤sica en loop + TTS despu茅s de intro)
124
- audio_mix_path = await procesar_audio(texto_tts, voz_seleccionada)
 
 
 
 
 
125
 
126
- # Combinar video y audio
127
- video_final = video_final.set_audio(AudioFileClip(audio_mix_path))
128
 
129
- # Renderizar
130
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_out:
131
- video_final.write_videofile(
132
- tmp_out.name,
133
- codec="libx264",
134
- audio_codec="aac",
135
- fps=24
136
- )
137
- temp_files.append(tmp_out.name)
138
- return tmp_out.name
139
  except Exception as e:
140
- logging.error(f"Error general: {e}")
141
  raise
142
  finally:
143
- # Eliminar archivos temporales
144
  for file in temp_files:
145
  try:
146
- os.remove(file)
147
- except:
148
- pass
 
 
149
 
150
- # Interfaz Gradio
151
  with gr.Blocks() as demo:
152
  gr.Markdown("# Video Editor IA")
153
- gr.Markdown("""
154
- **Ejemplo preconfigurado:**
155
- - Video: `ejemplo.mp4` (duraci贸n aproximada: 20s)
156
- - Texto TTS: "Este es un ejemplo de texto sintetizado usando TTS. La m煤sica suena en loop durante todo el video."
157
- """)
158
-
159
  with gr.Tab("Principal"):
160
- video_input = gr.Video(label="Subir video (opcional)")
161
- texto_tts = gr.Textbox(label="Texto para TTS", value="Este es un ejemplo de texto sintetizado usando TTS. La m煤sica suena en loop durante todo el video.")
 
 
 
 
162
  voz_seleccionada = gr.Dropdown(
163
- label="Voz",
164
  choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"],
165
  value="es-ES-AlvaroNeural"
166
  )
 
 
 
 
167
  metodo_corte = gr.Radio(
168
  ["inteligente", "manual"],
169
- label="M茅todo de corte",
170
  value="inteligente"
171
  )
172
- duracion_corte = gr.Slider(1, 60, 10, label="Duraci贸n por corte (solo manual)", value=10)
173
- procesar_btn = gr.Button("Generar Video")
174
- video_output = gr.Video(label="Resultado")
175
-
176
- # EJEMPLO PREDEFINIDO EN EL FOOTER DE GRADIO
177
- demo.load(
178
- lambda: "ejemplo.mp4",
179
- inputs=None,
180
- outputs=video_input
181
- )
182
 
183
  procesar_btn.click(
184
  procesar_video,
@@ -187,4 +190,5 @@ with gr.Blocks() as demo:
187
  )
188
 
189
  if __name__ == "__main__":
 
190
  demo.queue().launch()
 
 
1
  import math
2
  import tempfile
3
  import logging
4
+ import os
5
+ import asyncio
6
  from moviepy.editor import (
7
  VideoFileClip, AudioFileClip, ImageClip,
8
+ concatenate_videoclips, CompositeVideoClip, CompositeAudioClip
9
  )
10
  import edge_tts
11
  import gradio as gr
12
+ from pydub import AudioSegment
13
 
14
+ # Configuraci贸n de Logs
15
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
16
 
17
+ # CONSTANTES DE ARCHIVOS
18
+ INTRO_VIDEO = "introvideo.mp4"
19
+ OUTRO_VIDEO = "outrovideo.mp4"
20
  MUSIC_BG = "musicafondo.mp3"
21
  FX_SOUND = "fxsound.mp3"
22
  WATERMARK = "watermark.png"
23
+ EJEMPLO_VIDEO = "ejemplo.mp4" # Video de ejemplo en el root
24
 
25
+ # Validar existencia de archivos obligatorios
26
+ for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK, EJEMPLO_VIDEO]:
27
  if not os.path.exists(file):
28
+ logging.error(f"Falta archivo necesario: {file}")
29
+ raise FileNotFoundError(f"Falta archivo necesario: {file}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  def cortar_video(video_path, metodo="inteligente", duracion=10):
32
+ """Corta el video en clips seg煤n el m茅todo especificado."""
33
  try:
34
+ logging.info("Iniciando corte de video...")
35
  video = VideoFileClip(video_path)
36
+
37
  if metodo == "manual":
38
+ clips = [video.subclip(i * duracion, (i + 1) * duracion)
39
+ for i in range(math.ceil(video.duration / duracion))]
40
+ logging.info(f"Video cortado en {len(clips)} clips manuales.")
41
+ return clips
42
 
43
+ # M茅todo "inteligente" simplificado (cortes cada 5 segundos como fallback)
44
  clips = []
45
  ultimo_corte = 0
46
  for i in range(1, math.ceil(video.duration)):
47
+ if i % 5 == 0: # Corte cada 5 segundos (puedes mejorar esto con VAD m谩s adelante)
48
  clips.append(video.subclip(ultimo_corte, i))
49
  ultimo_corte = i
50
+ if ultimo_corte < video.duration:
51
+ clips.append(video.subclip(ultimo_corte, video.duration))
52
+ logging.info(f"Video cortado en {len(clips)} clips autom谩ticos.")
53
+ return clips
54
+ except Exception as e:
55
+ logging.error(f"Error al cortar video: {e}")
56
+ raise
57
+
58
+ async def procesar_audio(texto, voz, duracion_total, duracion_intro, duracion_video_editado):
59
+ """Genera el TTS y mezcla con la m煤sica de fondo."""
60
+ try:
61
+ logging.info("Generando TTS y mezclando audio...")
62
+ communicate = edge_tts.Communicate(texto, voz)
63
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
64
+ await communicate.save(tmp.name)
65
+ tts_audio = AudioFileClip(tmp.name)
66
+
67
+ # Ajustar duraci贸n del TTS
68
+ if tts_audio.duration > duracion_video_editado:
69
+ tts_audio = tts_audio.subclip(0, duracion_video_editado)
70
+
71
+ # Preparar m煤sica de fondo
72
+ bg_music = AudioSegment.from_mp3(MUSIC_BG)
73
+ if len(bg_music) < duracion_total * 1000:
74
+ bg_music = bg_music * math.ceil(duracion_total * 1000 / len(bg_music))
75
+ bg_music = bg_music[:duracion_total * 1000].fade_out(3000)
76
+
77
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
78
+ bg_music.export(tmp.name, format="mp3")
79
+ bg_audio = AudioFileClip(tmp.name).volumex(0.10)
80
+
81
+ # Combinar audios
82
+ audio_final = CompositeAudioClip([
83
+ bg_audio,
84
+ tts_audio.volumex(0.9).set_start(duracion_intro)
85
+ ])
86
+ logging.info("Audio procesado correctamente.")
87
+ return audio_final
88
  except Exception as e:
89
+ logging.error(f"Error al procesar audio: {e}")
90
  raise
91
 
92
  def agregar_transiciones(clips):
93
+ """Agrega transiciones con watermark y sonido FX entre clips."""
94
  try:
95
+ logging.info("Agregando transiciones...")
96
  fx_audio = AudioFileClip(FX_SOUND).set_duration(2.5)
97
  transicion = ImageClip(WATERMARK).set_duration(2.5).resize(height=clips[0].h).set_position(("center", 0.1))
98
 
99
  clips_con_fx = []
100
  for i, clip in enumerate(clips):
 
101
  clip_watermarked = CompositeVideoClip([clip, transicion])
102
  clips_con_fx.append(clip_watermarked)
103
+ if i < len(clips) - 1:
 
 
104
  clips_con_fx.append(
105
+ CompositeVideoClip([transicion.set_position("center")]).set_audio(fx_audio)
 
106
  )
107
+ logging.info("Transiciones agregadas correctamente.")
108
  return concatenate_videoclips(clips_con_fx)
109
  except Exception as e:
110
+ logging.error(f"Error al agregar transiciones: {e}")
111
  raise
112
 
113
+ async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte):
114
+ """Procesa el video completo con intro, cortes, TTS y outro."""
 
 
 
 
 
115
  temp_files = []
116
  try:
117
+ logging.info("Iniciando procesamiento de video...")
118
+ # Cortar y editar video
119
  clips = cortar_video(video_input, metodo_corte, duracion_corte)
120
  video_editado = agregar_transiciones(clips)
121
 
122
+ # Cargar intro y outro
123
  intro = VideoFileClip(INTRO_VIDEO)
124
  outro = VideoFileClip(OUTRO_VIDEO)
 
125
 
126
+ # Calcular duraciones
127
+ duracion_intro = intro.duration
128
+ duracion_video_editado = video_editado.duration
129
+ duracion_total = duracion_intro + duracion_video_editado + outro.duration
130
+
131
+ # Concatenar clips
132
+ video_final = concatenate_videoclips([intro, video_editado, outro])
133
 
134
+ # Procesar audio
135
+ audio_final = await procesar_audio(texto_tts, voz_seleccionada, duracion_total, duracion_intro, duracion_video_editado)
136
 
137
+ # Renderizar video final
138
+ video_final = video_final.set_audio(audio_final)
139
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
140
+ video_final.write_videofile(tmp.name, codec="libx264", fps=24)
141
+ temp_files.append(tmp.name)
142
+ logging.info("Video procesado y guardado temporalmente.")
143
+ return tmp.name
 
 
 
144
  except Exception as e:
145
+ logging.error(f"Error durante el procesamiento: {e}")
146
  raise
147
  finally:
 
148
  for file in temp_files:
149
  try:
150
+ if os.path.exists(file):
151
+ os.remove(file)
152
+ logging.info(f"Archivo temporal eliminado: {file}")
153
+ except Exception as e:
154
+ logging.warning(f"No se pudo eliminar el archivo temporal {file}: {e}")
155
 
156
+ # Interfaz Gradio con ejemplo de uso
157
  with gr.Blocks() as demo:
158
  gr.Markdown("# Video Editor IA")
159
+
 
 
 
 
 
160
  with gr.Tab("Principal"):
161
+ video_input = gr.Video(label="Subir video", value=EJEMPLO_VIDEO) # Video de ejemplo
162
+ texto_tts = gr.Textbox(
163
+ label="Texto para TTS",
164
+ lines=3,
165
+ value="hola esto es una prueba suscribete al canal ya empieza el video" # Texto de ejemplo
166
+ )
167
  voz_seleccionada = gr.Dropdown(
168
+ label="Seleccionar voz",
169
  choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"],
170
  value="es-ES-AlvaroNeural"
171
  )
172
+ procesar_btn = gr.Button("Generar Video")
173
+ video_output = gr.Video(label="Resultado")
174
+
175
+ with gr.Tab("Ajustes"):
176
  metodo_corte = gr.Radio(
177
  ["inteligente", "manual"],
178
+ label="M茅todo de cortes",
179
  value="inteligente"
180
  )
181
+ duracion_corte = gr.Slider(
182
+ 1, 60, 10,
183
+ label="Segundos por corte (solo manual)"
184
+ )
 
 
 
 
 
 
185
 
186
  procesar_btn.click(
187
  procesar_video,
 
190
  )
191
 
192
  if __name__ == "__main__":
193
+ logging.info("Iniciando aplicaci贸n Gradio...")
194
  demo.queue().launch()