0

I have an appliaction which asks the user questions which they should answer. The application uses pyttsx3 (version 2.9) to generate speech and I would like that while it speaks and only then, a gif animation would play for as long as it speaks. How can this be accomplished? For example:

import pyttsx3
engine = pyttsx3.init()
engine.say("I will speak this text")
engine.runAndWait()

And i want the gif animation to start running while runAndWait runs and stop when it stops

  • What pyttsx3 version are you running? A little coding example would help. – Derek Jul 14 '21 at 22:27
  • Ok I've found version 2.90 has problems with event handling. Version 2.5 works for me. I'll just find some code that may help. – Derek Jul 15 '21 at 04:36

1 Answers1

0

Ok I've found another program that works with the GIF images I have.

This program speaks for itself and displays a gif image.

I'm using tkinter Text object for output and a Label to hold the image.

It should allow GIF animes to run but it requires specific information about the GIF.

This runs on windows 10 and pyttsx3 - 2.5

"""
Program outputs text while voice is speaking.

This program works on Python 2.6.14 and Python 3.7.3

"""

import tkinter as tk
from tkinter import filedialog as fido
import random, pyttsx3

class chatter:

    terminate = False

    def showText( self, m ):
        """talk.showText( m )"""
        self.main.insert( "insert", m )
        self.master.update( )
        self.main.edit_separator( )

    def Start( self, name ) ->"":
        """talk.Start( name )"""
        self.sentence = name.split(  )

    def Use( self, name, location, length ) ->"":
        """talk.Use( name, location, length )"""
        name = self.sentence.pop( 0 )
        self.showText( f"{name} " )

    def End( self, name, completed ) ->"":
        """talk.End( name, completed )"""
        if len( self.sentence ) > 0:
            while len( self.sentence ) > 0:
                name = self.sentence.pop( 0 )
                self.showText( f"{name}\n" )
        else:
            self.showText( "\n" )

    def Error( self, name, exception ) ->"":
        """talk.Error( name, exception )"""
        self.terminate = True
        self.engine.stop( )
        self.showText( f"\n{name}\n{exception}\n" )

    def say( self, m ):
        """talk.say( m )"""
        self.engine.say( m, m )
        self.engine.runAndWait(  )

    def intro( self ):
        for m in [
            "This program works on Python 2.6 and Python 3",
            "I am PYTTSX version 2.5" ]:
            talk.say( m )
        self.master.after( 100, self.imageUpdate, 0 )

    def displayInCanvas( self ):
        """talk.displayInCanvas( )"""
        self.image = fido.askopenfilename( title = 'Pick Gif' )
        w, h = self.image.width() + 14, self.image.height() + 46
        self.item = self.canvas.create_image( 2,2, anchor = "nw" )
        self.canvas.itemconfig( self.item, image = self.image )
        self.master.geometry( f"{w}x{h}" )
        self.labelframe[ "text" ] = self.image

    def getImage( self ):
        """talk.getImage( )"""
        self.item = tk.Label( anchor = "nw" )
        self.item.grid( row=0, column=1, sticky="nsew" )
        self.image = fido.askopenfilename( title = 'Pick Gif' )
        # This requires specific knowledge about the GIF image
        # n = number of animation frames
        # self.frame = [ tk.PhotoImage( master = self.master, file=self.image, format = 'gif -index %i' %( i ) ) for i in range( n ) ]
        self.frame = [ tk.PhotoImage(
            master = self.master, file = self.image, format = "gif -index 0" ) ]
        self.item.configure( image = self.frame[ 0 ] )

    def imageUpdate( self, ind ):
        """imageUpdate( ind )"""
        frame = self.frame[ ind ]
        ind += 1
        print( ind )
        # Will play gif infinitely
        if ind > 0: 
            ind = 0
        self.item.configure( image = frame )
        self.master.after( 100, self.imageUpdate, ind )

    def closer( self, ev = None ):
        self.master.destroy( )

    def __init__( self ):
        """talk.init"""

        self.engine =  pyttsx3.init(  )

        self.startUtter = self.engine.connect(
            "started-utterance", self.Start )
        self.wordsUtter = self.engine.connect(
            "started-word", self.Use )
        self.endsUtter  = self.engine.connect(
            "finished-utterance", self.End )
        self.erraUtter  = self.engine.connect(
            "error", self.Error )

        self.persona = self.engine.getProperty( "voices" )[~0].id
        self.engine.setProperty( "voice", self.persona )

        for a,b in [ ("rate", 150 ), ( "volume", 0.25 ) ]:
            self.engine.setProperty( a,b )
        self.engine.runAndWait( )

        self.master = tk.Tk()
        self.main = tk.Text(
            self.master, undo =1, wrap = "word",
            block = 1, width = 80, height = 25 )
        self.main.grid( row = 0, column = 0, sticky = "nsew" )

        self.master.bind( "<Escape>", self.closer )
        self.showText( __doc__ + "\n" )
        self.main.update_idletasks( )
        self.getImage( ) # load and display gif
        self.main.focus_set()
        self.master.after( 100, self.intro )

if __name__ == "__main__":

    talk = chatter( )
    tk.mainloop()
Derek
  • 1,916
  • 2
  • 5
  • 15
  • So the gif runs, and the program speaks but they're not coordinated. Do you have an idea how to make the animation run while the program speaks and stop while it's not? – username_94 Jul 15 '21 at 18:04
  • If that works then `pyttsx` events are working, that's good news. If you check the gif routine you'll find I've set the number of images to zero. In other words only the first image will be shown. If you want animations then you will need to know the number of anim frames and set `n` to that number then un-rem related code for `self.frame`. – Derek Jul 16 '21 at 01:40
  • I initially used `PIL ImageTk` but had trouble with `tkinter Text` freezing up so changed to pure `tkinter` solution. `PIL` is still an option if this doesn't work out. – Derek Jul 16 '21 at 01:44
  • So what I managed to do which is rather pleasing - I created 2 separate objects which inherit from tkinter objects - one has the TTS methods and the other activates the animation. The animation component listens all the time and updates the gif based on some property in the TTS wrapper which is changed based on whether the computer speaks or not. It's working and managed by the tkinter event loop I think but the gif has some delays – username_94 Jul 17 '21 at 13:35
  • Excellent @username_94. I've been tinkering with `PIL ImageTk` with limited success so good one. – Derek Jul 17 '21 at 16:00