aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/music
diff options
context:
space:
mode:
Diffstat (limited to 'cogs/music')
-rw-r--r--cogs/music/audio_playback.py42
-rw-r--r--cogs/music/media_processing.py2
-rw-r--r--cogs/music/play_commands.py10
3 files changed, 38 insertions, 16 deletions
diff --git a/cogs/music/audio_playback.py b/cogs/music/audio_playback.py
index 28c6118..1313d01 100644
--- a/cogs/music/audio_playback.py
+++ b/cogs/music/audio_playback.py
@@ -137,8 +137,16 @@ class AudioPlaybackManager:
137 return absolute_source, display_name, is_url 137 return absolute_source, display_name, is_url
138 138
139 @staticmethod 139 @staticmethod
140 async def execute_playback(ctx, source, display_name, is_url): 140 async def execute_playback(ctx, source, display_name, is_url, interrupt=True):
141 """Execute the actual audio playback.""" 141 """Execute the actual audio playback.
142
143 Args:
144 ctx: Discord context
145 source: Audio source path or URL
146 display_name: Name to display
147 is_url: Whether source is a URL
148 interrupt: If True, stop any currently playing audio. If False, skip if audio is playing.
149 """
142 target_channel = await AudioPlaybackManager.validate_voice_channel(ctx) 150 target_channel = await AudioPlaybackManager.validate_voice_channel(ctx)
143 if not target_channel: 151 if not target_channel:
144 return False 152 return False
@@ -169,9 +177,12 @@ class AudioPlaybackManager:
169 logger.info(f"Moving to voice channel: {target_channel.name}") 177 logger.info(f"Moving to voice channel: {target_channel.name}")
170 await vc.move_to(target_channel) 178 await vc.move_to(target_channel)
171 179
172 # Stop any currently playing audio 180 # Stop any currently playing audio (if interrupt is True)
173 if vc.is_playing() or vc.is_paused(): # type: ignore[union-attr] 181 if interrupt and (vc.is_playing() or vc.is_paused()): # type: ignore[union-attr]
174 vc.stop() # type: ignore[union-attr] 182 vc.stop() # type: ignore[union-attr]
183 elif not interrupt and vc.is_playing(): # type: ignore[union-attr]
184 logger.info("Audio already playing and interrupt=False, skipping playback")
185 return False
175 186
176 # Prepare audio source 187 # Prepare audio source
177 ( 188 (
@@ -181,13 +192,14 @@ class AudioPlaybackManager:
181 source, is_url, display_name 192 source, is_url, display_name
182 ) 193 )
183 194
184 # Track if playback failed 195 # Track playback state
185 playback_failed = {"error": None} 196 playback_state = {"error": None, "finished": False}
186 197
187 def after_playing(error): 198 def after_playing(error):
188 if error: 199 if error:
189 playback_failed["error"] = str(error) 200 playback_state["error"] = str(error)
190 logger.error(f"Playback error: {error}") 201 logger.error(f"Playback error: {error}")
202 playback_state["finished"] = True
191 203
192 # Play the audio 204 # Play the audio
193 vc.play(audio_source, after=after_playing) 205 vc.play(audio_source, after=after_playing)
@@ -197,14 +209,19 @@ class AudioPlaybackManager:
197 # Wait for FFmpeg to attempt connection - errors usually happen within 5 seconds 209 # Wait for FFmpeg to attempt connection - errors usually happen within 5 seconds
198 await asyncio.sleep(5) 210 await asyncio.sleep(5)
199 211
200 # Check if playback actually started 212 # Check if playback failed to start (not just finished quickly)
201 if playback_failed["error"]: 213 if playback_state["error"]:
202 logger.error(f"Playback failed: {playback_failed['error']}") 214 logger.error(f"Playback failed: {playback_state['error']}")
203 await ctx.followup.send( 215 await ctx.followup.send(
204 "eror: Timeout - nie udało się połączyć ze streamem", ephemeral=True 216 "eror: Timeout - nie udało się połączyć ze streamem", ephemeral=True
205 ) 217 )
206 return False 218 return False
207 219
220 # If playback finished within 5s, it was a short file - not an error
221 if playback_state["finished"]:
222 logger.info(f"Playback completed quickly: {final_display_name}")
223 return True
224
208 if not vc.is_playing(): 225 if not vc.is_playing():
209 logger.error(f"Stream failed - not playing after 5s: {source}") 226 logger.error(f"Stream failed - not playing after 5s: {source}")
210 await ctx.followup.send( 227 await ctx.followup.send(
@@ -232,13 +249,14 @@ class AudioPlaybackManager:
232 return prepared_source, display_name, is_url 249 return prepared_source, display_name, is_url
233 250
234 @staticmethod 251 @staticmethod
235 async def play(ctx, source, delete_after_play=False): 252 async def play(ctx, source, delete_after_play=False, interrupt=True):
236 """Play audio from a local file or URL. 253 """Play audio from a local file or URL.
237 254
238 Args: 255 Args:
239 ctx: Discord context 256 ctx: Discord context
240 source: Either a local file path or a URL to stream from 257 source: Either a local file path or a URL to stream from
241 delete_after_play: If True, delete the file after playing (only for local files) 258 delete_after_play: If True, delete the file after playing (only for local files)
259 interrupt: If True, stop any currently playing audio. If False, skip if audio is playing.
242 """ 260 """
243 # Validate and prepare source 261 # Validate and prepare source
244 ( 262 (
@@ -254,7 +272,7 @@ class AudioPlaybackManager:
254 272
255 # Execute playback 273 # Execute playback
256 success = await AudioPlaybackManager.execute_playback( 274 success = await AudioPlaybackManager.execute_playback(
257 ctx, prepared_source, display_name, is_url 275 ctx, prepared_source, display_name, is_url, interrupt
258 ) 276 )
259 277
260 # Cleanup if needed 278 # Cleanup if needed
diff --git a/cogs/music/media_processing.py b/cogs/music/media_processing.py
index b9b814c..91bd406 100644
--- a/cogs/music/media_processing.py
+++ b/cogs/music/media_processing.py
@@ -9,6 +9,7 @@ import os
9 9
10import yt_dlp 10import yt_dlp
11from youtubesearchpython import VideosSearch 11from youtubesearchpython import VideosSearch
12from cogs.utils.url_helper import clean_url_for_ytdlp
12 13
13logger = logging.getLogger(__name__) 14logger = logging.getLogger(__name__)
14 15
@@ -36,6 +37,7 @@ class MediaDownloader:
36 } 37 }
37 38
38 try: 39 try:
40 url = clean_url_for_ytdlp(url)
39 with yt_dlp.YoutubeDL(ytdl_opts) as ydl: 41 with yt_dlp.YoutubeDL(ytdl_opts) as ydl:
40 info_dict = ydl.extract_info(url, download=True) 42 info_dict = ydl.extract_info(url, download=True)
41 # Get the base filename without extension 43 # Get the base filename without extension
diff --git a/cogs/music/play_commands.py b/cogs/music/play_commands.py
index 5373436..bdb021d 100644
--- a/cogs/music/play_commands.py
+++ b/cogs/music/play_commands.py
@@ -18,6 +18,8 @@ from discord.ext import commands
18 18
19from cogs.music import audio_playback, media_processing 19from cogs.music import audio_playback, media_processing
20from cogs.utils.file_search import UniversalSearcher 20from cogs.utils.file_search import UniversalSearcher
21from cogs.utils.settings import get_uploadurl
22from cogs.utils.url_helper import clean_url_for_ytdlp
21 23
22logger = logging.getLogger(__name__) 24logger = logging.getLogger(__name__)
23 25
@@ -192,6 +194,7 @@ class PlayCommands(commands.Cog):
192 } 194 }
193 195
194 loop = asyncio.get_event_loop() 196 loop = asyncio.get_event_loop()
197 url = clean_url_for_ytdlp(url)
195 with YoutubeDL(ytdl_opts) as ydl: 198 with YoutubeDL(ytdl_opts) as ydl:
196 info_dict = await loop.run_in_executor( 199 info_dict = await loop.run_in_executor(
197 None, lambda: ydl.extract_info(url, download=True) 200 None, lambda: ydl.extract_info(url, download=True)
@@ -229,14 +232,13 @@ class PlayCommands(commands.Cog):
229 232
230 try: 233 try:
231 logger.info(f"Starting async upload: {file_path}") 234 logger.info(f"Starting async upload: {file_path}")
232 baseurl = os.getenv("COPYPARTY_URL_UPLOAD") 235 baseurl = get_uploadurl()
233 password = os.getenv("COPYPARTY_PASSWORD", "") 236 password = os.getenv("COPYPARTY_PASSWORD", "")
234 237
235 logger.info(f"Upload - baseurl: {baseurl}, password set: {bool(password)}") 238 logger.info(f"Upload - baseurl: {baseurl}, password set: {bool(password)}")
236 239
237 if not baseurl: 240 if baseurl == "http://localhost:3939":
238 logger.error("COPYPARTY_URL_UPLOAD not set!") 241 logger.warning("COPYPARTY_URL_UPLOAD not set! Using default")
239 return
240 242
241 with open(file_path, "rb") as f: 243 with open(file_path, "rb") as f:
242 content = f.read() 244 content = f.read()