On branch DiscordProfile
Initial commit
This commit is contained in:
@@ -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 *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
Reference in New Issue
Block a user