Spaces:
Running
Running
| from __future__ import annotations | |
| import time | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Any | |
| import httpx | |
| class ModalUploadError(RuntimeError): | |
| """Raised when hosted post-cut inference fails.""" | |
| DEFAULT_UPLOAD_SOURCE_ID = "public-demo" | |
| MAX_ERROR_DETAIL_CHARS = 240 | |
| def _safe_modal_detail(response: httpx.Response) -> str: | |
| try: | |
| payload = response.json() | |
| except ValueError: | |
| return _truncate_detail(response.text.strip() or response.reason_phrase) | |
| detail = payload.get("detail") if isinstance(payload, dict) else payload | |
| return _truncate_detail(_normalize_modal_detail(detail) or response.reason_phrase) | |
| def _normalize_modal_detail(detail: Any) -> str: | |
| if isinstance(detail, str): | |
| return detail | |
| if isinstance(detail, dict): | |
| for key in ("msg", "message", "error"): | |
| value = detail.get(key) | |
| if isinstance(value, str) and value: | |
| return value | |
| return str(detail) | |
| if isinstance(detail, list): | |
| messages = [_normalize_modal_detail(item) for item in detail[:3]] | |
| return "; ".join(message for message in messages if message) | |
| return str(detail) if detail is not None else "" | |
| def _truncate_detail(detail: str) -> str: | |
| normalized = " ".join(detail.split()) | |
| if len(normalized) <= MAX_ERROR_DETAIL_CHARS: | |
| return normalized | |
| return f"{normalized[: MAX_ERROR_DETAIL_CHARS - 1]}..." | |
| def _raise_for_modal_status(response: httpx.Response, action: str) -> None: | |
| try: | |
| response.raise_for_status() | |
| except httpx.HTTPStatusError: | |
| detail = _safe_modal_detail(response) | |
| raise ModalUploadError( | |
| f"Modal upload {action} failed ({response.status_code}): {detail}" | |
| ) from None | |
| class ModalUploadClient: | |
| base_url: str | |
| token: str | |
| http_client: httpx.Client | None = None | |
| poll_interval_s: float = 1.0 | |
| timeout_s: float = 900.0 | |
| def submit_video( | |
| self, | |
| video_path: str | Path, | |
| *, | |
| style_key: str = "deadpan", | |
| scene_hint: str = "", | |
| ) -> dict[str, Any]: | |
| close = self.http_client is None | |
| client = self.http_client or httpx.Client(timeout=30.0, follow_redirects=True) | |
| try: | |
| try: | |
| job_id = self._submit( | |
| client, | |
| Path(video_path), | |
| DEFAULT_UPLOAD_SOURCE_ID, | |
| style_key, | |
| scene_hint, | |
| ) | |
| except ModalUploadError: | |
| raise | |
| except httpx.HTTPError as exc: | |
| raise ModalUploadError( | |
| f"Modal upload request failed: {type(exc).__name__}" | |
| ) from None | |
| try: | |
| return self._poll(client, job_id) | |
| except ModalUploadError: | |
| raise | |
| except httpx.HTTPError as exc: | |
| raise ModalUploadError( | |
| f"Modal upload status failed: {type(exc).__name__}" | |
| ) from None | |
| finally: | |
| if close: | |
| client.close() | |
| def _submit( | |
| self, | |
| client: httpx.Client, | |
| video_path: Path, | |
| source_id: str, | |
| style_key: str, | |
| scene_hint: str, | |
| ) -> str: | |
| with video_path.open("rb") as handle: | |
| response = client.post( | |
| f"{self.base_url.rstrip('/')}/v1/cuts", | |
| headers={"Authorization": f"Bearer {self.token}"}, | |
| data={ | |
| "style_key": style_key, | |
| "scene_hint": scene_hint, | |
| "uploader_id": source_id, | |
| }, | |
| files={"video": (video_path.name, handle, "video/mp4")}, | |
| ) | |
| _raise_for_modal_status(response, "request") | |
| job_id = response.json().get("job_id") | |
| if not isinstance(job_id, str) or not job_id: | |
| raise ModalUploadError("Modal did not return a job_id") | |
| return job_id | |
| def submit_video_v2( | |
| self, | |
| video_path: str | Path, | |
| *, | |
| style_key: str = "deadpan", | |
| language: str = "English", | |
| context: str = "", | |
| ) -> dict[str, Any]: | |
| """Upload a clip to the v2 ``/v2/narrate`` pipeline in the chosen narration language. | |
| Mirrors :meth:`submit_video` but targets the v2 endpoint (``style_key`` + ``language`` + | |
| ``context`` form fields) and polls ``/v2/narrate/{job_id}``. ``context`` is the optional | |
| free-text *manner* steer (how the moment is told). Kept as a separate method so the v1 | |
| ``/v1/cuts`` path used by the live Space is untouched. | |
| """ | |
| close = self.http_client is None | |
| client = self.http_client or httpx.Client(timeout=30.0, follow_redirects=True) | |
| try: | |
| try: | |
| job_id = self._submit_v2(client, Path(video_path), style_key, language, context) | |
| except ModalUploadError: | |
| raise | |
| except httpx.HTTPError as exc: | |
| raise ModalUploadError( | |
| f"Modal upload request failed: {type(exc).__name__}" | |
| ) from None | |
| try: | |
| return self._poll_status(client, f"{self.base_url.rstrip('/')}/v2/narrate/{job_id}") | |
| except ModalUploadError: | |
| raise | |
| except httpx.HTTPError as exc: | |
| raise ModalUploadError( | |
| f"Modal upload status failed: {type(exc).__name__}" | |
| ) from None | |
| finally: | |
| if close: | |
| client.close() | |
| def _submit_v2( | |
| self, | |
| client: httpx.Client, | |
| video_path: Path, | |
| style_key: str, | |
| language: str, | |
| context: str = "", | |
| ) -> str: | |
| with video_path.open("rb") as handle: | |
| response = client.post( | |
| f"{self.base_url.rstrip('/')}/v2/narrate", | |
| headers={"Authorization": f"Bearer {self.token}"}, | |
| data={"style_key": style_key, "language": language, "context": context}, | |
| files={"video": (video_path.name, handle, "video/mp4")}, | |
| ) | |
| _raise_for_modal_status(response, "request") | |
| job_id = response.json().get("job_id") | |
| if not isinstance(job_id, str) or not job_id: | |
| raise ModalUploadError("Modal did not return a job_id") | |
| return job_id | |
| def _poll(self, client: httpx.Client, job_id: str) -> dict[str, Any]: | |
| return self._poll_status(client, f"{self.base_url.rstrip('/')}/v1/cuts/{job_id}") | |
| def _poll_status(self, client: httpx.Client, status_url: str) -> dict[str, Any]: | |
| deadline = time.monotonic() + self.timeout_s | |
| while time.monotonic() < deadline: | |
| response = client.get( | |
| status_url, | |
| headers={"Authorization": f"Bearer {self.token}"}, | |
| ) | |
| if response.status_code == 202: | |
| time.sleep(self.poll_interval_s) | |
| continue | |
| _raise_for_modal_status(response, "status") | |
| payload = response.json() | |
| scene = payload.get("scene") | |
| if not isinstance(scene, dict): | |
| raise ModalUploadError("Modal completed without a scene payload") | |
| return scene | |
| raise ModalUploadError("Modal upload timed out") | |