5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

discord.js+VOICEAVOXでdiscordのテキスト読み上げbot作成

Last updated at Posted at 2022-04-24

経緯

discord.py + openjtalkでdiscordのテキスト読み上げbot環境を構築していましたが、
discord.pyの開発終了受けて別の読み上げbotを作ろうかなと考えていました。

※openjtalkで間延びする問題も気になっていましたが、パッチはでているみたいです。
https://www.techscore.com/blog/2015/06/29/open-jtalk-japanese-text/
https://gist.github.com/ikegami-yukino/51cb4feb0739412be2e1

環境

Docker構築

VOICEVOXはエディタ/エンジン/コアの3つのモジュールに分かれていますが、今回はエンジンを使用します。

※私はDockerイメージ使用しました。CPU版とGPU版ありますが、CPU版使っています。

docker-compose.xml
version: "3"
services:
  voicevox-engine:
    container_name: voicevox-engine
    image: voicevox/voicevox_engine:cpu-ubuntu20.04-latest
    restart: always
    ports:
      - "50021:50021"

以下コマンドで起動します。

docker-compose build
docker-compose up

起動成功したら、
http://localhost:50021/docs
にアクセスできるか確認。
image.png

アプリ側はこんな感じ。

docker-compose.xml
services:
  node-web-app:
    build: .
    restart: always
    ports:
      - "8084:8084"
    depends_on:
      - "voicevox-engine"

Dockerfileでdiscord.jsとffmpegのインストールをします。

FROM node:17

WORKDIR /usr/src/app
COPY package*.json ./

RUN npm install
RUN npm install discord.js
RUN npm install @discordjs/voice @discordjs/opus tweetnacl
RUN npm install axios --save
RUN apt-get update
RUN apt-get -y install ffmpeg

COPY . .
EXPOSE 50021
CMD [ "node", "index.js" ]

必要そうな箇所のみ抜粋。
DISCORD_TOKENは事前に取得して.envに設定しておくこと。
事前にDiscordサーバーへの招待をしておくこと。

index.js
const { getVoiceConnection, createAudioResource, StreamType, createAudioPlayer, NoSubscriberBehavior, generateDependencyReport } = require("@discordjs/voice");

const { Client, Intents } = require('discord.js')
const client = new Client({
    intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_VOICE_STATES, Intents.FLAGS.GUILD_MESSAGES]
  });
 
const dotenv = require('dotenv');
dotenv.config()

client.on('ready', async () => {
  console.log("Ready!");
})

client.login(process.env.DISCORD_TOKEN)

起動していた場合、Ctrl+Cで終了。再度コマンドで起動します。

docker-compose build
docker-compose up

コンソールにReady!が表示されればOK!!
スクリーンショット 2022-03-30 010603.png

Discordでテキスト読み上げ

discord.jsの実装です

  • Voiceチャネルへの接続
join.js
const { joinVoiceChannel } = require("@discordjs/voice");
module.exports = {
    data: {
        name: "join",
        description: "Join voice channel!"
    },
    async execute(interaction) {
        const guild = interaction.guild;
        const member = await guild.members.fetch(interaction.member.id);
        const memberVC = member.voice.channel;
        if (!memberVC) {
            console.log("接続先のVCが見つかりません。")
        }
        if (!memberVC.joinable) {
            console.log("VCに接続できません。")
        }
        if (!memberVC.speakable) {
            console.log("VCで音声を再生する権限がありません。")
        }
        const connection = joinVoiceChannel({
            guildId: guild.id,
            channelId: memberVC.id,
            adapterCreator: guild.voiceAdapterCreator,
            selfMute: false,
            selfDeaf: true,
        });
        await interaction.reply('Join VC');
        return;
    },
}
  • Voiceチャネルからの切断
bye.js

const { getVoiceConnection } = require("@discordjs/voice");
module.exports = {
    data: {
        name: "bye",
        description: "Disconnect voice channel!"
    },
    async execute(interaction) {
        const guild = interaction.guild;
        const member = await guild.members.fetch(interaction.member.id);
        const memberVC = member.voice.channel;
        if (!memberVC) {
            console.log("接続先のVCが見つかりません。")
        }
        if (!memberVC.joinable) {
            console.log("VCに接続できません。")
        }
        if (!memberVC.speakable) {
            console.log("VCで音声を再生する権限がありません。")
        }

        const connection = getVoiceConnection(memberVC.guild.id);
        connection.destroy();
        await interaction.reply('Bye VC');
    },
}
  • テキスト読み上げ
    messageCreateでテキストを受信します。
index.js
client.on('messageCreate', async (msg) => {
  if (msg.author.bot) {
      return;
  }
  const filepath = "sounds/" + msg.author.id + ".wav"
  var voice = voiceMap.get(msg.author.id);
  if (!voice) {
      voice = default_voice;
  }

  var message = convertMessage(msg.cleanContent);
  await generateAudio(message, filepath, voice);
  await play(msg, filepath)
});

generateAudioでVOICEVOXのAPI叩いて、wavファイルへ変換します。

index.js
async function generateAudio(text, filepath, voice) {
  /* まずtextを渡してsynthesis宛のパラメータを生成する、textはURLに付けるのでencodeURIで変換しておく。*/
  const audio_query = await rpc.post('audio_query?text=' + encodeURI(text) + '&speaker=' + voice, {
      headers: { 'accept': 'application/json' },
  })

  //audio_queryで受け取った結果がaudio_query.dataに入っている。
  //このデータをメソッド:synthesisに渡すことで音声データを作ってもらえる
  //audio_query.dataはObjectで、synthesisに送る為にはstringで送る必要があるのでJSON.stringifyでstringに変換する
  const synthesis = await rpc.post("synthesis?speaker=" + voice, JSON.stringify(audio_query.data), {
      responseType: 'arraybuffer',
      headers: {
          "accept": "audio/wav",
          "Content-Type": "application/json"
      }
  });

  //受け取った後、Bufferに変換して書き出す
  fs.writeFileSync(filepath, new Buffer.from(synthesis.data), 'binary');
}

wavファイルを再生します。

index.js
async function play(interaction, filepath) {
  const connection = await getConnection(interaction);

  if (!connection) {
    console.log("VCに接続していません。")
    return;
  }
  const resource = createAudioResource(filepath, { inputType: StreamType.Arbitrary });
  const player = createAudioPlayer({
    behaviors: {
      noSubscriber: NoSubscriberBehavior.Pause,
    },
  });
  player.play(resource);

  connection.subscribe(player);
}

いろいろ割愛していますが、ソースコードをGithubにあげているので確認してみてください!
https://github.com/gettingsignals/discord-voicebox

その他

Discordのbot権限

スクリーンショット 2022-03-30 010601.png

スクリーンショット 2022-03-30 010602.png

5
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?