Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ These changes are available on the `master` branch, but have not yet been releas

### Changed

- Added support for Discord DAVE (Audio & Video E2EE) for voice-receive related features
and refactored the voice-reception system.
([#3159](https://github.com/Pycord-Development/pycord/pull/3159))

### Fixed

- Fixed a `TypeError` when using `Label.set_select` and not providing `default_values`.
Expand Down
5 changes: 0 additions & 5 deletions discord/opus.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,4 @@ def _decode_packet(self, packet: Packet) -> tuple[Packet, bytes]:
else:
pcm = self._decoder.decode(None, fec=False)

if HAS_DAVEY:
if user_id is not None and in_dave and dave.can_passthrough(user_id):
_log.debug("User ID %s can passthrough, decrypting with DAVE", user_id)
pcm = dave.decrypt(user_id, davey.MediaType.audio, pcm)

return packet, pcm
31 changes: 28 additions & 3 deletions discord/sinks/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@
from .errors import SinkException

if TYPE_CHECKING:
from ..member import Member
from ..user import User
from ..voice import VoiceClient
from ..voice.packets import VoiceData

__all__ = (
"Filters",
Expand Down Expand Up @@ -210,6 +213,8 @@ class Sink(Filters):
Audio may only be formatted after recording is finished.
"""

__sink_listeners__: list[tuple[str, str]] = []

def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
Expand All @@ -222,18 +227,38 @@ def __init__(self, *, filters=None):
def client(self) -> VoiceClient | None:
return self.vc

@property
def recording(self) -> bool:
"""Whether the voice client is currently recording."""
return self.vc is not None and self.vc.is_recording()

def is_opus(self) -> bool:
"""Whether this sink accepts raw opus packets instead of decoded PCM."""
return False

def walk_children(self):
"""Yields child sinks. Base implementation yields nothing."""
return
yield # make it a generator

def init(self, vc: VoiceClient): # called under listen
self.vc = vc
super().init()

@Filters.container
def write(self, data, user):
def write(self, data: VoiceData | bytes, user: User | Member | None) -> None:
from ..voice.packets import VoiceData

if isinstance(data, VoiceData):
pcm_data = data.pcm
else:
pcm_data = data

if user not in self.audio_data:
file = io.BytesIO()
self.audio_data.update({user: AudioData(file)})

file = self.audio_data[user]
file.write(data)
self.audio_data[user].write(pcm_data)

def cleanup(self):
self.finished = True
Expand Down
2 changes: 1 addition & 1 deletion discord/sinks/m4a.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def format_audio(self, audio):
M4ASinkError
Formatting the audio failed.
"""
if self.vc.recording:
if self.recording:
raise M4ASinkError(
"Audio may only be formatted after recording is finished."
)
Expand Down
2 changes: 1 addition & 1 deletion discord/sinks/mka.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def format_audio(self, audio):
MKASinkError
Formatting the audio failed.
"""
if self.vc.recording:
if self.recording:
raise MKASinkError(
"Audio may only be formatted after recording is finished."
)
Expand Down
2 changes: 1 addition & 1 deletion discord/sinks/mkv.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def format_audio(self, audio):
MKVSinkError
Formatting the audio failed.
"""
if self.vc.recording:
if self.recording:
raise MKVSinkError(
"Audio may only be formatted after recording is finished."
)
Expand Down
2 changes: 1 addition & 1 deletion discord/sinks/mp3.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def format_audio(self, audio):
MP3SinkError
Formatting the audio failed.
"""
if self.vc.recording:
if self.recording:
raise MP3SinkError(
"Audio may only be formatted after recording is finished."
)
Expand Down
2 changes: 1 addition & 1 deletion discord/sinks/mp4.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def format_audio(self, audio):
MP4SinkError
Formatting the audio failed.
"""
if self.vc.recording:
if self.recording:
raise MP4SinkError(
"Audio may only be formatted after recording is finished."
)
Expand Down
2 changes: 1 addition & 1 deletion discord/sinks/ogg.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def format_audio(self, audio):
OGGSinkError
Formatting the audio failed.
"""
if self.vc.recording:
if self.recording:
raise OGGSinkError(
"Audio may only be formatted after recording is finished."
)
Expand Down
12 changes: 9 additions & 3 deletions discord/sinks/wave.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"""

import wave
from io import BytesIO

from ..opus import Decoder as OpusDecoder
from .core import Filters, Sink, default_filters
from .errors import WaveSinkError

Expand Down Expand Up @@ -54,16 +56,20 @@ def format_audio(self, audio):
WaveSinkError
Formatting the audio failed.
"""
if self.vc.recording:
if self.recording:
raise WaveSinkError(
"Audio may only be formatted after recording is finished."
)
data = audio.file

audio.file.seek(0)
pcm_data = audio.file.read()

data = BytesIO()
with wave.open(data, "wb") as f:
f.setnchannels(self.vc.decoder.CHANNELS)
f.setsampwidth(self.vc.decoder.SAMPLE_SIZE // self.vc.decoder.CHANNELS)
f.setframerate(self.vc.decoder.SAMPLING_RATE)

f.writeframes(pcm_data)
data.seek(0)
audio.file = data
audio.on_format(self.encoding)
9 changes: 8 additions & 1 deletion discord/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
Iterator,
Literal,
Mapping,
ParamSpec,
Protocol,
Sequence,
TypeVar,
Expand Down Expand Up @@ -176,6 +177,8 @@ class _RequestLike(Protocol):

T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True)
_MC_P = ParamSpec("_MC_P")
_MC_T = TypeVar("_MC_T")
_Iter = Union[Iterator[T], AsyncIterator[T]]


Expand Down Expand Up @@ -880,7 +883,11 @@ def _parse_ratelimit_header(request: Any, *, use_clock: bool = False) -> float:
return (reset - now).total_seconds()


async def maybe_coroutine(f, *args, **kwargs):
async def maybe_coroutine(
f: Callable[_MC_P, _MC_T | Awaitable[_MC_T]],
*args: _MC_P.args,
**kwargs: _MC_P.kwargs,
) -> _MC_T:
value = f(*args, **kwargs)
if _isawaitable(value):
return await value
Expand Down
4 changes: 2 additions & 2 deletions discord/voice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from ..errors import MissingVoiceDependenciesError
from ..utils import get_missing_voice_dependencies

if missing := get_missing_voice_dependencies():
raise MissingVoiceDependenciesError(missing=missing)
if _missing := get_missing_voice_dependencies():
raise MissingVoiceDependenciesError(missing=_missing)

from ._types import *
from .client import *
Expand Down
Loading
Loading