47 lines
No EOL
1.5 KiB
Python
47 lines
No EOL
1.5 KiB
Python
# kokoro_utils.py
|
|
import os
|
|
import tempfile
|
|
import soundfile as sf
|
|
from kokoro_onnx import Kokoro
|
|
|
|
# Resolve paths relative to project root
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
MODEL_PATH = os.path.join(BASE_DIR, "kokoro-v1.0.onnx")
|
|
VOICES_PATH = os.path.join(BASE_DIR, "voices-v1.0.bin")
|
|
|
|
_kokoro_instance = None
|
|
|
|
def get_kokoro_engine():
|
|
global _kokoro_instance
|
|
if _kokoro_instance is None:
|
|
if os.path.exists(MODEL_PATH) and os.path.exists(VOICES_PATH):
|
|
_kokoro_instance = Kokoro(MODEL_PATH, VOICES_PATH)
|
|
return _kokoro_instance
|
|
|
|
def generate_local_speech(text: str, voice_name: str = "af_bella", speed: float = 1.0) -> str:
|
|
"""
|
|
Synthesizes speech locally using Kokoro and saves to a temp file,
|
|
returning the path to the generated audio file with correct language routing.
|
|
"""
|
|
engine = get_kokoro_engine()
|
|
if not engine:
|
|
raise FileNotFoundError(
|
|
f"Kokoro model files not found at root directory.\nExpected:\n- {MODEL_PATH}\n- {VOICES_PATH}"
|
|
)
|
|
|
|
# Route language code explicitly based on voice prefix
|
|
if voice_name.startswith("es") or voice_name.startswith("ef") or voice_name.startswith("em"):
|
|
lang_code = "es"
|
|
else:
|
|
lang_code = "en-us"
|
|
|
|
samples, sample_rate = engine.create(
|
|
text,
|
|
voice=voice_name,
|
|
speed=speed,
|
|
lang=lang_code
|
|
)
|
|
|
|
temp_file = os.path.join(tempfile.gettempdir(), "kokoro_review_audio.wav")
|
|
sf.write(temp_file, samples, sample_rate)
|
|
return temp_file |