Spaces:
Running
Running
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.""" | |
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 | |
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 | |
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 | |