import ipaddress import socket import subprocess import uuid from pathlib import Path from urllib.parse import urlparse import httpx from app.config import settings def _validate_remote_url(url: str) -> None: parsed = urlparse(url) if parsed.scheme not in {"http", "https"} or not parsed.hostname: raise ValueError("Only HTTP or HTTPS media URLs are supported") default_port = 443 if parsed.scheme == "https" else 80 for address in socket.getaddrinfo(parsed.hostname, parsed.port or default_port): ip = ipaddress.ip_address(address[4][0]) if ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_unspecified: raise ValueError("The media URL resolves to a prohibited address") async def download_media(url: str, suffix: str, max_bytes: int) -> Path: _validate_remote_url(url) settings.jobs_dir.mkdir(parents=True, exist_ok=True) destination = settings.jobs_dir / f"{uuid.uuid4().hex}{suffix}" total = 0 timeout = httpx.Timeout(settings.media_timeout_seconds) try: async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: async with client.stream("GET", url) as response: response.raise_for_status() with destination.open("wb") as output: async for chunk in response.aiter_bytes(1024 * 1024): total += len(chunk) if total > max_bytes: raise ValueError("Media file exceeds the configured size limit") output.write(chunk) if total == 0: raise ValueError("Downloaded media file is empty") return destination except Exception: destination.unlink(missing_ok=True) raise def decode_audio(path: Path, sample_rate: int = 16000) -> tuple[bytes, int]: process = subprocess.run( [ "ffmpeg", "-v", "error", "-i", str(path), "-f", "s16le", "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(sample_rate), "pipe:1", ], capture_output=True, check=False, ) if process.returncode != 0 or not process.stdout: error = process.stderr.decode("utf-8", errors="replace")[-500:] raise ValueError(f"Unable to decode audio: {error}") return process.stdout, sample_rate