"""Validation for the client-supplied parts of the Deepgram request URL. These values are interpolated into a URL authenticated with our Deepgram key, so they are allowlisted rather than escaped. Escaping would stop a client appending extra parameters with `&`, but would still let it choose any model — and `_log_stt_usage` only prices nova-3 and nova-2, so any other model would be logged at nova-3's rate and quietly under-count the daily budget. Unknown values fall back to the default rather than rejecting the connection: an unusual browser sample rate should not break the microphone. The language lists are Deepgram's, per model: https://developers.deepgram.com/docs/models-languages-overview They are the same lists the dropdown in static/transcribe2/index.html offers — `tests/test_transcribe2.py` fails if the two drift apart. A language nova-2 cannot do falls back to the default instead of failing the connection with a Deepgram error the page cannot explain. """ # What the UI actually offers — static/transcribe2/index.html. MODELS = frozenset({"nova-3", "nova-2"}) # "multi" is code-switching: 10 languages on nova-3, Spanish+English on nova-2. NOVA3_LANGUAGES = frozenset({ "multi", "af", "af-ZA", "ar", "ar-AE", "ar-SA", "ar-QA", "ar-KW", "ar-SY", "ar-LB", "ar-PS", "ar-JO", "ar-EG", "ar-SD", "ar-TD", "ar-MA", "ar-DZ", "ar-TN", "ar-IQ", "ar-IR", "hy", "as", "as-IN", "be", "bn", "bs", "bg", "ca", "zh-HK", "zh", "zh-CN", "zh-Hans", "zh-TW", "zh-Hant", "hr", "cs", "cs-CZ", "da", "da-DK", "nl", "en", "en-US", "en-AU", "en-GB", "en-IN", "en-NZ", "et", "fi", "nl-BE", "fr", "fr-CA", "ka", "ka-GE", "de", "de-CH", "el", "gu", "gu-IN", "he", "hi", "hu", "id", "it", "ja", "kn", "kk", "kk-KZ", "ko", "ko-KR", "lv", "lt", "mk", "ms", "mr", "mn", "ne", "no", "ps", "ps-AF", "fa", "pl", "pt", "pt-BR", "pt-PT", "pa", "pa-IN", "ro", "ru", "sr", "sk", "sl", "es", "es-419", "sv", "sv-SE", "tl", "ta", "te", "th", "th-TH", "tr", "tr-TR", "uk", "ur", "vi", }) NOVA2_LANGUAGES = frozenset({ "multi", "bg", "ca", "zh", "zh-CN", "zh-Hans", "zh-TW", "zh-Hant", "zh-HK", "cs", "da", "da-DK", "nl", "en", "en-US", "en-AU", "en-GB", "en-NZ", "en-IN", "et", "fi", "nl-BE", "fr", "fr-CA", "de", "de-CH", "el", "hi", "hu", "id", "it", "ja", "ko", "ko-KR", "lv", "lt", "ms", "no", "pl", "pt", "pt-BR", "pt-PT", "ro", "ru", "sk", "es", "es-419", "sv", "sv-SE", "th", "th-TH", "tr", "uk", "vi", }) LANGUAGES = NOVA3_LANGUAGES | NOVA2_LANGUAGES DEFAULT_MODEL = "nova-3" DEFAULT_LANGUAGE = "cs" def model(raw: str | None) -> str: return raw if raw in MODELS else DEFAULT_MODEL def language(raw: str | None, for_model: str = DEFAULT_MODEL) -> str: """Validate a language code against the model that has to transcribe it.""" supported = NOVA2_LANGUAGES if for_model == "nova-2" else NOVA3_LANGUAGES return raw if raw in supported else DEFAULT_LANGUAGE def bounded_int(raw: str | None, default: int, low: int, high: int) -> int: """Coerce a query parameter to an int within [low, high], else the default.""" try: value = int(raw) # type: ignore[arg-type] # None/junk raises, caught here except (TypeError, ValueError): return default return value if low <= value <= high else default