前提・実現したいこと
Musicクラスのplay()、もしくはloop()を実行した際にYTDLSource.from_url()以降を処理したい。
発生している問題・エラーメッセージ
Musicクラスのplay()、もしくはloop()を実行するとYTDLSource.from_url()を実行する行で処理が終了し、次以降を処理してくれない。
該当のソースコード
Python
1import asyncio 2 3import discord 4from discord.ext import commands 5import traceback 6import youtube_dl 7import os 8import urllib.request 9import re 10 11 12# Suppress noise about console usage from errors 13youtube_dl.utils.bug_reports_message = lambda: '' 14 15prefix = os.getenv('DISCORD_BOT_PREFIX', default='>') 16lang = os.getenv('DISCORD_BOT_LANG', default='ja') 17token = 'token' 18 19 20ytdl_format_options = { 21 'format': 'bestaudio/best', 22 'outtmpl': '%(extractor)s-%(id)s-%(title)s.mp3', 23 'restrictfilenames': True, 24 'noplaylist': True, 25 'nocheckcertificate': True, 26 'ignoreerrors': False, 27 'logtostderr': False, 28 'quiet': True, 29 'no_warnings': True, 30 'postprocessors': [{ 31 'key': 'FFmpegExtractAudio', 32 'preferredcodec': 'mp3', 33 'preferredquality': '192', 34 }], 35 'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes: True 36} 37 38ffmpeg_options = { 39 'options': '-vn' 40} 41 42ytdl = youtube_dl.YoutubeDL(ytdl_format_options) 43 44 45class YTDLSource(discord.PCMVolumeTransformer): 46 def __init__(self, source, *, data, volume=0.2): 47 super().__init__(source, volume) 48 49 self.data = data 50 51 self.title = data.get('title') 52 self.url = data.get('url') 53 54 @classmethod 55 async def from_url(cls, url, *, loop=None): 56 loop = loop or asyncio.get_event_loop() 57 data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=False)) 58 59 if 'entries' in data: 60 # take first item from a playlist 61 data = data['entries'][0] 62 63 filename = data['url'] 64 ytdl.prepare_filename(data) 65 return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) 66 67 68class AudioQueue(asyncio.Queue): 69 def __init__(self): 70 super().__init__(100) 71 72 def __getitem__(self, idx): 73 return self._queue[idx] 74 75 def to_list(self): 76 return list(self._queue) 77 78 def remove(self, idx): 79 del self._queue[idx] 80 81 def reset(self): 82 self._queue.clear() 83 84 85class AudioStatus: 86 def __init__(self, ctx: commands.Context, vc: discord.VoiceClient): 87 self.vc: discord.VoiceClient = vc 88 self.ctx: commands.Context = ctx 89 self.queue = AudioQueue() 90 self.playing = asyncio.Event() 91 self.loopMode = False 92 asyncio.create_task(self.playing_task()) 93 94 async def add_audio(self, title, path): 95 await self.queue.put([title, path]) 96 97 def get_list(self): 98 return self.queue.to_list() 99 100 def remove(self, idx): 101 self.queue.remove(idx) 102 103 def reset(self): 104 self.queue.reset() 105 106 async def playing_task(self): 107 while True: 108 self.playing.clear() 109 if self.loopMode: 110 title, player = self.queue[0] 111 else: 112 title, player = await self.queue.get() 113 self.vc.play(player, after=self.play_next) 114 if not self.loopMode: 115 await self.ctx.send(f'{title}を再生します。') 116 await self.playing.wait() 117 118 def play_next(self, err=None): 119 self.playing.set() 120 121 async def leave(self): 122 self.queue.reset() 123 if self.vc: 124 await self.vc.disconnect() 125 self.vc = None 126 127 @property 128 def is_playing(self): 129 return self.vc.is_playing() 130 131 def resume(self): 132 self.vc.stop() 133 134 def pause(self): 135 self.vc.stop() 136 137 def stop(self): 138 self.vc.stop() 139 140 141class Music(commands.Cog): 142 def __init__(self, bot): 143 self.bot = bot 144 self.audio_statuses = "" 145 146 # command to connect to voice channel 147 @commands.command(aliases=["s"]) 148 async def start(self, ctx): 149 """Joins a voice channel""" 150 151 if ctx.author.voice is None: 152 await ctx.send('先にボイスチャンネルに接続してください…。') 153 else: 154 if ctx.guild.voice_client: 155 if ctx.author.voice.channel == ctx.guild.voice_client.channel: 156 await ctx.send('既に接続済みです…。') 157 else: 158 await ctx.voice_client.disconnect() 159 await asyncio.sleep(0.5) 160 vc = await ctx.author.voice.channel.connect() 161 self.audio_statuses = AudioStatus(ctx, vc) 162 await ctx.send('ボイスチャンネルを変更しました。') 163 else: 164 vc = await ctx.author.voice.channel.connect() 165 self.audio_statuses = AudioStatus(ctx, vc) 166 await ctx.send('ボイスチャンネルに接続しました。') 167 168 # command to play sound from a youtube URL or add to queue 169 @commands.command(aliases=["pl"]) 170 async def play(self, ctx, *, entry): 171 """Play music""" 172 173 status = self.audio_statuses 174 status.loopMode = False 175 queue = status.get_list 176 177 player = await Music.get_player(ctx, entry) 178 if len(queue) != 0: 179 await ctx.send(f'`{player.title}`を追加しました。') 180 await status.add_audio(player.title, player) 181 182 # command to loop play sound from a youtube URL 183 @commands.command(aliases=["l"]) 184 async def loop(self, ctx, *, entry): 185 """Loop play music""" 186 187 status = self.audio_statuses 188 status.loopMode = True 189 190 player = await Music.get_player(ctx, entry) 191 await status.reset() 192 await ctx.send(f'`{player.title}`を追加しました。') 193 await status.add_audio(player.title, player) 194 195 @classmethod 196 async def get_player(self, ctx, entry): 197 if entry.startswith('http') is True: 198 url = entry 199 else: 200 search_keyword = entry.replace(" ", "+") 201 regex = r'[^\x00-\x7F]' 202 matchedList = re.findall(regex, search_keyword) 203 for m in matchedList: 204 search_keyword = search_keyword.replace(m, urllib.parse.quote_plus(m, encoding="utf-8")) 205 html = urllib.request.urlopen("https://www.youtube.com/results?search_query=" + search_keyword) 206 video_ids = re.findall(r"watch?v=(\S{11})", html.read().decode()) 207 url = "https://www.youtube.com/watch?v=" + video_ids[0] 208 async with ctx.typing(): 209 return await YTDLSource.from_url(url, loop=self.bot.loop) 210 211 212bot = commands.Bot(command_prefix=prefix, 213 description='Relatively simple music bot example') 214 215 216@bot.event 217async def on_ready(): 218 print('Logged in as {0} ({0.id})'.format(bot.user)) 219 print('------') 220 await bot.change_presence(activity=discord.Game(name=f'{prefix}hでコマンド一覧', type=1)) 221 222 223@bot.event 224async def on_command_error(ctx, error): 225 if isinstance(error, commands.errors.CommandNotFound): 226 await ctx.send('存在しないコマンドです…。') 227 elif isinstance(error, commands.errors.MissingRequiredArgument): 228 await ctx.send('引数が足りません…。') 229 elif isinstance(error, commands.CommandError): 230 pass 231 else: 232 orig_error = getattr(error, 'original', error) 233 error_msg = ''.join(traceback.TracebackException.from_exception(orig_error).format()) 234 await ctx.send("不明なエラーが発生…。") 235 await bot.get_channel(チャンネルID).send(error_msg) 236 print(error_msg) 237 238bot.add_cog(Music(bot)) 239bot.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
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。