"""Pure helpers of the LFM3 Space (no model, no network): text pieces and voices. Kept apart from app.py so they can be unit-tested without torch or transformers. """ import re FIRST_PIECE_MAX = 60 # first sentence longer than this is cut at its first clause _SENTENCE = re.compile(r"(?<=[.!?…])\s+") _CLAUSE = re.compile(r"(?<=[,;:])\s+") # The Kokoro voice ids the app already sends (af_heart, bm_george, ...) are # accepted, so the same client can be pointed at either Space. LFM3 has no # named voices: only a language/locale and a female or male voice. _KOKORO_LANG = {"a": "American English", "b": "British English"} _GENDER = {"f": "female", "m": "male"} _KOKORO_VOICE = re.compile(r"^([ab])([fm])_[a-z]+$") def split_for_streaming(text: str) -> list: """Sentences, with the first one cut at its first clause when long, so the first audio arrives after synthesizing only a few words.""" sentences = [s.strip() for s in _SENTENCE.split(text) if s.strip()] if not sentences: return [] first = sentences[0] if len(first) > FIRST_PIECE_MAX: parts = _CLAUSE.split(first, maxsplit=1) if len(parts) == 2 and len(parts[0]) >= 12 and len(parts[1]) >= 12: return [parts[0], parts[1]] + sentences[1:] return sentences def parse_voice(voice: str): """(language, gender) for a voice id, or None when it is not supported. "female" / "male" mean American English; "af_heart"-style ids map the first letter to American (a) or British (b) English and the second to the gender. Portuguese Kokoro voices (pf_, pm_) are not mapped on purpose: this Space is for comparing English.""" voice = (voice or "").strip().lower() if voice in ("female", "male"): return "American English", voice match = _KOKORO_VOICE.match(voice) if match: return _KOKORO_LANG[match.group(1)], _GENDER[match.group(2)] return None def system_prompt(language: str, gender: str) -> str: return f"Perform TTS in {language} using a {gender} voice."