aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cogs/music/play.py44
-rw-r--r--cogs/music/play_utils.py141
-rw-r--r--main.py22
3 files changed, 189 insertions, 18 deletions
diff --git a/cogs/music/play.py b/cogs/music/play.py
index 7066ae8..b7d409d 100644
--- a/cogs/music/play.py
+++ b/cogs/music/play.py
@@ -1,4 +1,8 @@
1import os
2
1import discord 3import discord
4import mutagen
5
2from . import play_utils 6from . import play_utils
3from discord.ext import commands 7from discord.ext import commands
4import logging 8import logging
@@ -10,13 +14,39 @@ class Play(commands.Cog):
10 self.bot = bot 14 self.bot = bot
11 15
12 @discord.slash_command(name="grajcuj", description="grajcujmy wszyscy!") 16 @discord.slash_command(name="grajcuj", description="grajcujmy wszyscy!")
13 async def play(self, ctx: discord.ApplicationContext, query: str = discord.Option(str, "co zagrać??", required=True)): 17 async def play(
14 await ctx.defer() 18 self,
15 url, title = await play_utils.PlayUtils(self.bot).get_url(ctx, query) 19 ctx: discord.ApplicationContext,
16 logger.info(f"Found video: {title}") 20 query: str = discord.Option(str, "co zagrać??", required=True),
17 filename = await play_utils.PlayUtils(self.bot).download(ctx, url) 21 source: str = discord.Option(str, "co zagrać??", required=True, choices=["local", "soundcloud", "youtube"])
18 logger.info(f"Downloaded file: {filename}") 22
19 await play_utils.PlayUtils(self.bot).play(ctx, filename, title) 23 ):
24 await ctx.defer(ephemeral=True)
25 if source == "local":
26 candidates = await play_utils.PlayUtils(self.bot).search_local(query)
27 if candidates:
28 embed = discord.Embed(title="Wyniki wyszukiwania", description="Wybierz plik do odtworzenia:", color=discord.Color.blue())
29 view = discord.ui.View(disable_on_timeout=True)
30 for i, candidate in enumerate(candidates, start=1):
31 filename = os.path.basename(candidate)
32 metadata = None
33 if os.path.splitext(filename)[1].lower() in ['.mp3', '.wav', '.opus']:
34 metadata = mutagen.File(candidate)
35 title = metadata.get('title', None)
36 if metadata and title:
37 embed.add_field(name=f"{i}. {title}", value="\u200b", inline=False)
38 else:
39 embed.add_field(name=f"{i}. {filename}", value="\u200b", inline=False)
40 button = discord.ui.Button(label=f"Play {i}")
41 async def button_callback(interaction: discord.Interaction, candidate=candidate):
42 await play_utils.PlayUtils(self.bot).play(ctx, candidate)
43 button.callback = button_callback
44 view.add_item(button)
45 await ctx.respond(embed=embed, view=view, ephemeral=True)
46 else:
47 await ctx.respond("Nie znaleziono pliku do odtworzenia", ephemeral=True)
48
49
20 50
21 @discord.slash_command(name="grajcuj_zkompresowane", description="grajcujmy wszyscy w wysokiej jakosci!") 51 @discord.slash_command(name="grajcuj_zkompresowane", description="grajcujmy wszyscy w wysokiej jakosci!")
22 async def play_compressed(self, ctx: discord.ApplicationContext, url: str = discord.Option(str, "co zagrać??", required=True)): 52 async def play_compressed(self, ctx: discord.ApplicationContext, url: str = discord.Option(str, "co zagrać??", required=True)):
diff --git a/cogs/music/play_utils.py b/cogs/music/play_utils.py
index b837ba0..bc20d7b 100644
--- a/cogs/music/play_utils.py
+++ b/cogs/music/play_utils.py
@@ -1,4 +1,7 @@
1import json
1import logging 2import logging
3
4import mutagen
2from youtubesearchpython import VideosSearch 5from youtubesearchpython import VideosSearch
3import yt_dlp 6import yt_dlp
4import discord 7import discord
@@ -7,11 +10,29 @@ import os
7 10
8logger = logging.getLogger(__name__) 11logger = logging.getLogger(__name__)
9 12
13class Utils:
14 @staticmethod
15 def levenshtein_distance(s1, s2):
16 if len(s1) > len(s2):
17 s1, s2 = s2, s1
18 distances = range(len(s1) + 1)
19 for i2, c2 in enumerate(s2):
20 distances_ = [i2+1]
21 for i1, c1 in enumerate(s1):
22 if c1 == c2:
23 distances_.append(distances[i1])
24 else:
25 distances_.append(
26 min(distances[i1], distances[i1 + 1], distances_[-1]) + 1)
27 distances = distances_
28 return distances[-1]
29
10class PlayUtils: 30class PlayUtils:
11 def __init__(self, bot): 31 def __init__(self, bot):
12 self.bot = bot 32 self.bot = bot
13 33
14 async def download(self, ctx, url): 34 @staticmethod
35 async def download(ctx, url):
15 36
16 # Create temp directory if it doesn't exist 37 # Create temp directory if it doesn't exist
17 temp_dir = os.path.join('cogs', 'temp', 'music') 38 temp_dir = os.path.join('cogs', 'temp', 'music')
@@ -42,7 +63,8 @@ class PlayUtils:
42 logger.error(f"Error downloading video: {e}") 63 logger.error(f"Error downloading video: {e}")
43 await ctx.respond("eror pobirajync wideło", ephemeral=True) 64 await ctx.respond("eror pobirajync wideło", ephemeral=True)
44 65
45 async def get_url(self, ctx, query): 66 @staticmethod
67 async def get_url(ctx, query):
46 search = VideosSearch(query, limit=1) 68 search = VideosSearch(query, limit=1)
47 if search.result()["result"]: 69 if search.result()["result"]:
48 title = search.result()["result"][0]["title"] 70 title = search.result()["result"][0]["title"]
@@ -54,7 +76,8 @@ class PlayUtils:
54 await ctx.respond("nic ni ma", ephemeral=True) 76 await ctx.respond("nic ni ma", ephemeral=True)
55 return None 77 return None
56 78
57 async def compress(self, ctx, filename): 79 @staticmethod
80 async def compress(ctx, filename):
58 if not filename: 81 if not filename:
59 await ctx.respond("dzie wideło", ephemeral=True) 82 await ctx.respond("dzie wideło", ephemeral=True)
60 return 83 return
@@ -76,12 +99,29 @@ class PlayUtils:
76 logger.error(f"Error compressing file: {e}") 99 logger.error(f"Error compressing file: {e}")
77 await ctx.respond("eror kompresujync pilk", ephemeral=True) 100 await ctx.respond("eror kompresujync pilk", ephemeral=True)
78 101
79 async def play(self, ctx, filename, title): 102 @staticmethod
103 async def play(ctx, filepath):
80 # Check if user is in a voice channel 104 # Check if user is in a voice channel
81 if not ctx.author.voice or not ctx.author.voice.channel: 105 if not ctx.author.voice or not ctx.author.voice.channel:
82 await ctx.respond("Musisz być na kanale głosowym, żebym mógł dołączyć!", ephemeral=True) 106 await ctx.respond("Musisz być na kanale głosowym, żebym mógł dołączyć!", ephemeral=True)
83 return 107 return
84 108
109 logger.info(f"Attempting to play: {filepath}")
110
111 # Get absolute path and verify file exists
112 if not os.path.isabs(filepath):
113 filepath = os.path.abspath(filepath)
114 logger.debug(f"Converted to absolute path: {filepath}")
115
116 if not os.path.exists(filepath):
117 error_msg = f"File not found: {filepath}"
118 logger.error(error_msg)
119 await ctx.respond("Nie znaleziono pliku do odtworzenia", ephemeral=True)
120 return
121
122 filename = os.path.basename(filepath)
123 logger.info(f"Playing file: {filename} (from: {filepath})")
124
85 # Get or create voice client 125 # Get or create voice client
86 vc = ctx.voice_client 126 vc = ctx.voice_client
87 target_channel = ctx.author.voice.channel 127 target_channel = ctx.author.voice.channel
@@ -89,29 +129,110 @@ class PlayUtils:
89 try: 129 try:
90 # Connect to voice channel if not connected 130 # Connect to voice channel if not connected
91 if vc is None: 131 if vc is None:
132 logger.info(f"Connecting to voice channel: {target_channel.name}")
92 vc = await target_channel.connect() 133 vc = await target_channel.connect()
93 logger.info(f"Connected to voice channel: {target_channel.name}") 134 logger.info(f"Connected to voice channel: {target_channel.name}")
94 # Move to user's channel if in a different one 135 # Move to user's channel if in a different one
95 elif vc.channel != target_channel: 136 elif vc.channel != target_channel:
137 logger.info(f"Moving to voice channel: {target_channel.name}")
96 await vc.move_to(target_channel) 138 await vc.move_to(target_channel)
97 logger.info(f"Moved to voice channel: {target_channel.name}") 139 logger.info(f"Moved to voice channel: {target_channel.name}")
98 140
99 if not filename or not os.path.exists(filename):
100 await ctx.respond("Nie znaleziono pliku do odtworzenia", ephemeral=True)
101 logger.warning(f"No file to play, filename: {filename}")
102 return
103
104 # Stop any currently playing audio 141 # Stop any currently playing audio
105 if vc.is_playing() or vc.is_paused(): 142 if vc.is_playing() or vc.is_paused():
106 vc.stop() 143 vc.stop()
107 144
108 # Play the audio 145 # Play the audio
109 source = discord.FFmpegPCMAudio(source=filename) 146 source = discord.FFmpegPCMAudio(source=filepath)
110 vc.play(source) 147 vc.play(source)
111 148
149 try:
150 metadata = mutagen.File(filepath)
151 title = metadata.get('title', None)
152 logger.debug(f"Read metadata - title: {title}")
153 except Exception as e:
154 logger.warning(f"Could not read metadata from {filepath}: {str(e)}")
155 title = None
156
157 if title is None:
158 title = os.path.splitext(os.path.basename(filepath))[0]
159 logger.debug(f"Using filename as title: {title}")
160
112 logger.info(f"Playing file: {title}") 161 logger.info(f"Playing file: {title}")
113 await ctx.respond(f"grom: {title}") 162 await ctx.respond(f"grom: {title}")
114 163
115 except Exception as e: 164 except Exception as e:
116 logger.error(f"Error in play: {str(e)}", exc_info=True) 165 logger.error(f"Error in play: {str(e)}", exc_info=True)
117 await ctx.respond(f"eror: {str(e)}", ephemeral=True) 166 await ctx.respond(f"eror: {str(e)}", ephemeral=True)
167
168 # -- LOCAL PLAY --
169
170 @staticmethod
171 async def search_local(query):
172 logger.info(f"Starting local search for query: '{query}'")
173 try:
174 with open('settings.json') as f:
175 data = json.load(f)
176 paths = data.get('audio_paths', [])
177 logger.info(f"Searching in paths: {paths}")
178 candidates = []
179 files_checked = 0
180
181 for path in paths:
182 if not os.path.exists(path):
183 logger.warning(f"Path does not exist: {path}")
184 continue
185
186 logger.debug(f"Searching in directory: {path}")
187 # Walk through all subdirectories
188 for root, dirs, files in os.walk(path):
189 logger.debug(f"Scanning directory: {root} (contains {len(files)} files)")
190 for file in files:
191 files_checked += 1
192 # Check if file has a valid extension
193 if file.lower().endswith(('.mp4', '.mp3', '.wav', '.opus', '.m4a')):
194 # Get relative path from the search path
195 rel_path = os.path.relpath(root, path)
196
197 # Get filename without extension
198 filename_without_ext = os.path.splitext(file)[0]
199
200 # Create searchable string with both filename and relative path
201 searchable_name = f"{os.path.join(rel_path, filename_without_ext)}".lower()
202
203 # Calculate Levenshtein distance for the searchable string
204 distance = Utils.levenshtein_distance(query.lower(), searchable_name)
205
206 # Also check just the filename for better matching of partial queries
207 filename_distance = Utils.levenshtein_distance(query.lower(), filename_without_ext.lower())
208
209 # Use the better (lower) distance score
210 final_distance = min(distance, filename_distance)
211
212 # Add to candidates with distance and full path
213 file_path = os.path.abspath(os.path.join(root, file))
214 candidates.append((final_distance, file_path))
215 logger.debug(f"Found match: {file_path} (distance: {distance})")
216
217 logger.info(f"Search complete. Checked {files_checked} files, found {len(candidates)} audio files.")
218
219 if not candidates:
220 logger.warning("No matching audio files found.")
221 return []
222
223 # Sort by distance and get top 5 matches
224 sorted_candidates = sorted(candidates, key=lambda x: x[0])
225 top_matches = sorted_candidates[:5]
226
227 logger.info(f"Top {len(top_matches)} matches:")
228 for i, (distance, path) in enumerate(top_matches, 1):
229 logger.info(f" {i}. {path} (distance: {distance})")
230
231 return [c[1] for c in top_matches]
232
233 except Exception as e:
234 logger.error(f"Error during local search: {str(e)}", exc_info=True)
235 return []
236
237
238
diff --git a/main.py b/main.py
index 708e04f..c038518 100644
--- a/main.py
+++ b/main.py
@@ -1,7 +1,7 @@
1import datetime
2import logging 1import logging
3import os 2import os
4import sys 3import sys
4import json
5 5
6# create a pycord bot template 6# create a pycord bot template
7import discord 7import discord
@@ -27,6 +27,26 @@ async def on_ready():
27 # send the message to channel ID 27 # send the message to channel ID
28 channel = bot.get_channel(1081667823799111804) 28 channel = bot.get_channel(1081667823799111804)
29 await channel.send("ŁUKASZ SPERMA AKTYWACJA") 29 await channel.send("ŁUKASZ SPERMA AKTYWACJA")
30 if not os.path.exists('settings.json'):
31 with open('settings.json', 'w') as f:
32 json.dump({
33 'volume': 1.0,
34 'audio_paths': ['audio', 'music', 'sounds']
35 }, f)
36 if os.path.exists('settings.json'):
37 try:
38 with open('settings.json') as f:
39 data = json.load(f)
40 assert 'volume' in data and isinstance(data['volume'], float)
41 assert 'audio_paths' in data and isinstance(data['audio_paths'], list)
42 except (json.JSONDecodeError, AssertionError) as e:
43 logger.error(f"Error loading settings.json: {e}")
44 with open('settings.json', 'w') as f:
45 json.dump({
46 'volume': 1.0,
47 'audio_paths': ['audio', 'music', 'sounds']
48 }, f)
49 logger.info("settings.json created")
30 50
31 51
32if __name__ == "__main__": 52if __name__ == "__main__":