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

Verified

This commit was signed with the committer’s verified signature.
martin-sucha Martin Sucha
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.