from http import server import sqlite3 import random import discord import asyncio from .translate import search_song db_path = "./data/music.db" # Base FFmpeg options (will be modified by effects) BASE_FFMPEG_OPTS = { 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn' } # Audio effects configurations def get_effect_options(effect_name): """Get FFmpeg options for a specific audio effect""" effects = { 'none': { **BASE_FFMPEG_OPTS }, 'bassboost': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "bass=g=20,dynaudnorm"' }, 'nightcore': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "asetrate=48000*1.25,atempo=1.25,bass=g=5"' }, 'slowed': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "atempo=0.8,asetrate=48000*0.8,aecho=0.8:0.9:1000:0.3"' }, 'distortion': { **BASE_FFMPEG_OPTS, # Pure bitcrushing distortion 'options': '-vn -af "acrusher=bits=6:mix=0.9,acompressor=threshold=0.01:ratio=15"' }, 'reverse': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "areverse"' }, 'chipmunk': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "asetrate=48000*1.5,atempo=1.5"' }, 'demonic': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "asetrate=48000*0.7,atempo=0.7,aecho=0.8:0.9:1000:0.5"' }, 'underwater': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "lowpass=f=800,aecho=0.8:0.88:60:0.4"' }, 'robot': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "afftfilt=real=\'hypot(re,im)*sin(0)\':imag=\'hypot(re,im)*cos(0)\':win_size=512:overlap=0.75"' }, '8d': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "apulsator=hz=0.08"' }, 'vibrato': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "vibrato=f=7:d=0.5"' }, 'tremolo': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "tremolo=f=5:d=0.9"' }, 'echo': { **BASE_FFMPEG_OPTS, 'options': '-vn -af "aecho=0.8:0.88:60:0.4"' }, } return effects.get(effect_name, effects['none']) # Creates the tables if they don't exist def initialize_tables(): # Connect to the database conn = sqlite3.connect(db_path) cursor = conn.cursor() # Create servers table if it doesn't exist cursor.execute('''CREATE TABLE IF NOT EXISTS servers ( server_id TEXT PRIMARY KEY, is_playing INTEGER DEFAULT 0, song_name TEXT, loop_mode TEXT DEFAULT 'off', volume INTEGER DEFAULT 100, effect TEXT DEFAULT 'none', song_start_time REAL DEFAULT 0, song_duration INTEGER DEFAULT 0 );''') # Set all to not playing cursor.execute("UPDATE servers SET is_playing = 0;") # Add new columns if they don't exist (for existing databases) try: cursor.execute("ALTER TABLE servers ADD COLUMN loop_mode TEXT DEFAULT 'off';") except sqlite3.OperationalError: pass # Column already exists try: cursor.execute("ALTER TABLE servers ADD COLUMN volume INTEGER DEFAULT 100;") except sqlite3.OperationalError: pass # Column already exists try: cursor.execute("ALTER TABLE servers ADD COLUMN effect TEXT DEFAULT 'none';") except sqlite3.OperationalError: pass # Column already exists try: cursor.execute("ALTER TABLE servers ADD COLUMN song_start_time REAL DEFAULT 0;") except sqlite3.OperationalError: pass # Column already exists try: cursor.execute("ALTER TABLE servers ADD COLUMN song_duration INTEGER DEFAULT 0;") except sqlite3.OperationalError: pass # Column already exists # Create queue table if it doesn't exist cursor.execute('''CREATE TABLE IF NOT EXISTS songs ( server_id TEXT NOT NULL, song_link TEXT, queued_by TEXT, position INTEGER NOT NULL, title TEXT, thumbnail TEXT, duration INTEGER, PRIMARY KEY (position), FOREIGN KEY (server_id) REFERENCES servers(server_id) );''') # Clear all entries cursor.execute("DELETE FROM songs;") # Commit the changes and close the connection conn.commit() conn.close() # Queue a song in the db async def add_song(server_id, details, queued_by): # Connect to db conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) max_order_num = await get_max(server_id, cursor) + 1 if isinstance(details, str): cursor.execute(""" INSERT INTO songs (server_id, song_link, queued_by, position, title, thumbnail, duration) VALUES (?, ?, ?, ?, ?, ?, ?) """, (server_id, "Not grabbed", queued_by, max_order_num, details, "Unkown", "Unkown")) else: cursor.execute(""" INSERT INTO songs (server_id, song_link, queued_by, position, title, thumbnail, duration) VALUES (?, ?, ?, ?, ?, ?, ?) """, (server_id, details['url'], queued_by, max_order_num, details['title'], details['thumbnail'], details['duration'])) conn.commit() conn.close() return max_order_num # Pop song from server (respects loop mode) async def pop(server_id, ignore=False): # Connect to db conn = sqlite3.connect(db_path) cursor = conn.cursor() # JUST INCASE! await add_server(server_id, cursor, conn) cursor.execute('''SELECT * FROM songs WHERE server_id = ? ORDER BY position LIMIT 1;''', (server_id,)) result = cursor.fetchone() conn.commit() conn.close() if result == None: return None elif ignore: await mark_song_as_finished(server_id, result[3]) return None elif result[1] == "Not grabbed": # Fetch song info song = await search_song(result[4]) if song == []: return None else: song = song[0] await set_current_song(server_id, song['title'], song.get('duration', 0)) # Check loop mode before removing loop_mode = await get_loop_mode(server_id) if loop_mode != 'song': # Only remove if not looping song await mark_song_as_finished(server_id, result[3]) return song['url'] await set_current_song(server_id, result[4], result[6]) # result[6] is duration # Check loop mode before removing loop_mode = await get_loop_mode(server_id) if loop_mode != 'song': # Only remove if not looping song await mark_song_as_finished(server_id, result[3]) return result[1] # Add server to db if first time queuing async def add_server(server_id, cursor, conn): # Check if the server exists cursor.execute('''SELECT COUNT(*) FROM servers WHERE server_id = ?''', (server_id,)) result = cursor.fetchone() server_exists = result[0] > 0 # If the server doesn't exist, add it if not server_exists: cursor.execute('''INSERT INTO servers (server_id, loop_mode, volume, effect) VALUES (?, 'off', 100, 'none')''', (server_id,)) conn.commit() # set song as played and update indexes async def mark_song_as_finished(server_id, order_num): # Connect to the database conn = sqlite3.connect(db_path) cursor = conn.cursor() # Update the song as finished cursor.execute('''DELETE FROM songs WHERE server_id = ? AND position = ?''', (server_id, order_num)) # Close connection conn.commit() conn.close() # set the current playing song of the server async def set_current_song(server_id, title, duration=0): # Connect to the database conn = sqlite3.connect(db_path) cursor = conn.cursor() import time start_time = time.time() cursor.execute(''' UPDATE servers SET song_name = ?, song_start_time = ?, song_duration = ? WHERE server_id = ?''', (title, start_time, duration, server_id)) # Close connection conn.commit() conn.close() async def get_current_song(server_id): # Connect to the database conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute(''' SELECT song_name FROM servers WHERE server_id = ? LIMIT 1;''', (server_id,)) result = cursor.fetchone() # Close connection conn.commit() conn.close() return result[0] if result else "Nothing" async def get_current_progress(server_id): """Get current playback progress (elapsed, duration, percentage)""" conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute('''SELECT song_start_time, song_duration, is_playing FROM servers WHERE server_id = ? LIMIT 1;''', (server_id,)) result = cursor.fetchone() conn.close() if not result or result[2] == 0: # Not playing return 0, 0, 0.0 start_time, duration, _ = result if duration == 0: return 0, 0, 0.0 import time elapsed = int(time.time() - start_time) elapsed = min(elapsed, duration) # Cap at duration percentage = (elapsed / duration) * 100 if duration > 0 else 0 return elapsed, duration, percentage return result[0] if result else "Nothing" # Grab max order from server async def get_max(server_id, cursor): cursor.execute(f""" SELECT MAX(position) FROM songs WHERE server_id = ? """, (server_id,)) result = cursor.fetchone() # Highnest number or 0 max_order_num = result[0] if result[0] is not None else -1 return max_order_num # Sets the playing variable in a server to true or false async def update_server(server_id, playing: bool): # Connect to database conn = sqlite3.connect(db_path) cursor = conn.cursor() # add server to db if not present await add_server(server_id, cursor, conn) value = 1 if playing else 0 # Update field cursor.execute("""UPDATE servers SET is_playing = ? WHERE server_id = ? """, (value, server_id)) # Close connection conn.commit() conn.close() async def is_server_playing(server_id): # Connect to db conn = sqlite3.connect(db_path) cursor = conn.cursor() # add server to db if not present await add_server(server_id, cursor, conn) cursor.execute("""SELECT is_playing FROM servers WHERE server_id = ?""", (server_id,)) result = cursor.fetchone() conn.commit() conn.close() return True if result[0] == 1 else False # Delete all songs from a server async def clear(server_id): # Connect to db conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) await update_server(server_id, False) # Delete all songs from the server cursor.execute('''DELETE FROM songs WHERE server_id = ?''', (server_id,)) conn.commit() conn.close() # Grabs all songs from a server for display purposes async def grab_songs(server_id): # Connect to db conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) # Grabs all songs from the server cursor.execute('''SELECT title, duration, queued_by FROM songs WHERE server_id = ? ORDER BY position LIMIT 10''', (server_id,)) songs = cursor.fetchall() max = await get_max(server_id, cursor) conn.commit() conn.close() return max, songs # ============= LOOP/SHUFFLE/VOLUME FEATURES ============= # Get/Set loop mode async def get_loop_mode(server_id): """Get the current loop mode: 'off', 'song', or 'queue'""" conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) cursor.execute("""SELECT loop_mode FROM servers WHERE server_id = ?""", (server_id,)) result = cursor.fetchone() conn.commit() conn.close() return result[0] if result else 'off' async def set_loop_mode(server_id, mode): """Set loop mode: 'off', 'song', or 'queue'""" if mode not in ['off', 'song', 'queue']: return False conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) cursor.execute("""UPDATE servers SET loop_mode = ? WHERE server_id = ?""", (mode, server_id)) conn.commit() conn.close() return True # Get/Set volume async def get_volume(server_id): """Get the current volume (0-200)""" conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) cursor.execute("""SELECT volume FROM servers WHERE server_id = ?""", (server_id,)) result = cursor.fetchone() conn.commit() conn.close() return result[0] if result else 100 async def set_volume(server_id, volume): """Set volume (0-200)""" volume = max(0, min(200, volume)) # Clamp between 0-200 conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) cursor.execute("""UPDATE servers SET volume = ? WHERE server_id = ?""", (volume, server_id)) conn.commit() conn.close() return volume # Shuffle the queue async def shuffle_queue(server_id): """Randomize the order of songs in the queue""" conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) # Get all songs cursor.execute('''SELECT position, song_link, queued_by, title, thumbnail, duration FROM songs WHERE server_id = ? ORDER BY position''', (server_id,)) songs = cursor.fetchall() if len(songs) <= 1: conn.close() return False # Nothing to shuffle # Shuffle the songs (keep positions but randomize order) random.shuffle(songs) # Delete all current songs cursor.execute('''DELETE FROM songs WHERE server_id = ?''', (server_id,)) # Re-insert in shuffled order for i, song in enumerate(songs): cursor.execute("""INSERT INTO songs (server_id, song_link, queued_by, position, title, thumbnail, duration) VALUES (?, ?, ?, ?, ?, ?, ?)""", (server_id, song[1], song[2], i, song[3], song[4], song[5])) conn.commit() conn.close() return True # ============= AUDIO EFFECTS FEATURES ============= async def get_effect(server_id): """Get the current audio effect""" conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) cursor.execute("""SELECT effect FROM servers WHERE server_id = ?""", (server_id,)) result = cursor.fetchone() conn.commit() conn.close() return result[0] if result else 'none' async def set_effect(server_id, effect_name): """Set the audio effect""" conn = sqlite3.connect(db_path) cursor = conn.cursor() await add_server(server_id, cursor, conn) cursor.execute("""UPDATE servers SET effect = ? WHERE server_id = ?""", (effect_name, server_id)) conn.commit() conn.close() return True def list_all_effects(): """Return a list of all available effects""" return [ 'none', 'bassboost', 'nightcore', 'slowed', 'earrape', 'deepfry', 'distortion', 'reverse', 'chipmunk', 'demonic', 'underwater', 'robot', '8d', 'vibrato', 'tremolo', 'echo', 'phone', 'megaphone' ] def get_effect_emoji(effect_name): """Get emoji representation for each effect""" emojis = { 'none': '🔊', 'bassboost': '🔉💥', 'nightcore': '⚡🎀', 'slowed': '🐌💤', 'distortion': '⚡🔊', 'reverse': '⏪🔄', 'chipmunk': '🐿️', 'demonic': '😈🔥', 'underwater': '🌊💦', 'robot': '🤖', '8d': '🎧🌀', 'vibrato': '〰️', 'tremolo': '📳', 'echo': '🗣️💭', } return emojis.get(effect_name, '🔊') def get_effect_description(effect_name): """Get user-friendly description for each effect""" descriptions = { 'none': 'Normal audio', 'bassboost': 'MAXIMUM BASS 🔊', 'nightcore': 'Speed + pitch up (anime vibes)', 'slowed': 'Slowed + reverb (TikTok aesthetic)', 'distortion': 'Heavy bitcrushing distortion', 'reverse': 'Plays audio BACKWARDS', 'chipmunk': 'High pitched and fast (Alvin mode)', 'demonic': 'Low pitched and slow (cursed)', 'underwater': 'Muffled underwater sound', 'robot': 'Robotic vocoder', '8d': 'Panning audio (use headphones!)', 'vibrato': 'Warbling pitch effect', 'tremolo': 'Volume oscillation', 'echo': 'Echo/reverb effect', } return descriptions.get(effect_name, 'Unknown effect') # Play and loop songs in server async def play(ctx): """Main playback loop - plays songs from queue sequentially with effects""" server_id = ctx.guild.id voice_client = ctx.voice_client # Safety check if voice_client is None: await update_server(server_id, False) return # Wait until current song finishes while voice_client.is_playing(): await asyncio.sleep(0.5) # Get next song url = await pop(server_id) # If no songs left, update status and return if url is None: await update_server(server_id, False) return try: # Get volume and effect settings volume_percent = await get_volume(server_id) volume = volume_percent / 100.0 # Convert to 0.0-2.0 range current_effect = await get_effect(server_id) ffmpeg_opts = get_effect_options(current_effect) # Create audio source with effect and volume control audio_source = discord.FFmpegPCMAudio(url, **ffmpeg_opts) audio_source = discord.PCMVolumeTransformer(audio_source, volume=volume) # Play with callback to continue queue def after_playing(error): if error: print(f"Player error: {error}") # Schedule the next song in the event loop if voice_client and not voice_client.is_connected(): return coro = play(ctx) fut = asyncio.run_coroutine_threadsafe(coro, ctx.bot.loop) try: fut.result() except Exception as e: print(f"Error playing next song: {e}") voice_client.play(audio_source, after=after_playing) except Exception as e: print(f"Error starting playback: {e}") # Try to continue with next song await play(ctx)