os.system() : trop lent

Bonsoir,

Dessous le code qui fonctionne à peu près si on affiche l’item actif de tout menu, mais si je fais vocaliser le label ( avec ‘espeak’) alors je constate une mise à jour très ralentie du nouvel item focussé (après appui sur une flèche).

Si vous n’avez donc pas ‘espeak’ installé, vous ne devriez pas pouvoir m’aider.

Mais en général, l’appel à une routine du système avec ‘os.system()’ est-il très lent ?
Existe-t-il une alternative ?

#!/usr/bin/python3.8

"""
Définition d'une fenêtre comportant une barre de menus dont tous les items sont vocalisés
"""

from tkinter import *
import os

# Dictionnaire pour la définition des menus
items = {
'Fichier':
[
['Nouveau','cmd1'],
['Ouvrir','cmd2'],
['Imprimer','cmd3'],
['Quitter','cmd4']
],
'Edition':
[
['Copier','cmd1'],
['Coller','cmd2'],
['Couper','cmd3']
]
}

class MyWindow(Tk):
  def __init__(self,items,font,*args,**kwargs):
    Tk.__init__(self,*args,**kwargs)
    self.items = items
    self.geometry('400x200')
    self.title('ACIAH')
    self.font = font
# Dictionnaire recevant les paires "nom du menu" / instance du menu associé
    self.menus = {}
    self.create_menu_bar(self.font)
    self.config(menu=self.menu_bar)  
# Méthode qui vocalise les items (en chantier !)
  def menu_select(self, event):
    label = self.eval(f'{event.widget} entrycget active -label')
#    print(label)
    os.system(f'/usr/bin/espeak {label}')
  def create_menu_bar(self,font):
    self.menu_bar = Menu(self,activebackground='Black',activeforeground='White',font=font,takefocus=1)
    for k in self.items:
      self.menus[k] = Menu(self.menu_bar, tearoff=0,activebackground='Black',activeforeground='White',font=font)
      for opt in self.items[k]:
        self.menus[k].add_command(label=opt[0],command=self.event_generate('<<MenuSelect>>'))
      self.menu_bar.add_cascade(label=k,menu=self.menus[k])
      self.menu_bar.bind_class('Menu','<<MenuSelect>>', lambda e: self.menu_select(e))

if __name__ == '__main__':
  window = MyWindow(items,('Arial',24,'bold'))
  window.mainloop()

Merci

pierre estrem

os.system() démarre un nouveau processus, et démarrer un processus c’est lent.

Mais pas du même ordre de grandeur de ce que tu constates.

espeak ne quitte que lorsqu’il a terminé de prononcer la phrase, et prononcer une phrase c’est lent.

Ensuite côté tkinter, selon tkinter — Python interface to Tcl/Tk — Python 3.12.1 documentation :

Because it is single-threaded, event handlers must respond quickly, otherwise they will block other events from being processed.

Puis plus loin :

To avoid this, any long-running computations should not run in an event handler, […] or run in another thread.

Pour ça il te faudra quelques imports :

from threading import Thread
from queue import Queue
from subprocess import run

On utilisera une Queue pour passer les labels à prononcer du thread de tkinter au thread de espeak. On utilisera run de subprocess au lieu de os.system ça nous évitera des problèmes au cas où des caractères spéciaux se trouvent dans la phrase à prononcer (mais non il n’est pas plus rapide).

Pour éviter la prolifération de globales j’ai mis la création de ta fenêtre dans un main, et j’y ai aussi créer la queue et le thread :

def main():
    espeak_queue = Queue()
    thread = Thread(target=espeak_thread, args=[espeak_queue], daemon=True)
    thread.start()
    window = MyWindow(items, ("Arial", 24, "bold"), espeak_queue)
    window.mainloop()


if __name__ == "__main__":
    main()

Il nous manque plus que deux choses : Comment on ajoute une chose à dire :

    # Méthode qui vocalise les items (en chantier !)
    def menu_select(self, event):
        label = self.eval(f"{event.widget} entrycget active -label")
        self.espeak_queue.put(label)

Et l’implémentation du thread :

def espeak_thread(espeak_queue):
    while True:
        to_say = espeak_queue.get()
        run(["espeak", to_say])

Ahh il manque un peu de plomberie, dans le constructeur de MyWindow pour récupérer la queue :

class MyWindow(Tk):
    def __init__(self, items, font, espeak_queue, *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)
        self.items = items
        self.espeak_queue = espeak_queue

Testé chez moi, ça fonctionne !

Voici le fichier complet au cas où
#!/usr/bin/python3.8

"""
Définition d'une fenêtre comportant une barre de menus dont tous les items sont vocalisés
"""

from threading import Thread
from queue import Queue

from tkinter import *
from subprocess import run
import os



# Dictionnaire pour la définition des menus
items = {
    "Fichier": [
        ["Nouveau", "cmd1"],
        ["Ouvrir", "cmd2"],
        ["Imprimer", "cmd3"],
        ["Quitter", "cmd4"],
    ],
    "Edition": [["Copier", "cmd1"], ["Coller", "cmd2"], ["Couper", "cmd3"]],
}


class MyWindow(Tk):
    def __init__(self, items, font, espeak_queue, *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)
        self.items = items
        self.espeak_queue = espeak_queue
        self.geometry("400x200")
        self.title("ACIAH")
        self.font = font
        # Dictionnaire recevant les paires "nom du menu" / instance du menu associé
        self.menus = {}
        self.create_menu_bar(self.font)
        self.config(menu=self.menu_bar)

    # Méthode qui vocalise les items (en chantier !)
    def menu_select(self, event):
        label = self.eval(f"{event.widget} entrycget active -label")
        self.espeak_queue.put(label)

    def create_menu_bar(self, font):
        self.menu_bar = Menu(
            self,
            activebackground="Black",
            activeforeground="White",
            font=font,
            takefocus=1,
        )
        for k in self.items:
            self.menus[k] = Menu(
                self.menu_bar,
                tearoff=0,
                activebackground="Black",
                activeforeground="White",
                font=font,
            )
            for opt in self.items[k]:
                self.menus[k].add_command(
                    label=opt[0], command=self.event_generate("<<MenuSelect>>")
                )
            self.menu_bar.add_cascade(label=k, menu=self.menus[k])
            self.menu_bar.bind_class(
                "Menu", "<<MenuSelect>>", lambda e: self.menu_select(e)
            )


def espeak_thread(espeak_queue):
    while True:
        to_say = espeak_queue.get()
        run(["espeak", to_say])


def main():
    espeak_queue = Queue()
    thread = Thread(target=espeak_thread, args=[espeak_queue], daemon=True)
    thread.start()
    window = MyWindow(items, ("Arial", 24, "bold"), espeak_queue)
    window.mainloop()


if __name__ == "__main__":
    main()

3 « J'aime »

Bonsoir “mdk” et le monde Libre,

Alors je dois vous remercier pour tout ce taf que vous avez fait (vous m’apprenez bien des choses !)

Oui ça fonctionne chez moi aussi à vitesse normale et vous corrigez des bugs.

Je vais mettre la sourdine car vous m’avez donné (don) de quoi étudier. :slight_smile:

Encore merci !

pierre estrem

1 « J'aime »

Bonjour,
@mdk Merci pour cet exemple… je me le note comme exemple :slightly_smiling_face:
@PETERPAN31 : Pour avoir une voix francisée, utilise avec espeak l’argument -v fr .
édit : Regarde aussi du coté de MBROLA qui associé avec espeak rend la voix encore plus juste …

Et de espeak-ng, meilleur que espeak, sur Debian en tout cas espeak-ng a plus de voix utilisant MBROLA que espeak :

$ dpkg -L espeak-ng-data | grep mb-fr
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr1
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr1-en
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr2
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr3
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr4
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr4-en
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr5
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr6
/usr/lib/x86_64-linux-gnu/espeak-ng-data/voices/mb/mb-fr7
1 « J'aime »

Bonjour,

Je vous avais adressé le code simplifié sans une voix de MBROLA parce que il était même possible que espeak ne soit pas installé sur vos machines et encore moins le moteur MBROLA.

Nous l’utilisons avec le “lecteur d’écran” Orca dans la distro aciah-linux.

Vous remarquez que ces voix issues de MBROLA ne sont pas “naturelles”.
Evidemment on parle d’une licence publique.
Mais si vous savez faire pour intégrer une véritable voix naturelle dites-le moi (nous).

Quant à mes menus causants j’en suis à l’écriture d’un type de fichiers de conf de menus.
Je compte ensuite sauver un dictionnaire par fichier de menus avec le module ‘pickle’.

Mon point de blocage actuel est que je ne parviens pas à lire tout mon fichier conf, mais le début et la fin ! lol

A suivre.

pierre estrem