Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Dispatching a Win32 COM in the background?

0 views
Skip to first unread message

Steven M. Castellotti

unread,
Jun 19, 2002, 5:48:36 PM6/19/02
to
Hey all-

I'm running a Python application under windows, and I want to make use
of the Microsoft Speech API to sythesize Text-To-Speech output, without
halting the execution of my program.

I'm making my calls like this:

(intialization)

from win32com.client import constants
import win32com.client
self.speech = win32com.client.Dispatch("SAPI.SpVoice")

(during execution)

self.speech.Speak(text)


I'm not very familiar with win32com calls, but I'm wondering if there's
an easy to to either:

1) Make the call such that the process occurs in the background, and
application execution continues to flow

2) Make the call inside a thread which produces the same effect.


Can anyone please offer me some tips/sample code to achieve this?


Thanks in advance.


Cheers,

Steve Castellotti
SteveC (at) innocent.com
http://cogengine.sourceforge.net/

Peter Hansen

unread,
Jun 19, 2002, 7:46:32 PM6/19/02
to
"Steven M. Castellotti" wrote:
>
> 2) Make the call inside a thread which produces the same effect.

This might work (I don't know anything about COM though). Try putting
your own code inside the run method of a thread:

import threading
class Background(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)

def run(self):
print 'Starting'
pass # your own code goes here
print 'Done'

>>> b = Background()
>>> b.start()
Starting
<possibly a pause here?>
Done

If at this point you get a >>> prompt back right away, but the
speech generation is carrying on "in the background" then you're
all set. Make sure you do this with code you've already tested
outside of the thread, so you'll know that any failure is not
the fault of your own code...

-Peter

David LeBlanc

unread,
Jun 19, 2002, 7:56:05 PM6/19/02
to
Hmmm... and the run method of Background could be sitting on one end of a
Queue (Queue is thread-safe: see your python doc for details) that's
buffering the asyncronous calls on the speacher. The main task then just
stuffs a talk request into the queue and the Background processes it when it
gets to it.

David LeBlanc
Seattle, WA USA

> --
> http://mail.python.org/mailman/listinfo/python-list

Steven M. Castellotti

unread,
Jun 20, 2002, 3:12:05 PM6/20/02
to
Thanks a lot guys, your suggestions have worked perfectly!

Just for the record, here's what the full implementation looks like.

This code allows text to be piped to Microsoft's Text-To-Speech
sythesizer, spoken in the background, in a thread-safe manner:

#####################################################################

import threading

class Background_TTS(threading.Thread):

def __init__(self, debug_mode):
threading.Thread.__init__(self)

self.debug_mode = debug_mode
self.loop = 1

import Queue
self.text_queue = Queue.Queue()


#####################################################################

def Speak(self, text):
self.text_queue.put(text)


#####################################################################

def stop(self):

self.loop = 0

#####################################################################

def run(self):


from win32com.client import constants
import win32com.client

import time

speech = win32com.client.Dispatch("SAPI.SpVoice")

while self.loop:

if not(self.text_queue.empty()):
text = self.text_queue.get()
speech.Speak(text)
time.sleep(.001)

#####################################################################

initialization looks like this:

try:
self.speech = Background_TTS(self.gameInformation.debug_mode)
self.speech.start()
except:
if (self.gameInformation.debug_mode):
print "Error Initializing Microsoft Speech API."
else:
self.text_to_speech_enabled = 1

#####################################################################

calling the synthesizer:

self.speech.Speak(text)

#####################################################################

and finally, stopping the process:

self.speech.stop()

Once again guys, thanks!

-Steve Castellotti

Peter Hansen

unread,
Jun 20, 2002, 9:22:40 PM6/20/02
to
"Steven M. Castellotti" wrote:
>
> Thanks a lot guys, your suggestions have worked perfectly!
>
> Just for the record, here's what the full implementation looks like.

Beautiful... thanks for the followup Steven. I especially like
your use of deferred imports to speed up initial program loading.

[...]


> if not(self.text_queue.empty()):
> text = self.text_queue.get()
> speech.Speak(text)
> time.sleep(.001)

Note that the time.sleep() as shown above is doing nothing
useful for you. I suspect you had it unindented but your
posting probably used tabs and spaces which makes it hard
to see properly on some readers.

The reason it does nothing useful is that Python will
automatically switch between different threads every so
often anyway, so the concept of giving up the CPU for
what amounts to a tiny fraction of a second is somewhat
unnecessary.

-Peter

David LeBlanc

unread,
Jun 21, 2002, 12:27:57 AM6/21/02
to
Hmmm... actually I don't think either the queue.empty or the sleep are
useful. I believe if you profile this, it's spending all it's time blocked
on the get().

Would this do as well?:

#done gets set by the other thread.
while not done:
# block here until queue has content
text = self.text_queue.get()
speech.Speak(text)

#done became true
#do clean up/exit stuff

David LeBlanc
Seattle, WA USA

> -----Original Message-----
> From: python-l...@python.org
> [mailto:python-l...@python.org]On Behalf Of Peter Hansen
> Sent: Thursday, June 20, 2002 18:23
> To: pytho...@python.org
> Subject: Re: Dispatching a Win32 COM in the background?
>
>

> --
> http://mail.python.org/mailman/listinfo/python-list

David LeBlanc

unread,
Jun 21, 2002, 2:50:58 AM6/21/02
to
Hmmm... if this is working for you, that's great - it just looks strange to
me.

If Background_TTS is the background speacher thread, shouldn't it have the
queue.get() in it and the foreground have the queue.put()?

It's also worth noting that self isn't used or needed at the global level,
so self.speech is kind of different ;-)

Actually... tell me how this works :) :

import threading

# Private class
class _speech_processor(threading.Thread):
def __init__(self, speech_queue, talker):
threading.Thread.__init__(self)
self._talk_queue = speech_queue
self._talker = talker

def run(self):
tq = self._talk_queue # tiny speed up?
talker = self._talker
while 1: # the main thread will kill us.
talker.Speak(tq.get())

# end of _speech_processor

class BackgroundSpeaker():
def __init__(self, debug_mode):
self._debug_mode = debug_mode
self._bg = None
self._speech_queue = None
self._talker = None
self._statusOK = 1 # Must be before try/excepts. Success unless failure!
try:
from win32com.client import constants # I think this is redundant
import win32com.client # since you'll get the constants here too
except ImportError:
if (self._debug_mode):
print "Unable to import Win32COM extensions - are they installed?"
self._statusOK = 0

try:
self._talker = win32com.client.Dispatch("SAPI.SpVoice")
except:
if (self._debug_mode):


print "Error Initializing Microsoft Speech API."

self._statusOK = 0

# leading '_' means private method (by agreement only - not enforced)
def _start_bg(self):
import Queue
self._speech_queue = Queue.Queue()
self._bg = _speech_processor(self._speech_queue, self._talker)
self._bg.start()

def say(self, speech):
if not self._statusOK:
if self._debug_mode:
print "Unable to say: %s." % speech, "Components not installed."
return
if self._bg is None:
self._start_bg()
self._speech_queue.put(speech)

# end of BackgroundSpeaker

if __name__ == '__main__'

import time
import sys

debug_mode = 1

Speaker = BackgroundSpeaker(debug_mode) # It always creates, even if it
can't talk.

while 1:
speech = raw_input("Enter a phrase to speak, then hit Enter")
if speech == "":
Speaker.say("All done")
time.sleep(20) # give some time for finishing up.
sys.exit()
Speaker.say(speech)

I would be interested in hearing if this works! It may have typos since I
did it all manually. Don't have the speech kit to test with.

HTH,

> --
> http://mail.python.org/mailman/listinfo/python-list

Peter Hansen

unread,
Jun 21, 2002, 9:30:36 AM6/21/02
to
David LeBlanc wrote:
>
> Hmmm... if this is working for you, that's great - it just looks strange to
> me.
>
> If Background_TTS is the background speacher thread, shouldn't it have the
> queue.get() in it and the foreground have the queue.put()?
>
> It's also worth noting that self isn't used or needed at the global level,
> so self.speech is kind of different ;-)

I'm pretty sure from previous postings he was going to call this from
within another object, so the "self." used elsewhere might refer to it.

As near as I can tell, with that in mind, the .get() and .put() are in
exactly the right places.

-Peter

Steven M. Castellotti

unread,
Jun 21, 2002, 3:06:49 PM6/21/02
to
On Thu, 20 Jun 2002 20:22:40 -0500, Peter Hansen wrote:

> "Steven M. Castellotti" wrote:
>>
>> Thanks a lot guys, your suggestions have worked perfectly!
>>
>> Just for the record, here's what the full implementation looks
>> like.
>
> Beautiful... thanks for the followup Steven. I especially like your use
> of deferred imports to speed up initial program loading.

Thank you for the compliment. Speeding up program loading by deferring
imports is really only a side bonus though. The program is actually
cross-platfrom, meant to be run under both Linux and Windows. Since I'm
making calls to the Festival TTS program under linux, I have a seperate
class object, depending on what gets returned by os.name.


> [...]
>> if not(self.text_queue.empty()):
>> text = self.text_queue.get()
>> speech.Speak(text)
>> time.sleep(.001)
>
> Note that the time.sleep() as shown above is doing nothing useful for
> you. I suspect you had it unindented but your posting probably used
> tabs and spaces which makes it hard to see properly on some readers.
>
> The reason it does nothing useful is that Python will automatically
> switch between different threads every so often anyway, so the concept
> of giving up the CPU for what amounts to a tiny fraction of a second is
> somewhat unnecessary.

That makes sense to me, I think I'll remove the time.sleep() call. The
reason I had it in there in the first place (and it was unindented) was
because I have a similar call in a loop to Pygame (a wrapper to the SDL
graphics library), which is not multithreaded. I needed the calls in the
Pygame loop in order to prevent CPU usage from sitting at 100%, but if
multithreading will happen properly without it, I won't use it.


Cheers,

Steven M. Castellotti

unread,
Jun 21, 2002, 3:10:29 PM6/21/02
to


That's right, this object is getting called from another class object.
All of this is part of a GPL'd program that allows children to create
their own video games using drawings and pictures, without them having to
learn how program.

Since the program runs under Linux and Windows, there's separate
objects loaded for each enviroment, both having a reference set to
"self.speech" in order to abstract the exact implementation away from
other parts of the program.


Cheers,

0 new messages