pyttsx3
pyttsx3 copied to clipboard
Pyttsx3 callback to detect when text is finished not triggering when threading
I have used threading as a way to get around the "run and wait" default functionality so I can interrupt something being said mid speech. However, this blocks the callbacks of the library so I cannot detect when a block of text is finished being said -I have to guess at it using length in seconds. I have read that one person successfully got around this by using multiprocessing but I don't know how to do this. Is there a way to get this callback to work with threading so I know for sure when a block of text is done speaking?
from threading import Event, Thread
import pyttsx3
class Voice(object):
def __init__(self, skip, play_beep):
self.t = None
self._running = False
self.engine = pyttsx3.init()
self.skip = skip
self.engine.connect('finished-utterance', self.onEnd)
def onEnd(self, name, completed):
print('finishing : ', name, completed)
self.stop()
def on_finished_utterance(self, name, completed):
print('END')
t = Thread(target=self.killme, args=(self.engine), daemon=True)
t.start()
def process_speech(self, text):
self.engine.say(str(text))
self.engine.startLoop(False)
while self._running:
self.engine.iterate()
def say(self, text, length=2):
# check if thread is running
if self.t and self._running:
# stop it if it is
self.stop()
# iterate speech in a thread
self.t = Thread(target=self.process_speech, args=(text,), daemon=True)
self._running = True
self.t.start()
elapsed_seconds = 0
poll_interval = .1
while not self.skip.is_set() and elapsed_seconds < length:
self.skip.wait(poll_interval)
elapsed_seconds += poll_interval
def stop(self):
self._running = False
try:
self.engine.endLoop()
except:
pass
try:
self.t.join()
except Exception as e:
pass
skip = Event()
myVoice = Voice(skip, 0)
myVoice.say("test", 2)
myVoice.say("test two", 2)
Hi @simileV , I am the person who got around the issue using multi processing. You can check out the code in my public repository 'Ava' for reference. Feel free to ask about more specific details.
Yes, finished-utterance doesnt work when using threading on windows anyways. Here's some boilerplate for people looking in future:
import pyttsx3
import multiprocessing
import time
import queue
def speech_digester_loop(sayQ):
engine = pyttsx3.init()
engine.setProperty('rate',500)
engine.startLoop(False)
ended=True
def onEnd(name,completed):
nonlocal ended
ended=True
engine.connect('finished-utterance',onEnd)
localQ=[]
while True:
try:
nextUtterance=sayQ.get(False)
except queue.Empty:
pass
if nextUtterance:
localQ.append(nextUtterance)
if (len(localQ)) and ended:
if (localQ[0]=="terminate"):
break
ended=not localQ[0][1] # if we must wait for end then set ended to false
engine.stop()
engine.say(localQ[0][0]) # unfortunately this is blocking on windows
localQ.pop(0)
engine.iterate()
# clean up
engine.endLoop()
sayQ=None
def output_text(text,waitFinish=False):
global sayQ
print (text)
sayQ.put((text,waitFinish))
def all_text_complete():
global sayQ
sayQ.put("terminate")
def start_engine():
global sayQ
multiprocessing.freeze_support()
sayQ = multiprocessing.Queue()
speechThread = multiprocessing.Process(target=speech_digester_loop,args=(sayQ,))
speechThread.start()
if __name__=="__main__":
start_engine()
output_text("hello world",True)
output_text("i bet you just copied the code and ran it",True)
output_text("anyways, the wait for finish doesnt work")
output_text("maybe you can split the string into words before putting it on the queue")
output_text("at least its non blocking",True)
output_text("good luck",True)
all_text_complete()
Hey @simileV and @ccarstens! I'm also working on a voice assistant using pyttsx3 and voice recognition. I'm also struggling with the fact that i have to wait for pyttsx3 to finish speaking before i can do voice recognition again, but i can't get it to work. I've also never worked with multiprocessing before.
If one of you is still active on this issue could you please explain the process of making this work a little bit more? I would really appreciate it.
Hey @Marterido I‘m not working on the project any more but maybe I can help. Can you post a minimal reproducible example of what you’ve tried so far? MRE Maybe also include a visual draft of how you’re structuring things in the processes. cheers, C
Hey @ccarstens
I started with trying the code from acenturyandabit with slight changes to see if i could get that example to work, but there i already ran into my first problem. I suspect it's something with my installation but i couldn't find anything useful about it. The error is FileNotFoundError: [Errno 2] No such file or directory
with a traceback to the multiprocessing module. Do you have any idea what i can do about that?