diff options
| author | SamlonDev <stankiewiczgracjan2019@gmail.com> | 2025-05-22 11:44:29 +0200 |
|---|---|---|
| committer | SamlonDev <stankiewiczgracjan2019@gmail.com> | 2025-05-22 11:44:29 +0200 |
| commit | 42132c6013b4a324b8a4aac2c3bf26b2ae4f67a7 (patch) | |
| tree | 2a65642365920841c6b2907e663ac13b681812b7 /cogs/music | |
| parent | easy video downloading (diff) | |
| download | senator-sperma-42132c6013b4a324b8a4aac2c3bf26b2ae4f67a7.tar.gz senator-sperma-42132c6013b4a324b8a4aac2c3bf26b2ae4f67a7.zip | |
use python yt-dlp instead of shell
Diffstat (limited to '')
| -rw-r--r-- | cogs/music/play.py | 139 | ||||
| -rw-r--r-- | 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 @@ | |||
| 1 | import discord | 1 | import discord |
| 2 | from discord.ext import commands | 2 | from discord.ext import commands |
| 3 | import asyncio | 3 | import asyncio |
| 4 | import yt_dlp | ||
| 4 | from youtubesearchpython import VideosSearch | 5 | from youtubesearchpython import VideosSearch |
| 5 | 6 | ||
| 7 | # Suppress noise about console usage from yt-dlp | ||
| 8 | yt_dlp.utils.bug_reports_message = lambda: '' | ||
| 9 | |||
| 6 | class Play(commands.Cog): | 10 | class Play(commands.Cog): |
| 7 | def __init__(self, bot): | 11 | def __init__(self, bot): |
| 8 | self.bot = bot | 12 | self.bot = bot |
| 13 | self.queues = {} | ||
| 14 | self.ydl_opts = { | ||
| 15 | 'format': 'bestaudio/best', | ||
| 16 | 'postprocessors': [{ | ||
| 17 | 'key': 'FFmpegExtractAudio', | ||
| 18 | 'preferredcodec': 'mp3', | ||
| 19 | 'preferredquality': '192', | ||
| 20 | }], | ||
| 21 | 'noplaylist': True, | ||
| 22 | 'quiet': True, | ||
| 23 | 'no_warnings': True, | ||
| 24 | 'default_search': 'auto', | ||
| 25 | 'source_address': '0.0.0.0', | ||
| 26 | } | ||
| 27 | |||
| 28 | def get_queue(self, guild_id): | ||
| 29 | return self.queues.setdefault(guild_id, []) | ||
| 30 | |||
| 31 | async def play_next(self, ctx, vc, guild_id): | ||
| 32 | queue = self.get_queue(guild_id) | ||
| 33 | if queue: | ||
| 34 | title, url = queue[0] | ||
| 35 | |||
| 36 | def after_playing(error): | ||
| 37 | if queue: | ||
| 38 | queue.pop(0) | ||
| 39 | if queue: | ||
| 40 | next_title, next_url = queue[0] | ||
| 41 | if not vc.is_playing() and not vc.is_paused(): | ||
| 42 | asyncio.run_coroutine_threadsafe( | ||
| 43 | self.play_audio(ctx, vc, next_url, next_title, guild_id), | ||
| 44 | self.bot.loop | ||
| 45 | ) | ||
| 46 | else: | ||
| 47 | asyncio.run_coroutine_threadsafe( | ||
| 48 | vc.disconnect(), | ||
| 49 | self.bot.loop | ||
| 50 | ) | ||
| 51 | |||
| 52 | if not vc.is_playing() and not vc.is_paused(): | ||
| 53 | await self.play_audio(ctx, vc, url, title, guild_id, after=after_playing) | ||
| 54 | |||
| 55 | async def play_audio(self, ctx, vc, url, title, guild_id, after=None): | ||
| 56 | try: | ||
| 57 | with yt_dlp.YoutubeDL(self.ydl_opts) as ydl: | ||
| 58 | info = await self.bot.loop.run_in_executor( | ||
| 59 | None, | ||
| 60 | lambda: ydl.extract_info(url, download=False) | ||
| 61 | ) | ||
| 62 | if 'entries' in info: | ||
| 63 | info = info['entries'][0] | ||
| 64 | |||
| 65 | url2 = info['url'] | ||
| 66 | |||
| 67 | FFMPEG_OPTIONS = { | ||
| 68 | 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', | ||
| 69 | 'options': '-vn' | ||
| 70 | } | ||
| 71 | |||
| 72 | source = discord.FFmpegPCMAudio(url2, **FFMPEG_OPTIONS) | ||
| 73 | vc.play(source, after=after) | ||
| 74 | await ctx.followup.send(f"gra: {title}") | ||
| 75 | except Exception as e: | ||
| 76 | await ctx.followup.send(f"Wystąpił błąd podczas odtwarzania: {str(e)}") | ||
| 77 | # Try to play next in queue if there's an error | ||
| 78 | if after: | ||
| 79 | after(None) | ||
| 9 | 80 | ||
| 10 | @commands.slash_command(name="grajcuj", description="grajcuj melodyje") | 81 | @commands.slash_command(name="grajcuj", description="grajcuj melodyje") |
| 11 | async def play(self, ctx, *, search: str): | 82 | async def play(self, ctx, *, search: str): |
| 12 | await ctx.defer() | 83 | await ctx.defer() |
| 84 | |||
| 13 | if not (ctx.author.voice and ctx.author.voice.channel): | 85 | if not (ctx.author.voice and ctx.author.voice.channel): |
| 14 | await ctx.followup.send("nie jesteś w kanale głosowym") | 86 | await ctx.followup.send("nie jesteś w kanale głosowym") |
| 15 | return | 87 | return |
| 88 | |||
| 16 | channel = ctx.author.voice.channel | 89 | channel = ctx.author.voice.channel |
| 17 | if ctx.voice_client is not None: | 90 | vc = ctx.voice_client or await channel.connect() |
| 18 | vc = ctx.voice_client | 91 | |
| 92 | if vc.channel != channel: | ||
| 19 | await vc.move_to(channel) | 93 | await vc.move_to(channel) |
| 20 | else: | 94 | |
| 21 | vc = await channel.connect() | 95 | # Search for the video |
| 22 | videos_search = VideosSearch(search, limit=1) | 96 | videos_search = VideosSearch(search, limit=1) |
| 23 | result = videos_search.result() | 97 | result = videos_search.result() |
| 98 | |||
| 24 | if not result['result']: | 99 | if not result['result']: |
| 25 | await ctx.followup.send("nie znaleziono wideo dla twojego wyszukiwania.", ephemeral=True) | 100 | await ctx.followup.send("nie znaleziono wideo dla twojego wyszukiwania.", ephemeral=True) |
| 26 | return | 101 | return |
| 102 | |||
| 27 | video = result['result'][0] | 103 | video = result['result'][0] |
| 28 | video_id = video['id'] | ||
| 29 | title = video['title'] | 104 | title = video['title'] |
| 30 | url = video['link'] | 105 | url = video['link'] |
| 31 | proc = await asyncio.create_subprocess_exec( | 106 | |
| 32 | "yt-dlp", "-f", "bestaudio", "-g", "--no-cache-dir", url, | 107 | # Add to queue |
| 33 | stdout=asyncio.subprocess.PIPE | 108 | queue = self.get_queue(ctx.guild.id) |
| 34 | ) | 109 | queue.append((title, url)) |
| 35 | stdout, _ = await proc.communicate() | 110 | |
| 36 | stream_url = stdout.decode().strip() | 111 | # If nothing is playing, start playing |
| 37 | FFMPEG_OPTIONS = { | 112 | if not vc.is_playing() and not vc.is_paused(): |
| 38 | 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', | 113 | await self.play_next(ctx, vc, ctx.guild.id) |
| 39 | 'options': '-vn' | 114 | else: |
| 40 | } | 115 | await ctx.followup.send(f"dodano do kolejki: {title}") |
| 41 | audio = discord.FFmpegPCMAudio( | 116 | |
| 42 | stream_url, | 117 | @commands.slash_command(name="stop", description="zatrzymaj odtwarzanie i wyczyść kolejkę") |
| 43 | before_options=FFMPEG_OPTIONS['before_options'], | 118 | async def stop(self, ctx): |
| 44 | options=FFMPEG_OPTIONS['options'] | 119 | if ctx.voice_client: |
| 45 | ) | 120 | queue = self.get_queue(ctx.guild.id) |
| 46 | if not vc.is_playing(): | 121 | queue.clear() |
| 47 | vc.play(audio) | 122 | await ctx.voice_client.disconnect() |
| 48 | await ctx.followup.send(f"gra: {title}") | 123 | await ctx.respond("Zatrzymano odtwarzanie i wyczyszczono kolejkę") |
| 124 | else: | ||
| 125 | await ctx.respond("Nie jestem połączony z kanałem głosowym") | ||
| 126 | |||
| 127 | @commands.slash_command(name="pauza", description="wstrzymaj odtwarzanie") | ||
| 128 | async def pause(self, ctx): | ||
| 129 | if ctx.voice_client and ctx.voice_client.is_playing(): | ||
| 130 | ctx.voice_client.pause() | ||
| 131 | await ctx.respond("Wstrzymano odtwarzanie") | ||
| 132 | else: | ||
| 133 | await ctx.respond("Nic nie jest teraz odtwarzane") | ||
| 134 | |||
| 135 | @commands.slash_command(name="wznow", description="wznów odtwarzanie") | ||
| 136 | async def resume(self, ctx): | ||
| 137 | if ctx.voice_client and ctx.voice_client.is_paused(): | ||
| 138 | ctx.voice_client.resume() | ||
| 139 | await ctx.respond("Wznowiono odtwarzanie") | ||
| 49 | else: | 140 | else: |
| 50 | await ctx.followup.send(f"dodano do koleyjki: {title}") | 141 | await ctx.respond("Nic nie jest teraz wstrzymane") |
| 51 | 142 | ||
| 52 | def setup(bot): | 143 | def setup(bot): |
| 53 | bot.add_cog(Play(bot)) \ No newline at end of file | 144 | 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 | |||
| 4 | class Skip(commands.Cog): | 4 | class Skip(commands.Cog): |
| 5 | def __init__(self, bot): | 5 | def __init__(self, bot): |
| 6 | self.bot = bot | 6 | self.bot = bot |
| 7 | self.queues = {} | ||
| 8 | |||
| 9 | def get_queue(self, guild_id): | ||
| 10 | return self.queues.setdefault(guild_id, []) | ||
| 11 | |||
| 12 | def play_next(self, ctx, vc, guild_id): | ||
| 13 | queue = self.get_queue(guild_id) | ||
| 14 | if queue: | ||
| 15 | _, _, file_path = queue[0] | ||
| 16 | def after_playing(error=None): | ||
| 17 | if queue: | ||
| 18 | queue.pop(0) | ||
| 19 | if queue: | ||
| 20 | _, _, next_file = queue[0] | ||
| 21 | if not vc.is_playing(): | ||
| 22 | vc.play(discord.FFmpegPCMAudio(next_file), after=after_playing) | ||
| 23 | if not vc.is_playing(): | ||
| 24 | vc.play(discord.FFmpegPCMAudio(file_path), after=after_playing) | ||
| 25 | 7 | ||
| 26 | @commands.slash_command(name="pomiń", description="przeskocz do nastepnej melodyji") | 8 | @commands.slash_command(name="pomiń", description="przeskocz do nastepnej melodyji") |
| 27 | async def pomin(self, ctx): | 9 | async def pomin(self, ctx): |
| 28 | guild_id = ctx.guild.id | 10 | """Skip the current song and play the next one in the queue.""" |
| 29 | vc = ctx.voice_client | 11 | vc = ctx.voice_client |
| 30 | if vc and vc.is_playing(): | 12 | |
| 31 | queue = self.get_queue(guild_id) | 13 | if not vc or not vc.is_connected(): |
| 32 | if queue: | 14 | await ctx.respond("Nie jestem połączony z kanałem głosowym.") |
| 33 | queue.pop(0) | 15 | return |
| 34 | if queue: | 16 | |
| 35 | vc.stop() | 17 | if not vc.is_playing() and not vc.is_paused(): |
| 36 | self.play_next(ctx, vc, guild_id) | 18 | await ctx.respond("Nic nie jest teraz odtwarzane.") |
| 37 | await ctx.respond("przeskoczono do nastepnej melodyji") | 19 | return |
| 38 | else: | 20 | |
| 39 | await ctx.respond("koleyjka jest pustawa") | 21 | # Get the play cog |
| 40 | await vc.disconnect() | 22 | play_cog = self.bot.get_cog('Play') |
| 41 | else: | 23 | if not play_cog: |
| 42 | await ctx.respond("nic nie gra") | 24 | await ctx.respond("Błąd: Nie można znaleźć modułu odtwarzania.") |
| 43 | else: | 25 | return |
| 44 | await ctx.respond("nic nie gra") | 26 | |
| 27 | queue = play_cog.get_queue(ctx.guild.id) | ||
| 28 | |||
| 29 | if not queue: | ||
| 30 | await ctx.respond("Kolejka jest pusta.") | ||
| 31 | return | ||
| 32 | |||
| 33 | # Stop the current song (this will trigger play_next in the after callback) | ||
| 34 | vc.stop() | ||
| 35 | await ctx.respond("Pominięto aktualną piosenkę.") | ||
| 36 | |||
| 37 | @commands.slash_command(name="kolejka", description="pokaż aktualną kolejkę") | ||
| 38 | async def queue_list(self, ctx): | ||
| 39 | """Show the current queue.""" | ||
| 40 | play_cog = self.bot.get_cog('Play') | ||
| 41 | if not play_cog: | ||
| 42 | await ctx.respond("Błąd: Nie można znaleźć modułu odtwarzania.") | ||
| 43 | return | ||
| 44 | |||
| 45 | queue = play_cog.get_queue(ctx.guild.id) | ||
| 46 | |||
| 47 | if not queue: | ||
| 48 | await ctx.respond("Kolejka jest pusta.") | ||
| 49 | return | ||
| 50 | |||
| 51 | queue_list = [] | ||
| 52 | for i, (title, _) in enumerate(queue[:10], 1): # Show up to 10 songs | ||
| 53 | queue_list.append(f"{i}. {title}") | ||
| 54 | |||
| 55 | if len(queue) > 10: | ||
| 56 | queue_list.append(f"...i {len(queue) - 10} więcej") | ||
| 57 | |||
| 58 | await ctx.respond("\n".join(["**Kolejka:**"] + queue_list)) | ||
| 45 | 59 | ||
| 46 | def setup(bot): | 60 | def setup(bot): |
| 47 | bot.add_cog(Skip(bot)) \ No newline at end of file | 61 | bot.add_cog(Skip(bot)) \ No newline at end of file |
