Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test eSpeak on macOS
Browse files Browse the repository at this point in the history
cclauss committed Oct 30, 2024
1 parent 5ec1fc9 commit 9bb3527
Showing 4 changed files with 73 additions and 20 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/python_publish.yml
Original file line number Diff line number Diff line change
@@ -41,6 +41,41 @@ jobs:
- name: Run tests
run: pytest -s -vvv --timeout=600

- name: macOS brew install eSpeak
if: runner.os == 'macOS'
run: |
brew install espeak
espeak --version
espeak "eSpeak version $(espeak --version) installed."
- name: macOS test eSpeak
if: runner.os == 'macOS'
shell: python
run: |
import pyttsx3
engine = pyttsx3.init('espeak')
engine.say('Hello eSpeak!')
engine.runAndWait()
- name: macOS brew uninstall eSpeak and install eSpeak-NG
if: runner.os == 'macOS'
run: |
brew uninstall espeak
brew install espeak-ng
espeak-ng --version
espeak "eSpeak-NG version $(espeak-ng --version) installed."
- name: macOS test eSpeak-NG
if: runner.os == 'macOS'
shell: python
run: |
import pyttsx3
engine = pyttsx3.init('espeak')
engine.say('Hello eSpeak-ng!')
engine.runAndWait()


build:
runs-on: ubuntu-latest
needs: [test] # This ensures tests pass before build
12 changes: 4 additions & 8 deletions pyttsx3/driver.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import sys
import importlib
import traceback
import weakref
import importlib


class DriverProxy(object):
@@ -25,7 +24,7 @@ class DriverProxy(object):
@type _iterator: iterator
'''

def __init__(self, engine, driverName, debug):
def __init__(self, engine, driverName: str, debug: bool):
'''
Constructor.
@@ -36,11 +35,8 @@ def __init__(self, engine, driverName, debug):
@type driverName: str
@param debug: Debugging output enabled or not
@type debug: bool
'''
driverName = driverName or {
'darwin': 'nsss',
'win32': 'sapi5',
}.get(sys.platform, 'espeak')
'''
assert driverName
# import driver module
self._module = importlib.import_module(f'pyttsx3.drivers.{driverName}')
# build driver instance
33 changes: 29 additions & 4 deletions pyttsx3/engine.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
from __future__ import annotations

import sys
import traceback
import weakref

from . import driver

# https://docs.python.org/3/library/sys.html#sys.platform
# The keys are values of Python sys.platform, the values are tuples of engine names.
# The first engine in the value tuple is the default engine for that platform.
_engines_by_sys_platform = {
"darwin": ("espeak", "nsss"), # NSSpeechSynthesizer (deprecated), TODO: Add avsynth to support AVSpeechSynthesizer
"win32": ("sapi5", "espeak"),
}


def engines_by_sys_platform() -> tuple[str]:
"""
Return the names of all TTS engines for the current operating system.
If sys.platform is not in _engines_by_sys_platform, return ("espeak",).
"""
return _engines_by_sys_platform.get(sys.platform, ("espeak",))


def default_engine_by_sys_platform() -> str:
"""
Return the name of the default TTS engine for the current operating system.
The first engine in the value tuple is the default engine for that platform.
"""
return engines_by_sys_platform()[0]


class Engine(object):
"""
@@ -30,8 +55,8 @@ def __init__(self, driverName: str | None = None, debug: bool = False):
@param debug: Debugging output enabled or not
@type debug: bool
"""
self.proxy = driver.DriverProxy(weakref.proxy(self), driverName, debug)
self.name = self.proxy._module.__name__.split('.')[-1]
self.driver_name = driverName or default_engine_by_sys_platform()
self.proxy = driver.DriverProxy(weakref.proxy(self), self.driver_name, debug)
self._connects = {}
self._debug = debug
self._driverLoop = True
@@ -42,13 +67,13 @@ def __repr__(self) -> str:
repr(pyttsx3.init('nsss')) -> "pyttsx3.engine.Engine('nsss', debug=False)"
"""
module_and_class = f"{self.__class__.__module__}.{self.__class__.__name__}"
return f"{module_and_class}('{self.name}', debug={self._debug})"
return f"{module_and_class}('{self.driver_name}', debug={self._debug})"

def __str__(self) -> str:
"""
str(pyttsx3.init('nsss')) -> 'nsss'
"""
return self.name
return self.driver_name

def _notify(self, topic, **kwargs):
"""
13 changes: 5 additions & 8 deletions tests/test_pyttsx3.py
Original file line number Diff line number Diff line change
@@ -11,19 +11,16 @@


@pytest.fixture
def engine():
def engine(driver_name: str | None = None) -> pyttsx3.engine.Engine:
"""Fixture for initializing pyttsx3 engine."""
engine = pyttsx3.init()
engine = pyttsx3.init(driver_name)
yield engine
engine.stop() # Ensure the engine stops after tests


def test_engine_name(engine):
expected = {
'darwin': 'nsss',
'win32': 'sapi5'
}.get(sys.platform, 'espeak')
assert engine.name == expected
expected = pyttsx3.engine.default_engine_by_sys_platform()
assert engine.driver_name == expected
assert str(engine) == expected
assert repr(engine) == f"pyttsx3.engine.Engine('{expected}', debug=False)"

@@ -64,7 +61,7 @@ def test_apple__nsss_voices(engine):
engine.setProperty("voice", _voice.id)
name = _voice.id.split(".")[-1]
names.append(name)
engine.say(f"{name} says {quick_brown_fox}")
engine.say(f"{name} says hello.")
name_str = ", ".join(names)
assert name_str == "Eddy, Flo, Grandma, Grandpa, Reed, Rocko, Sandy, Shelley"
print(f"({name_str})", end=" ", flush=True)

0 comments on commit 9bb3527

Please sign in to comment.