РУКОВОДСТВО

TTS-бот для Discord на Python: читает чат вслух в голосовом канале

Встроенный /tts в Discord читает сообщение на устройстве каждого слушателя его системным голосом и никогда не говорит в голосовом канале. Чтобы друзья без микрофона писали, а все в звонке их слышали, нужен бот, который заходит в голосовой канал и проигрывает сообщения там.

В этом руководстве мы соберём такого бота на Python с discord.py. Он читает выбранный текстовый канал любым голосом SuperClonica: бесплатным движком на вашем компьютере, офлайн и без лимитов, или через API озвучки SuperClonica, если не хочется запускать модель самому.

Что понадобится

Создайте приложение в Discord Developer Portal, добавьте бота, включите Message Content intent на странице Bot и пригласите бота с правами Connect и Speak. Установите FFmpeg так, чтобы он был в PATH, затем библиотеки:

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

Дополнение voice в discord.py 2.7 ставит библиотеки шифрования, которые нужны голосу Discord. Движок SuperClonica при первом запуске скачивает модель Supertonic 3, около 400 МБ, а дальше работает офлайн на обычном процессоре.

Бот на бесплатном движке

Задайте DISCORD_TOKEN и TTS_CHANNEL_ID — ID текстового канала для чтения — и запустите файл. Когда человек из голосового канала пишет в этот текстовый канал, бот заходит к нему в голосовой канал и читает сообщение выбранным голосом; реплики ждут в очереди и не перебивают друг друга.

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 — один из встроенных голосов M1–M5 и F1–F5 или файл голоса из каталога SuperClonica: бесплатные голоса скачиваются без регистрации, платные — после покупки. Синтез идёт в отдельном потоке, поэтому бот не зависает, пока озвучивается реплика.

Тот же бот через API

Если бот живёт на маленьком сервере, где нет места для модели, озвучивайте реплики через API озвучки SuperClonica: замените voice_line этой функцией и задайте SUPERCLONICA_KEY. Любой голос каталога работает по ID со своей страницы, без покупки.

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

Бесплатный ключ даёт минуту звука в день — хватит для проверки; «Старт» за $5 в месяц даёт до 3 минут речи в каждом 10-минутном окне — с запасом для оживлённого сервера.

Чтобы было приятно

Читайте только один канал, обрезайте длинные сообщения, вместо адреса сайта говорите «ссылка» и дайте модераторам перемещать или выгонять бота. Движок читает распространённый чатовый сленг словами, например «спс» и «хз» по-русски, idk и pls по-английски. Голоса персонажей в каталоге сделаны фанатами и не одобрены правообладателями: используйте их для веселья на сервере и никогда — чтобы выдавать себя за кого-то.