| import os |
| import tempfile |
| import uuid |
| from pathlib import Path |
| from urllib.parse import urlparse |
|
|
|
|
| DEFAULT_TTS_URL = "https://veronicaulises0--virtual-characters-tts-charactertts-tts.modal.run" |
|
|
|
|
| def synthesize_sentence(text: str, character: dict, voice_state: dict | None = None) -> str | None: |
| url = _tts_endpoint_url(os.environ.get("VC_MODAL_TTS_URL") or DEFAULT_TTS_URL) |
| if not url or not text.strip(): |
| return None |
|
|
| import httpx |
|
|
| voice = character.get("voice", {}) |
| voice_state = voice_state or {} |
| payload = { |
| "text": text, |
| "voice_id": voice_state.get("voice_id") or voice.get("voice_id", "default"), |
| "emotion": voice_state.get("emotion") or voice_state.get("style") or "neutral", |
| "speed": voice_state.get("speed", 1.0), |
| "energy": voice_state.get("energy", voice.get("energy", 0.5)), |
| "audio_prompt_path": voice_state.get("audio_prompt_path") or voice.get("audio_prompt_path"), |
| "backend": voice.get("backend", "chatterbox"), |
| } |
| response = httpx.post(url, json=payload, timeout=180, trust_env=False) |
| response.raise_for_status() |
| path = Path(tempfile.gettempdir()) / f"virtual_characters_tts_{uuid.uuid4().hex}.wav" |
| path.write_bytes(response.content) |
| return str(path) |
|
|
|
|
| def _tts_endpoint_url(url: str | None) -> str | None: |
| if not url: |
| return None |
| base = url.rstrip("/") |
| parsed = urlparse(base) |
| if not parsed.path or parsed.path == "/": |
| return base |
| if parsed.path.rstrip("/").rsplit("/", 1)[-1] == "tts": |
| return base |
| return base + "/tts" |
|
|