📝

Discordライブラリのpycordでボイスチャンネルの録音

2022/11/30に公開

本稿は9月にQittaに投稿したものと同様のものです。

DiscordAPIには音声を録音する機能があります。
ですがDiscord.pyではプライバシーの面で実装されておらず、Discord.jsは頻繁に変更が施されるため、簡単に録音できる手段は確立されていませんでした。

そんな中で22年3月、Discord.pyの派生ライブラリ、pycordに録音機能が実装されました。
日本語での解説が見当たらなかったので、ここで説明します。

環境

python 3.8.6 64bit(ここに関しては3.10とかでも問題なし)

pipインストール
pip install discord
pip install git+https://github.com/Pycord-Development/pycord
pip install pydub

必ずDiscord.pyをインストールした後にpycordをインストールしましょう!!
また、音声を使用するのでffmpegをインストールすることを忘れずに。

サンプルコード

録音したファイルをDiscord上にアップロードする場合。

import discord

intents = discord.Intents().all()
bot = discord.Bot(intents=intents)
Token=""

@bot.command()
async def start_record(ctx:discord.ApplicationContext):
    # コマンドを使用したユーザーのボイスチャンネルに接続
    try:
       vc = await ctx.author.voice.channel.connect()
       await ctx.respond("録音開始...")
    except AttributeError:
       await ctx.respond("ボイスチャンネルに入ってください。")
       return
        
    # 録音開始。mp3で帰ってくる。wavだとなぜか壊れる。
    ctx.voice_client.start_recording(discord.sinks.MP3Sink(), finished_callback, ctx)


async def finished_callback(sink:discord.sinks.MP3Sink, ctx:discord.ApplicationContext):
    # 録音したユーザーの音声を取り出す
    recorded_users = [
        f"<@{user_id}>"
        for user_id, audio in sink.audio_data.items()
    ]
    # discordにファイル形式で送信。拡張子はmp3。
    files = [discord.File(audio.file, f"{user_id}.{sink.encoding}") for user_id, audio in sink.audio_data.items()]
    await ctx.channel.send(f"録音終了! 音声ファイルはこちら! {', '.join(recorded_users)}.", files=files) 

@bot.command()
async def stop_recording(ctx:discord.ApplicationContext):
    # 録音停止
    ctx.voice_client.stop_recording()
    await ctx.respond("録音終了!")
    await ctx.voice_client.disconnect()


bot.run(Token)

ローカルに保存する場合。

import discord
from pydub import AudioSegment

intents = discord.Intents().all()
bot = discord.Bot(intents=intents)

Token=""

@bot.slash_command()
async def start_record(ctx:discord.ApplicationContext):

        # コマンドを使用したユーザーのボイスチャンネルに接続
        try:
            vc = await ctx.author.voice.channel.connect()
            await ctx.respond("録音開始...")
        except AttributeError:
            await ctx.respond("ボイスチャンネルに入ってください。")
            return
        
        # 録音開始。mp3で帰ってくる。wavだとなぜか壊れる。
        ctx.voice_client.start_recording(discord.sinks.MP3Sink(), finished_callback, ctx)

@bot.slash_command()
async def stop_recording(ctx:discord.ApplicationContext):
        # 録音停止
        ctx.voice_client.stop_recording() 
        await ctx.respond("録音終了!")
        await ctx.voice_client.disconnect()

# 録音終了時に呼び出される関数
async def finished_callback(sink:discord.sinks.MP3Sink, ctx:discord.ApplicationContext):
    # 録音したユーザーの音声を取り出す
    for user_id, audio in sink.audio_data.items():
        # mp3ファイルとして書き込み。その後wavファイルに変換。
        song = AudioSegment.from_file(audio.file, format="mp3")
        song.export(f"./{user_id}.wav", format='wav')


bot.run(Token)

ポイントとなる点はこちら。

# 録音開始。mp3で帰ってくる。wavだとなぜか壊れる。
ctx.voice_client.start_recording(discord.sinks.MP3Sink(), finished_callback, ctx)

これで録音が開始されます。
discord.sinks.MP3Sinkでmp3形式で録音することを指定しています。
これをdiscord.sinks.WaveSinkにするとwavファイル、discord.sinks.M4ASinkにするとm4aファイルとして録音を開始します。

また、finished_callbackは録音終了時に実行される関数でsink(録音データ)とctx(スラッシュコマンドのデータ)を引数として持ってきます。

sinkの中にはユーザーid毎に録音内容のバイナリデータが入っていて、forで一つ一つファイルに変換しています。

その他

録音開始のコマンドを打っても、何か発言しなければ録音は開始されません。

例えばa,b,cの3人がボイスチャンネルにいたとします。
録音を開始し、aだけが発言した後終了するとaの録音データのみが返却されます。

ローカルに音声を保存する場合、mp3は問題ないのですが、なぜかwavで保存するとファイルが壊れます

原因についてよくわからなかったのでサンプルコードではmp3にした後wavに変換しています。

(2022/09/26 追記)
また、ステージチャンネルでは録音できないようです。

最後に

初投稿なので色々雑な部分が多いですが、お役に立ったら幸いです。

参考

stackoverflow-discord-receive-audio

Discussion