futureselves / tts.py
Papajams's picture
Initial submission: FutureSelves Build Small
7f15dbc verified
Raw
History Blame Contribute Delete
2.86 kB
"""
tts.py — Kokoro TTS wrapper for on-device voice synthesis.
Kokoro is an 82M-parameter TTS model (MIT license) that runs
entirely locally. It's tiny enough to keep us under the Tiny
Titan threshold alongside MiniCPM 2.5B and Nemotron-Parse.
Usage:
from tts import generate_speech
audio_path = generate_speech("Hello from your future self.")
# -> returns path to a WAV file
"""
from __future__ import annotations
import logging
import os
import tempfile
from typing import Optional
logger = logging.getLogger(__name__)
def generate_speech(text: str, voice: str = "af_heart") -> Optional[str]:
"""
Synthesize speech from text using Kokoro TTS.
Args:
text: Text to speak (max 500 chars for reliability).
voice: Kokoro voice ID. Common options:
"af_heart" - warm, intimate (default)
"af_bella" - clear and articulate
"am_adam" - steady masculine
"am_mich" - warm masculine
"af_sky" - soft feminine
"af_nicole" - bright, energetic
Returns path to generated WAV file, or None on failure.
"""
if not text or not text.strip():
return None
try:
from kokoro import KPipeline
pipeline = KPipeline(lang_code="a")
generator = pipeline(
text.strip()[:500],
voice=voice,
speed=1.0,
)
output_dir = tempfile.mkdtemp(prefix="futureself_tts_")
output_path = os.path.join(output_dir, "transmission.wav")
audio_chunks = []
for _, _, audio in generator:
if audio is not None:
audio_chunks.append(audio)
if not audio_chunks:
logger.warning("Kokoro produced no audio")
return None
import numpy as np
import soundfile as sf
combined = np.concatenate(audio_chunks)
sf.write(output_path, combined, samplerate=24000)
logger.info("TTS generated at %s (%d samples)", output_path, len(combined))
return output_path
except ImportError:
logger.warning(
"kokoro not installed — install with: pip install kokoro "
)
return None
except Exception as exc:
logger.warning("TTS failed: %s", exc)
return None
def get_voice_for_cast_member(cast_member: str) -> str:
"""Map FutureSelves cast members to Kokoro voice IDs."""
voice_map = {
"future_self": "af_heart",
"future_partner": "af_bella",
"future_mentor": "am_adam",
"future_best_friend": "af_sky",
"shadow": "af_nicole",
"alternate_self": "af_heart",
"future_stranger": "af_nicole",
"future_employee": "am_mich",
"future_customer": "af_bella",
"future_child": "af_sky",
}
return voice_map.get(cast_member, "af_heart")