はじめに
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のベースを作る
まずは、ベースとなるコードを書いていきます。とりあえずはボイスチャンネルに参加・退出できるようにしておきます。
ここはメインではないので、コードの説明は省きます。
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を作ってみました。
ライブラリ側で対応しているので非常に扱いは簡単だと思います。みなさんも是非つくってみてください。
不具合等をありましたら、コメントください。
最後に、よろしければ私が作ったSmartAid Botを導入してみてください。録音機能を備えています。詳細はこちらから!
今回は以上です。