pyttsx3 icon indicating copy to clipboard operation
pyttsx3 copied to clipboard

Pyttsx3 callback to detect when text is finished not triggering when threading

Open ghost opened this issue 5 years ago • 5 comments

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)

ghost avatar Nov 03 '19 16:11 ghost

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.

ccarstens avatar Apr 27 '20 17:04 ccarstens

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

acenturyandabit avatar Oct 08 '20 00:10 acenturyandabit

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.

Marterido avatar Nov 22 '21 22:11 Marterido

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

ccarstens avatar Nov 22 '21 23:11 ccarstens

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?

Marterido avatar Nov 23 '21 07:11 Marterido