gnosticdev commited on
Commit
57af5e5
verified
1 Parent(s): 5c6a727

Update app.py

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