Spaces:
Sleeping
Sleeping
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
|