From 02d22f20b5976b16f9959f007fb1cf9b3ee7beed Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Sun, 21 May 2023 10:42:43 -0400 Subject: [PATCH] edit filters, log level matches handler, other fixes --- pomice/__init__.py | 2 +- pomice/filters.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++ pomice/player.py | 60 +++++++++++++++++++++++++++++++++++- pomice/pool.py | 8 +++-- 4 files changed, 142 insertions(+), 5 deletions(-) diff --git a/pomice/__init__.py b/pomice/__init__.py index d49ebf1..b6bc0be 100644 --- a/pomice/__init__.py +++ b/pomice/__init__.py @@ -20,7 +20,7 @@ if not discord.version_info.major >= 2: "using 'pip install discord.py'", ) -__version__ = "2.6.1a" +__version__ = "2.7.0a" __title__ = "pomice" __author__ = "cloudwithax" __license__ = "GPL-3.0" diff --git a/pomice/filters.py b/pomice/filters.py index 242e40b..f0df953 100644 --- a/pomice/filters.py +++ b/pomice/filters.py @@ -77,6 +77,12 @@ class Equalizer(Filter): def __repr__(self) -> str: return f"" + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, Equalizer): + return False + + return self.raw == __value.raw + @classmethod def flat(cls) -> "Equalizer": """Equalizer preset which represents a flat EQ board, @@ -231,6 +237,16 @@ class Timescale(Filter): def __repr__(self) -> str: return f"" + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, Timescale): + return False + + return ( + self.speed == __value.speed + and self.pitch == __value.pitch + and self.rate == __value.rate + ) + class Karaoke(Filter): """Filter which filters the vocal track from any song and leaves the instrumental. @@ -270,6 +286,17 @@ class Karaoke(Filter): f"filter_band={self.filter_band} filter_width={self.filter_width}>" ) + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, Karaoke): + return False + + return ( + self.level == __value.level + and self.mono_level == __value.mono_level + and self.filter_band == __value.filter_band + and self.filter_width == __value.filter_width + ) + class Tremolo(Filter): """Filter which produces a wavering tone in the music, @@ -305,6 +332,12 @@ class Tremolo(Filter): f"" ) + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, Tremolo): + return False + + return self.frequency == __value.frequency and self.depth == __value.depth + class Vibrato(Filter): """Filter which produces a wavering tone in the music, similar to the Tremolo filter, @@ -340,6 +373,12 @@ class Vibrato(Filter): f"" ) + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, Vibrato): + return False + + return self.frequency == __value.frequency and self.depth == __value.depth + class Rotation(Filter): """Filter which produces a stereo-like panning effect, which sounds like @@ -357,6 +396,12 @@ class Rotation(Filter): def __repr__(self) -> str: return f"" + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, Rotation): + return False + + return self.rotation_hertz == __value.rotation_hertz + class ChannelMix(Filter): """Filter which manually adjusts the panning of the audio, which can make @@ -418,6 +463,17 @@ class ChannelMix(Filter): f"right_to_left={self.right_to_left} right_to_right={self.right_to_right}>" ) + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, ChannelMix): + return False + + return ( + self.left_to_left == __value.left_to_left + and self.left_to_right == __value.left_to_right + and self.right_to_left == __value.right_to_left + and self.right_to_right == __value.right_to_right + ) + class Distortion(Filter): """Filter which generates a distortion effect. Useful for certain filter implementations where @@ -479,6 +535,21 @@ class Distortion(Filter): f"tan_scale={self.tan_scale} offset={self.offset} scale={self.scale}" ) + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, Distortion): + return False + + return ( + self.sin_offset == __value.sin_offset + and self.sin_scale == __value.sin_scale + and self.cos_offset == __value.cos_offset + and self.cos_scale == __value.cos_scale + and self.tan_offset == __value.tan_offset + and self.tan_scale == __value.tan_scale + and self.offset == __value.offset + and self.scale == __value.scale + ) + class LowPass(Filter): """Filter which supresses higher frequencies and allows lower frequencies to pass. @@ -495,3 +566,9 @@ class LowPass(Filter): def __repr__(self) -> str: return f"" + + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, LowPass): + return False + + return self.smoothing == __value.smoothing diff --git a/pomice/player.py b/pomice/player.py index 276a9df..da01696 100644 --- a/pomice/player.py +++ b/pomice/player.py @@ -79,6 +79,27 @@ class Filters: if filter.tag == filter_tag: del self._filters[index] + def edit_filter(self, *, filter_tag: str, to_apply: Filter) -> None: + """Edits a filter in the list of filters applied using its filter tag and replaces it with the new filter.""" + if not any(f for f in self._filters if f.tag == filter_tag): + raise FilterTagInvalid("A filter with that tag was not found.") + + for index, filter in enumerate(self._filters): + if filter.tag == filter_tag: + if not type(filter) == type(to_apply): + raise FilterInvalidArgument( + "Edited filter is not the same type as the current filter.", + ) + if self._filters[index] == to_apply: + raise FilterInvalidArgument("Edited filter is the same as the current filter.") + + if to_apply.tag != filter_tag: + raise FilterInvalidArgument( + "Edited filter tag is not the same as the current filter tag.", + ) + + self._filters[index] = to_apply + def has_filter(self, *, filter_tag: str) -> bool: """Checks if a filter exists in the list of filters using its filter tag""" return any(f for f in self._filters if f.tag == filter_tag) @@ -382,6 +403,16 @@ class Player(VoiceProtocol): """ return await self._node.get_tracks(query, ctx=ctx, search_type=search_type, filters=filters) + async def build_track(self, identifier: str, ctx: Optional[commands.Context] = None) -> Track: + """ + Builds a track using a valid track identifier + + You can also pass in a discord.py Context object to get a + Context object on the track it builds. + """ + + return await self._node.build_track(identifier, ctx=ctx) + async def get_recommendations( self, *, track: Track, ctx: Optional[commands.Context] = None ) -> Optional[Union[List[Track], Playlist]]: @@ -431,7 +462,7 @@ class Player(VoiceProtocol): except AttributeError: # 'NoneType' has no attribute '_get_voice_client_key' raised by self.cleanup() -> # assume we're already disconnected and cleaned up - assert not self.is_connected and not self.channel + assert self.channel is None and not self.is_connected self._node._players.pop(self.guild.id) if self.node.is_connected: @@ -637,6 +668,33 @@ class Player(VoiceProtocol): return self._filters + async def edit_filter( + self, *, filter_tag: str, edited_filter: Filter, fast_apply: bool = False + ) -> Filters: + """Edits a filter from the player using its filter tag and a new filter of the same type. + The filter to be replaced must have the same tag as the one you are replacing it with. + This will only work if you are using a version of Lavalink that supports filters. + + If you would like for the filter to apply instantly, set the `fast_apply` arg to `True`. + + (You must have a song playing in order for `fast_apply` to work.) + """ + + self._filters.edit_filter(filter_tag=filter_tag, to_apply=edited_filter) + payload = self._filters.get_all_payloads() + await self._node.send( + method="PATCH", + path=self._player_endpoint_uri, + guild_id=self._guild.id, + data={"filters": payload}, + ) + self._log.debug(f"Filter with tag {filter_tag} has been edited to {edited_filter!r}") + if fast_apply: + self._log.debug(f"Fast apply passed, now editing filter instantly.") + await self.seek(self.position) + + return self._filters + async def reset_filters(self, *, fast_apply: bool = False) -> None: """Resets all currently applied filters to their default parameters. You must have filters applied in order for this to work. diff --git a/pomice/pool.py b/pomice/pool.py index 599cdeb..107c64b 100644 --- a/pomice/pool.py +++ b/pomice/pool.py @@ -112,7 +112,7 @@ class Node: password: str, identifier: str, secure: bool = False, - heartbeat: int = 60, + heartbeat: int = 120, resume_key: Optional[str] = None, resume_timeout: int = 60, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -234,13 +234,15 @@ class Node: def _setup_logging(self, level: LogLevel) -> logging.Logger: logger = logging.getLogger("pomice") - logger.setLevel(level) + handler = None if self._log_handler: handler = self._log_handler + logger.setLevel(handler.level) else: handler = logging.StreamHandler() + logger.setLevel(level) dt_fmt = "%Y-%m-%d %H:%M:%S" formatter = logging.Formatter( "[{asctime}] [{levelname:<8}] {name}: {message}", @@ -960,7 +962,7 @@ class NodePool: password: str, identifier: str, secure: bool = False, - heartbeat: int = 30, + heartbeat: int = 120, resume_key: Optional[str] = None, resume_timeout: int = 60, loop: Optional[asyncio.AbstractEventLoop] = None,