From 260c3acf932719f621bd47aea59a3593cbfb629c Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 13 Jan 2026 16:37:53 +0100 Subject: add support for internet streams for playing --- cogs/music/play.py | 84 +++++++++++++++++++++++------------------- cogs/music/play_utils.py | 95 ++++++++++++++++++++++++++++++------------------ 2 files changed, 105 insertions(+), 74 deletions(-) (limited to 'cogs/music') diff --git a/cogs/music/play.py b/cogs/music/play.py index c9abe4b..2c5b641 100644 --- a/cogs/music/play.py +++ b/cogs/music/play.py @@ -137,60 +137,68 @@ class Play(commands.Cog): ctx: discord.ApplicationContext, file: str = discord.Option( str, - "Wybierz plik do odtworzenia", + "wyszukaj plik z serwera", autocomplete=_search_autocomplete, - required=True + required=False ), - source: str = discord.Option( - str, - "Źródło dźwięku", - choices=["local", "soundcloud", "youtube"], - default="local" + stream: str = discord.Option( + str, + "odtwórz stream internetowy (e.g. radio)", + required=False ), compressed: bool = discord.Option( bool, - "Czy skompresować dźwięk?", + "Czy skompresować dźwięk? (tylko dla plików lokalnych)", default=False ) ): - """Odtwarza wybrany plik dźwiękowy z opcjonalną kompresją.""" + """Odtwarza wybrany plik dźwiękowy lub stream internetowy.""" await ctx.defer() - if source != "local": - await ctx.respond("Tylko źródło 'local' jest obecnie obsługiwane w tej komendzie.", ephemeral=True) + # Check if either file or stream is provided + if not file and not stream: + await ctx.respond("Musisz podać plik do odtworzenia lub adres streamu.", ephemeral=True) return - - # Look up the full path from the display name - full_path = self.path_mapping.get(file, file) - - if not os.path.exists(full_path): - logger.warning(f"File not found: {full_path} (display name: {file})") - await ctx.respond("Wybrany plik nie istnieje.", ephemeral=True) + + if file and stream: + await ctx.respond("Możesz podać tylko plik LUB stream, nie oba naraz.", ephemeral=True) return try: - if compressed: - # Compress the file before playing - compressed_path = await play_utils.PlayUtils(self.bot).compress(ctx, full_path) - if not compressed_path: - await ctx.respond("Nie udało się skompresować pliku.", ephemeral=True) - return - - try: - await play_utils.PlayUtils(self.bot).play(ctx, compressed_path, delete_after_play=True) - except Exception as e: - # Clean up the compressed file if play fails - if os.path.exists(compressed_path): - try: - os.remove(compressed_path) - logger.info(f"Cleaned up temporary file after play error: {compressed_path}") - except Exception as cleanup_error: - logger.error(f"Error cleaning up file {compressed_path}: {str(cleanup_error)}") - raise e + if stream: + # Handle internet stream + await play_utils.PlayUtils(self.bot).play(ctx, stream) else: - await play_utils.PlayUtils(self.bot).play(ctx, full_path) + # Handle local file + full_path = self.path_mapping.get(file, file) + + if not os.path.exists(full_path): + logger.warning(f"File not found: {full_path} (display name: {file})") + await ctx.respond("Wybrany plik nie istnieje.", ephemeral=True) + return + + if compressed: + # Compress the file before playing + compressed_path = await play_utils.PlayUtils(self.bot).compress(ctx, full_path) + if not compressed_path: + await ctx.respond("Nie udało się skompresować pliku.", ephemeral=True) + return + + try: + await play_utils.PlayUtils(self.bot).play(ctx, compressed_path, delete_after_play=True) + except Exception as e: + # Clean up the compressed file if play fails + if os.path.exists(compressed_path): + try: + os.remove(compressed_path) + logger.info(f"Cleaned up temporary file after play error: {compressed_path}") + except Exception as cleanup_error: + logger.error(f"Error cleaning up file {compressed_path}: {str(cleanup_error)}") + raise e + else: + await play_utils.PlayUtils(self.bot).play(ctx, full_path) except Exception as e: - logger.error(f"Error playing file: {str(e)}", exc_info=True) + logger.error(f"Error playing: {str(e)}", exc_info=True) await ctx.respond(f"Wystąpił błąd podczas odtwarzania: {str(e)}", ephemeral=True) @discord.slash_command(name="grajcuj_plik", description="grajcuj plik") diff --git a/cogs/music/play_utils.py b/cogs/music/play_utils.py index 8de7543..0614157 100644 --- a/cogs/music/play_utils.py +++ b/cogs/music/play_utils.py @@ -226,34 +226,49 @@ class PlayUtils: return None @staticmethod - async def play(ctx, filepath, delete_after_play=False): + async def play(ctx, source, delete_after_play=False): + """Play audio from a local file or URL. + + Args: + ctx: Discord context + source: Either a local file path or a URL to stream from + delete_after_play: If True, delete the file after playing (only for local files) + """ # Check if user is in a voice channel if not ctx.author.voice or not ctx.author.voice.channel: await ctx.respond("Musisz być na kanale głosowym, żebym mógł dołączyć!", ephemeral=True) - if delete_after_play and os.path.exists(filepath): + if delete_after_play and os.path.exists(source): try: - os.remove(filepath) - logger.info(f"Cleaned up temporary file: {filepath}") + os.remove(source) + logger.info(f"Cleaned up temporary file: {source}") except Exception as e: - logger.error(f"Error cleaning up file {filepath}: {str(e)}") + logger.error(f"Error cleaning up file {source}: {str(e)}") return - - logger.info(f"Attempting to play: {filepath}") - - # Get absolute path and verify file exists - if not os.path.isabs(filepath): - filepath = os.path.abspath(filepath) - logger.debug(f"Converted to absolute path: {filepath}") + + is_url = source.startswith(('http://', 'https://')) - if not os.path.exists(filepath): - error_msg = f"File not found: {filepath}" - logger.error(error_msg) - await ctx.respond("Nie znaleziono pliku do odtworzenia", ephemeral=True) - return + if not is_url: + # Handle local file + logger.info(f"Attempting to play local file: {source}") - filename = os.path.basename(filepath) - logger.info(f"Playing file: {filename} (from: {filepath})") - + # Get absolute path and verify file exists + if not os.path.isabs(source): + source = os.path.abspath(source) + logger.debug(f"Converted to absolute path: {source}") + + if not os.path.exists(source): + error_msg = f"File not found: {source}" + logger.error(error_msg) + await ctx.respond("Nie znaleziono pliku do odtworzenia", ephemeral=True) + return + + filename = os.path.basename(source) + display_name = os.path.splitext(filename)[0] + else: + # Handle URL + logger.info(f"Attempting to play stream from URL: {source}") + display_name = source # Use URL as display name for streams + # Get or create voice client vc = ctx.voice_client target_channel = ctx.author.voice.channel @@ -275,23 +290,31 @@ class PlayUtils: vc.stop() # Play the audio - source = discord.FFmpegPCMAudio(source=filepath) - vc.play(source) - - try: - metadata = mutagen.File(filepath) - title = metadata.get('title', None) - logger.debug(f"Read metadata - title: {title}") - except Exception as e: - logger.warning(f"Could not read metadata from {filepath}: {str(e)}") - title = None - - if title is None: - title = os.path.splitext(os.path.basename(filepath))[0] - logger.debug(f"Using filename as title: {title}") + if is_url: + # For URLs, use FFmpeg with the direct URL + ffmpeg_options = { + 'options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', + 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5' + } + source = discord.FFmpegPCMAudio(source, **ffmpeg_options) + else: + # For local files, use default options + source = discord.FFmpegPCMAudio(source=source) + + # Try to get metadata for local files + try: + metadata = mutagen.File(source) + if metadata: + title = metadata.get('title', [None])[0] if hasattr(metadata, 'get') else None + if title: + display_name = title + logger.debug(f"Read metadata - title: {title}") + except Exception as e: + logger.warning(f"Could not read metadata from {source}: {str(e)}") - logger.info(f"Playing file: {title}") - await ctx.respond(f"grom: {title}") + vc.play(source) + logger.info(f"Playing: {display_name}") + await ctx.respond(f"grom: {display_name}") except Exception as e: logger.error(f"Error in play: {str(e)}", exc_info=True) -- cgit v1.2.3