đŸŽ™ïž CrĂ©er un gĂ©nĂ©rateur vocal en local avec Piper et Flask (sans prise de tĂȘte !)

Tim
February 21st, 2025
image description

CrĂ©er son propre gĂ©nĂ©rateur vocal avec Piper et Flask (sans prise de tĂȘte !)

On vit une Ă©poque oĂč les IA vocales sont partout : assistants vocaux, livres audio, vidĂ©os narrĂ©es
 et pourtant, beaucoup d’entre elles sont payantes et hĂ©bergĂ©es sur le cloud (coucou ElevenLabs 👋).

Mais et si on pouvait gĂ©nĂ©rer des voix naturelles en local, sans dĂ©pendre d’un service externe ?
💡 Bonne nouvelle : c’est possible avec Piper !

Dans cet article, je vous montre comment j’ai créé une petite application Flask qui transforme du texte en audio en local, avec des voix de qualitĂ©, sans abonnement, et sans se compliquer la vie.

PrĂȘt ? Let’s go ! đŸŽ™ïž


Pourquoi un TTS local ?

Avant d’entrer dans le vif du sujet, pourquoi se compliquer à installer un TTS en local alors qu’il existe des services ultra-performants ?

  • ⚡ C’est lĂ©ger et rapide : Piper tourne mĂȘme sur un petit PC.

  • ☁ Aucune dĂ©pendance au cloud : pas besoin d’ĂȘtre connectĂ©.

  • 📖 C’est gratuit et open source : pas d’abonnement, pas de restriction.

  • 💡 IdĂ©al pour des projets persos ou une intĂ©gration rapide.

Bien sûr, si vous cherchez une qualité de voix ultra-réaliste, ElevenLabs reste une solide option. Mais ici, le but est de se faire la main et de bricoler quelque chose de fonctionnel rapidement et en local.

1ïžâƒŁ Installer Piper sur Windows

Télécharger Piper

Piper est disponible sur GitHub :

🔗 https://github.com/rhasspy/piper

TĂ©lĂ©chargez la version adaptĂ©e Ă  votre OS. Pour Windows, il y a une version prĂ©compilĂ©e qui facilite l’installation.

Sur le repo Github de Piper, cliquez sur 'Releases' Pour mon cas, la version piper_windows_amd64.zip est le fichier qu'il me faut.

Installer Piper

Une fois tĂ©lĂ©chargĂ©, il suffit d’extraire le dossier et d’ouvrir un terminal dedans. Ensuite, testons si Piper fonctionne bien avec :

shell
# Je suis sous Windows donc la commande ressemble Ă  ceci
.\piper --help

Si tout va bien, Piper est prĂȘt ! 🎉

Téléchargement et installation des modÚles

Une fois Piper installĂ©, il faut lui fournir un modĂšle de voix pour qu’il puisse gĂ©nĂ©rer du son. Chaque modĂšle se compose deux fichiers essentiels :

  1. Le fichier modĂšle (.onnx) → Contient les donnĂ©es nĂ©cessaires Ă  la synthĂšse vocale.

  2. Le fichier de configuration (.json) → DĂ©finit les paramĂštres du modĂšle (vitesse, intonation, etc.).

Les modĂšles de voix pour Piper sont disponibles ici :

Chaque langue dispose de plusieurs voix, souvent nommĂ©es d’aprĂšs leur crĂ©ateur. Choisissez celle qui vous plaĂźt, tĂ©lĂ©chargez les deux fichiers (.onnx et .json) et placez-les dans le dossier piper/models/.

💡 Exemple pour une voix en français :
Si vous téléchargez "fr_FR-gilles", vous aurez :

  • fr_FR-gilles-low.onnx

  • fr_FR-gilles-low.onnx.json

Placez-les dans :
📂 piper/models/

Et voilĂ , votre modĂšle est prĂȘt Ă  l’emploi ! đŸŽ™ïž

Quelle version du modĂšle choisir ?

En explorant les modĂšles, vous remarquerez gĂ©nĂ©ralement plusieurs versions d'une mĂȘme voix : low, medium, high, vits, vits-high, etc.

  • Low → ModĂšle lĂ©ger, rapide Ă  exĂ©cuter, mais qualitĂ© audio rĂ©duite.

  • Medium → Bon compromis entre rapiditĂ© et qualitĂ©.

  • High → Meilleure qualitĂ© vocale, mais plus gourmand en ressources.

  • VITS → Version plus avancĂ©e avec une intonation plus naturelle.

💡 Si vous cherchez un bon Ă©quilibre, partez sur Medium. Si vous voulez une qualitĂ© optimale et que votre machine le permet, essayez High ou VITS-High.

📌 Astuce : Rien ne vous empĂȘche de tĂ©lĂ©charger plusieurs versions et de tester ! 🚀

Testez ensuite une synthĂšse vocale rapide :

shell
echo "Le métier de développeur est fascinant!" | .\piper --model models\fr\fr_FR-gilles-low.onnx --output_file test.wav

Petites précisions utiles

Avant d’exĂ©cuter la commande, voici deux petites choses Ă  savoir :

1ïžâƒŁ Organisation des modĂšles
J’ai créé un dossier models/ et, Ă  l’intĂ©rieur, j’ai classĂ© les modĂšles par langue (fr/, en/, es/, etc.). Cela permet de s’y retrouver facilement quand on tĂ©lĂ©charge plusieurs voix. C’est pour ça que le chemin du modĂšle dans la commande est un peu long :

shell
models\fr\fr_FR-gilles-low.onnx

2ïžâƒŁ ExĂ©cution sous PowerShell
Si vous utilisez PowerShell, vous devrez ajouter .\ devant piper, sinon il vous affichera un avertissement indiquant que la commande doit ĂȘtre explicitement rĂ©fĂ©rencĂ©e. D’oĂč :

shell
echo "Le métier de développeur est fascinant!" | .\piper --model models\fr\fr_FR-gilles-low.onnx --output_file test.wav

Si vous ĂȘtes sous cmd, pas besoin du .\, un simple piper suffit. 🚀

Si vous entendez votre fichier test.wav, tout fonctionne. 🚀

2ïžâƒŁ IntĂ©grer Piper dans une application Flask

Bon, Piper c’est cool en ligne de commande, mais ce serait mieux avec une interface. đŸ–„ïž

J’aurais pu faire une solution bien robuste avec Laravel et une file d’attente, comme j’en ai l’habitude, mais ici, l’objectif Ă©tait avant tout d’aller vite et de m’amuser avec Python, sans prise de tĂȘte. Pour ĂȘtre honnĂȘte, ça faisait un bon moment que je n’avais pas touchĂ© Ă  Python
 et ça commençait Ă  me manquer ! 😄

Installation des dépendances

On crée un environnement virtuel et on installe Flask :

shell
python -m venv .venv

Une fois l'environnement virtuel créé, il faut l'activer pour qu'il devienne l'environnement Python par défaut pour votre projet. Cela garantit que toutes les dépendances seront installées et exécutées dans cet environnement isolé.

shell
# Sous Windows
.venv\Scripts\activate

# Sous Linux
source .venv/bin/activate

Cette commande active l'environnement virtuel en exécutant le script activate situé dans le dossier Scripts.

Maintenant que l'environnement virtuel est activé, vous pouvez installer les dépendances dont vous avez besoin pour le projet. Dans ce cas, nous allons installer Flask et python-dotenv.

shell
pip install flask python-dotenv

Créer une API Flask

On va à présent créer un petit serveur Flask qui recevra du texte et retournera un fichier audio.

  • 👉 Route pour gĂ©nĂ©rer l’audio

  • 👉 Stockage des fichiers dans un dossier statique

  • 👉 Listing des fichiers gĂ©nĂ©rĂ©s

Le tout en mode asynchrone, pour Ă©viter de bloquer l’application.

L’objectif ici n'est pas de faire un tutoriel complet sur Flask, mais plutĂŽt de vous montrer comment j'ai intĂ©grĂ© Piper dans une application web. Pour ceux qui souhaitent explorer l’ensemble du code, vous pouvez consulter le dĂ©pĂŽt GitHub ici.

Dans cette section, je vais vous montrer la classe business, qui est la classe TextToSpeech. C’est elle qui gĂšre la logique de gĂ©nĂ©ration de la parole avec le modĂšle Piper. C'est le fichier principal du backend, et il contient toute la logique nĂ©cessaire pour interagir avec Piper et gĂ©nĂ©rer des fichiers audio.

python
import os
import subprocess
import re
import unicodedata

class TextToSpeech:
    def __init__(self, piper_path, models_dir, root_folder):
        """Initialize the TextToSpeech object with Piper executable path and models directory."""
        self.piper_path = piper_path
        self.models_dir = models_dir
        self.root_folder = root_folder

    def get_languages(self):
        """Dynamically load languages from the models directory."""
        return [lang for lang in os.listdir(self.models_dir) if os.path.isdir(os.path.join(self.models_dir, lang))]

    def get_models_for_language(self, language):
        """Get all models for a given language."""
        language_path = os.path.join(self.models_dir, language)
        return [f for f in os.listdir(language_path) if f.endswith('.onnx')]

    def get_safe_filename(self, text, n = 50):
        """Generate a safe, slugified filename based on the input text."""
        # Normalize the text to remove accents
        normalized_text = unicodedata.normalize('NFKD', text[:n]).encode('ascii', 'ignore').decode('ascii')
        
        # Replace non-alphanumeric characters (except spaces) with an empty string
        slugified_text = re.sub(r'[^a-zA-Z0-9\s]', '', normalized_text)
        
        # Replace spaces with hyphens and convert to lowercase
        safe_text = re.sub(r'\s+', '-', slugified_text).lower()
        
        return f"static/audio/{safe_text}.wav"

    def generate_speech(self, text, model_path, output_file, rate=1.0):
        """Run the Piper executable to generate speech from text."""
        
        output_file = os.path.join(self.root_folder, output_file)
        # Define the correct command for Piper
        command = f'"{self.piper_path}" --model "{model_path}" --output_file "{output_file}" --rate {rate}'

        print('-- 05 -> Command: ', command)
        
        try:
            # Run Piper with the given command
            process = subprocess.run(command, shell=True, text=True, 
                input=text, capture_output=True, encoding='utf-8'
            )
            print('-- 06 -> Process started successfully: ', command)
            
            if process.returncode != 0:
                raise Exception(f"Error running Piper: {process.stderr}")

        except Exception as e:
            print(f"Error in generate_speech: {e}")
            raise

    def process_speech_request(self, text, selected_language, selected_model, rate=1.0):
        """Process the text-to-speech request and return the output file path."""        
        model_path = os.path.join(self.models_dir, selected_language, selected_model)
        
        if not os.path.exists(model_path):
            raise FileNotFoundError(f"Model not found at {model_path}")        
        
        output_file = self.get_safe_filename(text)        
        self.generate_speech(text, model_path, output_file, rate)
        
        return output_file

3ïžâƒŁ Une interface web minimaliste avec Alpine.js

PlutĂŽt que de faire un gros front avec Vue ou React, j’ai choisi Alpine.js pour garder un projet simple et lĂ©ger.

On a donc une page HTML qui :

  • ✅ Permet d’entrer du texte

  • ✅ Choisir la langue et le modĂšle

  • ✅ GĂ©nĂ©rer l’audio en arriĂšre-plan

  • ✅ Afficher la liste des fichiers disponibles

Et tout ça sans recharger la page. ✹

Interface de notre App 'Audiobook', il y a un champs de texte, un sélecteur pour la langue, le modÚle. On peut également choisir le débit de parole.

4ïžâƒŁ Gestion des tĂąches en arriĂšre-plan

La gĂ©nĂ©ration de la parole peut prendre un certain temps, notamment si le modĂšle est complexe ou si le texte est long. Pour Ă©viter que l'application ne devienne lente ou qu’elle se fige pendant ce processus, il est essentiel d'effectuer cette tĂąche en arriĂšre-plan.

Sur des applications plus robustes comme celles dĂ©veloppĂ©es avec Laravel, il serait courant d'utiliser une file d'attente (queue worker) pour gĂ©rer cela, mais dans notre cas, l'objectif Ă©tait de tester rapidement Python tout en maintenant la simplicitĂ©. Nous avons donc optĂ© pour un thread qui exĂ©cute la tĂąche de gĂ©nĂ©ration de la parole en arriĂšre-plan, ce qui permet Ă  l’utilisateur de continuer Ă  interagir avec l’application sans interruption.

Voici le code que j’utilise pour gĂ©rer cette tĂąche dans Flask :

python
def generate_speech():
    if request.method == "POST":
        text = request.form.get("text")
        selected_language = request.form.get("language")
        selected_model = request.form.get("model")  
        rate = float(request.form.get("rate", 1.0))  # Default rate is 1.0 if not provided      

        task_id = str(uuid.uuid4())  # Generate a unique task ID
        with current_app.app_context():  # Pushing app context
            text_to_speech = current_app.config['TEXT_TO_SPEECH']
            thread = threading.Thread(target=background_generate_speech, args=(text_to_speech, task_id, text, selected_language, selected_model, rate))
        thread.start()

        return jsonify({"task_id": task_id, "status": "Processing in the background..."})

Dans cette partie du code, lorsque l'utilisateur soumet une requĂȘte pour gĂ©nĂ©rer un fichier audio, celle-ci est traitĂ©e dans un thread sĂ©parĂ©. Le processus ne bloque donc pas l'application et l'utilisateur reçoit immĂ©diatement une rĂ©ponse indiquant que le processus est en cours.

Ensuite, la tùche est réellement exécutée dans la fonction background_generate_speech() :

python
def background_generate_speech(text_to_speech, task_id, text, selected_language, selected_model, rate):
    """Run speech generation in the background and update status."""
    
    # Manually push the app and request context for the background thread    
    save_task_status(task_id, "processing")    
    try:
        # Process the speech request and save output file path
        output_file = text_to_speech.process_speech_request(text, selected_language, selected_model, rate)
        save_task_status(task_id, "completed")
    except Exception as e:
        save_task_status(task_id, f"failed: {str(e)}")
        return str(e)

Cette approche présente plusieurs avantages :

  1. Réactivité : L'utilisateur n'attend pas que le traitement soit terminé pour continuer à interagir avec l'application.

  2. Simplicité : Nous avons évité d'implémenter une solution plus complexe avec des queues ou autres outils de gestion de tùches asynchrones.

  3. ContrÎle : Nous avons la possibilité de gérer l'état des tùches (en cours, terminées, échouées) et de garder une trace des erreurs potentielles.

Cela Ă©tant dit, dans une application de plus grande envergure, il serait peut-ĂȘtre prĂ©fĂ©rable d’implĂ©menter une solution plus robuste et Ă©volutive, comme une queue via Laravel ou des outils Python comme Celery, qui permettent de mieux gĂ©rer les processus longs et d’offrir plus de souplesse.

Le résultat : une mini web app fonctionnelle !

🎯 On envoie du texte, on choisit la langue et le modùle, et on obtient un fichier audio !

L’interface est propre et simple, et la gĂ©nĂ©ration est fluide et rapide. 🚀


Et aprĂšs ?

Ce projet Ă©tait un bon moyen de jouer avec Flask, d’expĂ©rimenter un TTS local et open source, et de crĂ©er un outil pratique et rapide Ă  utiliser.

Si vous voulez tester par vous-mĂȘme, voici le repo GitHub :
👉 Lien vers le projet

Et vous, avez-vous dĂ©jĂ  tentĂ© d’intĂ©grer une synthĂšse vocale dans un projet ? đŸ€” (Je sais, je sais
 on n'a pas encore de section commentaire. Mais bon, tout vient Ă  point Ă  qui sait attendre
 ou Ă  qui a un peu de temps pour ajouter ça ! đŸ˜