gnosticdev commited on
Commit
f8ec5f8
·
verified ·
1 Parent(s): c7d476a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -24
app.py CHANGED
@@ -38,21 +38,50 @@ def eliminar_archivo_tiempo(ruta, delay=1800):
38
  logging.error(f"Error al eliminar {ruta}: {e}")
39
  Timer(delay, eliminar).start()
40
 
 
 
 
 
 
 
 
 
 
41
  async def procesar_audio(texto, voz, duracion_total, duracion_intro):
42
- """Genera TTS y mezcla con música"""
43
  temp_files = []
44
  try:
 
 
 
 
 
 
45
  # Generar TTS
46
  communicate = edge_tts.Communicate(texto, voz)
47
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
48
  await communicate.save(tmp.name)
49
  tts_audio = AudioFileClip(tmp.name)
 
 
 
 
 
50
  temp_files.append(tmp.name)
51
 
52
- # Preparar música de fondo en loop
53
- bg_music = AudioSegment.from_mp3(MUSIC_BG)
54
- bg_music = bg_music * (duracion_total // len(bg_music) + 1)
55
- bg_music = bg_music[:duracion_total * 1000].fade_out(5000)
 
 
 
 
 
 
 
 
 
56
 
57
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
58
  bg_music.export(tmp.name, format="mp3")
@@ -64,31 +93,35 @@ async def procesar_audio(texto, voz, duracion_total, duracion_intro):
64
  bg_audio,
65
  tts_audio.volumex(0.85).set_start(duracion_intro)
66
  ])
 
67
  return audio_final
 
 
 
68
  finally:
 
69
  for file in temp_files:
70
  try:
71
  os.remove(file)
72
- except: pass
 
73
 
74
  def agregar_transiciones(clips):
75
- """Transiciones suaves cada 40 segundos"""
76
  try:
77
- transicion_fx = AudioFileClip(FX_SOUND).subclip(0, 0.5)
78
  watermark = (ImageClip(WATERMARK)
79
  .set_duration(0.5)
80
  .resize(height=50)
81
- .margin(right=10, bottom=10, opacity=0)
82
  .set_pos(("right", "bottom")))
83
 
84
  clips_finales = []
85
  for i, clip in enumerate(clips):
86
- # Añadir watermark al clip principal
87
  clip_watermarked = CompositeVideoClip([clip, watermark])
88
 
89
- # Agregar transición cada 40 segundos
90
  if i > 0 and i % 40 == 0:
91
- transicion = CompositeVideoClip([watermark.set_duration(0.5)]).set_audio(transicion_fx)
92
  clips_finales.append(transicion)
93
 
94
  clips_finales.append(clip_watermarked)
@@ -100,17 +133,16 @@ def agregar_transiciones(clips):
100
 
101
  async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte):
102
  try:
103
- # Cargar video original con su audio
104
  video_original = VideoFileClip(video_input)
105
- audio_original = video_original.audio.volumex(0.7) # Conservar audio original al 70%
106
 
107
- # Cortar video según método
108
  clips = []
109
  if metodo_corte == "manual":
110
  for i in range(math.ceil(video_original.duration / duracion_corte)):
111
  clips.append(video_original.subclip(i*duracion_corte, (i+1)*duracion_corte))
112
  else:
113
- # Cortes automáticos cada 40 segundos para transiciones
114
  clips = [video_original.subclip(i, i+40) for i in range(0, math.ceil(video_original.duration), 40)]
115
 
116
  # Procesar transiciones
@@ -121,15 +153,16 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte,
121
  outro = VideoFileClip(OUTRO_VIDEO)
122
  video_final = concatenate_videoclips([intro, video_editado, outro])
123
 
124
- # Procesar audio completo
125
  duracion_total = video_final.duration
126
  audio_tts_bg = await procesar_audio(texto_tts, voz_seleccionada, duracion_total, intro.duration)
127
 
128
- # Combinar TODOS los audios
129
- audio_final = CompositeAudioClip([
130
- audio_original.set_duration(video_final.duration),
131
- audio_tts_bg
132
- ])
 
133
 
134
  # Renderizar video final
135
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
@@ -143,7 +176,7 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte,
143
  eliminar_archivo_tiempo(tmp.name)
144
  return tmp.name
145
  except Exception as e:
146
- logging.error(f"Error procesando video: {e}")
147
  raise
148
 
149
  # Interfaz Gradio
@@ -178,7 +211,13 @@ with gr.Blocks() as demo:
178
 
179
  procesar_btn.click(
180
  procesar_video,
181
- inputs=[video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte],
 
 
 
 
 
 
182
  outputs=video_output
183
  )
184
 
 
38
  logging.error(f"Error al eliminar {ruta}: {e}")
39
  Timer(delay, eliminar).start()
40
 
41
+ def validar_texto(texto):
42
+ """Valida el texto de entrada para TTS"""
43
+ texto_limpio = texto.strip()
44
+ if len(texto_limpio) < 3:
45
+ raise gr.Error("El texto debe tener al menos 3 caracteres")
46
+ if any(c in texto_limpio for c in ["|", "\n", "\r"]):
47
+ raise gr.Error("Caracteres no permitidos en el texto")
48
+ return texto_limpio
49
+
50
  async def procesar_audio(texto, voz, duracion_total, duracion_intro):
51
+ """Genera TTS y mezcla con música (versión segura)"""
52
  temp_files = []
53
  try:
54
+ # Validaciones críticas
55
+ if not texto.strip():
56
+ raise ValueError("Texto vacío")
57
+ if voz not in ["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"]:
58
+ raise ValueError(f"Voz no soportada: {voz}")
59
+
60
  # Generar TTS
61
  communicate = edge_tts.Communicate(texto, voz)
62
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
63
  await communicate.save(tmp.name)
64
  tts_audio = AudioFileClip(tmp.name)
65
+
66
+ # Verificar audio válido
67
+ if tts_audio.duration < 0.5:
68
+ raise RuntimeError(f"Audio TTS inválido ({tts_audio.duration}s)")
69
+
70
  temp_files.append(tmp.name)
71
 
72
+ # Procesar música de fondo
73
+ try:
74
+ bg_music = AudioSegment.from_mp3(MUSIC_BG)
75
+ except Exception as e:
76
+ raise ValueError(f"Error en música de fondo: {str(e)}")
77
+
78
+ if len(bg_music) == 0:
79
+ raise ValueError("Música de fondo vacía")
80
+
81
+ # Ajustar duración
82
+ needed_ms = duracion_total * 1000
83
+ bg_music = bg_music * (needed_ms // len(bg_music) + 1)
84
+ bg_music = bg_music[:needed_ms].fade_out(5000)
85
 
86
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
87
  bg_music.export(tmp.name, format="mp3")
 
93
  bg_audio,
94
  tts_audio.volumex(0.85).set_start(duracion_intro)
95
  ])
96
+
97
  return audio_final
98
+ except Exception as e:
99
+ logging.error(f" fallo en audio: {str(e)}")
100
+ raise
101
  finally:
102
+ # Limpiar archivos
103
  for file in temp_files:
104
  try:
105
  os.remove(file)
106
+ except Exception as e:
107
+ logging.warning(f"Error limpiando {file}: {e}")
108
 
109
  def agregar_transiciones(clips):
110
+ """Transiciones profesionales cada 40s"""
111
  try:
112
+ fx_audio = AudioFileClip(FX_SOUND).subclip(0, 0.5)
113
  watermark = (ImageClip(WATERMARK)
114
  .set_duration(0.5)
115
  .resize(height=50)
 
116
  .set_pos(("right", "bottom")))
117
 
118
  clips_finales = []
119
  for i, clip in enumerate(clips):
 
120
  clip_watermarked = CompositeVideoClip([clip, watermark])
121
 
122
+ # Agregar transición cada 40s
123
  if i > 0 and i % 40 == 0:
124
+ transicion = CompositeVideoClip([watermark.set_duration(0.5)]).set_audio(fx_audio)
125
  clips_finales.append(transicion)
126
 
127
  clips_finales.append(clip_watermarked)
 
133
 
134
  async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte):
135
  try:
136
+ # Cargar video con audio original
137
  video_original = VideoFileClip(video_input)
138
+ audio_original = video_original.audio.volumex(0.7) if video_original.audio else None
139
 
140
+ # Cortar video
141
  clips = []
142
  if metodo_corte == "manual":
143
  for i in range(math.ceil(video_original.duration / duracion_corte)):
144
  clips.append(video_original.subclip(i*duracion_corte, (i+1)*duracion_corte))
145
  else:
 
146
  clips = [video_original.subclip(i, i+40) for i in range(0, math.ceil(video_original.duration), 40)]
147
 
148
  # Procesar transiciones
 
153
  outro = VideoFileClip(OUTRO_VIDEO)
154
  video_final = concatenate_videoclips([intro, video_editado, outro])
155
 
156
+ # Calcular duraciones
157
  duracion_total = video_final.duration
158
  audio_tts_bg = await procesar_audio(texto_tts, voz_seleccionada, duracion_total, intro.duration)
159
 
160
+ # Combinar todos los audios
161
+ audios = [audio_tts_bg]
162
+ if audio_original:
163
+ audios.append(audio_original.set_duration(video_final.duration))
164
+
165
+ audio_final = CompositeAudioClip(audios)
166
 
167
  # Renderizar video final
168
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
 
176
  eliminar_archivo_tiempo(tmp.name)
177
  return tmp.name
178
  except Exception as e:
179
+ logging.error(f" fallo general: {str(e)}")
180
  raise
181
 
182
  # Interfaz Gradio
 
211
 
212
  procesar_btn.click(
213
  procesar_video,
214
+ inputs=[
215
+ video_input,
216
+ texto_tts.validate(validar_texto), # <-- Validación activada
217
+ voz_seleccionada,
218
+ metodo_corte,
219
+ duracion_corte
220
+ ],
221
  outputs=video_output
222
  )
223