How do I send pydub audio segment raw data using discord.py to a voice channel? I get error, hear nothing or gibberish noise depending on parameters

Viewed 416

As the title says, I've been trying to send chunks audio data using discord.py to a voice channel. So using pydub I basically load an mp3 file (and playback works so file is ok), make chunks, send them as packages. And then I tried to convert the mp3 file to opus file. It's playing too. Both files are ok.

There are a few stuff:

So the issue is, after I make the raw audio chunks list and try to send each element in it:

a) With Encode=True, I get an error about NoneType object not having encode attribute.

b) With Encode=False, it actually sends the data but I don't hear anything at all.

c) With Encode=False, but when loading the mp3 file with additional codec="opus" parameter, I hear gibberish noise. So it's not for conversion i guess okay...

d) With Encode=False, but when loading the opus file with additional codec="opus" parameter (without that codec="opus" i cant play the audio anyway), it's still the NoneType error.

-I have all the 3 executables (ffmpeg.exe, ffplay.exe and ffprobe.exe) in the same directory as the script. But not in PATH. (Didn't tell me anything about it and playback worked, plus i heard the gibberish noise too at least so no problem here)

-I also have m1.mp3 and m1.opus in the same directory as the script. It's ok.

-There are no subdirectories or anything else.

-I have PyNaCl and discord.py[audio] installed.

I don't know where I'm doing wrong. What kind of data was i supposed to send if not this? Can I encode the raw audio myself to opus without too much work and definitely not saving a file even if it is temp? I don't want to just load the mp3 file all at once and play, I need to be able to make chunks. I don't want to save these chunks anywhere either.

Here is the code:

import discord
import pydub
import os
from pydub.utils import make_chunks
import asyncio

TOKEN = ":("


base_path = os.getcwd()
pydub.AudioSegment.ffmpeg = os.path.join(base_path, "ffmpeg.exe")
pydub.AudioSegment.ffprobe = os.path.join(base_path, "ffprobe.exe")
pydub.AudioSegment.ffplay = os.path.join(base_path, "ffplay.exe")
pydub.AudioSegment.converter = os.path.join(base_path, "ffmpeg.exe")

#audio = pydub.AudioSegment.from_file(os.path.join(base_path, "m1.mp3"), format="mp3")#, codec="opus")
audio = pydub.AudioSegment.from_file(os.path.join(base_path, "m1.opus"), codec="opus") #dont add format="opus". format isnt extension ig. https://github.com/jiaaro/pydub/issues/257
#audio = pydub.AudioSegment.from_file(os.path.join(base_path, "m1.opus")) #didnt hear anything at all evn with play(audio) below.

audio = audio[:9*1000] #first 9 seconds

#let me be sure that it actually plays.
from pydub.playback import play #needs simpleaudio to work idk...
play(audio)

chunks = make_chunks(audio, (1/20)*1000)

client = discord.Client()

@client.event
async def on_ready():
    print("Ready.")

@client.event
async def on_message(message):
    if message.content.startswith(",test"):

        #connect to vc
        vp = await message.author.voice.channel.connect()

        #i tried with chunk._data as well. they are same anyway. 
        chunks_raw = [chunk.raw_data for chunk in chunks]
        #print(chunks_raw == [chunk._data for chunk in chunks]) #it's True
        
        print("Sending?")
        for i, chunk in enumerate(chunks_raw, start=1):
            vp.send_audio_packet(chunk, encode=True) #false --> not heard, true --> weird error (that occurs no matter if i have codec="opus" or not)
            print("Sent", i, "out of", len(chunks), "chunks.")
            await asyncio.sleep(1/20)

        vp.cleanup()
        await vp.disconnect()

        await message.channel.send("Playback is over.")
        
client.run(TOKEN)

so if encode=True i get this (and i have no idea why):

Ignoring exception in on_message
Traceback (most recent call last):
  File "C:\Users\h\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\client.py", line 343, in _run_event
    await coro(*args, **kwargs)
  File "C:\Users\h\Desktop\randomly lying pys\bas\broken_audio_showcase.py", line 38, in on_message
    vp.send_audio_packet(chunk, encode=True) #false --> not heard, true --> weird error
  File "C:\Users\h\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\voice_client.py", line 633, in send_audio_packet
    encoded_data = self.encoder.encode(data, self.encoder.SAMPLES_PER_FRAME)
AttributeError: 'NoneType' object has no attribute 'encode'

and if encode=False i can see "Sent i out of len(chunks) chunks" in console, yet i cant hear anything. I also see "Playback is over" message when it ends so it actually sends something... if i go ahead and change the voice_client.py myself to add this to the first line of send_audio_packet function:

self.encoder = opus.Encoder() #manually added

and then i set encode to True, then it actually sends a hearable voice. of course its gibberish again, but at least i hear something from discord voice chat.

can anyone actually help me with this?

Well I knew sound would be confusing, but never expected it to be this hard. I have no idea what counts as opus if not an opus file itself that's been converted via ffmpeg, not by literally changing the extension or anything.

0 Answers
Related