On branch DiscordProfile

Initial commit
This commit is contained in:
EG
2026-07-01 15:15:07 +03:00
commit d4bf750c9e
3125 changed files with 601334 additions and 0 deletions
@@ -0,0 +1,20 @@
"""
discord.sinks
~~~~~~~~~~~~~
A place to store all officially given voice sinks.
:copyright: 2021-present Pycord Development
:license: MIT, see LICENSE for more details.
"""
from .core import *
from .errors import *
from .m4a import *
from .mka import *
from .mkv import *
from .mp3 import *
from .mp4 import *
from .ogg import *
from .pcm import *
from .wave import *
@@ -0,0 +1,250 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2021 Rapptz
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
import io
import os
import struct
import sys
import threading
import time
from typing import TYPE_CHECKING
from ..types import snowflake
from .errors import SinkException
if TYPE_CHECKING:
from ..voice import VoiceClient
__all__ = (
"Filters",
"Sink",
"AudioData",
"RawData",
)
if sys.platform != "win32":
CREATE_NO_WINDOW = 0
else:
CREATE_NO_WINDOW = 0x08000000
default_filters = {
"time": 0,
"users": [],
"max_size": 0,
}
class Filters:
"""Filters for :class:`~.Sink`
.. versionadded:: 2.0
Parameters
----------
container
Container of all Filters.
"""
def __init__(self, **kwargs):
self.filtered_users = kwargs.get("users", default_filters["users"])
self.seconds = kwargs.get("time", default_filters["time"])
self.max_size = kwargs.get("max_size", default_filters["max_size"])
self.finished = False
@staticmethod
def container(func): # Contains all filters
def _filter(self, data, user):
if not self.filtered_users or user in self.filtered_users:
return func(self, data, user)
return _filter
def init(self):
if self.seconds != 0:
thread = threading.Thread(target=self.wait_and_stop)
thread.start()
def wait_and_stop(self):
time.sleep(self.seconds)
if self.finished:
return
self.vc.stop_recording()
class RawData:
"""Handles raw data from Discord so that it can be decrypted and decoded to be used.
.. versionadded:: 2.0
"""
def __init__(self, data, client):
self.data = bytearray(data)
self.client = client
unpacker = struct.Struct(">xxHII")
self.sequence, self.timestamp, self.ssrc = unpacker.unpack_from(self.data[:12])
# RFC3550 5.1: RTP Fixed Header Fields
if self.client.mode.endswith("_rtpsize"):
# If It Has CSRC Chunks
cutoff = 12 + (data[0] & 0b00_0_0_1111) * 4
# If It Has A Extension
if data[0] & 0b00_0_1_0000:
cutoff += 4
else:
cutoff = 12
self.header = data[:cutoff]
self.data = self.data[cutoff:]
self.decrypted_data = getattr(self.client, f"_decrypt_{self.client.mode}")(
self.header, self.data
)
self.decoded_data = None
self.user_id = None
self.receive_time = time.perf_counter()
class AudioData:
"""Handles data that's been completely decrypted and decoded and is ready to be saved to file.
.. versionadded:: 2.0
"""
def __init__(self, file):
self.file = file
self.finished = False
def write(self, data):
"""Writes audio data.
Raises
------
ClientException
The AudioData is already finished writing.
"""
if self.finished:
raise SinkException("The AudioData is already finished writing.")
try:
self.file.write(data)
except ValueError:
pass
def cleanup(self):
"""Finishes and cleans up the audio data.
Raises
------
ClientException
The AudioData is already finished writing.
"""
if self.finished:
raise SinkException("The AudioData is already finished writing.")
self.file.seek(0)
self.finished = True
def on_format(self, encoding):
"""Called when audio data is formatted.
Raises
------
ClientException
The AudioData is still writing.
"""
if not self.finished:
raise SinkException("The AudioData is still writing.")
class Sink(Filters):
"""A sink "stores" recorded audio data.
Can be subclassed for extra customizablilty.
.. warning::
It is recommended you use
the officially provided sink classes,
such as :class:`~discord.sinks.WaveSink`.
just replace the following like so: ::
vc.start_recording(
MySubClassedSink(),
finished_callback,
ctx.channel,
)
.. versionadded:: 2.0
Raises
------
ClientException
An invalid encoding type was specified.
ClientException
Audio may only be formatted after recording is finished.
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.vc: VoiceClient | None = None
self.audio_data = {}
@property
def client(self) -> VoiceClient | None:
return self.vc
def init(self, vc: VoiceClient): # called under listen
self.vc = vc
super().init()
@Filters.container
def write(self, data, user):
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)
def cleanup(self):
self.finished = True
for file in self.audio_data.values():
file.cleanup()
self.format_audio(file)
def get_all_audio(self):
"""Gets all audio files."""
return [x.file for x in self.audio_data.values()]
def get_user_audio(self, user: snowflake.Snowflake):
"""Gets the audio file(s) of one specific user."""
return os.path.realpath(self.audio_data.pop(user))
@@ -0,0 +1,89 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from discord.errors import DiscordException
class SinkException(DiscordException):
"""Raised when a Sink error occurs.
.. versionadded:: 2.0
"""
class RecordingException(SinkException):
"""Exception that's thrown when there is an error while trying to record
audio from a voice channel.
.. versionadded:: 2.0
"""
class MP3SinkError(SinkException):
"""Exception thrown when an exception occurs with :class:`MP3Sink`
.. versionadded:: 2.0
"""
class MP4SinkError(SinkException):
"""Exception thrown when an exception occurs with :class:`MP4Sink`
.. versionadded:: 2.0
"""
class OGGSinkError(SinkException):
"""Exception thrown when an exception occurs with :class:`OGGSink`
.. versionadded:: 2.0
"""
class MKVSinkError(SinkException):
"""Exception thrown when an exception occurs with :class:`MKVSink`
.. versionadded:: 2.0
"""
class WaveSinkError(SinkException):
"""Exception thrown when an exception occurs with :class:`WaveSink`
.. versionadded:: 2.0
"""
class M4ASinkError(SinkException):
"""Exception thrown when an exception occurs with :class:`M4ASink`
.. versionadded:: 2.0
"""
class MKASinkError(SinkException):
"""Exception thrown when an exception occurs with :class:`MKASink`
.. versionadded:: 2.0
"""
@@ -0,0 +1,103 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import io
import os
import subprocess
import time
from .core import CREATE_NO_WINDOW, Filters, Sink, default_filters
from .errors import M4ASinkError
class M4ASink(Sink):
"""A special sink for .m4a files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "m4a"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
"""Formats the recorded audio.
Raises
------
M4ASinkError
Audio may only be formatted after recording is finished.
M4ASinkError
Formatting the audio failed.
"""
if self.vc.recording:
raise M4ASinkError(
"Audio may only be formatted after recording is finished."
)
m4a_file = f"{time.time()}.tmp"
args = [
"ffmpeg",
"-f",
"s16le",
"-ar",
"48000",
"-loglevel",
"error",
"-ac",
"2",
"-i",
"-",
"-f",
"ipod",
m4a_file,
]
if os.path.exists(m4a_file):
os.remove(
m4a_file
) # process will get stuck asking whether to overwrite, if file already exists.
try:
process = subprocess.Popen(
args, creationflags=CREATE_NO_WINDOW, stdin=subprocess.PIPE
)
except FileNotFoundError:
raise M4ASinkError("ffmpeg was not found.") from None
except subprocess.SubprocessError as exc:
raise M4ASinkError(
"Popen failed: {0.__class__.__name__}: {0}".format(exc)
) from exc
process.communicate(audio.file.read())
with open(m4a_file, "rb") as f:
audio.file = io.BytesIO(f.read())
audio.file.seek(0)
os.remove(m4a_file)
audio.on_format(self.encoding)
@@ -0,0 +1,96 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import io
import subprocess
from .core import CREATE_NO_WINDOW, Filters, Sink, default_filters
from .errors import MKASinkError
class MKASink(Sink):
"""A special sink for .mka files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "mka"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
"""Formats the recorded audio.
Raises
------
MKASinkError
Audio may only be formatted after recording is finished.
MKASinkError
Formatting the audio failed.
"""
if self.vc.recording:
raise MKASinkError(
"Audio may only be formatted after recording is finished."
)
args = [
"ffmpeg",
"-f",
"s16le",
"-ar",
"48000",
"-loglevel",
"error",
"-ac",
"2",
"-i",
"-",
"-f",
"matroska",
"pipe:1",
]
try:
process = subprocess.Popen(
args,
creationflags=CREATE_NO_WINDOW,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
)
except FileNotFoundError:
raise MKASinkError("ffmpeg was not found.") from None
except subprocess.SubprocessError as exc:
raise MKASinkError(
"Popen failed: {0.__class__.__name__}: {0}".format(exc)
) from exc
out = process.communicate(audio.file.read())[0]
out = io.BytesIO(out)
out.seek(0)
audio.file = out
audio.on_format(self.encoding)
@@ -0,0 +1,95 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import io
import subprocess
from .core import Filters, Sink, default_filters
from .errors import MKVSinkError
class MKVSink(Sink):
"""A special sink for .mkv files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "mkv"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
"""Formats the recorded audio.
Raises
------
MKVSinkError
Audio may only be formatted after recording is finished.
MKVSinkError
Formatting the audio failed.
"""
if self.vc.recording:
raise MKVSinkError(
"Audio may only be formatted after recording is finished."
)
args = [
"ffmpeg",
"-f",
"s16le",
"-ar",
"48000",
"-loglevel",
"error",
"-ac",
"2",
"-i",
"-",
"-f",
"matroska",
"pipe:1",
]
try:
process = subprocess.Popen(
args, # creationflags=CREATE_NO_WINDOW,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
)
except FileNotFoundError:
raise MKVSinkError("ffmpeg was not found.") from None
except subprocess.SubprocessError as exc:
raise MKVSinkError(
"Popen failed: {0.__class__.__name__}: {0}".format(exc)
) from exc
out = process.communicate(audio.file.read())[0]
out = io.BytesIO(out)
out.seek(0)
audio.file = out
audio.on_format(self.encoding)
@@ -0,0 +1,96 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import io
import subprocess
from .core import CREATE_NO_WINDOW, Filters, Sink, default_filters
from .errors import MP3SinkError
class MP3Sink(Sink):
"""A special sink for .mp3 files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "mp3"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
"""Formats the recorded audio.
Raises
------
MP3SinkError
Audio may only be formatted after recording is finished.
MP3SinkError
Formatting the audio failed.
"""
if self.vc.recording:
raise MP3SinkError(
"Audio may only be formatted after recording is finished."
)
args = [
"ffmpeg",
"-f",
"s16le",
"-ar",
"48000",
"-loglevel",
"error",
"-ac",
"2",
"-i",
"-",
"-f",
"mp3",
"pipe:1",
]
try:
process = subprocess.Popen(
args,
creationflags=CREATE_NO_WINDOW,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
)
except FileNotFoundError:
raise MP3SinkError("ffmpeg was not found.") from None
except subprocess.SubprocessError as exc:
raise MP3SinkError(
"Popen failed: {0.__class__.__name__}: {0}".format(exc)
) from exc
out = process.communicate(audio.file.read())[0]
out = io.BytesIO(out)
out.seek(0)
audio.file = out
audio.on_format(self.encoding)
@@ -0,0 +1,103 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import io
import os
import subprocess
import time
from .core import CREATE_NO_WINDOW, Filters, Sink, default_filters
from .errors import MP4SinkError
class MP4Sink(Sink):
"""A special sink for .mp4 files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "mp4"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
"""Formats the recorded audio.
Raises
------
MP4SinkError
Audio may only be formatted after recording is finished.
MP4SinkError
Formatting the audio failed.
"""
if self.vc.recording:
raise MP4SinkError(
"Audio may only be formatted after recording is finished."
)
mp4_file = f"{time.time()}.tmp"
args = [
"ffmpeg",
"-f",
"s16le",
"-ar",
"48000",
"-loglevel",
"error",
"-ac",
"2",
"-i",
"-",
"-f",
"mp4",
mp4_file,
]
if os.path.exists(mp4_file):
os.remove(
mp4_file
) # process will get stuck asking whether to overwrite, if file already exists.
try:
process = subprocess.Popen(
args, creationflags=CREATE_NO_WINDOW, stdin=subprocess.PIPE
)
except FileNotFoundError:
raise MP4SinkError("ffmpeg was not found.") from None
except subprocess.SubprocessError as exc:
raise MP4SinkError(
"Popen failed: {0.__class__.__name__}: {0}".format(exc)
) from exc
process.communicate(audio.file.read())
with open(mp4_file, "rb") as f:
audio.file = io.BytesIO(f.read())
audio.file.seek(0)
os.remove(mp4_file)
audio.on_format(self.encoding)
@@ -0,0 +1,96 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import io
import subprocess
from .core import CREATE_NO_WINDOW, Filters, Sink, default_filters
from .errors import OGGSinkError
class OGGSink(Sink):
"""A special sink for .ogg files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "ogg"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
"""Formats the recorded audio.
Raises
------
OGGSinkError
Audio may only be formatted after recording is finished.
OGGSinkError
Formatting the audio failed.
"""
if self.vc.recording:
raise OGGSinkError(
"Audio may only be formatted after recording is finished."
)
args = [
"ffmpeg",
"-f",
"s16le",
"-ar",
"48000",
"-loglevel",
"error",
"-ac",
"2",
"-i",
"-",
"-f",
"ogg",
"pipe:1",
]
try:
process = subprocess.Popen(
args,
creationflags=CREATE_NO_WINDOW,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
except FileNotFoundError:
raise OGGSinkError("ffmpeg was not found.") from None
except subprocess.SubprocessError as exc:
raise OGGSinkError(
"Popen failed: {0.__class__.__name__}: {0}".format(exc)
) from exc
out = process.communicate(audio.file.read())[0]
out = io.BytesIO(out)
out.seek(0)
audio.file = out
audio.on_format(self.encoding)
@@ -0,0 +1,45 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from .core import Filters, Sink, default_filters
class PCMSink(Sink):
"""A special sink for .pcm files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "pcm"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
return
@@ -0,0 +1,69 @@
"""
The MIT License (MIT)
Copyright (c) 2021-present Pycord Development
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import wave
from .core import Filters, Sink, default_filters
from .errors import WaveSinkError
class WaveSink(Sink):
"""A special sink for .wav(wave) files.
.. versionadded:: 2.0
"""
def __init__(self, *, filters=None):
if filters is None:
filters = default_filters
self.filters = filters
Filters.__init__(self, **self.filters)
self.encoding = "wav"
self.vc = None
self.audio_data = {}
def format_audio(self, audio):
"""Formats the recorded audio.
Raises
------
WaveSinkError
Audio may only be formatted after recording is finished.
WaveSinkError
Formatting the audio failed.
"""
if self.vc.recording:
raise WaveSinkError(
"Audio may only be formatted after recording is finished."
)
data = audio.file
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)
data.seek(0)
audio.on_format(self.encoding)