Skip to content

Commit 60658bb

Browse files
committed
more voice recv
1 parent 74d6590 commit 60658bb

File tree

10 files changed

+514
-1314
lines changed

10 files changed

+514
-1314
lines changed

discord/sinks/core.py

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@
3232
import subprocess
3333
import shlex
3434
import threading
35-
from typing import IO, TYPE_CHECKING, Any, Literal, TypeVar
35+
from typing import IO, TYPE_CHECKING, Any, Literal, TypeVar, overload
3636

37+
from discord.file import File
3738
from discord.utils import MISSING, SequenceProxy
3839
from discord.player import FFmpegAudio
3940

41+
from .errors import FFmpegNotFound
42+
4043
if TYPE_CHECKING:
4144
from typing_extensions import ParamSpec, Self
4245

@@ -52,6 +55,9 @@
5255
__all__ = (
5356
"Sink",
5457
"RawData",
58+
"FFmpegSink",
59+
"FilterSink",
60+
"MultiSink",
5561
)
5662

5763

@@ -309,20 +315,77 @@ def __init__(self, **kwargs: Any) -> None:
309315
raise DeprecationWarning("RawData has been deprecated in favour of VoiceData")
310316

311317

312-
class _FFmpegSink(Sink):
318+
class FFmpegSink(Sink):
319+
"""A :class:`Sink` built to use ffmpeg executables.
320+
321+
You can find default implementations of this sink in:
322+
323+
- :class:`M4ASink`
324+
- :class:`MKASink`
325+
326+
.. versionadded:: 2.7
327+
328+
Parameters
329+
----------
330+
filename: :class:`str`
331+
The file in which the ffmpeg buffer should be saved to.
332+
Can not be mixed with ``buffer``.
333+
buffer: IO[:class:`bytes`]
334+
The buffer in which the ffmpeg result would be written to.
335+
Can not be mixed with ``filename``.
336+
executable: :class:`str`
337+
The executable in which ``ffmpeg`` is in.
338+
stderr: IO[:class:`bytes`] | :data:`None`
339+
The stderr buffer in whcih will be written. Defaults to ``None``.
340+
before_options: :class:`str` | :data:`None`
341+
The options to append **before** the default ones.
342+
options: :class:`str` | :data:`None`
343+
The options to append **after** the default ones. You can override the
344+
default ones with this.
345+
error_hook: Callable[[:class:`FFmpegSink`, :class:`Exception`, :class:`discord.voice.VoiceData` | :data:`None`], Any] | :data:`None`
346+
The callback to call when an error ocurrs with this sink.
347+
"""
348+
349+
@overload
350+
def __init__(
351+
self,
352+
*,
353+
filename: str,
354+
executable: str = ...,
355+
stderr: IO[bytes] = ...,
356+
before_options: str | None = ...,
357+
options: str | None = ...,
358+
error_hook: Callable[[Self, Exception, VoiceData | None], Any] | None = ...,
359+
) -> None: ...
360+
361+
@overload
362+
def __init__(
363+
self,
364+
*,
365+
buffer: IO[bytes],
366+
executable: str = ...,
367+
stderr: IO[bytes] = ...,
368+
before_options: str | None = ...,
369+
options: str | None = ...,
370+
error_hook: Callable[[Self, Exception, VoiceData | None], Any] | None = ...,
371+
) -> None: ...
372+
313373
def __init__(
314374
self,
315375
*,
316376
filename: str = MISSING,
317377
buffer: IO[bytes] = MISSING,
318-
executable: str = 'ffmpeg',
378+
executable: str = "ffmpeg",
319379
stderr: IO[bytes] | None = None,
320380
before_options: str | None = None,
321381
options: str | None = None,
322382
error_hook: Callable[[Self, Exception, VoiceData | None], Any] | None = None,
323383
) -> None:
324384
super().__init__()
325385

386+
if filename is not MISSING and buffer is not MISSING:
387+
raise TypeError("can't mix filename and buffer parameters")
388+
326389
self.filename: str = filename or "pipe:1"
327390
self.buffer: IO[bytes] = buffer
328391

@@ -345,7 +408,7 @@ def __init__(
345408
args.extend(shlex.split(before_options))
346409

347410
args.extend({
348-
"-f": "s161e",
411+
"-f": "s16le",
349412
"-ar": "48000",
350413
"-ac": "2",
351414
"-i": "pipe:0",
@@ -382,7 +445,7 @@ def __init__(
382445
self._stderr_reader_thread.start()
383446

384447
@staticmethod
385-
def _on_error(_self: _FFmpegSink, error: Exception, data: VoiceData | None) -> None:
448+
def _on_error(_self: FFmpegSink, error: Exception, data: VoiceData | None) -> None:
386449
_self.client.stop_recording() # type: ignore
387450

388451
def is_opus(self) -> bool:
@@ -404,6 +467,21 @@ def write(self, user: User | Member | None, data: VoiceData) -> None:
404467
self._kill_processes()
405468
self.on_error(self, exc, data)
406469

470+
471+
def to_file(self, filename: str, /, *, description: str | None = None, spoiler: bool = False) -> File | None:
472+
"""Returns the :class:`discord.File` of this sink.
473+
474+
This is only applicable if this sink uses a ``buffer`` instead of a ``filename``.
475+
476+
.. warning::
477+
478+
This should be used only after the sink has stopped recording.
479+
"""
480+
if self.buffer is not MISSING:
481+
fp = File(self.buffer.read(), filename=filename, description=description, spoiler=spoiler)
482+
return fp
483+
return None
484+
407485
def _spawn_process(self, args: Any, **subprocess_kwargs: Any) -> subprocess.Popen:
408486
_log.debug("Spawning ffmpeg process with command %s and kwargs %s", args, subprocess_kwargs)
409487
process = None
@@ -412,7 +490,7 @@ def _spawn_process(self, args: Any, **subprocess_kwargs: Any) -> subprocess.Pope
412490
process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, **subprocess_kwargs)
413491
except FileNotFoundError:
414492
executable = args.partition(' ')[0] if isinstance(args, str) else args[0]
415-
raise Exception(f"{executable!r} executable was not found") from None
493+
raise FFmpegNotFound(f"{executable!r} executable was not found") from None
416494
except subprocess.SubprocessError as exc:
417495
raise Exception(f"Popen failed: {exc.__class__.__name__}: {exc}") from exc
418496
else:

0 commit comments

Comments
 (0)