Skip to content

ボイチャ録音できる!DiscordBotの開発

Posted on:December 29, 2022 at 04:00 PM

はじめに

今回はDiscord Botにボイスチャンネルを録音する機能を実装する方法を説明します。

私が調べた限りでは、録音機能の実装方法を紹介をしている記事は少なく、数少ない検索結果の中にあったとある記事を読んでみましたが、DiscordのAPIを直接操作しているようで、難易度が高いように感じました。 しかし、Pycordというライブラリを使うことで、DiscordのAPIを直接操作しなくても簡単に録音機能を実装できちゃいます!

そんなわけで、今回はボイチャの録音ができるDiscord Botを作ってみたいと思います。 それでは早速作っていきましょう!

この記事はある程度Pythonが扱えることを前提とした表記・表現等があります。予めご了承ください。

この記事はQiitaのコピーです。内容は同じです。Qiita版はこちら

実行環境

Botの制作

実行環境にさらっと書きましたが、ffmpegが利用できることが前提条件としてあります。
Ubuntu等をご利用の方は、sudo apt install ffmpegと実行するだけで良いのですが、Windowsをご利用の方は公式サイトからダウンロード後に環境変数の設定を変更する必要がありますので、若干手間がかかります。 この辺は本記事では省きますので、詳しくはググるなりなんなりしてください。

ライブラリをインストール

使うライブラリはpycordです。PythonでDiscord Botを作ると言ったらdiscord.pyが有名ですが、このライブラリは録音の非対応っぽいので使いません。
ボイスチャンネルのサポートが必要なので、[voice]を追加しておきます。

$ pip install -U "py-cord[voice]"

Botのベースを作る

まずは、ベースとなるコードを書いていきます。とりあえずはボイスチャンネルに参加・退出できるようにしておきます。
ここはメインではないので、コードの説明は省きます。

import discord

bot = discord.Bot()

@bot.slash_command(description="ボイスチャンネルに参加します。")
async def join(
    ctx,
):
    if ctx.author.voice is None:
        embed = discord.Embed(title="エラー",description="あなたがボイスチャンネルに参加していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    try:
        await ctx.author.voice.channel.connect()
    except:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続できません。\nボイスチャンネルの権限を確認してください。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    embed = discord.Embed(title="成功",description="ボイスチャンネルに接続しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

@bot.slash_command(description="ボイスチャンネルから切断します。")
async def leave(
    ctx,
):
    if ctx.guild.voice_client is None:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    await ctx.guild.voice_client.disconnect()
    embed = discord.Embed(title="成功",description="ボイスチャンネルから切断しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

bot.run("Bot-Token")

録音機能の実装

それでは録音機能を先程のコードに実装していきます。

Audio_Formatという変数に辞書型で定義しているのは、Discord.Sinkというオブジェクトです。このオブジェクトは各録音ごとに再生成する必要があり、自分はここでハマりました。

finished_callbackという関数は録音を停止したときに呼び出されます。呼び出す関数は、start_recordingの部分で指定しています。pycordの仕様上、録音はユーザーごとに行われますので、全ユーザーの録音ファイルをアップロードするという処理をfor文で行っています。

この方法ではDiscordの仕様上、ファイルサイズが大きくなるとアップロードに失敗してしまいます。
そのため、実際に運用する際は一度ローカル上に書き出しを行い、ダウンロード用のURLを発行するなりしたほうが良いと思います。(私のBotではpydubを使うことで書き出しをしています。)

# 先程のコードがあるという前提で追加します

@bot.slash_command(description="録音を開始します。")
async def record(
    ctx,
    format: Option(str, '録音フォーマットを選んでください。', choices=["MP3", "WAV", "PCM", "OGG", "MKA", "KMV", "MP4", "M4A"]),
):
    format = str(f"{format}")
    Audio_Format = {
        "MP3": discord.sinks.MP3Sink(),
        "WAV": discord.sinks.WaveSink(),
        "PCM": discord.sinks.PCMSink(),
        "OGG": discord.sinks.OGGSink(),
        "MKA": discord.sinks.MKASink(),
        "MKV": discord.sinks.MKVSink(),
        "MP4": discord.sinks.MP4Sink(),
        "M4A": discord.sinks.M4ASink()
    }
    format_sink = Audio_Format[format]
    if ctx.guild.voice_client is None:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    ctx.voice_client.start_recording(format_sink, finished_callback, ctx)
    embed = discord.Embed(title="成功",description="ボイスチャンネルの録音を開始しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

@bot.slash_command(description="録音を停止します。")
async def record_stop(
    ctx,
):
    if ctx.guild.voice_client is None:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    ctx.voice_client.stop_recording()
    embed = discord.Embed(title="成功",description="ボイスチャンネルの録音を停止しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

async def finished_callback(sink, ctx, *args):
    recorded_users = [f"<@{user_id}>" for user_id, audio in sink.audio_data.items()]
    files = [discord.File(audio.file, f"{user_id}.{sink.encoding}") for user_id, audio in sink.audio_data.items()]
    await ctx.respond(f"録音が完了しました!\n録音されたユーザー: {', '.join(recorded_users)}.", files=files)

なんとこれだけで録音Botの完成です!お疲れさまでした。 最後にコードの全体像を貼っておきます。

余談

録音中、データはどこに書き込まれているのかということですが、メモリーです。私が試した限りでは、録音中はそこそこのメモリー消費をするので、試す際はメモリーには余裕をもって実行してください。

全体像

import discord

bot = discord.Bot()

@bot.slash_command(description="ボイスチャンネルに参加します。")
async def join(
    ctx,
):
    if ctx.author.voice is None:
        embed = discord.Embed(title="エラー",description="あなたがボイスチャンネルに参加していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    try:
        await ctx.author.voice.channel.connect()
    except:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続できません。\nボイスチャンネルの権限を確認してください。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    embed = discord.Embed(title="成功",description="ボイスチャンネルに接続しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

@bot.slash_command(description="ボイスチャンネルから切断します。")
async def leave(
    ctx,
):
    if ctx.guild.voice_client is None:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    await ctx.guild.voice_client.disconnect()
    embed = discord.Embed(title="成功",description="ボイスチャンネルから切断しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

@bot.slash_command(description="録音を開始します。")
async def record(
    ctx,
    format: Option(str, '録音フォーマットを選んでください。', choices=["MP3", "WAV", "PCM", "OGG", "MKA", "KMV", "MP4", "M4A"]),
):
    format = str(f"{format}")
    Audio_Format = {
        "MP3": discord.sinks.MP3Sink(),
        "WAV": discord.sinks.WaveSink(),
        "PCM": discord.sinks.PCMSink(),
        "OGG": discord.sinks.OGGSink(),
        "MKA": discord.sinks.MKASink(),
        "MKV": discord.sinks.MKVSink(),
        "MP4": discord.sinks.MP4Sink(),
        "M4A": discord.sinks.M4ASink()
    }
    format_sink = Audio_Format[format]
    if ctx.guild.voice_client is None:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    ctx.voice_client.start_recording(format_sink, finished_callback, ctx)
    embed = discord.Embed(title="成功",description="ボイスチャンネルの録音を開始しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

@bot.slash_command(description="録音を停止します。")
async def record_stop(
    ctx,
):
    if ctx.guild.voice_client is None:
        embed = discord.Embed(title="エラー",description="ボイスチャンネルに接続していません。",color=discord.Colour.red())
        await ctx.respond(embed=embed)
        return
    ctx.voice_client.stop_recording()
    embed = discord.Embed(title="成功",description="ボイスチャンネルの録音を停止しました。",color=discord.Colour.green())
    await ctx.respond(embed=embed)

async def finished_callback(sink, ctx, *args):
    recorded_users = [f"<@{user_id}>" for user_id, audio in sink.audio_data.items()]
    files = [discord.File(audio.file, f"{user_id}.{sink.encoding}") for user_id, audio in sink.audio_data.items()]
    await ctx.respond(f"録音が完了しました!\n録音されたユーザー: {', '.join(recorded_users)}.", files=files)

bot.run("Bot-Token")

上記のコードはあくまで録音機能の実装方法の紹介の為に、爆速コーディングしたものです。エラー処理等をしていませんので、実際に運用される場合はその辺りも考慮した実装をしてください。

まとめ

今回はpycordを使ってボイチャ録音できるDiscordBotを作ってみました。 ライブラリ側で対応しているので非常に扱いは簡単だと思います。みなさんも是非つくってみてください。 不具合等をありましたら、Qiitaへコメントください。

最後に、よろしければ私が作ったSmartAid Botを導入してみてください。録音機能を備えています。詳細はこちらから!

今回は以上です。

参考文献