diff options
| author | salmonik <raisersoft@proton.me> | 2026-04-29 14:47:32 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-04-29 14:47:32 +0200 |
| commit | 8a4352ebb5e15ff4be82a9b4e111c1535aab349e (patch) | |
| tree | 2c4d5b4b65bdc8308b35c411cffa7f17b1bfeb4d /cogs/music | |
| parent | fix: fallback to plaintext if no json is provided for assistant.py (diff) | |
| parent | proper context for settings (diff) | |
| download | senator-sperma-8a4352ebb5e15ff4be82a9b4e111c1535aab349e.tar.gz senator-sperma-8a4352ebb5e15ff4be82a9b4e111c1535aab349e.zip | |
Merge branch 'dev'
Signed-off-by: salmonik <raisersoft@proton.me>
Diffstat (limited to 'cogs/music')
| -rw-r--r-- | cogs/music/audio_playback.py | 42 | ||||
| -rw-r--r-- | cogs/music/media_processing.py | 2 | ||||
| -rw-r--r-- | cogs/music/play_commands.py | 10 |
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 | ||
| 10 | import yt_dlp | 10 | import yt_dlp |
| 11 | from youtubesearchpython import VideosSearch | 11 | from youtubesearchpython import VideosSearch |
| 12 | from cogs.utils.url_helper import clean_url_for_ytdlp | ||
| 12 | 13 | ||
| 13 | logger = logging.getLogger(__name__) | 14 | logger = 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 | ||
| 19 | from cogs.music import audio_playback, media_processing | 19 | from cogs.music import audio_playback, media_processing |
| 20 | from cogs.utils.file_search import UniversalSearcher | 20 | from cogs.utils.file_search import UniversalSearcher |
| 21 | from cogs.utils.settings import get_uploadurl | ||
| 22 | from cogs.utils.url_helper import clean_url_for_ytdlp | ||
| 21 | 23 | ||
| 22 | logger = logging.getLogger(__name__) | 24 | logger = 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() |
