Kokoro_TTS_Compare / espeak_util.py
Wismut's picture
initial commit
2eaa44a
import platform
import subprocess
import shutil
from pathlib import Path
import os
from typing import Optional, Tuple
from phonemizer.backend.espeak.wrapper import EspeakWrapper
class EspeakConfig:
"""Utility class for configuring espeak-ng library and binary."""
@staticmethod
def find_espeak_binary() -> tuple[bool, Optional[str]]:
"""
Find espeak-ng binary using multiple methods.
Returns:
tuple: (bool indicating if espeak is available, path to espeak binary if found)
"""
# Common binary names
binary_names = ["espeak-ng", "espeak"]
if platform.system() == "Windows":
binary_names = ["espeak-ng.exe", "espeak.exe"]
# Common installation directories for Linux
linux_paths = [
"/usr/bin",
"/usr/local/bin",
"/usr/lib/espeak-ng",
"/usr/local/lib/espeak-ng",
"/opt/espeak-ng/bin",
]
# First check if it's in PATH
for name in binary_names:
espeak_path = shutil.which(name)
if espeak_path:
return True, espeak_path
# For Linux, check common installation directories
if platform.system() == "Linux":
for directory in linux_paths:
for name in binary_names:
path = Path(directory) / name
if path.exists():
return True, str(path)
# Try running the command directly as a last resort
try:
subprocess.run(
["espeak-ng", "--version"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
return True, "espeak-ng"
except (subprocess.SubprocessError, FileNotFoundError):
pass
return False, None
@staticmethod
def find_library_path() -> Optional[str]:
"""
Find the espeak-ng library using multiple search methods.
Returns:
Optional[str]: Path to the library if found, None otherwise
"""
system = platform.system()
if system == "Linux":
lib_names = ["libespeak-ng.so", "libespeak-ng.so.1"]
common_paths = [
# Debian/Ubuntu paths
"/usr/lib/x86_64-linux-gnu",
"/usr/lib/aarch64-linux-gnu", # For ARM64
"/usr/lib/arm-linux-gnueabihf", # For ARM32
"/usr/lib",
"/usr/local/lib",
# Fedora/RHEL paths
"/usr/lib64",
"/usr/lib32",
# Common additional paths
"/usr/lib/espeak-ng",
"/usr/local/lib/espeak-ng",
"/opt/espeak-ng/lib",
]
# Check common locations first
for path in common_paths:
for lib_name in lib_names:
lib_path = Path(path) / lib_name
if lib_path.exists():
return str(lib_path)
# Search system library paths
try:
# Use ldconfig to find the library
result = subprocess.run(
["ldconfig", "-p"], capture_output=True, text=True, check=True
)
for line in result.stdout.splitlines():
if "libespeak-ng.so" in line:
# Extract path from ldconfig output
return line.split("=>")[-1].strip()
except (subprocess.SubprocessError, FileNotFoundError):
pass
elif system == "Darwin": # macOS
common_paths = [
Path("/opt/homebrew/lib/libespeak-ng.dylib"),
Path("/usr/local/lib/libespeak-ng.dylib"),
*list(
Path("/opt/homebrew/Cellar/espeak-ng").glob(
"*/lib/libespeak-ng.dylib"
)
),
*list(
Path("/usr/local/Cellar/espeak-ng").glob("*/lib/libespeak-ng.dylib")
),
]
for path in common_paths:
if path.exists():
return str(path)
elif system == "Windows":
common_paths = [
Path(os.environ.get("PROGRAMFILES", "C:\\Program Files"))
/ "eSpeak NG"
/ "libespeak-ng.dll",
Path(os.environ.get("PROGRAMFILES(X86)", "C:\\Program Files (x86)"))
/ "eSpeak NG"
/ "libespeak-ng.dll",
*[
Path(p) / "libespeak-ng.dll"
for p in os.environ.get("PATH", "").split(os.pathsep)
],
]
for path in common_paths:
if path.exists():
return str(path)
return None
@classmethod
def configure_espeak(cls) -> Tuple[bool, str]:
"""
Configure espeak-ng for use with the phonemizer.
Returns:
Tuple[bool, str]: (Success status, Status message)
"""
# First check if espeak binary is available
espeak_available, espeak_path = cls.find_espeak_binary()
if not espeak_available:
raise FileNotFoundError(
"Could not find espeak-ng binary. Please install espeak-ng:\n"
"Ubuntu/Debian: sudo apt-get install espeak-ng espeak-ng-data\n"
"Fedora: sudo dnf install espeak-ng\n"
"Arch: sudo pacman -S espeak-ng\n"
"MacOS: brew install espeak-ng\n"
"Windows: Download from https://github.com/espeak-ng/espeak-ng/releases"
)
# Find the library
library_path = cls.find_library_path()
if not library_path:
# On Linux, we might not need to explicitly set the library path
if platform.system() == "Linux":
return True, f"Using system espeak-ng installation at: {espeak_path}"
else:
raise FileNotFoundError(
"Could not find espeak-ng library. Please ensure espeak-ng is properly installed."
)
# Try to set the library path
try:
EspeakWrapper.set_library(library_path)
return True, f"Successfully configured espeak-ng library at: {library_path}"
except Exception as e:
if platform.system() == "Linux":
# On Linux, try to continue without explicit library path
return True, f"Using system espeak-ng installation at: {espeak_path}"
else:
raise RuntimeError(f"Failed to configure espeak-ng library: {str(e)}")
def setup_espeak():
"""
Set up espeak-ng for use with the phonemizer.
Raises appropriate exceptions if setup fails.
"""
try:
success, message = EspeakConfig.configure_espeak()
print(message)
except Exception as e:
print(f"Error configuring espeak-ng: {str(e)}")
raise
# Replace the original set_espeak_library function with this
set_espeak_library = setup_espeak