Spaces:
Running
Running
| # core/tts.py | |
| """Story text β grandmother-voice audio (EN/BN). | |
| EN β VoxCPM2 Voice Design persona (Build Order step 5 β still stubbed). | |
| BN β get_config().tts_bn_backend ('indic_parler' or 'indic_tts'), synthesised on | |
| Modal via Indic Parler-TTS. The grandmother persona is controlled by the | |
| caption below β tune it for warmth and storytelling pace (CLAUDE.md Β§9). | |
| Returns (None, label) if TTS unavailable or failed. Never raises. | |
| """ | |
| import tempfile | |
| from pathlib import Path | |
| from core.model_config import UI_MOCK, get_config | |
| from core.modal_infra import synthesize_bengali_remote, synthesize_english_remote | |
| # A pre-generated sample clip for RUPKOTHA_MOCK=1, so the audio player has something | |
| # to show during local UI dev (no Modal/GPU). None if the sample isn't present. | |
| _MOCK_WAV = next( | |
| (str(p) for p in [ | |
| Path("finetune/data/sample/demo_story_ai4bharat.wav"), | |
| Path("finetune/data/sample/demo_story_output_sampled.wav"), | |
| ] if p.exists()), | |
| None, | |
| ) | |
| # Grandmother voice caption β controls the Indic Parler-TTS speaker persona. | |
| # Tune for warmth and pace; run 5β10 samples and keep the best (CLAUDE.md Β§9). | |
| GRANDMOTHER_CAPTION_BN = ( | |
| "Aditi speaks in the warm, tender voice of a loving elderly Bengali " | |
| "grandmother telling a bedtime story to her beloved grandchild. Her pace is " | |
| "very slow, gentle, and unhurried, pausing softly at every sentence. Her " | |
| "delivery is affectionate and expressive, rising and falling with the natural " | |
| "soothing melody of Bengali storytelling, full of warmth and calm. The " | |
| "recording is very clear, close, and intimate, with no background noise." | |
| ) | |
| # Grandmother Voice Design persona for VoxCPM2 (English). This is prefixed to the | |
| # story text in parentheses by core/modal_infra.py β keep it short and concrete. | |
| # Tune for warmth and a slow, storytelling pace (CLAUDE.md Β§11, build step 5). | |
| GRANDMOTHER_VOICE_EN = ( | |
| "a warm, loving elderly grandmother reading a bedtime story, " | |
| "slow and gentle pace, soft and soothing tone" | |
| ) | |
| def _write_wav(audio_bytes: bytes) -> str: | |
| """Persist WAV bytes to a temp file and return its path.""" | |
| tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False) | |
| try: | |
| tmp.write(audio_bytes) | |
| finally: | |
| tmp.close() | |
| return tmp.name | |
| def speak( | |
| text: str, | |
| language: str, # 'en' or 'bn' | |
| voice: str = "grandmother", | |
| ) -> tuple[str | None, str]: | |
| """Returns (wav_path_or_None, tts_model_label). Never raises.""" | |
| cfg = get_config() | |
| if UI_MOCK: # local UI dev β sample clip, no Modal/GPU | |
| return _MOCK_WAV, "UI-mock" | |
| if language == "bn": | |
| label = cfg.tts_bn_backend | |
| if not (text or "").strip(): | |
| return None, label | |
| try: | |
| audio_bytes = synthesize_bengali_remote(text, GRANDMOTHER_CAPTION_BN) | |
| if not audio_bytes: | |
| return None, label | |
| return _write_wav(audio_bytes), label | |
| except Exception as e: # noqa: BLE001 β never raise to the UI | |
| print(f"[tts.py] Bengali TTS failed: {e}") | |
| return None, label | |
| # English path: VoxCPM2 Voice Design. | |
| label = cfg.tts_en_backend | |
| if not (text or "").strip(): | |
| return None, label | |
| try: | |
| audio_bytes = synthesize_english_remote(text, GRANDMOTHER_VOICE_EN) | |
| if not audio_bytes: | |
| return None, label | |
| return _write_wav(audio_bytes), label | |
| except Exception as e: # noqa: BLE001 β never raise to the UI | |
| print(f"[tts.py] English TTS failed: {e}") | |
| return None, label | |