Update app.py
Browse files
app.py
CHANGED
@@ -59,6 +59,8 @@ def validar_video(video_path):
|
|
59 |
# Validar que es un video
|
60 |
clip = VideoFileClip(video_path)
|
61 |
duracion = clip.duration
|
|
|
|
|
62 |
clip.close()
|
63 |
|
64 |
return True
|
@@ -66,18 +68,37 @@ def validar_video(video_path):
|
|
66 |
logging.error(f"El video no es v谩lido: {e}")
|
67 |
return False
|
68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
def convertir_video(video_path):
|
70 |
try:
|
|
|
|
|
|
|
|
|
71 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_converted:
|
72 |
output_path = tmp_converted.name
|
73 |
|
74 |
-
# Convertir a un formato m谩s eficiente pero manteniendo la resoluci贸n original
|
75 |
-
os.system(f'ffmpeg -i "{video_path}" -c:v libx264 -crf 28 -preset ultrafast -c:a aac -b:a 96k "{output_path}" -y')
|
76 |
|
77 |
# Comprobar si ahora cumple las limitaciones de tama帽o
|
78 |
if not validar_video(output_path):
|
79 |
# Si sigue sin cumplir, aumentar la compresi贸n pero sin cambiar la resoluci贸n
|
80 |
-
os.system(f'ffmpeg -i "{output_path}" -c:v libx264 -crf 32 -preset ultrafast -c:a aac -b:a 64k "{output_path}.tmp" -y')
|
81 |
os.remove(output_path)
|
82 |
os.rename(f"{output_path}.tmp", output_path)
|
83 |
|
@@ -155,6 +176,11 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
155 |
logging.info("Iniciando procesamiento")
|
156 |
progress(0, desc="Validando video")
|
157 |
|
|
|
|
|
|
|
|
|
|
|
158 |
if not validar_video(video_input):
|
159 |
progress(0.05, desc="Optimizando formato de video")
|
160 |
video_input = convertir_video(video_input)
|
@@ -197,6 +223,11 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
197 |
# Cargar solo la porci贸n del video que necesitamos
|
198 |
chunk_video = VideoFileClip(video_input).subclip(chunk_start, chunk_end)
|
199 |
|
|
|
|
|
|
|
|
|
|
|
200 |
# Extraer la porci贸n de audio correspondiente a este bloque
|
201 |
tts_chunk_end = min(chunk_end, tts_audio.duration)
|
202 |
chunk_tts = None
|
@@ -252,6 +283,7 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
252 |
audio_codec="aac",
|
253 |
preset="ultrafast",
|
254 |
bitrate="1M",
|
|
|
255 |
ffmpeg_params=["-crf", "28"],
|
256 |
verbose=False
|
257 |
)
|
@@ -268,13 +300,20 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
268 |
|
269 |
# A帽adir intro y outro - conservar resoluci贸n original para consistencia
|
270 |
progress(0.85, desc="Preparando intro y outro")
|
|
|
|
|
271 |
intro = VideoFileClip(INTRO_VIDEO)
|
|
|
|
|
|
|
|
|
272 |
with tempfile.NamedTemporaryFile(delete=False, suffix="_intro.mp4") as tmp_intro:
|
273 |
intro.write_videofile(
|
274 |
tmp_intro.name,
|
275 |
codec="libx264",
|
276 |
audio_codec="aac",
|
277 |
-
preset="ultrafast",
|
|
|
278 |
bitrate="1M",
|
279 |
ffmpeg_params=["-crf", "28"],
|
280 |
verbose=False
|
@@ -282,13 +321,19 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
282 |
segmentos_temp.insert(0, tmp_intro.name) # Intro al principio
|
283 |
intro.close()
|
284 |
|
|
|
285 |
outro = VideoFileClip(OUTRO_VIDEO)
|
|
|
|
|
|
|
|
|
286 |
with tempfile.NamedTemporaryFile(delete=False, suffix="_outro.mp4") as tmp_outro:
|
287 |
outro.write_videofile(
|
288 |
tmp_outro.name,
|
289 |
codec="libx264",
|
290 |
audio_codec="aac",
|
291 |
preset="ultrafast",
|
|
|
292 |
bitrate="1M",
|
293 |
ffmpeg_params=["-crf", "28"],
|
294 |
verbose=False
|
@@ -298,15 +343,21 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
298 |
|
299 |
# Unir todos los segmentos con ffmpeg
|
300 |
progress(0.9, desc="Generando video final")
|
|
|
|
|
301 |
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as concat_file:
|
302 |
# Escribir archivo de lista para concatenaci贸n
|
303 |
for segment in segmentos_temp:
|
304 |
concat_file.write(f"file '{segment}'\n".encode())
|
305 |
concat_path = concat_file.name
|
306 |
-
|
|
|
307 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_final:
|
308 |
output_path = tmp_final.name
|
309 |
-
|
|
|
|
|
|
|
310 |
|
311 |
# Limpiar archivos temporales
|
312 |
os.remove(concat_path)
|
@@ -314,6 +365,13 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
314 |
if os.path.exists(segment):
|
315 |
os.remove(segment)
|
316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
317 |
eliminar_archivo_tiempo(output_path, 3600) # Eliminaci贸n despu茅s de 1 hora
|
318 |
progress(1.0, desc="隆Video listo!")
|
319 |
logging.info(f"Video final guardado: {output_path}")
|
@@ -424,6 +482,7 @@ with gr.Blocks() as demo:
|
|
424 |
- Procesamiento por bloques para videos largos
|
425 |
- M谩ximo tama帽o de archivo: 200MB
|
426 |
- Mantiene la resoluci贸n original del video
|
|
|
427 |
- Texto TTS limitado a 1000 caracteres
|
428 |
- Las transiciones ocurren cada 30 segundos
|
429 |
- El video contiene intro y outro predefinidos
|
|
|
59 |
# Validar que es un video
|
60 |
clip = VideoFileClip(video_path)
|
61 |
duracion = clip.duration
|
62 |
+
fps = clip.fps
|
63 |
+
logging.info(f"Video validado: duraci贸n={duracion}s, fps={fps}")
|
64 |
clip.close()
|
65 |
|
66 |
return True
|
|
|
68 |
logging.error(f"El video no es v谩lido: {e}")
|
69 |
return False
|
70 |
|
71 |
+
def obtener_info_video(video_path):
|
72 |
+
"""Obtiene informaci贸n b谩sica del video como FPS, duraci贸n y tama帽o"""
|
73 |
+
try:
|
74 |
+
clip = VideoFileClip(video_path)
|
75 |
+
info = {
|
76 |
+
"fps": clip.fps,
|
77 |
+
"duration": clip.duration,
|
78 |
+
"size": clip.size
|
79 |
+
}
|
80 |
+
clip.close()
|
81 |
+
return info
|
82 |
+
except Exception as e:
|
83 |
+
logging.error(f"Error al obtener info del video: {e}")
|
84 |
+
return {"fps": 30, "duration": 0, "size": (640, 360)} # valores por defecto
|
85 |
+
|
86 |
def convertir_video(video_path):
|
87 |
try:
|
88 |
+
# Obtener FPS del video original para mantenerlo
|
89 |
+
info = obtener_info_video(video_path)
|
90 |
+
fps = info["fps"] if info["fps"] else 30 # Valor por defecto si no se puede determinar
|
91 |
+
|
92 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_converted:
|
93 |
output_path = tmp_converted.name
|
94 |
|
95 |
+
# Convertir a un formato m谩s eficiente pero manteniendo la resoluci贸n original Y el framerate
|
96 |
+
os.system(f'ffmpeg -i "{video_path}" -c:v libx264 -crf 28 -preset ultrafast -r {fps} -c:a aac -b:a 96k "{output_path}" -y')
|
97 |
|
98 |
# Comprobar si ahora cumple las limitaciones de tama帽o
|
99 |
if not validar_video(output_path):
|
100 |
# Si sigue sin cumplir, aumentar la compresi贸n pero sin cambiar la resoluci贸n
|
101 |
+
os.system(f'ffmpeg -i "{output_path}" -c:v libx264 -crf 32 -preset ultrafast -r {fps} -c:a aac -b:a 64k "{output_path}.tmp" -y')
|
102 |
os.remove(output_path)
|
103 |
os.rename(f"{output_path}.tmp", output_path)
|
104 |
|
|
|
176 |
logging.info("Iniciando procesamiento")
|
177 |
progress(0, desc="Validando video")
|
178 |
|
179 |
+
# Obtener informaci贸n del video original
|
180 |
+
original_info = obtener_info_video(video_input)
|
181 |
+
original_fps = original_info["fps"]
|
182 |
+
logging.info(f"Video original - FPS: {original_fps}, Tama帽o: {original_info['size']}")
|
183 |
+
|
184 |
if not validar_video(video_input):
|
185 |
progress(0.05, desc="Optimizando formato de video")
|
186 |
video_input = convertir_video(video_input)
|
|
|
223 |
# Cargar solo la porci贸n del video que necesitamos
|
224 |
chunk_video = VideoFileClip(video_input).subclip(chunk_start, chunk_end)
|
225 |
|
226 |
+
# Aseguramos que el framerate se mantiene en todos los clips
|
227 |
+
if original_fps and chunk_video.fps != original_fps:
|
228 |
+
logging.info(f"Ajustando FPS del chunk {chunk_idx+1} a {original_fps}")
|
229 |
+
chunk_video = chunk_video.set_fps(original_fps)
|
230 |
+
|
231 |
# Extraer la porci贸n de audio correspondiente a este bloque
|
232 |
tts_chunk_end = min(chunk_end, tts_audio.duration)
|
233 |
chunk_tts = None
|
|
|
283 |
audio_codec="aac",
|
284 |
preset="ultrafast",
|
285 |
bitrate="1M",
|
286 |
+
fps=original_fps, # Asegurar que se mantiene el FPS original
|
287 |
ffmpeg_params=["-crf", "28"],
|
288 |
verbose=False
|
289 |
)
|
|
|
300 |
|
301 |
# A帽adir intro y outro - conservar resoluci贸n original para consistencia
|
302 |
progress(0.85, desc="Preparando intro y outro")
|
303 |
+
|
304 |
+
# Procesamiento del intro
|
305 |
intro = VideoFileClip(INTRO_VIDEO)
|
306 |
+
# Asegurar que se utiliza el mismo FPS que el video original
|
307 |
+
if original_fps and intro.fps != original_fps:
|
308 |
+
intro = intro.set_fps(original_fps)
|
309 |
+
|
310 |
with tempfile.NamedTemporaryFile(delete=False, suffix="_intro.mp4") as tmp_intro:
|
311 |
intro.write_videofile(
|
312 |
tmp_intro.name,
|
313 |
codec="libx264",
|
314 |
audio_codec="aac",
|
315 |
+
preset="ultrafast",
|
316 |
+
fps=original_fps, # Usar FPS original
|
317 |
bitrate="1M",
|
318 |
ffmpeg_params=["-crf", "28"],
|
319 |
verbose=False
|
|
|
321 |
segmentos_temp.insert(0, tmp_intro.name) # Intro al principio
|
322 |
intro.close()
|
323 |
|
324 |
+
# Procesamiento del outro
|
325 |
outro = VideoFileClip(OUTRO_VIDEO)
|
326 |
+
# Asegurar que se utiliza el mismo FPS que el video original
|
327 |
+
if original_fps and outro.fps != original_fps:
|
328 |
+
outro = outro.set_fps(original_fps)
|
329 |
+
|
330 |
with tempfile.NamedTemporaryFile(delete=False, suffix="_outro.mp4") as tmp_outro:
|
331 |
outro.write_videofile(
|
332 |
tmp_outro.name,
|
333 |
codec="libx264",
|
334 |
audio_codec="aac",
|
335 |
preset="ultrafast",
|
336 |
+
fps=original_fps, # Usar FPS original
|
337 |
bitrate="1M",
|
338 |
ffmpeg_params=["-crf", "28"],
|
339 |
verbose=False
|
|
|
343 |
|
344 |
# Unir todos los segmentos con ffmpeg
|
345 |
progress(0.9, desc="Generando video final")
|
346 |
+
|
347 |
+
# Crear un archivo de metadatos para ffmpeg
|
348 |
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as concat_file:
|
349 |
# Escribir archivo de lista para concatenaci贸n
|
350 |
for segment in segmentos_temp:
|
351 |
concat_file.write(f"file '{segment}'\n".encode())
|
352 |
concat_path = concat_file.name
|
353 |
+
|
354 |
+
# Usar FFmpeg para concatenar todos los segmentos
|
355 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_final:
|
356 |
output_path = tmp_final.name
|
357 |
+
# Usar el par谩metro -vsync para asegurar que se mantiene la sincronizaci贸n de video
|
358 |
+
cmd = f'ffmpeg -f concat -safe 0 -i "{concat_path}" -vsync cfr -c copy "{output_path}" -y'
|
359 |
+
logging.info(f"Ejecutando comando FFmpeg: {cmd}")
|
360 |
+
os.system(cmd)
|
361 |
|
362 |
# Limpiar archivos temporales
|
363 |
os.remove(concat_path)
|
|
|
365 |
if os.path.exists(segment):
|
366 |
os.remove(segment)
|
367 |
|
368 |
+
# Verificar que el video final tiene la duraci贸n esperada
|
369 |
+
try:
|
370 |
+
final_info = obtener_info_video(output_path)
|
371 |
+
logging.info(f"Video final - Duraci贸n: {final_info['duration']}s, FPS: {final_info['fps']}")
|
372 |
+
except Exception as e:
|
373 |
+
logging.error(f"No se pudo verificar el video final: {e}")
|
374 |
+
|
375 |
eliminar_archivo_tiempo(output_path, 3600) # Eliminaci贸n despu茅s de 1 hora
|
376 |
progress(1.0, desc="隆Video listo!")
|
377 |
logging.info(f"Video final guardado: {output_path}")
|
|
|
482 |
- Procesamiento por bloques para videos largos
|
483 |
- M谩ximo tama帽o de archivo: 200MB
|
484 |
- Mantiene la resoluci贸n original del video
|
485 |
+
- Mantiene la velocidad original del video (FPS)
|
486 |
- Texto TTS limitado a 1000 caracteres
|
487 |
- Las transiciones ocurren cada 30 segundos
|
488 |
- El video contiene intro y outro predefinidos
|