指南

用 Python 制作 Discord TTS 机器人:在语音频道朗读聊天

请注意:Supertonic 3 目前不支持中文,本文中的机器人可朗读其支持的 31 种语言。Discord 自带的 /tts 会在每位听众的设备上用系统声音朗读消息,而且从不在语音频道里说话。想让没有麦克风的朋友打字、通话中的所有人都能听到,就需要一个加入语音频道并在那里播放消息的机器人。

本指南用 discord.py 在 Python 中制作这样一个机器人。它用任意 SuperClonica 声音朗读指定的文字频道:在自己电脑上用免费引擎离线运行、没有限制,或者如果不想自己运行模型,就通过 SuperClonica 语音合成 API。

准备工作

在 Discord Developer Portal 创建应用并添加机器人,在 Bot 页面开启 Message Content intent,并以“连接”和“说话”权限邀请机器人。安装 FFmpeg 并确保它在 PATH 中,然后安装库:

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

discord.py 2.7 的 voice 扩展会带上 Discord 语音所需的加密库。SuperClonica 引擎首次运行时会下载约 400 MB 的 Supertonic 3 模型,之后在普通 CPU 上离线运行。

使用免费引擎的机器人

设置 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 实现同一个机器人

如果机器人运行在放不下模型的小服务器上,就用 SuperClonica 语音合成 API 朗读:把 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

免费密钥每天 1 分钟音频,足够测试;每月 5 美元的入门套餐在每个 10 分钟窗口内最多 3 分钟语音,对活跃的服务器也绰绰有余。

让体验更好

只朗读一个频道,截断过长的消息,把网址读成“链接”,并让管理员可以移动或踢出机器人。引擎会把几种语言中常见的聊天俚语(例如英语的 idk、pls)读成完整的词。目录中的角色声音由粉丝制作,未经版权方认可:请在服务器里用于娱乐,绝不要用来冒充他人。