aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/music/play_commands.py
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-02-04 16:16:26 +0100
committersalmonik <raisersoft@proton.me>2026-02-04 16:16:26 +0100
commitce61bf6bd5949046d15f58609f572fedcb8a073e (patch)
treecafaa12f11a06337bda5de08881e395ac66c1c11 /cogs/music/play_commands.py
parentbetter reply context (diff)
downloadsenator-sperma-ce61bf6bd5949046d15f58609f572fedcb8a073e.tar.gz
senator-sperma-ce61bf6bd5949046d15f58609f572fedcb8a073e.zip
refactor music
Diffstat (limited to '')
-rw-r--r--cogs/music/play_commands.py (renamed from cogs/music/play.py)145
1 files changed, 81 insertions, 64 deletions
diff --git a/cogs/music/play.py b/cogs/music/play_commands.py
index c7dd9f1..b25ce84 100644
--- a/cogs/music/play.py
+++ b/cogs/music/play_commands.py
@@ -9,14 +9,16 @@ import mutagen
9from typing import List, Tuple, Callable, Awaitable 9from typing import List, Tuple, Callable, Awaitable
10from os import PathLike 10from os import PathLike
11 11
12from cogs.music import play_utils 12from cogs.music import search_utils, audio_playback, media_processing
13from discord.ext import commands 13from discord.ext import commands
14from discord.commands import OptionChoice 14from discord.commands import OptionChoice
15import logging 15import logging
16 16
17logger = logging.getLogger(__name__) 17logger = logging.getLogger(__name__)
18 18
19class Play(commands.Cog): 19class PlayCommands(commands.Cog):
20 """Discord slash commands for music playback."""
21
20 def __init__(self, bot): 22 def __init__(self, bot):
21 self.bot = bot 23 self.bot = bot
22 self.temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "temp") 24 self.temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "temp")
@@ -83,38 +85,38 @@ class Play(commands.Cog):
83 85
84 await ctx.respond(embed=embed, view=view, ephemeral=True) 86 await ctx.respond(embed=embed, view=view, ephemeral=True)
85 87
88 def _get_display_name_from_metadata(self, path: str, filename: str) -> str:
89 """Extract display name from audio file metadata."""
90 try:
91 if os.path.splitext(path)[1].lower() in ['.mp3', '.wav', '.opus', '.m4a']:
92 metadata = mutagen.File(path, easy=True)
93
94 title = metadata.get('title', [filename])[0]
95 artist = metadata.get('artist', [''])[0]
96 album_artist = metadata.get('albumartist', [artist])[0]
97
98 if album_artist and title:
99 return f"{album_artist} - {title}"
100 elif artist and title:
101 return f"{artist} - {title}"
102 elif title:
103 return title
104 except Exception as e:
105 logger.debug(f"Could not read metadata for {filename}: {str(e)}")
106
107 return filename
108
86 async def _search_autocomplete(self, ctx: discord.AutocompleteContext) -> List[OptionChoice]: 109 async def _search_autocomplete(self, ctx: discord.AutocompleteContext) -> List[OptionChoice]:
87 """Autocomplete handler for search queries.""" 110 """Autocomplete handler for search queries."""
88 query = ctx.value.lower() 111 query = ctx.value.lower()
89 try: 112 try:
90 results = await play_utils.PlayUtils.search_local_autocomplete(query, max_results=25) 113 results = await search_utils.AudioSearcher.search_local_autocomplete(query, max_results=25)
91 choices = [] 114 choices = []
92 self.path_mapping = {} # Reset the mapping for each new query 115 self.path_mapping = {} # Reset the mapping for each new query
93 116
94 for path in results: 117 for path in results:
95 filename = os.path.splitext(os.path.basename(path))[0] # Remove extension 118 filename = os.path.splitext(os.path.basename(path))[0] # Remove extension
96 display = filename # Default display name 119 display = self._get_display_name_from_metadata(path, filename)
97
98 # Try to get metadata for display
99 try:
100 if os.path.splitext(path)[1].lower() in ['.mp3', '.wav', '.opus', '.m4a']:
101 metadata = mutagen.File(path, easy=True)
102
103 # Get metadata fields, falling back to filename if not available
104 title = metadata.get('title', [filename])[0]
105 artist = metadata.get('artist', [''])[0]
106 album_artist = metadata.get('albumartist', [artist])[0]
107
108 # Build display name based on available metadata
109 if album_artist and title:
110 display = f"{album_artist} - {title}"
111 elif artist and title:
112 display = f"{artist} - {title}"
113 elif title:
114 display = title
115
116 except Exception as e:
117 logger.debug(f"Could not read metadata for {filename}: {str(e)}")
118 120
119 # Clean up the display name (remove any extra whitespace or brackets) 121 # Clean up the display name (remove any extra whitespace or brackets)
120 display = display.strip() 122 display = display.strip()
@@ -138,6 +140,55 @@ class Play(commands.Cog):
138 logger.error(f"Error in autocomplete: {str(e)}", exc_info=True) 140 logger.error(f"Error in autocomplete: {str(e)}", exc_info=True)
139 return [] 141 return []
140 142
143 def _validate_play_inputs(self, file: str, stream: str, ctx: discord.ApplicationContext) -> bool:
144 """Validate input parameters for play command."""
145 if not file and not stream:
146 ctx.respond("Musisz podać plik do odtworzenia lub adres streamu.", ephemeral=True)
147 return False
148
149 if file and stream:
150 ctx.respond("Możesz podać tylko plik LUB stream, nie oba naraz.", ephemeral=True)
151 return False
152
153 return True
154
155 async def _handle_stream_play(self, ctx: discord.ApplicationContext, stream: str) -> None:
156 """Handle internet stream playback."""
157 await audio_playback.AudioPlaybackManager.play(ctx, stream)
158
159 async def _handle_compressed_file_play(self, ctx: discord.ApplicationContext, full_path: str) -> None:
160 """Handle compressed file playback with cleanup."""
161 compressed_path = await media_processing.AudioCompressor.compress(ctx, full_path)
162 if not compressed_path:
163 await ctx.respond("Nie udało się skompresować pliku.", ephemeral=True)
164 return
165
166 try:
167 await audio_playback.AudioPlaybackManager.play(ctx, compressed_path, delete_after_play=True)
168 except Exception as e:
169 # Clean up the compressed file if play fails
170 if os.path.exists(compressed_path):
171 try:
172 os.remove(compressed_path)
173 logger.info(f"Cleaned up temporary file after play error: {compressed_path}")
174 except Exception as cleanup_error:
175 logger.error(f"Error cleaning up file {compressed_path}: {str(cleanup_error)}")
176 raise e
177
178 async def _handle_local_file_play(self, ctx: discord.ApplicationContext, file: str, compressed: bool) -> None:
179 """Handle local file playback."""
180 full_path = self.path_mapping.get(file, file)
181
182 if not os.path.exists(full_path):
183 logger.warning(f"File not found: {full_path} (display name: {file})")
184 await ctx.respond("Wybrany plik nie istnieje.", ephemeral=True)
185 return
186
187 if compressed:
188 await self._handle_compressed_file_play(ctx, full_path)
189 else:
190 await audio_playback.AudioPlaybackManager.play(ctx, full_path)
191
141 @discord.slash_command(name="grajcuj", description="grajcujmy wszyscy!") 192 @discord.slash_command(name="grajcuj", description="grajcujmy wszyscy!")
142 async def play( 193 async def play(
143 self, 194 self,
@@ -162,48 +213,14 @@ class Play(commands.Cog):
162 """Odtwarza wybrany plik dźwiękowy lub stream internetowy.""" 213 """Odtwarza wybrany plik dźwiękowy lub stream internetowy."""
163 await ctx.defer() 214 await ctx.defer()
164 215
165 # Check if either file or stream is provided 216 if not self._validate_play_inputs(file, stream, ctx):
166 if not file and not stream:
167 await ctx.respond("Musisz podać plik do odtworzenia lub adres streamu.", ephemeral=True)
168 return
169
170 if file and stream:
171 await ctx.respond("Możesz podać tylko plik LUB stream, nie oba naraz.", ephemeral=True)
172 return 217 return
173 218
174 try: 219 try:
175 if stream: 220 if stream:
176 # Handle internet stream 221 await self._handle_stream_play(ctx, stream)
177 await play_utils.PlayUtils(self.bot).play(ctx, stream)
178 else: 222 else:
179 # Handle local file 223 await self._handle_local_file_play(ctx, file, compressed)
180 full_path = self.path_mapping.get(file, file)
181
182 if not os.path.exists(full_path):
183 logger.warning(f"File not found: {full_path} (display name: {file})")
184 await ctx.respond("Wybrany plik nie istnieje.", ephemeral=True)
185 return
186
187 if compressed:
188 # Compress the file before playing
189 compressed_path = await play_utils.PlayUtils(self.bot).compress(ctx, full_path)
190 if not compressed_path:
191 await ctx.respond("Nie udało się skompresować pliku.", ephemeral=True)
192 return
193
194 try:
195 await play_utils.PlayUtils(self.bot).play(ctx, compressed_path, delete_after_play=True)
196 except Exception as e:
197 # Clean up the compressed file if play fails
198 if os.path.exists(compressed_path):
199 try:
200 os.remove(compressed_path)
201 logger.info(f"Cleaned up temporary file after play error: {compressed_path}")
202 except Exception as cleanup_error:
203 logger.error(f"Error cleaning up file {compressed_path}: {str(cleanup_error)}")
204 raise e
205 else:
206 await play_utils.PlayUtils(self.bot).play(ctx, full_path)
207 except Exception as e: 224 except Exception as e:
208 logger.error(f"Error playing: {str(e)}", exc_info=True) 225 logger.error(f"Error playing: {str(e)}", exc_info=True)
209 await ctx.respond(f"Wystąpił błąd podczas odtwarzania: {str(e)}", ephemeral=True) 226 await ctx.respond(f"Wystąpił błąd podczas odtwarzania: {str(e)}", ephemeral=True)
@@ -213,7 +230,7 @@ class Play(commands.Cog):
213 await ctx.defer() 230 await ctx.defer()
214 file_path: PathLike[str] | str = os.path.join(self.temp_dir, attachment.filename) 231 file_path: PathLike[str] | str = os.path.join(self.temp_dir, attachment.filename)
215 await attachment.save(file_path) 232 await attachment.save(file_path)
216 await play_utils.PlayUtils(self.bot).play(ctx, file_path) 233 await audio_playback.AudioPlaybackManager.play(ctx, file_path)
217 234
218def setup(bot): 235def setup(bot):
219 bot.add_cog(Play(bot)) \ No newline at end of file 236 bot.add_cog(PlayCommands(bot))