# Gestion d'une entrée voix par python

![tag](python)
![category](developpement)

Avec la montée en puissance de l'IA on trouve de plus en plus d'outil qui permettent de faire du **speechToText** en sommes transformé la voix en texte.

Dans le cadre d'un usage de saisie de note j'ai essayé de chercher des solutions multiplateforme, pas cher et facile de mise en oeuvre. et je dois reconnaitre que la tâche n'est pas simple et que souvent on nous propose des solutions onéreuse.

J'ai pourtant trouvé un projet **vosk** écrit en python qui réalise très bien ce job avec une emprise machine très faible ... cela fonctionne très bien avec très peux de mémoire et un bête CPU classique ... je n'ai pas constaté de perte de performance à l'usage.


Mon projet est de faire un outil simple qui permettrait de taper ce que je dicte.

## Installation des outils tiers

Une des premières difficultés rencontrées n'est pas de faire du **speechToText** mais de pouvoir simuler un clavier. Sous ubuntu waylland (et non X11) cela est tout bonnement impossible: gnome et ubuntu estimant que l'émulation d'un clavier par une application étant risqué au niveau sécurité (cela pourrait être un bot ... ce qui va être la cas à la finale) ubuntu ne l'autorise pas et vas jusqu'à fermer les APIs que peux proposer Gnome.

Ma solution a été de passer par un simple copier/coller.
Pour la gestion du copier j'utilise *xclip* et pour le coller *ydotool*

L'installation de ce dernier est compliqué car on doit pouvoir l'utiliser sans être root

```bash
apt-get install xclip ydotool ydotoold
```

ydotool utilise le module uinput qui n'est autorisé que pour root

> ls -l /dev/uinput

> crw------- 1 root root 10, 223 mai   19 13:12 /dev/uinput

On va donc créer un groupe dans lequel notre user sera présent

```bash
sudo groupadd inputusers
sudo usermod -aG inputusers $USER
sudo reboot -h now
```

puis ajouter une règle pour donner à ce groupe l'autorisation sur uinput

En tant que **root**

```bash
vi /etc/udev/rules.d/99-uinput.rules
# on ajoute KERNEL=="uinput", GROUP="inputusers", MODE="0660"
udevadm control --reload-rules
udevadm trigger
ls -l /dev/uinput
```

Maintenant en tant que simple utilisation dans un terminal on doit pouvoir lancer

> ydotool type "coucou"


## Installation de vosk

Son installation est assez simple, comme dans notre projet on va utiliser le micro pour envoyer le son (et non un fichier mp3) on va installer des élément gérant le micro

```bash
apt install portaudio19-dev (si besoin libportaudio2)
pip install vosk sounddevice
```

une fois installer il faut aussi récupérer le modèle qui va permettre le speechTotext ... je télécharge le modèle fait pour les francophones

```bash
kdir vosk
cd vosk
wget https://alphacephei.com/vosk/models/vosk-model-small-fr-0.22.zip
unzip vosk-model-small-fr-0.22.zip
```

## Mon programme

L'idée est simple, on lance le programme en indiquant

- ou se trouve le modèle
- un mot clé (içi jarvis) permettant l'activation du speechToText puis du copier/coller
- un mot clé (içi bye bye) permettant l'arrêt

```python
import subprocess
import queue
import threading
import sounddevice as sd
import json
from vosk import Model, KaldiRecognizer

# -------------------
# QUEUE COMMANDES
# -------------------
cmd_queue = queue.Queue()

# -------------------
# VOSK
# -------------------
model = Model("./vosk/vosk-model-small-fr-0.22")
rec = KaldiRecognizer(model, 16000)

# -------------------
# CLIPBOARD + PASTE
# -------------------
def execute_text(txt):
    # 1. mettre dans clipboard
    subprocess.run(f'echo "{txt}" | xclip -selection clipboard', shell=True)

    # 2. coller
    subprocess.run(["ydotool", "key", "ctrl+v"])


# -------------------
# WORKER THREAD
# -------------------
def worker():
    while True:
        txt = cmd_queue.get()
        if txt is None:
            break

        print(f"[EXEC] {txt}")
        execute_text(txt)


# -------------------
# AUDIO CALLBACK
# -------------------
q = queue.Queue()

def callback(indata, frames, time, status):
    q.put(bytes(indata))


# -------------------
# THREAD LANCEMENT
# -------------------
threading.Thread(target=worker, daemon=True).start()

# -------------------
# STREAM MICRO
# -------------------
is_enabled = False

with sd.RawInputStream(
        samplerate=16000,
        blocksize=8000,
        dtype='int16',
        channels=1,
        callback=callback):

    print("Parlez... Ctrl+C pour arrêter")

    try:
        while True:
            data = q.get()

            if rec.AcceptWaveform(data):
                result = json.loads(rec.Result())
                texte = result.get("text", "").strip()

                if not texte:
                    continue

                print(texte)

                # activation
                if "jarvis" in texte:
                    is_enabled = True
                    print("jarvis enabled")
                    continue

                # desactivation
                if "bye bye" in texte:
                    is_enabled = False
                    print("jarvis disabled")
                    continue

                # push queue
                if is_enabled:
                    cmd_queue.put(texte)

    except (KeyboardInterrupt, EOFError):
        print("vosk terminated")
        cmd_queue.put(None)

```

Une remarque important: dans certains outils le Crtl+V ne fonctionne pas ... par exemple dans mon terminal je dois utiliser Crtl+Shift+V

Cela ouvre beaucoup d'idée ... maintenant que j'ai un outil qui m'écoute on va pouvoir lancer autres choses que du copier/coller et cela avec un outil peu gourmand
