Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
28
Help us understand the problem. What is going on with this article?
@coolwind0202

Discord.py×Herokuで音声ファイルを流した

HerokuでDiscord.py製のDiscordBOTで音声ファイルを流したので、その流れを紹介します。
(操作に関して、Heroku CLIは使用していません)

もしHerokuでBotを稼働するのが初めて、という場合には以下の記事が親切です。
Discord Bot 最速チュートリアル【Python&Heroku&GitHub】

この記事が対象としているのは、既にHerokuでのBot稼働を一通り完了させた方になります。
あくまでも音声部分についての概説とします。

(投稿時点のDiscord.pyのバージョン:1.2.5

Discord.pyの音声サポートを受ける

変更するファイル: requirements.txt

音声ファイルを流すにはまず、Discord.pyの音声サポート付きパッケージをインストールする必要があります。

requirements.txt
discord.py[voice]

既に行っていれば、もちろんこの変更は必要ありません。

FFmpegのインストール

Heroku CLIの場合は以下の記事が参考になると思います。
Herokuでffmpegを使えるようにする

変更するページ: HerokuのSettingsタブ
https://dashboard.heroku.com/apps/[アプリ名]/settings

image.png

Build PacksAdd buildpackをクリック。

image.png

このような画面が出現します。
Enter Buildpack URLとあるので、その下のe.g. heroku...と書いてあるテキストボックスに

https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git

と入力して、Save changes

Buildpacksの表示が以下のようになったら成功です。
image.png

同じく、

https://github.com/Crazycatz00/heroku-buildpack-libopus.git

と入力してlibopusという音声コーデックのビルドパックを追加します。

Save changesしたら、今度はPythonのビルドパックを追加します。

もう一度Add buildpackをクリックし、今度はOr select from our officially supported buildpacksの下にある「Python」を選択してSave changesします。

音声を流す

上で基本となる準備は完了しました。
音声を流す処理を書きます。

なお、この記事では簡便化のためdiscord.ext.commands.commandでコマンドを作成しています。
変数名など調整すればon_messageでも活用できるとは思いますが・・・


注意すべき点として、音声を流すには先ほどビルドパックを追加した「libopus」など規定の音声コーデックをロードしなければなりません。
手順通りに操作していれば、以下のようにlibopusをロードできます。

if not discord.opus.is_loaded(): 
    #もし未ロードだったら
    discord.opus.load_opus("heroku-buildpack-libopus")

入室

botをボイスチャンネルに接続させるには、await discord.VoiceChannel.connect()を使用します(公式ドキュメント)。

この記事では入室コマンドの送信者がいるチャンネルにbotを接続させるようにしてみます。

メンバーのいるボイスチャンネルはdiscord.Member.voice.channelです(公式ドキュメント)。
discord.Member.voiceはVoiceStateという、メンバーの音声状態を示すオブジェクトを示します。
そして、VoiceStateオブジェクトは、channel属性にボイスチャンネルを保持しています。

@bot.command() 
async def join(ctx):
    """Botをボイスチャンネルに入室させます。"""
    voice_state = ctx.author.voice

    if (not voice_state) or (not voice_state.channel):
        #もし送信者がどこのチャンネルにも入っていないなら
        await ctx.send("先にボイスチャンネルに入っている必要があります。")
        return

    channel = voice_state.channel #送信者のチャンネル

    await channel.connect() #VoiceChannel.connect()を使用

退出

退出の場合、ボイスチャンネルにdisconnectなどといったメソッドは用意されておらず、await discord.VoiceClient.disconnect()を呼び出す必要があります(公式ドキュメント)。

VoiceClientオブジェクトはボイスチャンネルにconnect()した戻り値や、discord.Guildから取得することができます(公式ドキュメント)。

コードに関しては後ほど。

再生

再生するには、discord.VoiceClient.play()を呼び出します(公式ドキュメント)。
playの引数にはdiscord.AudioSourceオブジェクトを渡すのですが(参考)、discord.AudioSourceクラスはそれを継承するクラスのインスタンスが可能とする動作を定義しているだけなので、実際にはdiscord.FFmpegPCMAudioオブジェクトなどを使用します(公式ドキュメント)。

FFmpegといえば、手順通りに操作していればビルドパックを追加していますね。
以下はdiscord.FFmpegPCMAudioを使って、"hoge.mp3"という名前の音声を再生する例です。

source = discord.FFmpegPCMAudio("hoge.mp3")
voice_client.play(source)

実装

import discord
from discord.ext import commands

import os

bot = commands.Bot(command_prefix="$")
token = os.environ['DISCORD_BOT_TOKEN']

if not discord.opus.is_loaded():
    discord.opus.load_opus("heroku-buildpack-libopus")

@bot.command(aliases=["connect","summon"]) #connectやsummonでも呼び出せる
async def join(ctx):
    """Botをボイスチャンネルに入室させます。"""
    voice_state = ctx.author.voice

    if (not voice_state) or (not voice_state.channel):
        await ctx.send("先にボイスチャンネルに入っている必要があります。")
        return

    channel = voice_state.channel

    await channel.connect()
    print("connected to:",channel.name)


@bot.command(aliases=["disconnect","bye"])
async def leave(ctx):
    """Botをボイスチャンネルから切断します。"""
    voice_client = ctx.message.guild.voice_client

    if not voice_client:
        await ctx.send("Botはこのサーバーのボイスチャンネルに参加していません。")
        return

    await voice_client.disconnect()
    await ctx.send("ボイスチャンネルから切断しました。")


@bot.command()
async def play(ctx):
    """指定された音声ファイルを流します。"""
    voice_client = ctx.message.guild.voice_client

    if not voice_client:
        await ctx.send("Botはこのサーバーのボイスチャンネルに参加していません。")
        return

    if not ctx.message.attachments:
        await ctx.send("ファイルが添付されていません。")
        return

    await ctx.message.attachments[0].save("tmp.mp3")

    ffmpeg_audio_source = discord.FFmpegPCMAudio("tmp.mp3")
    voice_client.play(ffmpeg_audio_source)

    await ctx.send("再生しました。")

bot.run(token)

この記事で書いたコードだけでは低機能で、実用的ではないかもしれません。
またこの記事の内容は一例に過ぎません。
ですが、開発の参考になれば幸いです。

28
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
28
Help us understand the problem. What is going on with this article?