CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-discord-py

A modern, feature-rich, and async-ready API wrapper for Discord written in Python

Pending
Overview
Eval results
Files

voice-audio.mddocs/

Voice & Audio

Voice connection management and audio playback capabilities for Discord bots. Discord.py provides comprehensive voice support including connection management, audio sources, and real-time voice data processing with opus encoding.

Capabilities

Voice Connections

Voice clients manage connections to Discord voice channels with audio playback and recording capabilities.

class VoiceClient:
    """
    Voice connection to a Discord voice channel.
    """
    def __init__(self, client: Client, channel: VoiceChannel): ...
    
    # Connection properties
    client: Client  # Associated Discord client
    channel: VoiceChannel  # Connected voice channel
    guild: Guild  # Guild containing the voice channel
    user: ClientUser  # Bot user
    session_id: str  # Voice session ID
    token: str  # Voice token
    endpoint: str  # Voice server endpoint
    
    # Connection state
    is_connected: bool  # Whether connected to voice
    is_playing: bool  # Whether currently playing audio
    is_paused: bool  # Whether audio is paused
    source: Optional[AudioSource]  # Current audio source
    
    # Connection management
    async def connect(self, *, timeout: float = 60.0, reconnect: bool = True, self_deaf: bool = False, self_mute: bool = False) -> None:
        """
        Connect to the voice channel.
        
        Parameters:
        - timeout: Connection timeout in seconds
        - reconnect: Whether to attempt reconnection on disconnect
        - self_deaf: Whether to deafen the bot
        - self_mute: Whether to mute the bot
        """
    
    async def disconnect(self, *, force: bool = False) -> None:
        """
        Disconnect from voice channel.
        
        Parameters:
        - force: Whether to force disconnect immediately
        """
    
    async def move_to(self, channel: VoiceChannel) -> None:
        """Move to a different voice channel."""
    
    # Audio playback
    def play(self, source: AudioSource, *, after: Optional[Callable[[Optional[Exception]], None]] = None) -> None:
        """
        Play an audio source.
        
        Parameters:
        - source: Audio source to play
        - after: Callback function called when playback finishes
        """
    
    def stop(self) -> None:
        """Stop current audio playback."""
    
    def pause(self) -> None:
        """Pause current audio playback."""
    
    def resume(self) -> None:
        """Resume paused audio playback."""
    
    # Voice state management
    async def edit(self, *, mute: bool = None, deafen: bool = None) -> None:
        """Edit bot's voice state."""
    
    # Properties
    @property
    def latency(self) -> float:
        """Voice connection latency in seconds."""
    
    @property
    def average_latency(self) -> float:
        """Average voice latency over recent packets."""

class VoiceProtocol:
    """
    Abstract base class for voice protocol implementations.
    """
    def __init__(self, client: Client, channel: VoiceChannel): ...
    
    async def connect(self, **kwargs) -> None:
        """Connect to voice channel."""
        raise NotImplementedError
    
    async def disconnect(self, **kwargs) -> None:
        """Disconnect from voice channel."""
        raise NotImplementedError

Audio Sources

Audio sources provide audio data for playback with support for various formats and streaming.

class AudioSource:
    """
    Base class for audio sources.
    """
    def read(self) -> bytes:
        """
        Read audio data (20ms of audio at 48kHz stereo).
        
        Returns:
        bytes: Audio data in opus format, or empty bytes to signal end
        """
        raise NotImplementedError
    
    def cleanup(self) -> None:
        """Clean up audio source resources."""
        pass
    
    @property
    def is_opus(self) -> bool:
        """Whether audio source provides opus-encoded data."""
        return False

class AudioPlayer:
    """
    Audio player that handles audio source playback.
    """
    def __init__(self, source: AudioSource, client: VoiceClient, *, after: Optional[Callable] = None): ...
    
    source: AudioSource  # Audio source
    client: VoiceClient  # Voice client
    
    def start(self) -> None:
        """Start audio playback."""
    
    def stop(self) -> None:
        """Stop audio playback."""
    
    def pause(self) -> None:
        """Pause audio playback."""
    
    def resume(self) -> None:
        """Resume audio playback."""
    
    @property
    def is_playing(self) -> bool:
        """Whether audio is currently playing."""
    
    @property
    def is_paused(self) -> bool:
        """Whether audio is paused."""

class PCMAudio(AudioSource):
    """
    Audio source for raw PCM audio data.
    
    Parameters:
    - stream: File-like object or file path containing PCM data
    - executable: FFmpeg executable path (defaults to 'ffmpeg')
    """
    def __init__(self, source: Union[str, io.BufferedIOBase], *, executable: str = 'ffmpeg'): ...
    
    def read(self) -> bytes:
        """Read PCM audio data."""
    
    def cleanup(self) -> None:
        """Clean up PCM audio resources."""

class FFmpegAudio(AudioSource):
    """
    Audio source using FFmpeg for format conversion and streaming.
    
    Parameters:
    - source: Audio source (file path, URL, or stream)
    - executable: FFmpeg executable path
    - pipe: Whether to use pipe for streaming
    - stderr: Where to redirect stderr output
    - before_options: FFmpeg options before input
    - options: FFmpeg options after input
    """
    def __init__(
        self,
        source: Union[str, io.BufferedIOBase],
        *,
        executable: str = 'ffmpeg',
        pipe: bool = False,
        stderr: Optional[io.IOBase] = None,
        before_options: Optional[str] = None,
        options: Optional[str] = None
    ): ...
    
    def read(self) -> bytes:
        """Read audio data from FFmpeg."""
    
    def cleanup(self) -> None:
        """Clean up FFmpeg process."""
    
    @classmethod
    def from_probe(cls, source: Union[str, io.BufferedIOBase], **kwargs) -> FFmpegAudio:
        """Create FFmpegAudio with automatic format detection."""

class FFmpegPCMAudio(FFmpegAudio):
    """
    FFmpeg audio source that outputs PCM format.
    """
    def __init__(self, source: Union[str, io.BufferedIOBase], **kwargs): ...

class FFmpegOpusAudio(FFmpegAudio):
    """
    FFmpeg audio source that outputs Opus format.
    """
    def __init__(self, source: Union[str, io.BufferedIOBase], **kwargs): ...
    
    @property
    def is_opus(self) -> bool:
        """Opus audio sources are opus-encoded."""
        return True

class PCMVolumeTransformer(AudioSource):
    """
    Audio source that applies volume transformation to PCM audio.
    
    Parameters:
    - original: Original audio source
    - volume: Volume multiplier (0.0 to 2.0, default 1.0)
    """
    def __init__(self, original: AudioSource, *, volume: float = 1.0): ...
    
    original: AudioSource  # Original audio source
    volume: float  # Volume level (0.0 to 2.0)
    
    def read(self) -> bytes:
        """Read volume-adjusted audio data."""
    
    def cleanup(self) -> None:
        """Clean up transformer resources."""
    
    @classmethod
    def from_other_source(cls, other: AudioSource, *, volume: float = 1.0) -> PCMVolumeTransformer:
        """Create volume transformer from another source."""

Opus Codec

Opus codec interface for voice encoding and decoding.

class Encoder:
    """
    Opus encoder for voice data.
    
    Parameters:
    - sampling_rate: Audio sampling rate (default: 48000)
    - channels: Number of audio channels (default: 2)
    - application: Encoder application type
    """
    def __init__(
        self, 
        sampling_rate: int = 48000, 
        channels: int = 2, 
        application: int = None
    ): ...
    
    def encode(self, pcm: bytes, frame_size: int) -> bytes:
        """
        Encode PCM audio to Opus format.
        
        Parameters:
        - pcm: PCM audio data
        - frame_size: Frame size in samples
        
        Returns:
        bytes: Opus-encoded audio data
        """
    
    def set_bitrate(self, kbps: int) -> None:
        """Set encoder bitrate in kbps."""
    
    def set_bandwidth(self, req: int) -> None:
        """Set encoder bandwidth."""
    
    def set_signal_type(self, req: int) -> None:
        """Set signal type (voice/music)."""

# Opus utilities
def is_loaded() -> bool:
    """Check if Opus library is loaded."""

def load_opus(name: str) -> None:
    """Load Opus library from specified path."""

# Opus exceptions
class OpusError(DiscordException):
    """Base exception for Opus codec errors."""
    pass

class OpusNotLoaded(OpusError):
    """Opus library is not loaded."""
    pass

Voice State Management

Voice state objects track user voice connection status and properties.

class VoiceState:
    """
    Represents a user's voice state in a guild.
    """
    def __init__(self, *, data: Dict[str, Any], channel: Optional[VoiceChannel] = None): ...
    
    # User and guild info
    user_id: int  # User ID
    guild: Guild  # Guild containing the voice state
    member: Optional[Member]  # Member object
    
    # Voice channel info
    channel: Optional[VoiceChannel]  # Connected voice channel
    session_id: str  # Voice session ID
    
    # Voice settings
    deaf: bool  # Whether user is server deafened
    mute: bool  # Whether user is server muted
    self_deaf: bool  # Whether user is self deafened
    self_mute: bool  # Whether user is self muted
    self_stream: bool  # Whether user is streaming
    self_video: bool  # Whether user has video enabled
    suppress: bool  # Whether user is suppressed (priority speaker)
    afk: bool  # Whether user is in AFK channel
    
    # Voice activity
    requested_to_speak_at: Optional[datetime]  # When user requested to speak (stage channels)
    
    @property
    def is_afk(self) -> bool:
        """Whether user is in the AFK channel."""

Usage Examples

Basic Voice Bot

import discord
from discord.ext import commands
import asyncio

bot = commands.Bot(command_prefix='!', intents=discord.Intents.all())

@bot.command()
async def join(ctx):
    """Join the user's voice channel."""
    if ctx.author.voice is None:
        await ctx.send("You're not in a voice channel!")
        return
    
    channel = ctx.author.voice.channel
    if ctx.voice_client is not None:
        await ctx.voice_client.move_to(channel)
        await ctx.send(f"Moved to {channel.name}")
    else:
        await channel.connect()
        await ctx.send(f"Joined {channel.name}")

@bot.command()
async def leave(ctx):
    """Leave the voice channel."""
    if ctx.voice_client is None:
        await ctx.send("I'm not connected to a voice channel!")
        return
    
    await ctx.voice_client.disconnect()
    await ctx.send("Disconnected from voice channel")

@bot.command()
async def play(ctx, *, url):
    """Play audio from a URL."""
    if ctx.voice_client is None:
        await ctx.send("I'm not connected to a voice channel!")
        return
    
    if ctx.voice_client.is_playing():
        await ctx.send("Already playing audio!")
        return
    
    try:
        # Create audio source
        source = discord.FFmpegPCMAudio(url)
        
        # Play audio
        ctx.voice_client.play(source, after=lambda e: print(f'Player error: {e}') if e else None)
        await ctx.send(f"Now playing: {url}")
    except Exception as e:
        await ctx.send(f"Error playing audio: {e}")

@bot.command()
async def stop(ctx):
    """Stop audio playback."""
    if ctx.voice_client is None or not ctx.voice_client.is_playing():
        await ctx.send("Not playing any audio!")
        return
    
    ctx.voice_client.stop()
    await ctx.send("Stopped audio playback")

@bot.command()
async def pause(ctx):
    """Pause audio playback."""
    if ctx.voice_client is None or not ctx.voice_client.is_playing():
        await ctx.send("Not playing any audio!")
        return
    
    ctx.voice_client.pause()
    await ctx.send("Paused audio playback")

@bot.command()
async def resume(ctx):
    """Resume audio playback."""
    if ctx.voice_client is None or not ctx.voice_client.is_paused():
        await ctx.send("Audio is not paused!")
        return
    
    ctx.voice_client.resume()
    await ctx.send("Resumed audio playback")

Advanced Music Bot with Queue

import asyncio
import youtube_dl
from collections import deque

# Suppress noise about console usage from errors
youtube_dl.utils.bug_reports_message = lambda: ''

ytdl_format_options = {
    'format': 'bestaudio/best',
    'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    'ignoreerrors': False,
    'logtostderr': False,
    'quiet': True,
    'no_warnings': True,
    'default_search': 'auto',
    'source_address': '0.0.0.0'
}

ffmpeg_options = {
    'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
    'options': '-vn'
}

ytdl = youtube_dl.YoutubeDL(ytdl_format_options)

class YTDLSource(discord.PCMVolumeTransformer):
    def __init__(self, source, *, data, volume=0.5):
        super().__init__(source, volume=volume)
        self.data = data
        self.title = data.get('title')
        self.url = data.get('url')
        self.duration = data.get('duration')
        self.uploader = data.get('uploader')

    @classmethod
    async def from_url(cls, url, *, loop=None, stream=False):
        loop = loop or asyncio.get_event_loop()
        data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))

        if 'entries' in data:
            data = data['entries'][0]

        filename = data['url'] if stream else ytdl.prepare_filename(data)
        return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)

class MusicBot(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.queue = deque()
        self.current = None
        self.volume = 0.5

    def get_queue_embed(self):
        if not self.queue:
            return discord.Embed(title="Queue", description="Queue is empty", color=0xff0000)
        
        embed = discord.Embed(title="Music Queue", color=0x00ff00)
        queue_list = []
        
        for i, song in enumerate(list(self.queue)[:10], 1):  # Show first 10 songs
            duration = f" ({song.duration // 60}:{song.duration % 60:02d})" if song.duration else ""
            queue_list.append(f"{i}. {song.title}{duration}")
        
        embed.description = "\n".join(queue_list)
        
        if len(self.queue) > 10:
            embed.set_footer(text=f"... and {len(self.queue) - 10} more songs")
        
        return embed

    @commands.command()
    async def play(self, ctx, *, url):
        """Add a song to the queue and play it."""
        async with ctx.typing():
            try:
                source = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
                source.volume = self.volume
                
                self.queue.append(source)
                
                if not ctx.voice_client.is_playing():
                    await self.play_next(ctx)
                else:
                    embed = discord.Embed(
                        title="Added to Queue",
                        description=f"**{source.title}**\nby {source.uploader}",
                        color=0x00ff00
                    )
                    embed.set_footer(text=f"Position in queue: {len(self.queue)}")
                    await ctx.send(embed=embed)
                    
            except Exception as e:
                await ctx.send(f"Error: {e}")

    async def play_next(self, ctx):
        """Play the next song in the queue."""
        if not self.queue:
            self.current = None
            return
        
        source = self.queue.popleft()
        self.current = source
        
        def after_playing(error):
            if error:
                print(f'Player error: {error}')
            
            # Schedule next song
            coro = self.play_next(ctx)
            fut = asyncio.run_coroutine_threadsafe(coro, self.bot.loop)
            try:
                fut.result()
            except:
                pass
        
        ctx.voice_client.play(source, after=after_playing)
        
        embed = discord.Embed(
            title="Now Playing",
            description=f"**{source.title}**\nby {source.uploader}",
            color=0x0099ff
        )
        if source.duration:
            embed.add_field(name="Duration", value=f"{source.duration // 60}:{source.duration % 60:02d}")
        
        await ctx.send(embed=embed)

    @commands.command()
    async def queue(self, ctx):
        """Show the current queue."""
        embed = self.get_queue_embed()
        
        if self.current:
            embed.add_field(
                name="Currently Playing",
                value=f"**{self.current.title}**",
                inline=False
            )
        
        await ctx.send(embed=embed)

    @commands.command()
    async def skip(self, ctx):
        """Skip the current song."""
        if ctx.voice_client and ctx.voice_client.is_playing():
            ctx.voice_client.stop()
            await ctx.send("⏭️ Skipped!")
        else:
            await ctx.send("Nothing is playing!")

    @commands.command()
    async def volume(self, ctx, volume: int = None):
        """Change or show the current volume."""
        if volume is None:
            await ctx.send(f"Current volume: {int(self.volume * 100)}%")
            return
        
        if not 0 <= volume <= 100:
            await ctx.send("Volume must be between 0 and 100")
            return
        
        self.volume = volume / 100
        
        if self.current:
            self.current.volume = self.volume
        
        await ctx.send(f"🔊 Volume set to {volume}%")

    @commands.command()
    async def clear(self, ctx):
        """Clear the queue."""
        self.queue.clear()
        await ctx.send("🗑️ Queue cleared!")

    @commands.command()
    async def nowplaying(self, ctx):
        """Show information about the currently playing song."""
        if not self.current:
            await ctx.send("Nothing is playing!")
            return
        
        embed = discord.Embed(
            title="Now Playing",
            description=f"**{self.current.title}**",
            color=0x0099ff
        )
        embed.add_field(name="Uploader", value=self.current.uploader)
        embed.add_field(name="Volume", value=f"{int(self.current.volume * 100)}%")
        
        if self.current.duration:
            embed.add_field(name="Duration", value=f"{self.current.duration // 60}:{self.current.duration % 60:02d}")
        
        await ctx.send(embed=embed)

async def setup(bot):
    await bot.add_cog(MusicBot(bot))

Voice State Monitoring

@bot.event
async def on_voice_state_update(member, before, after):
    """Monitor voice state changes."""
    
    # User joined a voice channel
    if before.channel is None and after.channel is not None:
        print(f"{member} joined {after.channel}")
        
        # Welcome message for specific channel
        if after.channel.name == "General Voice":
            channel = discord.utils.get(member.guild.text_channels, name="general")
            if channel:
                await channel.send(f"👋 {member.mention} joined voice chat!")
    
    # User left a voice channel
    elif before.channel is not None and after.channel is None:
        print(f"{member} left {before.channel}")
        
        # If bot is alone in voice channel, disconnect
        if before.channel.guild.voice_client:
            voice_client = before.channel.guild.voice_client
            if len(voice_client.channel.members) == 1:  # Only bot left
                await voice_client.disconnect()
                
                channel = discord.utils.get(member.guild.text_channels, name="general")
                if channel:
                    await channel.send("🔇 Left voice channel (nobody else listening)")
    
    # User moved between voice channels
    elif before.channel != after.channel:
        print(f"{member} moved from {before.channel} to {after.channel}")
    
    # Voice state changes (mute, deafen, etc.)
    if before.self_mute != after.self_mute:
        if after.self_mute:
            print(f"{member} muted themselves")
        else:
            print(f"{member} unmuted themselves")
    
    if before.self_deaf != after.self_deaf:
        if after.self_deaf:
            print(f"{member} deafened themselves")
        else:
            print(f"{member} undeafened themselves")

Opus Audio Processing

Low-level opus audio encoding and decoding functionality for advanced voice applications.

# Opus Module Functions
def load_opus(name: str) -> None:
    """Load a specific opus library by name."""

def is_loaded() -> bool:
    """Check if opus library is loaded and available."""

# Opus Encoder/Decoder Classes
class Encoder:
    """Opus audio encoder for voice data."""
    def __init__(self, sampling_rate: int = 48000, channels: int = 2): ...
    def encode(self, pcm: bytes, frame_size: int) -> bytes: ...

class Decoder:
    """Opus audio decoder for voice data."""  
    def __init__(self, sampling_rate: int = 48000, channels: int = 2): ...
    def decode(self, data: bytes, *, decode_fec: bool = False) -> bytes: ...

# Opus Exceptions
class OpusError(DiscordException):
    """Exception raised when opus operation fails."""

class OpusNotLoaded(DiscordException):
    """Exception raised when opus library is not loaded."""

Install with Tessl CLI

npx tessl i tessl/pypi-discord-py

docs

app-commands.md

commands-framework.md

core-objects.md

event-handling.md

index.md

user-interface.md

utilities.md

voice-audio.md

webhooks.md

tile.json