Skip to content

Commit 4006de0

Browse files
committed
docs(examples): added example about how to handle discords waveform
1 parent 7f2beb7 commit 4006de0

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed

examples/voice_message_waveform.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
from __future__ import annotations
2+
3+
import io
4+
from typing import Any, Union
5+
import numpy as np
6+
import discord
7+
import base64
8+
from PIL import Image, ImageDraw
9+
10+
11+
class WaveformVisualizer:
12+
"""
13+
A class to visualize audio waveforms.
14+
15+
Attributes
16+
----------
17+
waveform_byte_data : numpy.ndarray
18+
The decoded waveform byte data.
19+
20+
Methods
21+
-------
22+
decode_waveform(base64_waveform: str) -> np.ndarray[Any, np.dtype[np.uint8]]:
23+
Decodes the base64 encoded waveform string into a numpy array.
24+
25+
create_waveform_image(width: int = 500, height: int = 100,
26+
background_color: Union[float, tuple[float, ...], str, None] = (0, 0, 0),
27+
bar_colors: Union[None, list[tuple[int, int, int]]] = None) -> Image.Image:
28+
Creates a visual representation of the waveform as an image.
29+
"""
30+
31+
def __init__(self, base64_waveform: str) -> None:
32+
"""
33+
Initializes the WaveformVisualizer with the provided base64 waveform string.
34+
35+
Parameters
36+
----------
37+
base64_waveform : str
38+
A base64 encoded string representing the waveform.
39+
"""
40+
self.waveform_byte_data: np.ndarray[Any, np.dtype[np.uint8]] = self.decode_waveform(base64_waveform)
41+
42+
@staticmethod
43+
def decode_waveform(base64_waveform: str) -> np.ndarray[Any, np.dtype[np.uint8]]:
44+
"""
45+
Decodes the base64 encoded waveform string into a numpy array.
46+
47+
Parameters
48+
----------
49+
base64_waveform : str
50+
The base64 encoded string of the waveform.
51+
52+
Returns
53+
-------
54+
np.ndarray
55+
A numpy array containing the decoded waveform byte data.
56+
"""
57+
return np.frombuffer(base64.b64decode(base64_waveform), dtype=np.uint8)
58+
59+
def create_waveform_image(self, width: int = 500, height: int = 100,
60+
background_color: Union[float, tuple[float, ...], str, None] = (0, 0, 0),
61+
bar_colors: Union[None, list[tuple[int, int, int]]] = None) -> Image.Image:
62+
"""
63+
Creates a visual representation of the waveform as an image.
64+
65+
Parameters
66+
----------
67+
width : int, optional
68+
The width of the resulting image, by default 500.
69+
height : int, optional
70+
The height of the resulting image, by default 100.
71+
background_color : float | tuple[float, ...] | str | None, optional
72+
The background color of the image, by default (0, 0, 0).
73+
bar_colors : list[tuple[int, int, int]] | None, optional
74+
A list of colors for the waveform bars, by default None.
75+
76+
Returns
77+
-------
78+
Image.Image
79+
A PIL Image object representing the waveform.
80+
"""
81+
if bar_colors is None:
82+
bar_colors = [(173, 216, 230), (135, 206, 235), (0, 191, 255)]
83+
84+
image = Image.new('RGB', (width, height), background_color)
85+
draw = ImageDraw.Draw(image)
86+
87+
bar_width = width / len(self.waveform_byte_data) / 2
88+
x_scale = width / len(self.waveform_byte_data)
89+
y_scale = height / 2 / 255
90+
91+
for i, value in enumerate(self.waveform_byte_data):
92+
if isinstance(value, tuple): # Check if value is a tuple
93+
value = value[0] # Extract the first item if it's a tuple
94+
95+
x1 = i * x_scale
96+
bar_height = max(2.0, float(value) * y_scale)
97+
y1 = height / 2 - bar_height
98+
y2 = height / 2 + bar_height
99+
100+
color_index = i % len(bar_colors)
101+
color = bar_colors[color_index]
102+
103+
draw.rectangle([x1, y1, x1 + bar_width, y2], fill=color)
104+
105+
return image
106+
107+
108+
intents = discord.Intents.default()
109+
intents.message_content = True
110+
intents.members = True
111+
112+
bot = discord.Bot(intents=intents)
113+
114+
115+
@bot.event
116+
async def on_ready() -> None:
117+
"""
118+
Event handler for when the bot is ready.
119+
120+
This method is called automatically by the pycord library when the bot has successfully connected to Discord
121+
and is ready to start receiving events and commands.
122+
"""
123+
print("Ready!")
124+
125+
126+
@bot.event
127+
async def on_message(message: discord.Message) -> None:
128+
"""
129+
Event handler for when a message is received.
130+
131+
This method is called automatically whenever a new message is sent in any channel the bot can access.
132+
133+
Parameters
134+
----------
135+
message : discord.Message
136+
The message object containing information about the message sent.
137+
"""
138+
if message.author.id == bot.user.id:
139+
return
140+
141+
if message.attachments and len(message.attachments) == 1:
142+
target_attachment = message.attachments[0]
143+
if target_attachment.content_type == "audio/ogg" and target_attachment.filename == "voice-message.ogg":
144+
print("We got a voice message!")
145+
await handle_voice_message(message, target_attachment)
146+
147+
148+
async def handle_voice_message(message: discord.Message, attachment: discord.Attachment) -> None:
149+
"""
150+
Handles the processing of voice message attachments.
151+
152+
Converts the waveform of the voice message to a visual image and sends it back in an embed.
153+
154+
Parameters
155+
----------
156+
message : discord.Message
157+
The message object containing the voice message.
158+
attachment : discord.Attachment
159+
The attachment object representing the voice message.
160+
"""
161+
image = WaveformVisualizer(attachment.waveform).create_waveform_image()
162+
image_buffer = io.BytesIO()
163+
image.save(image_buffer, format='PNG')
164+
image_buffer.seek(0)
165+
file = discord.File(image_buffer, "waveform.png", description="A neat waveform image!")
166+
embed = discord.Embed()
167+
embed.set_author(name=message.author.display_name, icon_url=message.author.display_avatar)
168+
embed.title = "Voice Message"
169+
embed.add_field(name="Duration", value=str(attachment.duration_secs))
170+
embed.set_image(url="attachment://waveform.png")
171+
embed.timestamp = message.created_at
172+
await message.reply(None, embed=embed, file=file)
173+
174+
175+
bot.run("TOKEN")

0 commit comments

Comments
 (0)