ガイド

PythonでつくるDiscordのTTSボット:ボイスチャンネルでチャットを読み上げる

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エンジンは初回にSupertonic 3のモデル(約400 MB)をダウンロードし、その後は普通のCPUでオフラインで動きます。

無料エンジンを使うボット

DISCORD_TOKENと、読み上げるテキストチャンネルのIDであるTTS_CHANNEL_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日1分で、試すには十分です。月5ドルのスタートなら10分ごとの枠で最大3分の音声を使え、にぎやかなサーバーでも余裕があります。

快適に使うために

読み上げるのは1チャンネルだけにし、長いメッセージは切り、URLの代わりに「リンク」と読ませ、モデレーターがボットを移動・退出させられるようにしましょう。エンジンは英語のidkやplsなど、いくつかの言語のよくあるチャットのスラングを単語として読みます。カタログのキャラクターの声はファンがつくったもので、権利者は承認していません。サーバーで楽しむために使い、なりすましには決して使わないでください。