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 ! đïž
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.
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.
Une fois tĂ©lĂ©chargĂ©, il suffit dâextraire le dossier et dâouvrir un terminal dedans. Ensuite, testons si Piper fonctionne bien avec :
# Je suis sous Windows donc la commande ressemble Ă ceci
.\piper --help
Si tout va bien, Piper est prĂȘt ! đ
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 :
Le fichier modĂšle (.onnx
) â Contient les donnĂ©es nĂ©cessaires Ă la synthĂšse vocale.
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 ! đïž
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 :
echo "Le métier de développeur est fascinant!" | .\piper --model models\fr\fr_FR-gilles-low.onnx --output_file test.wav
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 :
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Ăč :
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. đ
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 ! đ
On crée un environnement virtuel et on installe Flask :
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é.
# 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.
pip install flask python-dotenv
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.
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
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. âš
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 :
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()
:
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 :
Réactivité : L'utilisateur n'attend pas que le traitement soit terminé pour continuer à interagir avec l'application.
Simplicité : Nous avons évité d'implémenter une solution plus complexe avec des queues ou autres outils de gestion de tùches asynchrones.
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.
đŻ 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. đ
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 ! đ