From 42132c6013b4a324b8a4aac2c3bf26b2ae4f67a7 Mon Sep 17 00:00:00 2001 From: SamlonDev Date: Thu, 22 May 2025 11:44:29 +0200 Subject: use python yt-dlp instead of shell --- cogs/music/play.py | 139 ++++++++++++++++++++++++++++++++++++++++++++--------- cogs/music/skip.py | 84 ++++++++++++++++++-------------- 2 files changed, 164 insertions(+), 59 deletions(-) diff --git a/cogs/music/play.py b/cogs/music/play.py index 1eafbca..bf9d081 100644 --- a/cogs/music/play.py +++ b/cogs/music/play.py @@ -1,53 +1,144 @@ import discord from discord.ext import commands import asyncio +import yt_dlp from youtubesearchpython import VideosSearch +# Suppress noise about console usage from yt-dlp +yt_dlp.utils.bug_reports_message = lambda: '' + class Play(commands.Cog): def __init__(self, bot): self.bot = bot + self.queues = {} + self.ydl_opts = { + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '192', + }], + 'noplaylist': True, + 'quiet': True, + 'no_warnings': True, + 'default_search': 'auto', + 'source_address': '0.0.0.0', + } + + def get_queue(self, guild_id): + return self.queues.setdefault(guild_id, []) + + async def play_next(self, ctx, vc, guild_id): + queue = self.get_queue(guild_id) + if queue: + title, url = queue[0] + + def after_playing(error): + if queue: + queue.pop(0) + if queue: + next_title, next_url = queue[0] + if not vc.is_playing() and not vc.is_paused(): + asyncio.run_coroutine_threadsafe( + self.play_audio(ctx, vc, next_url, next_title, guild_id), + self.bot.loop + ) + else: + asyncio.run_coroutine_threadsafe( + vc.disconnect(), + self.bot.loop + ) + + if not vc.is_playing() and not vc.is_paused(): + await self.play_audio(ctx, vc, url, title, guild_id, after=after_playing) + + async def play_audio(self, ctx, vc, url, title, guild_id, after=None): + try: + with yt_dlp.YoutubeDL(self.ydl_opts) as ydl: + info = await self.bot.loop.run_in_executor( + None, + lambda: ydl.extract_info(url, download=False) + ) + if 'entries' in info: + info = info['entries'][0] + + url2 = info['url'] + + FFMPEG_OPTIONS = { + 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', + 'options': '-vn' + } + + source = discord.FFmpegPCMAudio(url2, **FFMPEG_OPTIONS) + vc.play(source, after=after) + await ctx.followup.send(f"gra: {title}") + except Exception as e: + await ctx.followup.send(f"Wystąpił błąd podczas odtwarzania: {str(e)}") + # Try to play next in queue if there's an error + if after: + after(None) @commands.slash_command(name="grajcuj", description="grajcuj melodyje") async def play(self, ctx, *, search: str): await ctx.defer() + if not (ctx.author.voice and ctx.author.voice.channel): await ctx.followup.send("nie jesteś w kanale głosowym") return + channel = ctx.author.voice.channel - if ctx.voice_client is not None: - vc = ctx.voice_client + vc = ctx.voice_client or await channel.connect() + + if vc.channel != channel: await vc.move_to(channel) - else: - vc = await channel.connect() + + # Search for the video videos_search = VideosSearch(search, limit=1) result = videos_search.result() + if not result['result']: await ctx.followup.send("nie znaleziono wideo dla twojego wyszukiwania.", ephemeral=True) return + video = result['result'][0] - video_id = video['id'] title = video['title'] url = video['link'] - proc = await asyncio.create_subprocess_exec( - "yt-dlp", "-f", "bestaudio", "-g", "--no-cache-dir", url, - stdout=asyncio.subprocess.PIPE - ) - stdout, _ = await proc.communicate() - stream_url = stdout.decode().strip() - FFMPEG_OPTIONS = { - 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', - 'options': '-vn' - } - audio = discord.FFmpegPCMAudio( - stream_url, - before_options=FFMPEG_OPTIONS['before_options'], - options=FFMPEG_OPTIONS['options'] - ) - if not vc.is_playing(): - vc.play(audio) - await ctx.followup.send(f"gra: {title}") + + # Add to queue + queue = self.get_queue(ctx.guild.id) + queue.append((title, url)) + + # If nothing is playing, start playing + if not vc.is_playing() and not vc.is_paused(): + await self.play_next(ctx, vc, ctx.guild.id) + else: + await ctx.followup.send(f"dodano do kolejki: {title}") + + @commands.slash_command(name="stop", description="zatrzymaj odtwarzanie i wyczyść kolejkę") + async def stop(self, ctx): + if ctx.voice_client: + queue = self.get_queue(ctx.guild.id) + queue.clear() + await ctx.voice_client.disconnect() + await ctx.respond("Zatrzymano odtwarzanie i wyczyszczono kolejkę") + else: + await ctx.respond("Nie jestem połączony z kanałem głosowym") + + @commands.slash_command(name="pauza", description="wstrzymaj odtwarzanie") + async def pause(self, ctx): + if ctx.voice_client and ctx.voice_client.is_playing(): + ctx.voice_client.pause() + await ctx.respond("Wstrzymano odtwarzanie") + else: + await ctx.respond("Nic nie jest teraz odtwarzane") + + @commands.slash_command(name="wznow", description="wznów odtwarzanie") + async def resume(self, ctx): + if ctx.voice_client and ctx.voice_client.is_paused(): + ctx.voice_client.resume() + await ctx.respond("Wznowiono odtwarzanie") else: - await ctx.followup.send(f"dodano do koleyjki: {title}") + await ctx.respond("Nic nie jest teraz wstrzymane") def setup(bot): bot.add_cog(Play(bot)) \ No newline at end of file diff --git a/cogs/music/skip.py b/cogs/music/skip.py index f48218b..67c391e 100644 --- a/cogs/music/skip.py +++ b/cogs/music/skip.py @@ -4,44 +4,58 @@ import discord class Skip(commands.Cog): def __init__(self, bot): self.bot = bot - self.queues = {} - - def get_queue(self, guild_id): - return self.queues.setdefault(guild_id, []) - - def play_next(self, ctx, vc, guild_id): - queue = self.get_queue(guild_id) - if queue: - _, _, file_path = queue[0] - def after_playing(error=None): - if queue: - queue.pop(0) - if queue: - _, _, next_file = queue[0] - if not vc.is_playing(): - vc.play(discord.FFmpegPCMAudio(next_file), after=after_playing) - if not vc.is_playing(): - vc.play(discord.FFmpegPCMAudio(file_path), after=after_playing) @commands.slash_command(name="pomiń", description="przeskocz do nastepnej melodyji") async def pomin(self, ctx): - guild_id = ctx.guild.id + """Skip the current song and play the next one in the queue.""" vc = ctx.voice_client - if vc and vc.is_playing(): - queue = self.get_queue(guild_id) - if queue: - queue.pop(0) - if queue: - vc.stop() - self.play_next(ctx, vc, guild_id) - await ctx.respond("przeskoczono do nastepnej melodyji") - else: - await ctx.respond("koleyjka jest pustawa") - await vc.disconnect() - else: - await ctx.respond("nic nie gra") - else: - await ctx.respond("nic nie gra") + + if not vc or not vc.is_connected(): + await ctx.respond("Nie jestem połączony z kanałem głosowym.") + return + + if not vc.is_playing() and not vc.is_paused(): + await ctx.respond("Nic nie jest teraz odtwarzane.") + return + + # Get the play cog + play_cog = self.bot.get_cog('Play') + if not play_cog: + await ctx.respond("Błąd: Nie można znaleźć modułu odtwarzania.") + return + + queue = play_cog.get_queue(ctx.guild.id) + + if not queue: + await ctx.respond("Kolejka jest pusta.") + return + + # Stop the current song (this will trigger play_next in the after callback) + vc.stop() + await ctx.respond("Pominięto aktualną piosenkę.") + + @commands.slash_command(name="kolejka", description="pokaż aktualną kolejkę") + async def queue_list(self, ctx): + """Show the current queue.""" + play_cog = self.bot.get_cog('Play') + if not play_cog: + await ctx.respond("Błąd: Nie można znaleźć modułu odtwarzania.") + return + + queue = play_cog.get_queue(ctx.guild.id) + + if not queue: + await ctx.respond("Kolejka jest pusta.") + return + + queue_list = [] + for i, (title, _) in enumerate(queue[:10], 1): # Show up to 10 songs + queue_list.append(f"{i}. {title}") + + if len(queue) > 10: + queue_list.append(f"...i {len(queue) - 10} więcej") + + await ctx.respond("\n".join(["**Kolejka:**"] + queue_list)) def setup(bot): - bot.add_cog(Skip(bot)) \ No newline at end of file + bot.add_cog(Skip(bot)) \ No newline at end of file -- cgit v1.2.3