Create a helper class that runs pyttsx
in a separate thread:
import threading
import pyttsx
class Say(object):
"""Function-like class to speak using pyttsx."""
_thread = None
def __init__(self, message):
if not isinstance(message, str):
raise ValueError("message is not a string")
if Say._thread is not None:
Say._thread.join()
Say._thread = None
Say._thread = threading.Thread(target=self._worker,
name="Say",
args=(message,))
Say._thread.start()
def _worker(self, message):
engine = pyttsx.init()
engine.say(message)
engine.runAndWait()
def WaitAllSaid():
if Say._thread is not None:
Say._thread.join()
Say._thread = None
Because pyttsx behaves like a singleton, and only one instance of pyttsx can speak in the same Python process at any time, I encapsulated the global variables into the Say
class, and have the instance constructor wait for any existing utterances to complete, then start a new thread for pyttsx to speak.
Essentially, Say(message)
waits for any utterances in progress to complete, then starts speaking the new voice, and returns. It does not wait for the message to be completely spoken before it returns; it returns immediately when the message begins.
WaitAllSaid()
waits for any utterances in progress, then reaps the worker thread. If you want to modify the pyttsx engine or voice properties, call WaitAllSaid()
first to make sure no utterances are in progress at that time. Otherwise poor pyttsx might get confused.
The last four lines of OP's wikipediaSearch
function now becomes something like
print(new_text)
Say(new_text)
Mbox(user_input, new_text, 0)
WaitAllSaid()
If pyttsx is already speaking, then Say()
blocks until all previous messages have been said. It returns immediately when the specified message starts to play.
The WaitAllSaid()
just blocks until everything that has been said has been uttered. You can omit it from the wikipediaSearch()
function, as long as you make sure that WaitAllSaid()
is called before the Python program exits.
On the not-exactly conventional design: On Linux at least, pyttsx has issues if one tries to use the same pyttsx object for separate statements. Having the helper thread create the instance works much better. Testing on Linux, this pattern was the most robust one among global variables and various forms of singleton classes. I do not use Windows at all, so I cannot test on it, alas.