"""Validate public inputs before reserving GPU time; no model imports.""" from dataclasses import dataclass import json import math import secrets from config import LANGUAGES, MAX_CHARS, MAX_SEED class InputError(ValueError): pass @dataclass(frozen=True) class SpeechRequest: text: str voice: str language: str source: str seed: int normalize: bool temperature: float top_p: float top_k: int repetition_penalty: float rate: float | None def number(value, label, low, high, *, integer=False): if isinstance(value, bool): raise InputError(f"{label} must be a number.") try: result = float(value) except (TypeError, ValueError): raise InputError(f"{label} must be a number.") from None if not math.isfinite(result) or not low <= result <= high: raise InputError(f"{label} must be between {low} and {high}.") if integer and result != int(result): raise InputError(f"{label} must be a whole number.") return int(result) if integer else result def validate(text, picker, source, seed, randomize, normalize, temperature, top_p, top_k, repetition_penalty, rate, voice_names, clip_present, consent): if not isinstance(text, str) or not text.strip(): raise InputError("Write something to say first.") text = text.strip() if len(text) > MAX_CHARS: raise InputError(f"Use at most {MAX_CHARS} characters for one take.") try: data = json.loads(picker) if isinstance(picker, str) else picker voice, language = data["voice"], data["language"] except (ValueError, TypeError, KeyError): raise InputError("Choose a voice and a text language.") from None if not isinstance(language, str) or language not in LANGUAGES: raise InputError("This text language is not supported by this runtime.") if not isinstance(source, str) or source not in {"Preset", "Voice clip"}: raise InputError("Choose Preset or Voice clip.") if source == "Preset" and (not isinstance(voice, str) or voice not in voice_names): raise InputError("Choose one of the available preset voices.") if source == "Voice clip" and not clip_present: raise InputError("Upload or record a reference voice first.") if source == "Voice clip" and consent is not True: raise InputError("Confirm that you have permission to use the reference voice.") if not isinstance(randomize, bool) or not isinstance(normalize, bool): raise InputError("Seed and normalization switches must be true or false.") selected_seed = secrets.randbelow(MAX_SEED + 1) if randomize else number(seed, "Seed", 0, MAX_SEED, integer=True) rate = number(rate, "Speaking rate", 0, 30) return SpeechRequest( text, voice, language, source, selected_seed, normalize, number(temperature, "Temperature", .1, 1.5), number(top_p, "Top-p", .1, 1), number(top_k, "Top-k", 1, 400, integer=True), number(repetition_penalty, "Repetition penalty", 1, 4), rate if rate else None, )