|
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) |
|
""" |
|
|
|
binary_names = ["espeak-ng", "espeak"] |
|
if platform.system() == "Windows": |
|
binary_names = ["espeak-ng.exe", "espeak.exe"] |
|
|
|
|
|
linux_paths = [ |
|
"/usr/bin", |
|
"/usr/local/bin", |
|
"/usr/lib/espeak-ng", |
|
"/usr/local/lib/espeak-ng", |
|
"/opt/espeak-ng/bin", |
|
] |
|
|
|
|
|
for name in binary_names: |
|
espeak_path = shutil.which(name) |
|
if espeak_path: |
|
return True, espeak_path |
|
|
|
|
|
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: |
|
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 = [ |
|
|
|
"/usr/lib/x86_64-linux-gnu", |
|
"/usr/lib/aarch64-linux-gnu", |
|
"/usr/lib/arm-linux-gnueabihf", |
|
"/usr/lib", |
|
"/usr/local/lib", |
|
|
|
"/usr/lib64", |
|
"/usr/lib32", |
|
|
|
"/usr/lib/espeak-ng", |
|
"/usr/local/lib/espeak-ng", |
|
"/opt/espeak-ng/lib", |
|
] |
|
|
|
|
|
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) |
|
|
|
|
|
try: |
|
|
|
result = subprocess.run( |
|
["ldconfig", "-p"], capture_output=True, text=True, check=True |
|
) |
|
for line in result.stdout.splitlines(): |
|
if "libespeak-ng.so" in line: |
|
|
|
return line.split("=>")[-1].strip() |
|
except (subprocess.SubprocessError, FileNotFoundError): |
|
pass |
|
|
|
elif system == "Darwin": |
|
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) |
|
""" |
|
|
|
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" |
|
) |
|
|
|
|
|
library_path = cls.find_library_path() |
|
if not 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: |
|
EspeakWrapper.set_library(library_path) |
|
return True, f"Successfully configured espeak-ng library at: {library_path}" |
|
except Exception as e: |
|
if platform.system() == "Linux": |
|
|
|
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 |
|
|
|
|
|
|
|
set_espeak_library = setup_espeak |
|
|