Spaces:
Sleeping
Sleeping
Commit ·
d08a9af
1
Parent(s): 9f6fc53
Add video segment compression for Replicate uploads. Implemented logic to prepare segments for upload, ensuring they meet the 100 MiB file size limit. Added functionality to compress segments exceeding the limit and updated upload handling to use the prepared segments.
Browse files- app/main.py +76 -1
app/main.py
CHANGED
|
@@ -27,6 +27,9 @@ API_BASE = "https://api.replicate.com/v1"
|
|
| 27 |
MODEL_PATH = "heygen/video-translate"
|
| 28 |
|
| 29 |
POLL_SECONDS = 8
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
class RunCancelledError(RuntimeError):
|
|
@@ -62,6 +65,14 @@ assert _summarize_segment_failures(
|
|
| 62 |
assert _summarize_segment_failures([{"error": "network timeout"}]) == "network timeout"
|
| 63 |
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
def ensure_ffmpeg() -> None:
|
| 66 |
if shutil.which("ffmpeg") is None:
|
| 67 |
raise RuntimeError("`ffmpeg` is not installed or not on PATH.")
|
|
@@ -366,7 +377,7 @@ class TranslationRunManager:
|
|
| 366 |
self._update_segment(run_id, index, status="uploading", error=None)
|
| 367 |
if self._is_cancelled(run_id):
|
| 368 |
raise RunCancelledError("Run cancelled by user.")
|
| 369 |
-
file_url = self._upload_segment(
|
| 370 |
self._update_segment(run_id, index, input_video_url=file_url)
|
| 371 |
|
| 372 |
self._update_segment(run_id, index, status="processing")
|
|
@@ -401,6 +412,70 @@ class TranslationRunManager:
|
|
| 401 |
self._update_segment(run_id, index, status="failed", error=str(exc))
|
| 402 |
return SegmentTaskResult(index=index, status="failed", error=str(exc))
|
| 403 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
def _upload_segment(self, file_path: Path, token: str) -> str:
|
| 405 |
with file_path.open("rb") as handle:
|
| 406 |
response = requests.post(
|
|
|
|
| 27 |
MODEL_PATH = "heygen/video-translate"
|
| 28 |
|
| 29 |
POLL_SECONDS = 8
|
| 30 |
+
# Replicate Files API rejects uploads over 100 MiB.
|
| 31 |
+
REPLICATE_MAX_UPLOAD_BYTES = 95 * 1024 * 1024
|
| 32 |
+
UPLOAD_AUDIO_BITRATE = 128_000
|
| 33 |
|
| 34 |
|
| 35 |
class RunCancelledError(RuntimeError):
|
|
|
|
| 65 |
assert _summarize_segment_failures([{"error": "network timeout"}]) == "network timeout"
|
| 66 |
|
| 67 |
|
| 68 |
+
def _target_video_bitrate(duration_seconds: float, max_bytes: int, factor: float) -> int:
|
| 69 |
+
audio_bps = UPLOAD_AUDIO_BITRATE
|
| 70 |
+
return max(400_000, int((max_bytes * 8 * factor) / max(duration_seconds, 1.0) - audio_bps))
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
assert 1_000_000 < _target_video_bitrate(60, REPLICATE_MAX_UPLOAD_BYTES, 0.85) < 15_000_000
|
| 74 |
+
|
| 75 |
+
|
| 76 |
def ensure_ffmpeg() -> None:
|
| 77 |
if shutil.which("ffmpeg") is None:
|
| 78 |
raise RuntimeError("`ffmpeg` is not installed or not on PATH.")
|
|
|
|
| 377 |
self._update_segment(run_id, index, status="uploading", error=None)
|
| 378 |
if self._is_cancelled(run_id):
|
| 379 |
raise RunCancelledError("Run cancelled by user.")
|
| 380 |
+
file_url = self._upload_segment(self._prepare_segment_for_upload(run_id, index), token)
|
| 381 |
self._update_segment(run_id, index, input_video_url=file_url)
|
| 382 |
|
| 383 |
self._update_segment(run_id, index, status="processing")
|
|
|
|
| 412 |
self._update_segment(run_id, index, status="failed", error=str(exc))
|
| 413 |
return SegmentTaskResult(index=index, status="failed", error=str(exc))
|
| 414 |
|
| 415 |
+
def _prepare_segment_for_upload(self, run_id: str, index: int) -> Path:
|
| 416 |
+
run = self.get_run(run_id)
|
| 417 |
+
if not run:
|
| 418 |
+
raise RuntimeError("Run missing.")
|
| 419 |
+
segment = run["segments"][index]
|
| 420 |
+
src = Path(segment["path"])
|
| 421 |
+
dest = src.with_name(f"{src.stem}_fit.mp4")
|
| 422 |
+
if dest.exists() and 0 < dest.stat().st_size <= REPLICATE_MAX_UPLOAD_BYTES:
|
| 423 |
+
return dest
|
| 424 |
+
size = src.stat().st_size
|
| 425 |
+
if size <= REPLICATE_MAX_UPLOAD_BYTES:
|
| 426 |
+
return src
|
| 427 |
+
self._log(
|
| 428 |
+
run,
|
| 429 |
+
f"[{segment['name']}] {size // (1024 * 1024)} MiB exceeds Replicate's 100 MiB file limit; compressing.",
|
| 430 |
+
)
|
| 431 |
+
self._compress_for_upload(src, dest, REPLICATE_MAX_UPLOAD_BYTES)
|
| 432 |
+
fitted = dest.stat().st_size // (1024 * 1024)
|
| 433 |
+
self._log(run, f"[{segment['name']}] compressed to {fitted} MiB.")
|
| 434 |
+
return dest
|
| 435 |
+
|
| 436 |
+
@staticmethod
|
| 437 |
+
def _compress_for_upload(src: Path, dest: Path, max_bytes: int) -> None:
|
| 438 |
+
duration = TranslationRunManager._probe_duration(src) or 60.0
|
| 439 |
+
last_size = src.stat().st_size
|
| 440 |
+
for factor in (0.85, 0.55, 0.35):
|
| 441 |
+
video_bps = _target_video_bitrate(duration, max_bytes, factor)
|
| 442 |
+
command = [
|
| 443 |
+
"ffmpeg",
|
| 444 |
+
"-hide_banner",
|
| 445 |
+
"-loglevel",
|
| 446 |
+
"error",
|
| 447 |
+
"-y",
|
| 448 |
+
"-i",
|
| 449 |
+
str(src),
|
| 450 |
+
"-c:v",
|
| 451 |
+
"libx264",
|
| 452 |
+
"-preset",
|
| 453 |
+
"veryfast",
|
| 454 |
+
"-b:v",
|
| 455 |
+
str(video_bps),
|
| 456 |
+
"-maxrate",
|
| 457 |
+
str(video_bps),
|
| 458 |
+
"-bufsize",
|
| 459 |
+
str(video_bps * 2),
|
| 460 |
+
"-pix_fmt",
|
| 461 |
+
"yuv420p",
|
| 462 |
+
"-c:a",
|
| 463 |
+
"aac",
|
| 464 |
+
"-b:a",
|
| 465 |
+
"128k",
|
| 466 |
+
"-movflags",
|
| 467 |
+
"+faststart",
|
| 468 |
+
str(dest),
|
| 469 |
+
]
|
| 470 |
+
TranslationRunManager._run_cmd(command, "Compressing segment for Replicate upload failed.")
|
| 471 |
+
last_size = dest.stat().st_size
|
| 472 |
+
if last_size <= max_bytes:
|
| 473 |
+
return
|
| 474 |
+
raise RuntimeError(
|
| 475 |
+
f"Segment still too large after compress ({last_size // (1024 * 1024)} MiB). "
|
| 476 |
+
"Replicate's file limit is 100 MiB."
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
def _upload_segment(self, file_path: Path, token: str) -> str:
|
| 480 |
with file_path.open("rb") as handle:
|
| 481 |
response = requests.post(
|