GUIDE

Un bot TTS Discord en Python qui lit le chat à voix haute dans un salon vocal

Le /tts intégré de Discord lit un message sur l’appareil de chaque auditeur avec sa voix système et ne parle jamais dans un salon vocal. Pour que des amis sans micro écrivent et que tout le monde dans l’appel les entende, il faut un bot qui rejoint le salon vocal et y joue les messages.

Ce guide en construit un en Python avec discord.py. Il lit un salon textuel choisi avec n’importe quelle voix SuperClonica : avec le moteur gratuit sur votre ordinateur, hors ligne et sans limite, ou via l’API vocale de SuperClonica si vous préférez ne pas faire tourner le modèle vous-même.

Ce qu’il vous faut

Créez une application dans le Discord Developer Portal, ajoutez un bot, activez l’intent Message Content sur la page Bot et invitez le bot avec les permissions Se connecter et Parler. Installez FFmpeg pour qu’il soit dans le PATH, puis les bibliothèques :

python -m pip install "discord.py[voice]" superclonica-cpu

L’extra voice de discord.py 2.7 apporte les bibliothèques de chiffrement dont la voix Discord a besoin. Le moteur SuperClonica télécharge le modèle Supertonic 3, environ 400 Mo, au premier lancement, puis fonctionne hors ligne sur un processeur ordinaire.

Le bot avec le moteur gratuit

Définissez DISCORD_TOKEN et TTS_CHANNEL_ID, l’ID du salon textuel à lire, et lancez le fichier. Quand une personne présente dans un salon vocal écrit dans ce salon textuel, le bot rejoint son salon vocal et lit le message avec la voix choisie ; les phrases attendent dans une file et ne se chevauchent jamais.

import asyncio, os, re, tempfile
import discord
from superclonica import Superclonica

TOKEN = os.environ["DISCORD_TOKEN"]
TEXT_CHANNEL = int(os.environ["TTS_CHANNEL_ID"])  # the text channel the bot reads
VOICE = "announcer.json"  # a voice file from the catalog, or M1..M5, F1..F5
LINK = re.compile(r"https?://\S+")

intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
tts = Superclonica()
queue = asyncio.Queue()

def voice_line(text):
    audio, _ = tts.synthesize(text, voice=VOICE, lang="en")
    path = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name
    tts.save(path, audio)
    return path

async def speaker():
    while True:
        voice_client, text = await queue.get()
        path = await asyncio.to_thread(voice_line, text)
        done = asyncio.Event()
        voice_client.play(discord.FFmpegPCMAudio(path), after=lambda error: client.loop.call_soon_threadsafe(done.set))
        await done.wait()
        os.remove(path)

@client.event
async def on_ready():
    client.loop.create_task(speaker())

@client.event
async def on_message(message):
    if message.author.bot or message.channel.id != TEXT_CHANNEL or not message.author.voice:
        return
    voice_client = message.guild.voice_client or await message.author.voice.channel.connect()
    text = LINK.sub("link", message.clean_content)[:300]
    await queue.put((voice_client, f"{message.author.display_name} says: {text}"))

client.run(TOKEN)

VOICE peut être l’une des voix intégrées M1 à M5 et F1 à F5, ou un fichier de voix du catalogue SuperClonica : les voix gratuites se téléchargent sans compte, les payantes après achat. La synthèse se fait dans un fil séparé, le bot reste donc réactif pendant qu’une phrase est lue.

Le même bot via l’API

Si le bot tourne sur un petit serveur sans place pour le modèle, faites lire les phrases par l’API vocale de SuperClonica : remplacez voice_line par cette fonction et définissez SUPERCLONICA_KEY. Toute voix du catalogue fonctionne avec l’ID de sa page, sans l’acheter.

import os, tempfile
import requests

API = "https://superclonica.com/api/v1/speech"
KEY = os.environ["SUPERCLONICA_KEY"]
VOICE_ID = "49cd4d119695528694fac0a1b9fb58a8"  # any catalog voice: the ID on its page

def voice_line(text):
    answer = requests.post(API, headers={"Authorization": "Bearer " + KEY}, timeout=120,
                           json={"text": text, "voice": VOICE_ID, "language": "en", "format": "mp3"})
    answer.raise_for_status()
    path = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False).name
    with open(path, "wb") as file:
        file.write(answer.content)
    return path

Une clé gratuite donne une minute d’audio par jour, de quoi tester ; Débutant, à 5 $ par mois, donne jusqu’à 3 minutes de parole par fenêtre de 10 minutes, largement assez pour un serveur animé.

Pour que ce soit agréable

Ne lisez qu’un salon, coupez les longs messages, dites « lien » au lieu d’une adresse web et laissez les modérateurs déplacer ou expulser le bot. Le moteur lit comme des mots l’argot de chat courant en français, comme stp ou mdr, et dans d’autres langues. Les voix de personnages du catalogue sont faites par des fans et non approuvées par les ayants droit : utilisez-les pour vous amuser sur votre serveur, jamais pour vous faire passer pour quelqu’un.