mattrguerra/transcription-pipelinemainPrivate on GitHub, published here in full

deepinfra.py

Language Python2.9 KB68 lines

transcription_pipeline/engines/deepinfra.py

  1. """DeepInfra's hosted Whisper large-v3-turbo.
  2. The audio leaves the machine for this step only; alignment and speaker
  3. separation still run locally.
  4. """
  5. from __future__ import annotations
  6. import json
  7. import urllib.error
  8. import urllib.request
  9. import uuid
  10. from pathlib import Path
  11. API_URL = "https://api.deepinfra.com/v1/inference/openai/whisper-large-v3-turbo"
  12. MODEL_LABEL = "openai/whisper-large-v3-turbo (DeepInfra)"
  13. class EngineError(RuntimeError):
  14. pass
  15. def _multipart(fields: dict[str, str], file_field: str, path: Path) -> tuple[bytes, str]:
  16. boundary = uuid.uuid4().hex
  17. parts = []
  18. for k, v in fields.items():
  19. parts.append((f'--{boundary}\r\nContent-Disposition: form-data; name="{k}"\r\n\r\n'
  20. f'{v}\r\n').encode())
  21. parts.append((f'--{boundary}\r\nContent-Disposition: form-data; name="{file_field}"; '
  22. f'filename="{path.name}"\r\n'
  23. 'Content-Type: application/octet-stream\r\n\r\n').encode())
  24. parts.append(path.read_bytes())
  25. parts.append(f"\r\n--{boundary}--\r\n".encode())
  26. return b"".join(parts), f"multipart/form-data; boundary={boundary}"
  27. def transcribe(audio_path: Path, token: str | None, language: str | None = None,
  28. api_url: str = API_URL, timeout: float = 900) -> dict:
  29. if not token:
  30. raise EngineError("No DeepInfra token: set DEEPINFRA_TOKEN or add it to the Keychain "
  31. "(see docs/SETUP.md).")
  32. fields = {"language": language} if language else {}
  33. body, ctype = _multipart(fields, "audio", audio_path)
  34. headers = {"Authorization": f"bearer {token}", "Content-Type": ctype}
  35. req = urllib.request.Request(api_url, data=body, method="POST", headers=headers)
  36. try:
  37. with urllib.request.urlopen(req, timeout=timeout) as res:
  38. data = json.loads(res.read().decode("utf-8"))
  39. except urllib.error.HTTPError as e:
  40. if e.code in (401, 403):
  41. raise EngineError(
  42. "DeepInfra rejected the token: it has been revoked or rotated.") from e
  43. if e.code == 429:
  44. raise EngineError("DeepInfra is rate limiting, or the account is out of credit.") from e
  45. if e.code == 413:
  46. raise EngineError("The file is too large for DeepInfra.") from e
  47. detail = e.read().decode("utf-8", "replace")[:300]
  48. raise EngineError(f"DeepInfra error {e.code}: {detail}") from e
  49. except urllib.error.URLError as e:
  50. raise EngineError(f"Could not reach DeepInfra: {e.reason}") from e
  51. segments = [{"start": float(s.get("start") or 0), "end": float(s.get("end") or 0),
  52. "text": (s.get("text") or "").strip()}
  53. for s in (data.get("segments") or []) if (s.get("text") or "").strip()]
  54. if not segments and (data.get("text") or "").strip():
  55. segments = [{"start": 0.0, "end": 0.0, "text": data["text"].strip()}]
  56. return {"segments": segments, "language": data.get("language") or language,
  57. "model": MODEL_LABEL}