mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-03-21 22:56:47 +00:00
* fix: local processing instead of http server for cpu * add fallback token if service worker doesnt work * chore: rename processors to keep processor pattern up to date and allow other processors to be createed and used with env vars
113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
"""
|
|
Modal.com backend for audio padding.
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
|
|
import httpx
|
|
|
|
from reflector.hatchet.constants import TIMEOUT_AUDIO
|
|
from reflector.logger import logger
|
|
from reflector.processors.audio_padding import AudioPaddingProcessor, PaddingResponse
|
|
from reflector.processors.audio_padding_auto import AudioPaddingAutoProcessor
|
|
|
|
|
|
class AudioPaddingModalProcessor(AudioPaddingProcessor):
|
|
"""Audio padding processor using Modal.com CPU backend via HTTP."""
|
|
|
|
def __init__(
|
|
self, padding_url: str | None = None, modal_api_key: str | None = None
|
|
):
|
|
self.padding_url = padding_url or os.getenv("PADDING_URL")
|
|
if not self.padding_url:
|
|
raise ValueError(
|
|
"PADDING_URL required to use AudioPaddingModalProcessor. "
|
|
"Set PADDING_URL environment variable or pass padding_url parameter."
|
|
)
|
|
|
|
self.modal_api_key = modal_api_key or os.getenv("MODAL_API_KEY")
|
|
|
|
async def pad_track(
|
|
self,
|
|
track_url: str,
|
|
output_url: str,
|
|
start_time_seconds: float,
|
|
track_index: int,
|
|
) -> PaddingResponse:
|
|
"""Pad audio track with silence via Modal backend.
|
|
|
|
Args:
|
|
track_url: Presigned GET URL for source audio track
|
|
output_url: Presigned PUT URL for output WebM
|
|
start_time_seconds: Amount of silence to prepend
|
|
track_index: Track index for logging
|
|
"""
|
|
if not track_url:
|
|
raise ValueError("track_url cannot be empty")
|
|
if start_time_seconds <= 0:
|
|
raise ValueError(
|
|
f"start_time_seconds must be positive, got {start_time_seconds}"
|
|
)
|
|
|
|
log = logger.bind(track_index=track_index, padding_seconds=start_time_seconds)
|
|
log.info("Sending Modal padding HTTP request")
|
|
|
|
url = f"{self.padding_url}/pad"
|
|
|
|
headers = {}
|
|
if self.modal_api_key:
|
|
headers["Authorization"] = f"Bearer {self.modal_api_key}"
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=TIMEOUT_AUDIO) as client:
|
|
response = await client.post(
|
|
url,
|
|
headers=headers,
|
|
json={
|
|
"track_url": track_url,
|
|
"output_url": output_url,
|
|
"start_time_seconds": start_time_seconds,
|
|
"track_index": track_index,
|
|
},
|
|
follow_redirects=True,
|
|
)
|
|
|
|
if response.status_code != 200:
|
|
error_body = response.text
|
|
log.error(
|
|
"Modal padding API error",
|
|
status_code=response.status_code,
|
|
error_body=error_body,
|
|
)
|
|
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
|
|
# Check if work was cancelled
|
|
if result.get("cancelled"):
|
|
log.warning("Modal padding was cancelled by disconnect detection")
|
|
raise asyncio.CancelledError(
|
|
"Padding cancelled due to client disconnect"
|
|
)
|
|
|
|
log.info("Modal padding complete", size=result["size"])
|
|
return PaddingResponse(**result)
|
|
except asyncio.CancelledError:
|
|
log.warning(
|
|
"Modal padding cancelled (Hatchet timeout, disconnect detected on Modal side)"
|
|
)
|
|
raise
|
|
except httpx.TimeoutException as e:
|
|
log.error("Modal padding timeout", error=str(e), exc_info=True)
|
|
raise Exception(f"Modal padding timeout: {e}") from e
|
|
except httpx.HTTPStatusError as e:
|
|
log.error("Modal padding HTTP error", error=str(e), exc_info=True)
|
|
raise Exception(f"Modal padding HTTP error: {e}") from e
|
|
except Exception as e:
|
|
log.error("Modal padding unexpected error", error=str(e), exc_info=True)
|
|
raise
|
|
|
|
|
|
AudioPaddingAutoProcessor.register("modal", AudioPaddingModalProcessor)
|