fix: resolve espeak-ng vs legacy espeak conflict for Kokoro TTS
When both espeak (1.48) and espeak-ng (1.52) are installed (common on Arch/Manjaro), phonemizer may fall back to the old espeak which lacks the "tie" option (requires >=1.49), causing synthesis to fail. - Add _ensure_espeak_ng() to KokoroTTSProvider that probes for the espeak-ng shared library and sets PHONEMIZER_ESPEAK_LIBRARY before kokoro/phonemizer imports - Improve install.sh pacman handling to warn about the conflict https://claude.ai/code/session_01965m1v3f1cK6gDV1JHioA8
+46 −2 · 2 files changed
-
scripts/install.sh+9 −2 -
src/tts_provider.py+37 −0
@@ -429,7 +429,10 @@ except ImportError as e: install_kokoro() { echo -e "${YELLOW}Installing Kokoro TTS...${NC}" - # Check for espeak-ng system dependency + # Check for espeak-ng system dependency. + # On Arch/Manjaro the legacy 'espeak' (1.48) package conflicts with + # espeak-ng (1.50+). phonemizer's "tie" option needs >=1.49, so we + # must ensure espeak-ng wins. if ! command -v espeak-ng &> /dev/null; then echo -e "${YELLOW}espeak-ng not found. Attempting to install...${NC}" if command -v apt-get &> /dev/null; then @@ -440,7 +443,11 @@ install_kokoro() { echo -e "${BLUE} Please install manually: sudo apt-get install espeak-ng${NC}" } elif command -v pacman &> /dev/null; then - sudo pacman -S --noconfirm espeak-ng 2>/dev/null || true + # espeak-ng conflicts with legacy espeak; let pacman resolve it + sudo pacman -S --noconfirm espeak-ng 2>/dev/null || { + echo -e "${YELLOW}⚠ pacman could not install espeak-ng (legacy espeak conflict?)${NC}" + echo -e "${BLUE} Try: sudo pacman -Rdd espeak && sudo pacman -S espeak-ng${NC}" + } elif command -v dnf &> /dev/null; then sudo dnf install -y espeak-ng 2>/dev/null || true else @@ -655,11 +655,48 @@ class KokoroTTSProvider(BaseTTSProvider): "p": "p"}.get(voice[0], "a") return "a" + @staticmethod + def _ensure_espeak_ng(): + """Point phonemizer at the espeak-ng shared library. + + When both legacy ``espeak`` (≤1.48) and ``espeak-ng`` (≥1.50) are + installed, ``ctypes.util.find_library('espeak-ng')`` may return + ``None`` (e.g. on Arch/Manjaro where only the .so symlink exists + under a non-standard name), causing phonemizer to fall back to the + old ``espeak`` which lacks the *tie* option (requires ≥1.49). + + We probe a few common paths and, if found, set + ``PHONEMIZER_ESPEAK_LIBRARY`` so phonemizer picks the right one. + """ + if os.environ.get("PHONEMIZER_ESPEAK_LIBRARY"): + return # user already configured + + # ctypes.util.find_library returns the bare filename (or None) + lib = ctypes.util.find_library("espeak-ng") + if lib: + os.environ["PHONEMIZER_ESPEAK_LIBRARY"] = lib + return + + # Manual probe for common distro paths + candidates = [ + "/usr/lib/libespeak-ng.so", + "/usr/lib/libespeak-ng.so.1", + "/usr/lib64/libespeak-ng.so", + "/usr/lib64/libespeak-ng.so.1", + "/usr/lib/x86_64-linux-gnu/libespeak-ng.so.1", + "/usr/lib/aarch64-linux-gnu/libespeak-ng.so.1", + ] + for path in candidates: + if os.path.isfile(path): + os.environ["PHONEMIZER_ESPEAK_LIBRARY"] = path + return + def _ensure_initialized(self): if self._initialized: return try: + self._ensure_espeak_ng() from kokoro import KPipeline tts_timer_reset()