LoginSignup
2
3

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-12-29

はじめに

Discord Bot作るならPython一択だよなぁ↑という思考の元(?)Discord Botを開発されている皆さん、録音機能を実装したくなったことありませんか?

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

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

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

実行環境

  • OS: Ubuntu Server 22.04
  • Python: v3.10.8 (Anaconda)
  • pycord: v2.3.2
  • ffmpeg: v4.4.2

Botの制作

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

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

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

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

Botのベースを作る

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

bot.py
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の完成です!お疲れさまでした。
最後にコードの全体像を貼っておきます。

余談

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

全体像

bot.py
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を作ってみました。
ライブラリ側で対応しているので非常に扱いは簡単だと思います。みなさんも是非つくってみてください。
不具合等をありましたら、コメントください。

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

今回は以上です。

参考文献

2
3
0

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
2
3