Download stt_budget.py from Jindrich3/openajaj: direct link, hf CLI and curl.
- Browser
- Download file 4.68 kB
-
https://huggingface.co/spaces/Jindrich3/openajaj/resolve/42b5eaec648dee8207d219269ecc521ebe42f330/stt_budget.py
- Command line
-
hf download hf://spaces/Jindrich3/openajaj@42b5eaec648dee8207d219269ecc521ebe42f330/stt_budget.py
-
curl -L -o stt_budget.py https://huggingface.co/spaces/Jindrich3/openajaj/resolve/42b5eaec648dee8207d219269ecc521ebe42f330/stt_budget.py
4.68 kB
| """Spend limits for the Deepgram speech-to-text proxy. | |
| Deliberately generous — a ceiling on a runaway or a bot, not a quota aimed at | |
| real use. Deepgram is metered (see PRICE_PER_MIN) and the rate-limit | |
| middleware cannot see the WebSocket route: Starlette's BaseHTTPMiddleware skips | |
| non-http scopes, so `_RATE_LIMITS["/api/stt"]` has never applied. Limits are | |
| enforced at the socket or nowhere. | |
| The daily budget is per client. That needs a stable per-client key in the usage | |
| log, but the raw key is a captcha session token or an IP address and neither is | |
| worth storing — so the log holds `client_id()`, a keyed hash under the captcha | |
| secret. It is stable enough to count against and cannot be turned back into an | |
| address by anyone reading the file. | |
| """ | |
| import hashlib | |
| import json | |
| import secrets | |
| import time | |
| MAX_CONCURRENT = 4 # simultaneous microphones per client. Locally, | |
| # and behind the HF router, every browser shares | |
| # one key — so this counts browsers, not people. | |
| MAX_SESSION_S = 8 * 3600 # one microphone may stay open this long: a | |
| # whole party, not a meeting. DAILY_BUDGET_S | |
| # is the real ceiling. | |
| DAILY_BUDGET_S = 10 * 3600 # audio relayed per client per day | |
| DIRECT_SLOT_S = 11 * 60 # a direct socket's slot: its page reports | |
| # usage every 5 min, two missed reports free it | |
| # Deepgram pay-as-you-go streaming, $/min — deepgram.com/pricing, 2026-09-25. | |
| # ponytail: nova-3 is a limited-time promo (regular 0.0077); bump when it ends. | |
| # nova-2 is listed at $0.35/hour. static/transcribe2/index.html mirrors these. | |
| PRICE_PER_MIN = {"nova-3": 0.0048, "nova-3-multi": 0.0058, "nova-2": 0.005833} | |
| DIARIZE_PER_MIN = 0.002 # streaming add-on; the page always asks for it | |
| KEYTERM_PER_MIN = 0.0013 # nova-3 keyterm; nova-2 `keywords` isn't priced | |
| def claim_slot(pages: dict[str, float], sid: str, now: float) -> str | None: | |
| """Keep `sid`'s slot or open a new one; None when MAX_CONCURRENT are live. | |
| `pages` maps slot id → expiry for one client's sockets straight to | |
| Deepgram EU. The server never sees those sockets, only the token requests | |
| and usage reports of the pages that hold them. | |
| """ | |
| for old in [s for s, until in pages.items() if until < now]: | |
| del pages[old] | |
| if sid not in pages: | |
| if len(pages) >= MAX_CONCURRENT: | |
| return None | |
| sid = secrets.token_urlsafe(9) | |
| pages[sid] = now + DIRECT_SLOT_S | |
| return sid | |
| def client_id(raw_key: str, secret: bytes) -> str: | |
| """Pseudonymous, stable id for a client. The raw key is never stored.""" | |
| return hashlib.blake2b( | |
| raw_key.encode("utf-8", "replace"), key=secret[:64], digest_size=8 | |
| ).hexdigest() | |
| def cost_per_min(model: str, lang: str, diarize: bool, keyterm: bool) -> float: | |
| """What Deepgram charges per streamed minute for this request.""" | |
| is_multi = model == "nova-3" and lang == "multi" | |
| rate = PRICE_PER_MIN["nova-3-multi" if is_multi else model] | |
| if diarize: | |
| rate += DIARIZE_PER_MIN | |
| if keyterm and model == "nova-3": | |
| rate += KEYTERM_PER_MIN | |
| return rate | |
| def seconds_today(usage_file: str, client: str | None = None, | |
| now: float | None = None) -> float: | |
| """Sum today's `duration_s`, for one client or for everyone. | |
| Tolerates a missing file and half-written lines: the log is appended to | |
| without locking, so a truncated final line is normal and must not be | |
| allowed to abort the count — under-counting here would silently disable | |
| the daily cap. | |
| """ | |
| today = time.strftime("%Y-%m-%d", time.localtime(now)) | |
| total = 0.0 | |
| try: | |
| with open(usage_file, encoding="utf-8") as log: | |
| for line in log: | |
| try: | |
| entry = json.loads(line) | |
| except ValueError: | |
| continue | |
| if not isinstance(entry, dict): | |
| continue | |
| if not str(entry.get("ts", "")).startswith(today): | |
| continue | |
| if client is not None and entry.get("client") != client: | |
| continue | |
| duration = entry.get("duration_s") or 0 | |
| if isinstance(duration, (int, float)): | |
| total += duration | |
| except FileNotFoundError: | |
| return 0.0 | |
| return total | |
| def is_exhausted(usage_file: str, client: str, now: float | None = None) -> bool: | |
| """True when this client has reached its daily budget.""" | |
| return seconds_today(usage_file, client, now) >= DAILY_BUDGET_S | |