I dont want the duration settings in the play command to be the time the bot stays in the channel, it should be the duration the sound is played. make sure its a optional input. but I also want a seperate command to change the durationa after the queue is empty. also in the queue, make it list the sounds tha tiwll come up with their duration

Code:
import discord
from discord import app_commands
from discord.ext import commands
import os
from dotenv import load_dotenv
import asyncio
from pydub import AudioSegment
import requests
import json
import logging

# Configure logging with timestamps
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s:%(levelname)s:%(name)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Load environment variables from .env
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

# Define intents
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
intents.voice_states = True

# Initialize the bot
class SoundboardBot(commands.Bot):
    def __init__(self):
        super().__init__(
            command_prefix="!",  # Prefix is not used for slash commands but required
            intents=intents,
        )
        self.queue = {}            # Dictionary to hold queues for each guild
        self.sounds = {}           # Dictionary to hold sound metadata
        self.disconnect_tasks = {} # Dictionary to hold disconnect tasks for each guild
        self.guild_durations = {}  # Dictionary to hold disconnect durations per guild

    async def setup_hook(self):
        # Sync the command tree with Discord
        await self.tree.sync()
        # Load sounds from JSON
        self.load_sounds()

    def load_sounds(self):
        sounds_file = os.path.join("data", "sounds.json")
        if os.path.exists(sounds_file):
            with open(sounds_file, "r") as f:
                self.sounds = json.load(f)
            logging.info("Loaded sounds from sounds.json")
        else:
            self.sounds = {}
            os.makedirs(os.path.dirname(sounds_file), exist_ok=True)
            with open(sounds_file, "w") as f:
                json.dump(self.sounds, f, indent=4)
            logging.info("Created new sounds.json")

    def save_sounds(self):
        sounds_file = os.path.join("data", "sounds.json")
        with open(sounds_file, "w") as f:
            json.dump(self.sounds, f, indent=4)
        logging.info("Saved sounds to sounds.json")


bot = SoundboardBot()

# Directory to store sound files
SOUND_DIR = "sounds"

# Ensure the sounds and data directories exist
for directory in [SOUND_DIR, "data"]:
    if not os.path.exists(directory):
        os.makedirs(directory)
        logging.info(f"Created directory: {directory}")

# Allowed audio formats
ALLOWED_EXTENSIONS = ('.mp3', '.wav', '.ogg', '.flac')
# Maximum file size (in bytes) for uploads via `/add` command
MAX_FILE_SIZE = 8 * 1024 * 1024  # 8MB

# Helper function to extract duration of audio file in seconds
def get_audio_duration(file_path):
    try:
        audio = AudioSegment.from_file(file_path)
        duration = len(audio) / 1000  # pydub works in milliseconds
        return round(duration, 2)
    except Exception as e:
        logging.error(f"Error getting duration of {file_path}: {e}")
        return None

# Helper function to download a file from a URL
async def download_file(url, dest):
    loop = asyncio.get_event_loop()
    try:
        response = requests.get(url, stream=True, timeout=120)
        response.raise_for_status()
        # Download in chunks to handle large files efficiently
        with open(dest, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        return True, ""
    except requests.exceptions.RequestException as e:
        return False, str(e)

# Helper function to handle disconnect after duration
async def monitor_queue(guild_id: int):
    duration = bot.guild_durations.get(guild_id, 60)  # Default to 60 seconds
    logging.info(f"Monitoring guild {guild_id} for disconnect in {duration} seconds.")
    await asyncio.sleep(duration)
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
    if voice_client and voice_client.is_connected():
        await voice_client.disconnect()
        logging.info(f"Disconnected from guild {guild_id} after {duration} seconds of inactivity.")

# Function to play the next sound in the queue
async def play_next(guild_id: int):
    if guild_id not in bot.queue:
        return

    if bot.queue[guild_id].empty():
        # Start a disconnect timer
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]
        disconnect_task = asyncio.create_task(monitor_queue(guild_id))
        bot.disconnect_tasks[guild_id] = disconnect_task
        return

    sound_file, repeat, duration_play = await bot.queue[guild_id].get()
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if not voice_client or not voice_client.is_connected():
        logging.warning(f"No active voice client in guild {guild_id}.")
        return

    if voice_client.is_playing():
        return

    # Play the sound with specified duration
    try:
        # Prepare FFmpeg options to limit playback duration
        before_options = f"-ss 0 -t {duration_play}"
        source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options="-vn")
        voice_client.play(source, after=lambda e: asyncio.run_coroutine_threadsafe(play_next(guild_id), bot.loop))
        sound_name = os.path.splitext(os.path.basename(sound_file))[0]
        logging.info(f"Playing sound '{sound_name}' in guild {guild_id}. Repeat left: {repeat}")
    except Exception as e:
        logging.error(f"Error playing sound {sound_file} in guild {guild_id}: {e}")
        # Proceed to next sound
        await play_next(guild_id)

# 🔹 Slash Command: Add a sound via file upload
@bot.tree.command(name="add", description="Add a new sound to the soundboard.")
@app_commands.describe(
    name="Name for the sound",
    file="Audio file to upload (MP3, WAV, OGG, FLAC)"
)
async def add_sound(interaction: discord.Interaction, name: str, file: discord.Attachment):
    await interaction.response.defer(ephemeral=True)

    # Validate file extension
    if not any(file.filename.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS):
        await interaction.followup.send(
            f"❌ Unsupported file format. Please upload a file with one of the following extensions: {', '.join(ALLOWED_EXTENSIONS)}.",
            ephemeral=True
        )
        return

    # Validate file size
    if file.size > MAX_FILE_SIZE:
        await interaction.followup.send(
            f"❌ File size exceeds the 8MB limit.",
            ephemeral=True
        )
        return

    # Sanitize the sound name to prevent filesystem issues
    safe_name = "".join([c if c.isalnum() or c in (' ', '_') else '_' for c in name]).strip()
    if not safe_name:
        await interaction.followup.send(
            "❌ Invalid sound name. Please use alphanumeric characters, spaces, or underscores.",
            ephemeral=True
        )
        return

    file_extension = os.path.splitext(file.filename)[1]
    file_path = os.path.join(SOUND_DIR, f"{safe_name}{file_extension}")

    # Check for name conflicts
    if any(safe_name.lower() == existing.lower() for existing in bot.sounds.keys()):
        await interaction.followup.send(
            f"❌ A sound with the name `{safe_name}` already exists.",
            ephemeral=True
        )
        return

    try:
        # Save the uploaded file
        await file.save(file_path)

        # Extract duration
        duration = get_audio_duration(file_path)
        if duration is None:
            os.remove(file_path)
            await interaction.followup.send(
                "❌ Failed to extract duration from the audio file. Ensure it's a valid audio file.",
                ephemeral=True
            )
            return

        # Add sound metadata
        bot.sounds[safe_name] = {
            "file_path": file_path,
            "duration": duration
        }
        bot.save_sounds()

        # Confirm addition with an embed
        embed = discord.Embed(
            title="✅ Sound Added ✅",
            description=f"Sound `{safe_name}` has been added successfully.",
            color=discord.Color.green()
        )
        embed.set_thumbnail(url=interaction.user.avatar.url if interaction.user.avatar else discord.Embed.Empty)
        embed.add_field(name="File", value=f"[{file.filename}]({file.url})", inline=False)
        embed.add_field(name="Duration", value=f"{duration} seconds", inline=False)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to add sound: {e}")
        await interaction.followup.send(f"❌ Failed to add sound: {e}", ephemeral=True)

# 🔹 Slash Command: Add a sound from a URL (no file upload limit)
@bot.tree.command(name="add_from_url", description="Add a new sound to the soundboard by downloading from a URL.")
@app_commands.describe(
    name="Name for the sound",
    url="Direct URL to the audio file (MP3, WAV, OGG, FLAC)"
)
async def add_from_url(interaction: discord.Interaction, name: str, url: str):
    await interaction.response.defer(ephemeral=True)

    # Validate file extension
    if not any(url.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS):
        await interaction.followup.send(
            f"❌ Unsupported file format. Please provide a URL ending with one of the following extensions: {', '.join(ALLOWED_EXTENSIONS)}.",
            ephemeral=True
        )
        return

    # Sanitize the sound name
    safe_name = "".join([c if c.isalnum() or c in (' ', '_') else '_' for c in name]).strip()
    if not safe_name:
        await interaction.followup.send(
            "❌ Invalid sound name. Please use alphanumeric characters, spaces, or underscores.",
            ephemeral=True
        )
        return

    file_extension = os.path.splitext(url)[1]
    file_path = os.path.join(SOUND_DIR, f"{safe_name}{file_extension}")

    # Check for name conflicts
    if any(safe_name.lower() == existing.lower() for existing in bot.sounds.keys()):
        await interaction.followup.send(
            f"❌ A sound with the name `{safe_name}` already exists.",
            ephemeral=True
        )
        return

    try:
        # Download the file
        success, error = await download_file(url, file_path)
        if not success:
            await interaction.followup.send(
                f"❌ Failed to download the file: {error}",
                ephemeral=True
            )
            return

        # Extract duration
        duration = get_audio_duration(file_path)
        if duration is None:
            os.remove(file_path)
            await interaction.followup.send(
                "❌ Failed to extract duration from the audio file. Ensure it's a valid audio file.",
                ephemeral=True
            )
            return

        # Add sound metadata
        bot.sounds[safe_name] = {
            "file_path": file_path,
            "duration": duration
        }
        bot.save_sounds()

        # Confirm addition with an embed
        embed = discord.Embed(
            title="✅ Sound Added from URL ✅",
            description=f"Sound `{safe_name}` has been added successfully from the provided URL.",
            color=discord.Color.green()
        )
        embed.add_field(name="URL", value=f"[Download Here]({url})", inline=False)
        embed.add_field(name="Duration", value=f"{duration} seconds", inline=False)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to add sound from URL: {e}")
        # Clean up if download failed
        if os.path.exists(file_path):
            os.remove(file_path)
        await interaction.followup.send(f"❌ Failed to add sound from URL: {e}", ephemeral=True)

# 🔹 Slash Command: Play a sound with optional repeat and play duration
@bot.tree.command(name="play", description="Play a sound in your current voice channel.")
@app_commands.describe(
    name="Name of the sound to play",
    repeat="Number of times to play the sound (default: 1)",
    play_duration="Time in seconds to play the sound (default: full duration)"
)
async def play_sound(interaction: discord.Interaction, name: str, repeat: int = 1, play_duration: float = None):
    await interaction.response.defer()
    
    # Validate repeat parameter
    if repeat < 1:
        await interaction.followup.send("❌ Repeat count must be at least 1.", ephemeral=True)
        return

    # Check if the user is in a voice channel
    if not interaction.user.voice or not interaction.user.voice.channel:
        await interaction.followup.send("❌ You need to be in a voice channel to use this command.", ephemeral=True)
        return

    voice_channel = interaction.user.voice.channel
    guild_id = interaction.guild.id

    # Locate the sound file
    if name not in bot.sounds:
        await interaction.followup.send(f"❌ Sound `{name}` not found.", ephemeral=True)
        return

    sound_file = bot.sounds[name]["file_path"]
    sound_duration = bot.sounds[name]["duration"]

    # Determine play duration
    if play_duration is None:
        play_duration = sound_duration
    elif play_duration > sound_duration:
        play_duration = sound_duration
    elif play_duration <= 0:
        await interaction.followup.send("❌ Play duration must be a positive number.", ephemeral=True)
        return

    # Initialize the queue if not present
    if guild_id not in bot.queue:
        bot.queue[guild_id] = asyncio.Queue()

    # Enqueue the sound with repeat count and play duration
    await bot.queue[guild_id].put((sound_file, repeat, play_duration))

    # Set the disconnect duration for the guild (if not already set)
    # *Note*: The disconnect duration is managed separately via /set_disconnect_duration command

    # Cancel any existing disconnect task
    if guild_id in bot.disconnect_tasks:
        bot.disconnect_tasks[guild_id].cancel()
        del bot.disconnect_tasks[guild_id]

    # Connect to the voice channel if not already connected
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if not voice_client:
        try:
            voice_client = await voice_channel.connect()
            logging.info(f"Connected to voice channel {voice_channel} in guild {guild_id}.")
        except Exception as e:
            logging.error(f"Failed to connect to voice channel: {e}")
            await interaction.followup.send(f"❌ Failed to connect to voice channel: {e}", ephemeral=True)
            return
    else:
        if voice_client.channel != voice_channel:
            try:
                await voice_client.move_to(voice_channel)
                logging.info(f"Moved to voice channel {voice_channel} in guild {guild_id}.")
            except Exception as e:
                logging.error(f"Failed to move to voice channel: {e}")
                await interaction.followup.send(f"❌ Failed to move to voice channel: {e}", ephemeral=True)
                return

    # If the voice client is not playing, start playing the next sound
    if not voice_client.is_playing():
        await play_next(guild_id)

    # Send confirmation with embed (excluding disconnect duration)
    queue_length = bot.queue[guild_id].qsize()
    embed = discord.Embed(
        title="🎶 Sound Enqueued 🎶",
        description=f"Sound `{name}` has been added to the queue.",
        color=discord.Color.blue()
    )
    embed.set_thumbnail(url=interaction.user.avatar.url if interaction.user.avatar else discord.Embed.Empty)
    embed.add_field(name="Repeat Count", value=f"{repeat}", inline=False)
    embed.add_field(name="Play Duration", value=f"{play_duration} seconds", inline=False)
    embed.add_field(name="Position in Queue", value=f"{queue_length}", inline=False)
    embed.set_footer(text=f"Total in Queue: {queue_length}")
    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Set Disconnect Duration After Queue Empty
@bot.tree.command(name="set_disconnect_duration", description="Set how long the bot stays connected after the queue is empty.")
@app_commands.describe(
    duration="Time in seconds to stay connected after queue is empty (minimum: 10)"
)
@commands.has_permissions(administrator=True)
async def set_disconnect_duration(interaction: discord.Interaction, duration: int):
    await interaction.response.defer(ephemeral=True)

    # Validate duration parameter
    if duration < 10:
        await interaction.followup.send("❌ Duration must be at least 10 seconds.", ephemeral=True)
        return

    guild_id = interaction.guild.id
    bot.guild_durations[guild_id] = duration

    # If there's an existing disconnect task, cancel it
    if guild_id in bot.disconnect_tasks:
        bot.disconnect_tasks[guild_id].cancel()
        del bot.disconnect_tasks[guild_id]

    # Confirm setting with an embed
    embed = discord.Embed(
        title="⏰ Disconnect Duration Set ⏰",
        description=f"The bot will stay connected for {duration} seconds after the queue is empty.",
        color=discord.Color.blue()
    )
    embed.set_thumbnail(url=interaction.user.avatar.url if interaction.user.avatar else discord.Embed.Empty)
    await interaction.followup.send(embed=embed, ephemeral=True)

# 🔹 Slash Command: Skip the Currently Playing Sound
@bot.tree.command(name="skip", description="Skip the currently playing sound.")
async def skip_sound(interaction: discord.Interaction):
    await interaction.response.defer()
    guild_id = interaction.guild.id
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if voice_client and voice_client.is_playing():
        voice_client.stop()
        logging.info(f"Skipped sound in guild {guild_id}.")
        await interaction.followup.send("✅ Skipped the current sound.")
    else:
        await interaction.followup.send("❌ No sound is currently playing.", ephemeral=True)

# 🔹 Slash Command: Stop Playback and Clear Queue
@bot.tree.command(name="stop", description="Stop playback and clear the queue.")
async def stop_sound(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if guild_id in bot.queue:
        while not bot.queue[guild_id].empty():
            bot.queue[guild_id].get_nowait()

    if voice_client and voice_client.is_connected():
        voice_client.stop()
        await voice_client.disconnect()
        logging.info(f"Stopped playback and disconnected from guild {guild_id}.")
        await interaction.followup.send("✅ Stopped playback and cleared the queue.")
    else:
        await interaction.followup.send("❌ No active voice connection found.", ephemeral=True)

# 🔹 Slash Command: Clear the Entire Queue
@bot.tree.command(name="clear", description="Clear the entire sound queue.")
async def clear_queue(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id

    if guild_id in bot.queue and not bot.queue[guild_id].empty():
        while not bot.queue[guild_id].empty():
            bot.queue[guild_id].get_nowait()
        # Cancel any disconnect tasks as the queue is now empty
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]
        await interaction.followup.send("✅ The queue has been cleared.")
        logging.info(f"Cleared the queue in guild {guild_id}.")
    else:
        await interaction.followup.send("❌ The queue is already empty.", ephemeral=True)

# 🔹 Slash Command: List All Sounds with Durations
@bot.tree.command(name="list", description="List all available sounds.")
async def list_sounds(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)

    if not bot.sounds:
        embed = discord.Embed(
            title="❌ No Sounds Available ❌",
            description="Add some sounds using the `/add` or `/add_from_url` commands.",
            color=discord.Color.red()
        )
        await interaction.followup.send(embed=embed)
        return

    embed = discord.Embed(
        title="🎶 Available Sounds 🎶",
        description="Here are the sounds you can play:",
        color=discord.Color.green()
    )

    # Discord embeds have a limit of 25 fields; handle accordingly
    sounds_sorted = sorted(bot.sounds.items(), key=lambda item: item[0].lower())
    for name, meta in sounds_sorted:
        embed.add_field(
            name=name,
            value=f"Duration: {meta['duration']} seconds",
            inline=False
        )

    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Display the Current Sound Queue
@bot.tree.command(name="queue", description="Display the current sound queue.")
async def display_queue(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id

    if guild_id not in bot.queue or bot.queue[guild_id].empty():
        embed = discord.Embed(
            title="🔄 Sound Queue 🔄",
            description="The queue is currently empty.",
            color=discord.Color.orange()
        )
        await interaction.followup.send(embed=embed)
        return

    # Peek into the queue without removing items
    temp_list = list(bot.queue[guild_id].queue)

    embed = discord.Embed(
        title="🔄 Sound Queue 🔄",
        description="Here's the current queue:",
        color=discord.Color.purple()
    )

    for idx, (sound, repeat, duration_play) in enumerate(temp_list, start=1):
        sound_name = os.path.splitext(os.path.basename(sound))[0]
        embed.add_field(name=f"{idx}. {sound_name}", value=f"Repeat: {repeat} time(s)", inline=False)

    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Delete a Sound (Admin Only)
@bot.tree.command(name="delete", description="Delete a sound from the soundboard. (Admin only)")
@app_commands.describe(name="Name of the sound to delete")
@app_commands.default_permissions(administrator=True)
async def delete_sound(interaction: discord.Interaction, name: str):
    await interaction.response.defer(ephemeral=True)

    # Locate the sound file
    if name not in bot.sounds:
        await interaction.followup.send(f"❌ Sound `{name}` not found.", ephemeral=True)
        return

    sound_file = bot.sounds[name]["file_path"]

    try:
        os.remove(sound_file)
        del bot.sounds[name]
        bot.save_sounds()

        # Also remove the sound from all guild queues if present
        for guild_id, queue in bot.queue.items():
            temp_queue = asyncio.Queue()
            while not queue.empty():
                item = await queue.get()
                if item[0] != sound_file:
                    await temp_queue.put(item)
            bot.queue[guild_id] = temp_queue

        # Confirm deletion with an embed
        embed = discord.Embed(
            title="🗑️ Sound Deleted 🗑️",
            description=f"Sound `{name}` has been deleted successfully.",
            color=discord.Color.dark_red()
        )
        embed.set_thumbnail(url=interaction.user.avatar.url if interaction.user.avatar else discord.Embed.Empty)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to delete sound: {e}")
        await interaction.followup.send(f"❌ Failed to delete sound: {e}", ephemeral=True)

# 🔹 Error handling for delete command permissions
@delete_sound.error
async def delete_sound_error(interaction: discord.Interaction, error):
    if isinstance(error, app_commands.errors.MissingPermissions):
        await interaction.response.send_message("🔒 You don't have permission to delete sounds.", ephemeral=True)
    else:
        await interaction.response.send_message("⚠️ An error occurred while trying to delete the sound.", ephemeral=True)

# 🔹 Slash Command: Set Disconnect Duration After Queue Empty (Admin Only)
@bot.tree.command(name="set_disconnect_duration", description="Set how long the bot stays connected after the queue is empty. (Admin only)")
@app_commands.describe(duration="Time in seconds to stay connected after queue is empty (minimum: 10)")
@app_commands.default_permissions(administrator=True)
async def set_disconnect_duration(interaction: discord.Interaction, duration: int):
    await interaction.response.defer(ephemeral=True)

    # Validate duration parameter
    if duration < 10:
        await interaction.followup.send("❌ Duration must be at least 10 seconds.", ephemeral=True)
        return

    guild_id = interaction.guild.id
    bot.guild_durations[guild_id] = duration

    # If there's an existing disconnect task, cancel it
    if guild_id in bot.disconnect_tasks:
        bot.disconnect_tasks[guild_id].cancel()
        del bot.disconnect_tasks[guild_id]

    # Confirm setting with an embed
    embed = discord.Embed(
        title="⏰ Disconnect Duration Set ⏰",
        description=f"The bot will stay connected for {duration} seconds after the queue is empty.",
        color=discord.Color.blue()
    )
    embed.set_thumbnail(url=interaction.user.avatar.url if interaction.user.avatar else discord.Embed.Empty)
    await interaction.followup.send(embed=embed, ephemeral=True)

# 🔹 Event: Bot Ready
@bot.event
async def on_ready():
    print(f'Bot connected as {bot.user}')  # Removed Unicode emojis to prevent encoding errors
    logging.info(f'✅ Bot connected as {bot.user} ✅')

# 🔹 Run the bot
bot.run(TOKEN)