Search code examples
pythondiscord.pyyoutube-dlffmpeg-python

discord.ext.commands.errors.CommandInvokeError: TypeError: VoiceClient.play() missing 1 required positional argument: 'source'


I'm using python to work on a discord bot that plays music. I'm using YoutubeDL and FFmpeg

import discord
from discord.ext import commands
from functools import wraps
from youtube_dl import YoutubeDL

#Voice Client
voice_client = discord.VoiceClient

#YoutubeDL Options
ytdl_opts = {
    'format':'best audio/best',
    'noplaylist':'True'
}

ytdl = YoutubeDL(ytdl_opts)

class test(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    def search_yt(self, song):
        song_info = ytdl.extract_info('ytsearch:%s'%song, download = False)['entries'][0]
        return {'source': song_info['formats'][0]['url'], 'title': song_info['title']}

    #Music Play Function
    @commands.command()
    async def play_music(self, ctx, *args):
        '''Play music of your choice. Type in url or name'''
        song = ' '.join(args)
        source = self.search_yt(song)
        voice_channel = ctx.author.voice.channel
        print(isinstance(discord.FFmpegPCMAudio(source['source']), discord.AudioSource))
        await voice_channel.connect()
        await ctx.send(source['source'])
        voice_client.play(discord.FFmpegPCMAudio(source['source']), after=None)

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

With the command !play (song name), the bot searches (song name) on youtube and picks the first item and extracts the url. But the error I get is

Traceback (most recent call last):
  File "B:\Programming Stuff\Python\Python Programme Files\Lib\site-packages\discord\ext\commands\core.py", line 229, in wrapped
    ret = await coro(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "b:\Programming Stuff\Python\Coding_Files\Discord Bot\Cogs\test.py", line 37, in play
    voice_client.play(discord.FFmpegPCMAudio(source['source']), after=None)
TypeError: VoiceClient.play() missing 1 required positional argument: 'source'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "B:\Programming Stuff\Python\Python Programme Files\Lib\site-packages\discord\ext\commands\bot.py", line 1349, in invoke
    await ctx.command.invoke(ctx)
  File "B:\Programming Stuff\Python\Python Programme Files\Lib\site-packages\discord\ext\commands\core.py", line 1023, in invoke
    await injected(*ctx.args, **ctx.kwargs)  # type: ignore
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "B:\Programming Stuff\Python\Python Programme Files\Lib\site-packages\discord\ext\commands\core.py", line 238, in wrapped
    raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: VoiceClient.play() missing 1 required positional argument: 'source'

The error tells me that TypeError: VoiceClient.play() missing 1 required positional argument: 'source'. However, I checked whether discord.FFmpegPCMAudio(source['source']) is an AudioSource using print(isinstance(discord.FFmpegPCMAudio(source['source']), discord.AudioSource)). The output is True. But the error states that it's missing an AudioSource.

I have also tried to play the mp3 file directly using

    @commands.command()
    async def play(self, ctx):
        voice_channel = ctx.author.voice.channel
        await voice_channel.connect()
        print(isinstance(discord.FFmpegPCMAudio('song.mp3'), discord.AudioSource))
        voice_client.play(discord.FFmpegPCMAudio('song.mp3'))

but I still get the same error.

I have downloaded the full build of FFmpeg. I have added the path to FFmpeg.exe both the user variables and system variables for the environment variables. I am not sure what I'm doing wrong.

Is there a way to do this without having to download the audio? (i.e set download=True in song_info = ytdl.extract_info('ytsearch:%s'%song, download = False)['entries'][0]) Any help is much appreciated. Thanks


Solution

  • Your voice_client variable is not an instance of the VoiceClient class:

    #Voice Client
    voice_client = discord.VoiceClient
    

    it's just the name of the class. You have to actually create an instance of it.

    However, you should never have to do this manually. The library will create one for you when connecting to a channel, and then you can just access that one.

    Refer to the official Voice example: https://github.com/Rapptz/discord.py/blob/master/examples/basic_voice.py

    Is there a way to do this without having to download the audio?

    The official example above also does this already at line 47.

    But the error states that it's missing an AudioSource.

    The error is telling you that the argument is missing, because the first argument of a method is always self. This is passed automatically if you create an instance. However, as you did not, this is never passed, so your source argument is treated as self and the value for the actual argument (source) is missing.