gnosticdev commited on
Commit
839ab4c
verified
1 Parent(s): 1bfb2c8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -111
app.py CHANGED
@@ -19,13 +19,13 @@ MUSIC_BG = "musicafondo.mp3"
19
  EJEMPLO_VIDEO = "ejemplo.mp4"
20
 
21
  # CONSTANTES DE LIMITACIONES
22
- MAX_VIDEO_DURATION = 300 # M谩xima duraci贸n en segundos (5 minutos)
23
- MAX_VIDEO_SIZE = 100 * 1024 * 1024 # Tama帽o m谩ximo en bytes (100MB)
24
- MAX_RESOLUTION = (1280, 720) # Resoluci贸n m谩xima (720p)
25
 
26
  # Configuraci贸n de chunks
27
  SEGMENT_DURATION = 30 # Duraci贸n exacta entre transiciones (sin overlap)
28
  TRANSITION_DURATION = 1.5 # Duraci贸n del efecto slide
 
29
 
30
  # Validar existencia de archivos
31
  for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, EJEMPLO_VIDEO]:
@@ -38,7 +38,7 @@ def mostrar_uso_memoria():
38
  memoria_uso = proceso.memory_info().rss / 1024 / 1024
39
  logging.info(f"Uso de memoria: {memoria_uso:.2f} MB")
40
 
41
- def eliminar_archivo_tiempo(ruta, delay=1800):
42
  def eliminar():
43
  try:
44
  if os.path.exists(ruta):
@@ -61,11 +61,6 @@ def validar_video(video_path):
61
  clip = VideoFileClip(video_path)
62
  duracion = clip.duration
63
  clip.close()
64
-
65
- # Comprobar duraci贸n
66
- if duracion > MAX_VIDEO_DURATION:
67
- logging.warning(f"El video excede la duraci贸n m谩xima: {duracion}s > {MAX_VIDEO_DURATION}s")
68
- return False
69
 
70
  return True
71
  except Exception as e:
@@ -77,23 +72,15 @@ def convertir_video(video_path):
77
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_converted:
78
  output_path = tmp_converted.name
79
 
80
- # Primero convertir a un formato m谩s eficiente y con menor resoluci贸n
81
- os.system(f'ffmpeg -i "{video_path}" -vf "scale=640:360" -c:v libx264 -crf 28 -preset ultrafast -c:a aac -b:a 96k "{output_path}" -y')
82
 
83
- # Comprobar si ahora cumple las limitaciones
84
  if not validar_video(output_path):
85
- # Si sigue sin cumplir, recortar duraci贸n
86
- nuevo_clip = VideoFileClip(output_path)
87
- duracion_maxima = min(nuevo_clip.duration, MAX_VIDEO_DURATION)
88
- nuevo_clip = nuevo_clip.subclip(0, duracion_maxima)
89
-
90
- temp_recortado = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
91
- nuevo_clip.write_videofile(temp_recortado, codec="libx264", audio_codec="aac",
92
- preset="ultrafast", bitrate="1M")
93
- nuevo_clip.close()
94
-
95
  os.remove(output_path)
96
- return temp_recortado
97
 
98
  return output_path
99
  except Exception as e:
@@ -104,10 +91,10 @@ async def generar_tts(texto, voz, duracion_total):
104
  try:
105
  if not texto.strip():
106
  raise ValueError("El texto para TTS no puede estar vac铆o.")
107
- # Limitar el texto a 500 caracteres para procesar m谩s r谩pido
108
- if len(texto) > 500:
109
- texto = texto[:500]
110
- logging.info("Texto para TTS truncado a 500 caracteres para optimizar rendimiento")
111
 
112
  logging.info(f"Generando TTS con voz: {voz}")
113
  communicate = edge_tts.Communicate(texto, voz)
@@ -137,9 +124,9 @@ def create_slide_transition(clip1, clip2, duration=TRANSITION_DURATION):
137
  transition = CompositeVideoClip([
138
  part1.fx(vfx.fadeout, duration),
139
  part2.fx(vfx.fadein, duration).set_position(
140
- lambda t: ('center', 720 - (720 * (t/duration)))
141
  )
142
- ], size=(640, 360)).set_duration(duration) # Reducido a 640x360 para optimizar
143
  return transition
144
 
145
  def liberar_memoria(objetos_cerrar=None):
@@ -167,7 +154,7 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
167
  progress(0, desc="Validando video")
168
 
169
  if not validar_video(video_input):
170
- progress(0.05, desc="Convirtiendo formato de video")
171
  video_input = convertir_video(video_input)
172
  temp_files.append(video_input)
173
 
@@ -175,12 +162,11 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
175
  # Reducir resoluci贸n para optimizar procesamiento
176
  video_original = VideoFileClip(video_input)
177
  duracion_video = video_original.duration
 
 
 
 
178
 
179
- # Limitar duraci贸n si es necesario
180
- if duracion_video > MAX_VIDEO_DURATION:
181
- duracion_video = MAX_VIDEO_DURATION
182
- video_original = video_original.subclip(0, duracion_video)
183
-
184
  if duracion_video <= 0:
185
  raise ValueError("El video debe tener una duraci贸n mayor que cero.")
186
 
@@ -192,84 +178,116 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
192
  bg_audio, bg_path = crear_musica_fondo(duracion_video)
193
  temp_files.append(bg_path)
194
 
195
- progress(0.35, desc="Mezclando audio")
196
- audio_original = video_original.audio.volumex(0.5) if video_original.audio else None
197
- audios = [bg_audio.set_duration(duracion_video)]
198
- if audio_original:
199
- audios.append(audio_original)
200
- audios.append(tts_audio.set_start(0).volumex(0.85))
201
- audio_final = CompositeAudioClip(audios).set_duration(duracion_video)
202
 
203
- # Procesar por segmentos para optimizar memoria
204
- if duracion_video > SEGMENT_DURATION:
205
- progress(0.4, desc="Procesando segmentos de video")
206
- clips = []
207
- num_segments = int(duracion_video // SEGMENT_DURATION) + (1 if duracion_video % SEGMENT_DURATION > 0 else 0)
208
 
209
- for i in range(num_segments):
210
- progress_val = 0.4 + (0.3 * (i / num_segments))
211
- progress(progress_val, desc=f"Procesando segmento {i+1}/{num_segments}")
212
-
213
- start_time = i * SEGMENT_DURATION
214
- end_time = min(start_time + SEGMENT_DURATION, duracion_video)
215
- segment = video_original.subclip(start_time, end_time)
216
-
217
- # Reducir resoluci贸n si es necesario
218
- if segment.size[0] > MAX_RESOLUTION[0] or segment.size[1] > MAX_RESOLUTION[1]:
219
- segment = segment.resize(height=MAX_RESOLUTION[1])
220
-
221
- if i == 0:
222
- clips.append(segment)
223
- else:
224
- prev_segment = clips[-1]
225
- transition = create_slide_transition(prev_segment, segment)
226
- prev_end = prev_segment.duration - TRANSITION_DURATION
227
- if prev_end > 0:
228
- clips[-1] = prev_segment.subclip(0, prev_end)
229
- clips.append(transition)
230
- clips.append(segment)
 
 
 
231
 
232
- # Liberar memoria despu茅s de cada 2 segmentos
233
- if i % 2 == 1:
234
- liberar_memoria()
 
235
 
236
- video_final = concatenate_videoclips(clips, method="compose")
237
- else:
238
- video_final = video_original.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- # Asignar audio final
241
- progress(0.7, desc="Asignando audio")
242
- video_final = video_final.set_audio(audio_final)
 
243
 
244
- # A帽adir intro y outro
245
- progress(0.75, desc="A帽adiendo intro y outro")
246
- intro = VideoFileClip(INTRO_VIDEO, target_resolution=(360, 640)) # Reducido para optimizar
247
- outro = VideoFileClip(OUTRO_VIDEO, target_resolution=(360, 640)) # Reducido para optimizar
248
 
249
- # Crear el video final por partes
 
 
250
  with tempfile.NamedTemporaryFile(delete=False, suffix="_intro.mp4") as tmp_intro:
251
- intro.write_videofile(tmp_intro.name, codec="libx264", audio_codec="aac",
252
- preset="ultrafast", bitrate="1M",
253
- ffmpeg_params=["-crf", "30"])
254
- segmentos_temp.append(tmp_intro.name)
255
-
256
- with tempfile.NamedTemporaryFile(delete=False, suffix="_main.mp4") as tmp_main:
257
- video_final.write_videofile(tmp_main.name, codec="libx264", audio_codec="aac",
258
- preset="ultrafast", bitrate="1M",
259
- ffmpeg_params=["-crf", "30"])
260
- segmentos_temp.append(tmp_main.name)
261
-
262
- with tempfile.NamedTemporaryFile(delete=False, suffix="_outro.mp4") as tmp_outro:
263
- outro.write_videofile(tmp_outro.name, codec="libx264", audio_codec="aac",
264
- preset="ultrafast", bitrate="1M",
265
- ffmpeg_params=["-crf", "30"])
266
- segmentos_temp.append(tmp_outro.name)
267
 
268
- # Liberar memoria antes de la uni贸n final
269
- liberar_memoria([video_original, intro, outro, video_final])
270
- video_original = intro = outro = video_final = None
 
 
 
 
 
 
 
 
 
 
271
 
272
- # Unir los segmentos con ffmpeg directamente
273
  progress(0.9, desc="Generando video final")
274
  with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as concat_file:
275
  # Escribir archivo de lista para concatenaci贸n
@@ -287,7 +305,7 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
287
  if os.path.exists(segment):
288
  os.remove(segment)
289
 
290
- eliminar_archivo_tiempo(output_path, 3600) # Extendido a 1 hora
291
  progress(1.0, desc="隆Video listo!")
292
  logging.info(f"Video final guardado: {output_path}")
293
  mostrar_uso_memoria()
@@ -319,7 +337,7 @@ with gr.Blocks() as demo:
319
  with gr.Tab("Principal"):
320
  video_input = gr.Video(label="Subir video")
321
  texto_tts = gr.Textbox(
322
- label="Texto para TTS (m谩x. 500 caracteres)",
323
  lines=3,
324
  placeholder="Escribe aqu铆 tu texto..."
325
  )
@@ -393,15 +411,15 @@ with gr.Blocks() as demo:
393
 
394
  gr.Markdown("""
395
  ### 鈩癸笍 Notas importantes:
396
- - **Limitaciones para Hugging Face Spaces:**
397
- - M谩xima duraci贸n de video: 5 minutos
398
- - M谩ximo tama帽o de archivo: 100MB
399
- - Resoluci贸n reducida a 640x360 para procesamiento
400
- - Texto TTS limitado a 500 caracteres
401
  - Las transiciones ocurren cada 30 segundos
402
  - El video contiene intro y outro predefinidos
403
  - El archivo generado se elimina despu茅s de 1 hora
404
- - Para videos m谩s pesados, considera usar este c贸digo localmente
405
  """)
406
 
407
  if __name__ == "__main__":
 
19
  EJEMPLO_VIDEO = "ejemplo.mp4"
20
 
21
  # CONSTANTES DE LIMITACIONES
22
+ MAX_VIDEO_SIZE = 200 * 1024 * 1024 # Tama帽o m谩ximo en bytes (200MB)
23
+ MAX_RESOLUTION = (640, 360) # Resoluci贸n m谩xima (360p para optimizar)
 
24
 
25
  # Configuraci贸n de chunks
26
  SEGMENT_DURATION = 30 # Duraci贸n exacta entre transiciones (sin overlap)
27
  TRANSITION_DURATION = 1.5 # Duraci贸n del efecto slide
28
+ PROCESSING_CHUNK = 120 # Procesar en bloques de 2 minutos para optimizar memoria
29
 
30
  # Validar existencia de archivos
31
  for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, EJEMPLO_VIDEO]:
 
38
  memoria_uso = proceso.memory_info().rss / 1024 / 1024
39
  logging.info(f"Uso de memoria: {memoria_uso:.2f} MB")
40
 
41
+ def eliminar_archivo_tiempo(ruta, delay=3600):
42
  def eliminar():
43
  try:
44
  if os.path.exists(ruta):
 
61
  clip = VideoFileClip(video_path)
62
  duracion = clip.duration
63
  clip.close()
 
 
 
 
 
64
 
65
  return True
66
  except Exception as e:
 
72
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_converted:
73
  output_path = tmp_converted.name
74
 
75
+ # Convertir a un formato m谩s eficiente y con menor resoluci贸n para optimizar
76
+ os.system(f'ffmpeg -i "{video_path}" -vf "scale={MAX_RESOLUTION[0]}:{MAX_RESOLUTION[1]}" -c:v libx264 -crf 28 -preset ultrafast -c:a aac -b:a 96k "{output_path}" -y')
77
 
78
+ # Comprobar si ahora cumple las limitaciones de tama帽o
79
  if not validar_video(output_path):
80
+ # Si sigue sin cumplir, aumentar la compresi贸n
81
+ os.system(f'ffmpeg -i "{output_path}" -vf "scale={MAX_RESOLUTION[0]}:{MAX_RESOLUTION[1]}" -c:v libx264 -crf 32 -preset ultrafast -c:a aac -b:a 64k "{output_path}.tmp" -y')
 
 
 
 
 
 
 
 
82
  os.remove(output_path)
83
+ os.rename(f"{output_path}.tmp", output_path)
84
 
85
  return output_path
86
  except Exception as e:
 
91
  try:
92
  if not texto.strip():
93
  raise ValueError("El texto para TTS no puede estar vac铆o.")
94
+ # Limitar el texto a 1000 caracteres para procesar m谩s r谩pido
95
+ if len(texto) > 1000:
96
+ texto = texto[:1000]
97
+ logging.info("Texto para TTS truncado a 1000 caracteres para optimizar rendimiento")
98
 
99
  logging.info(f"Generando TTS con voz: {voz}")
100
  communicate = edge_tts.Communicate(texto, voz)
 
124
  transition = CompositeVideoClip([
125
  part1.fx(vfx.fadeout, duration),
126
  part2.fx(vfx.fadein, duration).set_position(
127
+ lambda t: ('center', MAX_RESOLUTION[1] - (MAX_RESOLUTION[1] * (t/duration)))
128
  )
129
+ ], size=MAX_RESOLUTION).set_duration(duration) # Reducido para optimizar
130
  return transition
131
 
132
  def liberar_memoria(objetos_cerrar=None):
 
154
  progress(0, desc="Validando video")
155
 
156
  if not validar_video(video_input):
157
+ progress(0.05, desc="Optimizando formato de video")
158
  video_input = convertir_video(video_input)
159
  temp_files.append(video_input)
160
 
 
162
  # Reducir resoluci贸n para optimizar procesamiento
163
  video_original = VideoFileClip(video_input)
164
  duracion_video = video_original.duration
165
+ video_original.close() # Cerrar para liberar memoria
166
+
167
+ # Informaci贸n importante sobre el video original
168
+ logging.info(f"Duraci贸n total del video: {duracion_video} segundos")
169
 
 
 
 
 
 
170
  if duracion_video <= 0:
171
  raise ValueError("El video debe tener una duraci贸n mayor que cero.")
172
 
 
178
  bg_audio, bg_path = crear_musica_fondo(duracion_video)
179
  temp_files.append(bg_path)
180
 
181
+ # Procesar por bloques para optimizar memoria
182
+ num_chunks = int(duracion_video // PROCESSING_CHUNK) + (1 if duracion_video % PROCESSING_CHUNK > 0 else 0)
183
+ logging.info(f"Procesando video en {num_chunks} bloques")
 
 
 
 
184
 
185
+ for chunk_idx in range(num_chunks):
186
+ chunk_start = chunk_idx * PROCESSING_CHUNK
187
+ chunk_end = min((chunk_idx + 1) * PROCESSING_CHUNK, duracion_video)
 
 
188
 
189
+ progress(0.35 + (0.45 * chunk_idx / num_chunks),
190
+ desc=f"Procesando bloque {chunk_idx+1}/{num_chunks} ({chunk_start:.1f}s - {chunk_end:.1f}s)")
191
+
192
+ # Cargar solo la porci贸n del video que necesitamos
193
+ chunk_video = VideoFileClip(video_input).subclip(chunk_start, chunk_end)
194
+
195
+ # Extraer la porci贸n de audio correspondiente a este bloque
196
+ chunk_tts = tts_audio.subclip(chunk_start, chunk_end) if chunk_start < tts_audio.duration else None
197
+ chunk_bg = bg_audio.subclip(chunk_start, chunk_end)
198
+
199
+ # Crear la mezcla de audio para este bloque
200
+ audio_chunks = [chunk_bg]
201
+ if chunk_video.audio:
202
+ audio_chunks.append(chunk_video.audio.volumex(0.5))
203
+ if chunk_tts:
204
+ audio_chunks.append(chunk_tts.volumex(0.85))
205
+
206
+ chunk_audio_final = CompositeAudioClip(audio_chunks)
207
+ chunk_video = chunk_video.set_audio(chunk_audio_final)
208
+
209
+ # Procesar las transiciones dentro de este chunk si es necesario
210
+ if chunk_end - chunk_start > SEGMENT_DURATION:
211
+ segments_in_chunk = []
212
+ segments_count = int((chunk_end - chunk_start) // SEGMENT_DURATION) + \
213
+ (1 if (chunk_end - chunk_start) % SEGMENT_DURATION > 0 else 0)
214
 
215
+ for i in range(segments_count):
216
+ seg_start = i * SEGMENT_DURATION
217
+ seg_end = min(seg_start + SEGMENT_DURATION, chunk_end - chunk_start)
218
+ segment = chunk_video.subclip(seg_start, seg_end)
219
 
220
+ if i == 0:
221
+ segments_in_chunk.append(segment)
222
+ else:
223
+ prev_segment = segments_in_chunk[-1]
224
+ transition = create_slide_transition(prev_segment, segment)
225
+
226
+ prev_end = prev_segment.duration - TRANSITION_DURATION
227
+ if prev_end > 0:
228
+ segments_in_chunk[-1] = prev_segment.subclip(0, prev_end)
229
+
230
+ segments_in_chunk.append(transition)
231
+ segments_in_chunk.append(segment)
232
+
233
+ chunk_processed = concatenate_videoclips(segments_in_chunk, method="compose")
234
+ else:
235
+ chunk_processed = chunk_video
236
+
237
+ # Guardar este chunk procesado como archivo temporal
238
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f"_chunk{chunk_idx}.mp4") as chunk_file:
239
+ chunk_path = chunk_file.name
240
+ chunk_processed.write_videofile(
241
+ chunk_path,
242
+ codec="libx264",
243
+ audio_codec="aac",
244
+ preset="ultrafast",
245
+ bitrate="1M",
246
+ ffmpeg_params=["-crf", "28"],
247
+ verbose=False
248
+ )
249
+ segmentos_temp.append(chunk_path)
250
 
251
+ # Liberar memoria
252
+ chunk_video.close()
253
+ chunk_processed.close()
254
+ liberar_memoria()
255
 
256
+ # Liberar memoria antes de procesar intro/outro
257
+ liberar_memoria([tts_audio, bg_audio])
258
+ tts_audio = bg_audio = None
 
259
 
260
+ # A帽adir intro y outro
261
+ progress(0.85, desc="Preparando intro y outro")
262
+ intro = VideoFileClip(INTRO_VIDEO, target_resolution=MAX_RESOLUTION)
263
  with tempfile.NamedTemporaryFile(delete=False, suffix="_intro.mp4") as tmp_intro:
264
+ intro.write_videofile(
265
+ tmp_intro.name,
266
+ codec="libx264",
267
+ audio_codec="aac",
268
+ preset="ultrafast",
269
+ bitrate="1M",
270
+ ffmpeg_params=["-crf", "28"],
271
+ verbose=False
272
+ )
273
+ segmentos_temp.insert(0, tmp_intro.name) # Intro al principio
274
+ intro.close()
 
 
 
 
 
275
 
276
+ outro = VideoFileClip(OUTRO_VIDEO, target_resolution=MAX_RESOLUTION)
277
+ with tempfile.NamedTemporaryFile(delete=False, suffix="_outro.mp4") as tmp_outro:
278
+ outro.write_videofile(
279
+ tmp_outro.name,
280
+ codec="libx264",
281
+ audio_codec="aac",
282
+ preset="ultrafast",
283
+ bitrate="1M",
284
+ ffmpeg_params=["-crf", "28"],
285
+ verbose=False
286
+ )
287
+ segmentos_temp.append(tmp_outro.name) # Outro al final
288
+ outro.close()
289
 
290
+ # Unir todos los segmentos con ffmpeg
291
  progress(0.9, desc="Generando video final")
292
  with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as concat_file:
293
  # Escribir archivo de lista para concatenaci贸n
 
305
  if os.path.exists(segment):
306
  os.remove(segment)
307
 
308
+ eliminar_archivo_tiempo(output_path, 3600) # Eliminaci贸n despu茅s de 1 hora
309
  progress(1.0, desc="隆Video listo!")
310
  logging.info(f"Video final guardado: {output_path}")
311
  mostrar_uso_memoria()
 
337
  with gr.Tab("Principal"):
338
  video_input = gr.Video(label="Subir video")
339
  texto_tts = gr.Textbox(
340
+ label="Texto para TTS (m谩x. 1000 caracteres)",
341
  lines=3,
342
  placeholder="Escribe aqu铆 tu texto..."
343
  )
 
411
 
412
  gr.Markdown("""
413
  ### 鈩癸笍 Notas importantes:
414
+ - **Optimizaciones para Hugging Face Spaces:**
415
+ - Procesamiento por bloques para videos largos
416
+ - M谩ximo tama帽o de archivo: 200MB
417
+ - Resoluci贸n reducida a 640x360 para procesamiento m谩s r谩pido
418
+ - Texto TTS limitado a 1000 caracteres
419
  - Las transiciones ocurren cada 30 segundos
420
  - El video contiene intro y outro predefinidos
421
  - El archivo generado se elimina despu茅s de 1 hora
422
+ - Para videos de alta calidad, considera usar este c贸digo localmente
423
  """)
424
 
425
  if __name__ == "__main__":