Actualizar nodo OmniVision
Browse files
ffmpeg.py
CHANGED
|
@@ -269,20 +269,33 @@ def normalize_thumbnail_image(src_path: str, dst_path: str) -> bool:
|
|
| 269 |
return False
|
| 270 |
|
| 271 |
|
| 272 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
input_path: str,
|
| 274 |
output_path: str,
|
| 275 |
settings: Dict[str, Any],
|
| 276 |
wm_image_path: str,
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
is_cancelled: Optional[Callable[[], bool]] = None,
|
| 281 |
-
) -> None:
|
| 282 |
-
"""
|
| 283 |
-
Ejecuta la compresión con FFmpeg reportando el progreso en vivo.
|
| 284 |
-
Implementa el escaneo exacto de CRLF y CR de FFmpeg.
|
| 285 |
-
"""
|
| 286 |
settings = sanitize_settings(settings)
|
| 287 |
|
| 288 |
args = ["-y", "-i", input_path]
|
|
@@ -315,13 +328,15 @@ def compress_with_cmd(
|
|
| 315 |
|
| 316 |
is_avi = settings.get("format", "").lower() == "avi" or output_path.lower().endswith(".avi")
|
| 317 |
|
|
|
|
| 318 |
# Subtítulos de texto para contenedores no AVI
|
| 319 |
-
if not is_avi:
|
| 320 |
text_sub_streams = get_text_subtitle_streams(input_path)
|
| 321 |
for idx in text_sub_streams:
|
| 322 |
args.extend(["-map", f"0:{idx}"])
|
| 323 |
if text_sub_streams:
|
| 324 |
args.extend(["-c:s", "mov_text"])
|
|
|
|
| 325 |
|
| 326 |
codec = settings.get("codec") or "libx264"
|
| 327 |
args.extend(["-c:v", codec])
|
|
@@ -354,7 +369,16 @@ def compress_with_cmd(
|
|
| 354 |
args.extend(["-movflags", "+faststart"])
|
| 355 |
|
| 356 |
args.extend(["-threads", "0", output_path])
|
|
|
|
|
|
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
cmd_list = ["ffmpeg"] + args
|
| 359 |
|
| 360 |
proc = subprocess.Popen(
|
|
@@ -451,3 +475,39 @@ def compress_with_cmd(
|
|
| 451 |
proc.kill()
|
| 452 |
except Exception:
|
| 453 |
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
return False
|
| 270 |
|
| 271 |
|
| 272 |
+
def is_subtitle_or_mux_error(err_str: str) -> bool:
|
| 273 |
+
"""Comprueba si el error de stderr de FFmpeg está relacionado con subtítulos corruptos o fallos de muxing."""
|
| 274 |
+
lower = err_str.lower()
|
| 275 |
+
indicators = [
|
| 276 |
+
"application provided duration",
|
| 277 |
+
"mov_text",
|
| 278 |
+
"subtitle",
|
| 279 |
+
"av_interleaved_write_frame",
|
| 280 |
+
"invalid argument",
|
| 281 |
+
"error while writing stream",
|
| 282 |
+
"pts has no value",
|
| 283 |
+
"out of range for mov/mp4",
|
| 284 |
+
"invalid dts",
|
| 285 |
+
"invalid pts",
|
| 286 |
+
"muxing",
|
| 287 |
+
]
|
| 288 |
+
return any(ind in lower for ind in indicators)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def build_compress_args(
|
| 292 |
input_path: str,
|
| 293 |
output_path: str,
|
| 294 |
settings: Dict[str, Any],
|
| 295 |
wm_image_path: str,
|
| 296 |
+
include_subtitles: bool = True,
|
| 297 |
+
) -> Tuple[List[str], bool]:
|
| 298 |
+
"""Genera la lista de argumentos para FFmpeg, permitiendo habilitar o deshabilitar subtítulos."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
settings = sanitize_settings(settings)
|
| 300 |
|
| 301 |
args = ["-y", "-i", input_path]
|
|
|
|
| 328 |
|
| 329 |
is_avi = settings.get("format", "").lower() == "avi" or output_path.lower().endswith(".avi")
|
| 330 |
|
| 331 |
+
has_subtitles = False
|
| 332 |
# Subtítulos de texto para contenedores no AVI
|
| 333 |
+
if not is_avi and include_subtitles:
|
| 334 |
text_sub_streams = get_text_subtitle_streams(input_path)
|
| 335 |
for idx in text_sub_streams:
|
| 336 |
args.extend(["-map", f"0:{idx}"])
|
| 337 |
if text_sub_streams:
|
| 338 |
args.extend(["-c:s", "mov_text"])
|
| 339 |
+
has_subtitles = True
|
| 340 |
|
| 341 |
codec = settings.get("codec") or "libx264"
|
| 342 |
args.extend(["-c:v", codec])
|
|
|
|
| 369 |
args.extend(["-movflags", "+faststart"])
|
| 370 |
|
| 371 |
args.extend(["-threads", "0", output_path])
|
| 372 |
+
return args, has_subtitles
|
| 373 |
+
|
| 374 |
|
| 375 |
+
def execute_ffmpeg_cmd(
|
| 376 |
+
args: List[str],
|
| 377 |
+
total_duration: float,
|
| 378 |
+
on_cmd: Optional[Callable[[subprocess.Popen], None]] = None,
|
| 379 |
+
on_progress: Optional[Callable[[Dict[str, Any]], None]] = None,
|
| 380 |
+
is_cancelled: Optional[Callable[[], bool]] = None,
|
| 381 |
+
) -> None:
|
| 382 |
cmd_list = ["ffmpeg"] + args
|
| 383 |
|
| 384 |
proc = subprocess.Popen(
|
|
|
|
| 475 |
proc.kill()
|
| 476 |
except Exception:
|
| 477 |
pass
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
def compress_with_cmd(
|
| 481 |
+
input_path: str,
|
| 482 |
+
output_path: str,
|
| 483 |
+
settings: Dict[str, Any],
|
| 484 |
+
wm_image_path: str,
|
| 485 |
+
total_duration: float,
|
| 486 |
+
on_cmd: Optional[Callable[[subprocess.Popen], None]] = None,
|
| 487 |
+
on_progress: Optional[Callable[[Dict[str, Any]], None]] = None,
|
| 488 |
+
is_cancelled: Optional[Callable[[], bool]] = None,
|
| 489 |
+
) -> None:
|
| 490 |
+
"""
|
| 491 |
+
Ejecuta la compresión con FFmpeg reportando el progreso en vivo.
|
| 492 |
+
Si la compresión falla debido a subtítulos corruptos o timestamps inválidos,
|
| 493 |
+
reintenta automáticamente descartando los subtítulos para no fallar la tarea.
|
| 494 |
+
"""
|
| 495 |
+
args, has_subtitles = build_compress_args(input_path, output_path, settings, wm_image_path, include_subtitles=True)
|
| 496 |
+
try:
|
| 497 |
+
execute_ffmpeg_cmd(args, total_duration, on_cmd, on_progress, is_cancelled)
|
| 498 |
+
return
|
| 499 |
+
except RuntimeError as e:
|
| 500 |
+
if is_cancelled and is_cancelled():
|
| 501 |
+
raise
|
| 502 |
+
if has_subtitles and is_subtitle_or_mux_error(str(e)):
|
| 503 |
+
logger.warning(f"[ffmpeg] Falló compresión con subtítulos ({e}). Reintentando sin subtítulos...")
|
| 504 |
+
if os.path.isfile(output_path):
|
| 505 |
+
try:
|
| 506 |
+
os.remove(output_path)
|
| 507 |
+
except Exception:
|
| 508 |
+
pass
|
| 509 |
+
retry_args, _ = build_compress_args(input_path, output_path, settings, wm_image_path, include_subtitles=False)
|
| 510 |
+
execute_ffmpeg_cmd(retry_args, total_duration, on_cmd, on_progress, is_cancelled)
|
| 511 |
+
logger.info("[ffmpeg] Compresión completada con éxito tras descartar subtítulos corruptos.")
|
| 512 |
+
return
|
| 513 |
+
raise
|