前提・実現したいこと
Musicクラスのplay()、もしくはloop()を実行した際にYTDLSource.from_url()以降を処理したい。
発生している問題・エラーメッセージ
Musicクラスのplay()、もしくはloop()を実行するとYTDLSource.from_url()を実行する行で処理が終了し、次以降を処理してくれない。
該当のソースコード
Python
import asyncio import discord from discord.ext import commands import traceback import youtube_dl import os import urllib.request import re # Suppress noise about console usage from errors youtube_dl.utils.bug_reports_message = lambda: '' prefix = os.getenv('DISCORD_BOT_PREFIX', default='>') lang = os.getenv('DISCORD_BOT_LANG', default='ja') token = 'token' ytdl_format_options = { 'format': 'bestaudio/best', 'outtmpl': '%(extractor)s-%(id)s-%(title)s.mp3', 'restrictfilenames': True, 'noplaylist': True, 'nocheckcertificate': True, 'ignoreerrors': False, 'logtostderr': False, 'quiet': True, 'no_warnings': True, 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192', }], 'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes: True } ffmpeg_options = { 'options': '-vn' } ytdl = youtube_dl.YoutubeDL(ytdl_format_options) class YTDLSource(discord.PCMVolumeTransformer): def __init__(self, source, *, data, volume=0.2): super().__init__(source, volume) self.data = data self.title = data.get('title') self.url = data.get('url') @classmethod async def from_url(cls, url, *, loop=None): loop = loop or asyncio.get_event_loop() data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=False)) if 'entries' in data: # take first item from a playlist data = data['entries'][0] filename = data['url'] ytdl.prepare_filename(data) return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) class AudioQueue(asyncio.Queue): def __init__(self): super().__init__(100) def __getitem__(self, idx): return self._queue[idx] def to_list(self): return list(self._queue) def remove(self, idx): del self._queue[idx] def reset(self): self._queue.clear() class AudioStatus: def __init__(self, ctx: commands.Context, vc: discord.VoiceClient): self.vc: discord.VoiceClient = vc self.ctx: commands.Context = ctx self.queue = AudioQueue() self.playing = asyncio.Event() self.loopMode = False asyncio.create_task(self.playing_task()) async def add_audio(self, title, path): await self.queue.put([title, path]) def get_list(self): return self.queue.to_list() def remove(self, idx): self.queue.remove(idx) def reset(self): self.queue.reset() async def playing_task(self): while True: self.playing.clear() if self.loopMode: title, player = self.queue[0] else: title, player = await self.queue.get() self.vc.play(player, after=self.play_next) if not self.loopMode: await self.ctx.send(f'{title}を再生します。') await self.playing.wait() def play_next(self, err=None): self.playing.set() async def leave(self): self.queue.reset() if self.vc: await self.vc.disconnect() self.vc = None @property def is_playing(self): return self.vc.is_playing() def resume(self): self.vc.stop() def pause(self): self.vc.stop() def stop(self): self.vc.stop() class Music(commands.Cog): def __init__(self, bot): self.bot = bot self.audio_statuses = "" # command to connect to voice channel @commands.command(aliases=["s"]) async def start(self, ctx): """Joins a voice channel""" if ctx.author.voice is None: await ctx.send('先にボイスチャンネルに接続してください…。') else: if ctx.guild.voice_client: if ctx.author.voice.channel == ctx.guild.voice_client.channel: await ctx.send('既に接続済みです…。') else: await ctx.voice_client.disconnect() await asyncio.sleep(0.5) vc = await ctx.author.voice.channel.connect() self.audio_statuses = AudioStatus(ctx, vc) await ctx.send('ボイスチャンネルを変更しました。') else: vc = await ctx.author.voice.channel.connect() self.audio_statuses = AudioStatus(ctx, vc) await ctx.send('ボイスチャンネルに接続しました。') # command to play sound from a youtube URL or add to queue @commands.command(aliases=["pl"]) async def play(self, ctx, *, entry): """Play music""" status = self.audio_statuses status.loopMode = False queue = status.get_list player = await Music.get_player(ctx, entry) if len(queue) != 0: await ctx.send(f'`{player.title}`を追加しました。') await status.add_audio(player.title, player) # command to loop play sound from a youtube URL @commands.command(aliases=["l"]) async def loop(self, ctx, *, entry): """Loop play music""" status = self.audio_statuses status.loopMode = True player = await Music.get_player(ctx, entry) await status.reset() await ctx.send(f'`{player.title}`を追加しました。') await status.add_audio(player.title, player) @classmethod async def get_player(self, ctx, entry): if entry.startswith('http') is True: url = entry else: search_keyword = entry.replace(" ", "+") regex = r'[^\x00-\x7F]' matchedList = re.findall(regex, search_keyword) for m in matchedList: search_keyword = search_keyword.replace(m, urllib.parse.quote_plus(m, encoding="utf-8")) html = urllib.request.urlopen("https://www.youtube.com/results?search_query=" + search_keyword) video_ids = re.findall(r"watch?v=(\S{11})", html.read().decode()) url = "https://www.youtube.com/watch?v=" + video_ids[0] async with ctx.typing(): return await YTDLSource.from_url(url, loop=self.bot.loop) bot = commands.Bot(command_prefix=prefix, description='Relatively simple music bot example') @bot.event async def on_ready(): print('Logged in as {0} ({0.id})'.format(bot.user)) print('------') await bot.change_presence(activity=discord.Game(name=f'{prefix}hでコマンド一覧', type=1)) @bot.event async def on_command_error(ctx, error): if isinstance(error, commands.errors.CommandNotFound): await ctx.send('存在しないコマンドです…。') elif isinstance(error, commands.errors.MissingRequiredArgument): await ctx.send('引数が足りません…。') elif isinstance(error, commands.CommandError): pass else: orig_error = getattr(error, 'original', error) error_msg = ''.join(traceback.TracebackException.from_exception(orig_error).format()) await ctx.send("不明なエラーが発生…。") await bot.get_channel(チャンネルID).send(error_msg) print(error_msg) bot.add_cog(Music(bot)) bot.run(token)
試したこと
①get_player()を削除して該当処理をplay()の中で処理してみた。→YTDLSource.from_url()の行までは進んだが、そこで処理が終了。
②get_player()の次の行にprint()を記述→実行されたがprint()以降は実行されず。
③デバッグしてみた。→YTDLSource.from_url()の行までは進んだが、そこでステップインを行うと処理が終了。
④YTDLSource.from_url()をコメントアウトし、別の戻り値を設定。→同じ箇所で処理が終了する。
⑤YTDLSource.from_url()だけをplay()の中で実行→③と同じ
⑥スタックトレースを確認→出力なし
補足情報(FW/ツールのバージョンなど)
Python 3.9.5
youtube-dl 2021/12/17
ffmpeg 4.4
まだ回答がついていません
会員登録して回答してみよう