| 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 |
|
|