From a6a0b18ef3b0afd740a7852dc15b66f2b1d8a16d Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Sun, 23 Apr 2023 13:46:50 -0400 Subject: [PATCH 01/14] Added the ability to set rate and channels on Sound --- twitchio/ext/sounds/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/twitchio/ext/sounds/__init__.py b/twitchio/ext/sounds/__init__.py index 54989d12..7c38c06d 100644 --- a/twitchio/ext/sounds/__init__.py +++ b/twitchio/ext/sounds/__init__.py @@ -216,11 +216,21 @@ def channels(self): """The audio source channels.""" return self._channels + @channels.setter + def channels(self, channels): + """Set audio source channels.""" + self._channels = channels + @property def rate(self): """The audio source sample rate.""" return self._rate + @rate.setter + def rate(self, rate): + """Set audio source sample rate.""" + self._rate = rate + @property def source(self): """The raw audio source.""" From 20ba986f0d7f1be049d405044355d2c2714decc5 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Sun, 23 Apr 2023 14:09:37 -0400 Subject: [PATCH 02/14] Added the ability to set rate and channels on Sound --- docs/exts/sounds.rst | 5 ++++- twitchio/ext/sounds/__init__.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/exts/sounds.rst b/docs/exts/sounds.rst index eb5b9a14..fc37ec86 100644 --- a/docs/exts/sounds.rst +++ b/docs/exts/sounds.rst @@ -91,11 +91,14 @@ This bot will search YouTube for a relevant video and playback its audio. **Sound with a Local File:** -This Sound will target a local file on your machine. Just pass the location to source. +This Sound will target a local file on your machine. Pass the location to source. You +may manually set the bitrate and number of channels if needed. .. code-block:: python3 sound = sounds.Sound(source='my_audio.mp3') + sound.channel = 1 # play mono channel + sound.rate = 24_000 # set bitrate **Multiple Players:** diff --git a/twitchio/ext/sounds/__init__.py b/twitchio/ext/sounds/__init__.py index 7c38c06d..3e0bec4b 100644 --- a/twitchio/ext/sounds/__init__.py +++ b/twitchio/ext/sounds/__init__.py @@ -217,7 +217,7 @@ def channels(self): return self._channels @channels.setter - def channels(self, channels): + def channels(self, channels: int): """Set audio source channels.""" self._channels = channels @@ -227,7 +227,7 @@ def rate(self): return self._rate @rate.setter - def rate(self, rate): + def rate(self, rate: int): """Set audio source sample rate.""" self._rate = rate From 03e52ba19542f9d0b2adb1ce130d30b99c7dc08e Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Sun, 23 Apr 2023 14:40:02 -0400 Subject: [PATCH 03/14] typo in docs --- docs/exts/sounds.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exts/sounds.rst b/docs/exts/sounds.rst index fc37ec86..58643f44 100644 --- a/docs/exts/sounds.rst +++ b/docs/exts/sounds.rst @@ -97,7 +97,7 @@ may manually set the bitrate and number of channels if needed. .. code-block:: python3 sound = sounds.Sound(source='my_audio.mp3') - sound.channel = 1 # play mono channel + sound.channels = 1 # play mono channel sound.rate = 24_000 # set bitrate From 2bf418c904f7a7c738f98565e3fcde91daacfb51 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Sun, 23 Apr 2023 15:08:18 -0400 Subject: [PATCH 04/14] typo in docs --- docs/exts/sounds.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/exts/sounds.rst b/docs/exts/sounds.rst index 58643f44..51836437 100644 --- a/docs/exts/sounds.rst +++ b/docs/exts/sounds.rst @@ -92,13 +92,13 @@ This bot will search YouTube for a relevant video and playback its audio. **Sound with a Local File:** This Sound will target a local file on your machine. Pass the location to source. You -may manually set the bitrate and number of channels if needed. +may manually set the sample rate and number of channels if needed. .. code-block:: python3 sound = sounds.Sound(source='my_audio.mp3') sound.channels = 1 # play mono channel - sound.rate = 24_000 # set bitrate + sound.rate = 24_000 # set sample **Multiple Players:** From 215e5b2017c1ed45e9d43ebb0b8e314f26320edc Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Mon, 24 Apr 2023 14:24:45 -0400 Subject: [PATCH 05/14] add to changelog --- docs/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index c91f6b40..0b47c554 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,9 @@ Master - Fix :func:`~twitchio.Client.search_categories` due to :attr:`~twitchio.Game.igdb_id` being added to :class:`~twitchio.Game` - Made Chatter :attr:`~twitchio.Chatter.id` property public +- ext.sounds + - Bug fixes + - Added setters for Sounds.rate and Sounds.channels properties 2.6.0 ====== - TwitchIO From e7e2047696d131481a926bd2606030146bc9179e Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Tue, 25 Apr 2023 22:57:23 -0400 Subject: [PATCH 06/14] Added tinytag dependency to retrieve audio file metadata This allows for automatic detetction of local audio files' sample rate and audio channels so the files play correctly --- twitchio/ext/sounds/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/twitchio/ext/sounds/__init__.py b/twitchio/ext/sounds/__init__.py index 3e0bec4b..00c770f2 100644 --- a/twitchio/ext/sounds/__init__.py +++ b/twitchio/ext/sounds/__init__.py @@ -33,6 +33,7 @@ import pyaudio from yt_dlp import YoutubeDL +from tinytag import TinyTag __all__ = ("Sound", "AudioPlayer") @@ -173,6 +174,9 @@ def __init__( elif isinstance(source, str): self.title = source + tag = TinyTag.get(source) + self._rate = tag.samplerate + self._channels = tag.channels self.proc = subprocess.Popen( [ @@ -189,8 +193,6 @@ def __init__( stdout=subprocess.PIPE, ) - self._channels = 2 - self._rate = 48000 @classmethod async def ytdl_search(cls, search: str, *, loop: Optional[asyncio.BaseEventLoop] = None): From a2176dc7a79f88be1dc00c88ffef1c9e72b27482 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Tue, 25 Apr 2023 23:01:01 -0400 Subject: [PATCH 07/14] Updated Sounds Docs to reflect auto-detect of sample rate etc --- docs/exts/sounds.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/exts/sounds.rst b/docs/exts/sounds.rst index 51836437..23734828 100644 --- a/docs/exts/sounds.rst +++ b/docs/exts/sounds.rst @@ -92,7 +92,8 @@ This bot will search YouTube for a relevant video and playback its audio. **Sound with a Local File:** This Sound will target a local file on your machine. Pass the location to source. You -may manually set the sample rate and number of channels if needed. +may manually set the sample rate and number of channels if needed, however it should +be automatically detected. .. code-block:: python3 From 0c46ba733addd75367384de536356526f75cbab9 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Tue, 18 Jun 2024 02:39:25 -0400 Subject: [PATCH 08/14] Added new dependency for resolving stereo channels and sample rate from audio files. Added setters for rate and channel to expose them incase the meta data is incorrect. --- docs/changelog.rst | 6 ++++++ docs/requirements.txt | 3 ++- setup.py | 1 + twitchio/ext/sounds/__init__.py | 18 +++++++++++++++--- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index eea17896..382355ef 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,12 @@ :method:`Twitchio.ext.eventsub.EventSubWSClient.subscribe_channel_unban_request_create ` - Added :method:`Twitchio.ext.eventsub.EventSubClient.subscribe_channel_unban_request_resolve ` / :method:`Twitchio.ext.eventsub.EventSubWSClient.subscribe_channel_unban_request_resolve ` +- ext.sounds + - Additions + - Added TinyTag as a dependency to support retrieving audio metadata using TinyTag in `ext.sounds.__init__.py`. + - added :method:`Twitchio.ext.sounds.rate setter. + - added :method:`Twitchio.ext.sounds.channels setter. + 2.9.2 ======= diff --git a/docs/requirements.txt b/docs/requirements.txt index 0e5db09c..40eb98e7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -7,4 +7,5 @@ sphinxext-opengraph Pygments furo pyaudio==0.2.11 -yt-dlp>=2022.2.4 \ No newline at end of file +yt-dlp>=2022.2.4 +tinytag>=1.9.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 3e706895..3c0f4d5d 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ sounds = [ "yt-dlp>=2022.2.4", 'pyaudio==0.2.11; platform_system!="Windows"', + 'tinytag>=1.9.0', ] speed = [ "ujson>=5.2,<6", diff --git a/twitchio/ext/sounds/__init__.py b/twitchio/ext/sounds/__init__.py index 54989d12..24ff1b87 100644 --- a/twitchio/ext/sounds/__init__.py +++ b/twitchio/ext/sounds/__init__.py @@ -20,6 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + import asyncio import audioop import dataclasses @@ -33,6 +34,7 @@ import pyaudio from yt_dlp import YoutubeDL +from tinytag import TinyTag __all__ = ("Sound", "AudioPlayer") @@ -173,6 +175,9 @@ def __init__( elif isinstance(source, str): self.title = source + tag = TinyTag.get(source) + self._rate = tag.samplerate + self._channels = tag.channels self.proc = subprocess.Popen( [ @@ -189,9 +194,6 @@ def __init__( stdout=subprocess.PIPE, ) - self._channels = 2 - self._rate = 48000 - @classmethod async def ytdl_search(cls, search: str, *, loop: Optional[asyncio.BaseEventLoop] = None): """|coro| @@ -216,11 +218,21 @@ def channels(self): """The audio source channels.""" return self._channels + @channels.setter + def channels(self, channels: int): + """Set audio source channels.""" + self._channels = channels + @property def rate(self): """The audio source sample rate.""" return self._rate + @rate.setter + def rate(self, rate: int): + """Set audio source sample rate.""" + self._rate = rate + @property def source(self): """The raw audio source.""" From a935e80fea5530b1c509b6e0e004fdf1298e6883 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Tue, 9 Jul 2024 00:35:14 -0400 Subject: [PATCH 09/14] Added queuemanager to sounds.ext --- docs/changelog.rst | 18 ++++ examples/music_queue.py | 57 +++++++++++ twitchio/ext/sounds/queuemanager.py | 141 ++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 examples/music_queue.py create mode 100644 twitchio/ext/sounds/queuemanager.py diff --git a/docs/changelog.rst b/docs/changelog.rst index 14213d7c..930d323a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,24 @@ :orphan: +2.10.1 +======= +- ext.sounds + - Additions + - Added :class:`Twitchio.ext.sounds.AudioQueueManager` to manage a queue of audio files to be played sequentially with optional repeat functionality. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.add_audio` to add a new audio file to the queue. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.play_next` to play the next audio file in the queue. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.skip_audio` to stop the currently playing audio file. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.stop_audio` to stop the currently playing audio file and reset the playing flag. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.pause_audio` to pause the currently playing audio file. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.resume_audio` to resume the currently paused audio file. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.clear_queue` to clear all audio files from the queue. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.pause_queue` to pause the processing of the queue. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.resume_queue` to resume the processing of the queue. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.get_queue_contents` to retrieve the current contents of the queue. + - Added :method:`Twitchio.ext.sounds.AudioQueueManager.queue_loop` to continuously check the queue and play the next audio file if not currently playing and not paused. + + 2.10.0 ======= - TwitchIO diff --git a/examples/music_queue.py b/examples/music_queue.py new file mode 100644 index 00000000..9a332ade --- /dev/null +++ b/examples/music_queue.py @@ -0,0 +1,57 @@ +import asyncio +from twitchio.ext import commands, sounds +from twitchio.ext.sounds import queuemanager + + +class Bot(commands.Bot): + + def __init__(self): + super().__init__(token="TOKEN", prefix="!", initial_channels=["CHANNEL"]) + self.audio_manager = queuemanager.AudioQueueManager() + + # Adding sound files paths to the queue for uses to choose from + song_dict = { + "song_one": "\\PATH\\TO\\FILE.mp3", + "song_two": "\\PATH\\TO\\FILE.mp3", + "song_three": "\\PATH\\TO\\FILE.mp3", + } + + async def event_ready(self): + loop = asyncio.get_event_loop() + + # Start the queue loop + self.task = loop.create_task(self.audio_manager.queue_loop()) + + @commands.command(name="sr") + async def addsound(self, ctx: commands.Context, sound: str): + sound_path = self.song_dict[sound] + await self.audio_manager.add_audio(sound_path) + await ctx.send(f"Added sound to queue: {sound_path}") + + @commands.command(name="skip") + async def skip(self, ctx: commands.Context): + await ctx.send(f"Skipped the current sound. {self.audio_manager.current_sound}") + await self.audio_manager.skip_audio() + + @commands.command(name="pause") + async def pause(self, ctx: commands.Context): + await self.audio_manager.pause_audio() + + @commands.command(name="resume") + async def resume(self, ctx: commands.Context): + await self.audio_manager.resume_audio() + + @commands.command(name="queue") + async def queue(self, ctx: commands.Context): + queue_contents = await self.audio_manager.get_queue_contents() + await ctx.send(f"Queue contents: {queue_contents}") + + # Override close method to gracefully cancel the task + async def close(self): + self.task.cancel() + await super().close() + + +if __name__ == "__main__": + bot = Bot() + bot.run() diff --git a/twitchio/ext/sounds/queuemanager.py b/twitchio/ext/sounds/queuemanager.py new file mode 100644 index 00000000..c0c1cb59 --- /dev/null +++ b/twitchio/ext/sounds/queuemanager.py @@ -0,0 +1,141 @@ +import asyncio +from twitchio.ext import sounds + + +class AudioQueueManager: + """ + Manages a queue of audio files to be played sequentially with optional repeat and pause functionalities. + + Attributes: + queue (asyncio.Queue[str]): A queue to hold paths of audio files to be played. + is_playing (bool): Indicates whether an audio file is currently being played. + repeat_queue (bool): If True, adds the current playing audio file back to the queue after playing. + queue_paused (bool): If True, pauses the processing of the queue. + player (sounds.AudioPlayer): An instance of AudioPlayer to play audio files. + current_sound (str): Path of the currently playing audio file. + """ + + def __init__(self): + """ + Initializes an instance of AudioQueueManager with an empty queue and default settings. + """ + self.queue: asyncio.Queue[str] = asyncio.Queue() + self.is_playing: bool = False + self.repeat_queue: bool = True + self.queue_paused: bool = False + self.player: sounds.AudioPlayer = sounds.AudioPlayer( + callback=self.player_done) + self.current_sound: str = "" + + async def player_done(self) -> None: + """ + Callback method called when the player finishes playing an audio file. + Resets the is_playing flag and marks the current task as done in the queue. + """ + await asyncio.sleep(0.1) + self.is_playing = False + self.queue.task_done() + + async def add_audio(self, sound_path: str) -> None: + """ + Adds a new audio file to the queue. + + Args: + sound_path (str): Path of the audio file to add to the queue. + """ + await asyncio.sleep(0.1) + await self.queue.put(sound_path) + + async def play_next(self) -> None: + """ + Plays the next audio file in the queue if the queue is not empty and not paused. + Sets the is_playing flag, retrieves the next audio file from the queue, and plays it. + If repeat_queue is True, adds the current audio file back to the queue after playing. + """ + await asyncio.sleep(0.1) + if not self.queue.empty() and not self.queue_paused: + self.is_playing = True + sound_path = await self.queue.get() + self.current_sound = sound_path + sound = sounds.Sound(source=sound_path) + self.player.play(sound) + if self.repeat_queue: + await self.queue.put(self.current_sound) + + async def skip_audio(self) -> None: + """ + Stops the currently playing audio file if there is one. + """ + await asyncio.sleep(0.1) + if self.is_playing: + self.player.stop() + self.is_playing = False + + async def stop_audio(self) -> None: + """ + Stops the currently playing audio file. + Resets the playing flag but leaves the queue intact. + """ + await asyncio.sleep(0.1) + if self.is_playing: + self.player.stop() + self.is_playing = False + + async def pause_audio(self) -> None: + """ + Pauses the currently playing audio file. + """ + await asyncio.sleep(0.1) + self.player.pause() + + async def resume_audio(self) -> None: + """ + Resumes the currently paused audio file. + """ + await asyncio.sleep(0.1) + self.player.resume() + + async def clear_queue(self) -> None: + """ + Clears all audio files from the queue. + """ + await asyncio.sleep(0.1) + while not self.queue.empty(): + await self.queue.get() + self.queue.task_done() + + async def pause_queue(self) -> None: + """ + Pauses the processing of the queue. + """ + await asyncio.sleep(0.1) + self.queue_paused = True + + async def resume_queue(self) -> None: + """ + Resumes the processing of the queue. + """ + await asyncio.sleep(0.1) + self.queue_paused = False + + async def get_queue_contents(self) -> list: + """ + Retrieves the current contents of the queue. + + Returns: + list: List of paths of audio files in the queue. + """ + await asyncio.sleep(0.1) + return list(self.queue._queue) + + async def queue_loop(self) -> None: + """ + Continuously checks the queue and plays the next audio file if not currently playing and not paused. + """ + try: + while True: + await asyncio.sleep(0.2) + if not self.is_playing and not self.queue.empty() and not self.queue_paused: + await self.play_next() + finally: + return From 2a739ed1d593a43039cb34c39c5295b2acbd80cb Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Wed, 10 Jul 2024 01:17:53 -0400 Subject: [PATCH 10/14] Fixed docstrings, removed unnecessary async definitions for methods that didn't need to be coroutines, updated example --- examples/music_queue.py | 23 ++++---- twitchio/ext/sounds/queuemanager.py | 82 +++++++++++++++++------------ 2 files changed, 57 insertions(+), 48 deletions(-) diff --git a/examples/music_queue.py b/examples/music_queue.py index 9a332ade..5bc5b432 100644 --- a/examples/music_queue.py +++ b/examples/music_queue.py @@ -6,20 +6,16 @@ class Bot(commands.Bot): def __init__(self): - super().__init__(token="TOKEN", prefix="!", initial_channels=["CHANNEL"]) + super().__init__(token="TOKEN", prefix="!", initial_channels=["sockheadrps"]) self.audio_manager = queuemanager.AudioQueueManager() - - # Adding sound files paths to the queue for uses to choose from - song_dict = { - "song_one": "\\PATH\\TO\\FILE.mp3", - "song_two": "\\PATH\\TO\\FILE.mp3", - "song_three": "\\PATH\\TO\\FILE.mp3", + self.song_dict = { + "song_one": "C:\\PATH\\TO\\FILE.mp3", + "song_two": "C:\\PATH\\TO\\FILE.mp3", + "song_three": "C:\\PATH\\TO\\FILE.mp3", } async def event_ready(self): loop = asyncio.get_event_loop() - - # Start the queue loop self.task = loop.create_task(self.audio_manager.queue_loop()) @commands.command(name="sr") @@ -31,22 +27,21 @@ async def addsound(self, ctx: commands.Context, sound: str): @commands.command(name="skip") async def skip(self, ctx: commands.Context): await ctx.send(f"Skipped the current sound. {self.audio_manager.current_sound}") - await self.audio_manager.skip_audio() + self.audio_manager.skip_audio() @commands.command(name="pause") async def pause(self, ctx: commands.Context): - await self.audio_manager.pause_audio() + self.audio_manager.pause_audio() @commands.command(name="resume") async def resume(self, ctx: commands.Context): - await self.audio_manager.resume_audio() + self.audio_manager.resume_audio() @commands.command(name="queue") async def queue(self, ctx: commands.Context): - queue_contents = await self.audio_manager.get_queue_contents() + queue_contents = self.audio_manager.get_queue_contents() await ctx.send(f"Queue contents: {queue_contents}") - # Override close method to gracefully cancel the task async def close(self): self.task.cancel() await super().close() diff --git a/twitchio/ext/sounds/queuemanager.py b/twitchio/ext/sounds/queuemanager.py index c0c1cb59..267a226f 100644 --- a/twitchio/ext/sounds/queuemanager.py +++ b/twitchio/ext/sounds/queuemanager.py @@ -1,58 +1,75 @@ import asyncio from twitchio.ext import sounds +from typing import Optional class AudioQueueManager: """ Manages a queue of audio files to be played sequentially with optional repeat and pause functionalities. - Attributes: - queue (asyncio.Queue[str]): A queue to hold paths of audio files to be played. - is_playing (bool): Indicates whether an audio file is currently being played. - repeat_queue (bool): If True, adds the current playing audio file back to the queue after playing. - queue_paused (bool): If True, pauses the processing of the queue. - player (sounds.AudioPlayer): An instance of AudioPlayer to play audio files. - current_sound (str): Path of the currently playing audio file. + Attributes + ---------- + queue: asyncio.Queue[:class:`str`] + A queue to hold paths of audio files to be played. + is_playing: :class:`bool` + Indicates whether an audio file is currently being played. + repeat_queue: :class:`bool` + If True, adds the current playing audio file back to the queue after playing. + queue_paused: :class:`bool` + If True, pauses the processing of the queue. + player: :class:`sounds.AudioPlayer` + An instance of AudioPlayer to play audio files. + current_sound: :class:`str` + Path of the currently playing audio file. """ - def __init__(self): + def __init__(self, repeat_queue: Optional[bool] = True) -> None: """ Initializes an instance of AudioQueueManager with an empty queue and default settings. + + Parameters + ---------- + repeat_queue: Optional[:class:`bool`] + If True, adds the current playing audio file back to the queue after playing, by default True """ self.queue: asyncio.Queue[str] = asyncio.Queue() self.is_playing: bool = False - self.repeat_queue: bool = True + self.repeat_queue: bool = repeat_queue if repeat_queue is not None else True self.queue_paused: bool = False - self.player: sounds.AudioPlayer = sounds.AudioPlayer( - callback=self.player_done) + self.player: sounds.AudioPlayer = sounds.AudioPlayer(callback=self.player_done) self.current_sound: str = "" async def player_done(self) -> None: """ + |coro| + Callback method called when the player finishes playing an audio file. Resets the is_playing flag and marks the current task as done in the queue. """ - await asyncio.sleep(0.1) self.is_playing = False self.queue.task_done() async def add_audio(self, sound_path: str) -> None: """ + |coro| + Adds a new audio file to the queue. - Args: - sound_path (str): Path of the audio file to add to the queue. + Parameters + ---------- + sound_path: :class:`str` + Path of the audio file to add to the queue. """ - await asyncio.sleep(0.1) await self.queue.put(sound_path) async def play_next(self) -> None: """ + |coro| + Plays the next audio file in the queue if the queue is not empty and not paused. Sets the is_playing flag, retrieves the next audio file from the queue, and plays it. If repeat_queue is True, adds the current audio file back to the queue after playing. """ - await asyncio.sleep(0.1) if not self.queue.empty() and not self.queue_paused: self.is_playing = True sound_path = await self.queue.get() @@ -62,74 +79,71 @@ async def play_next(self) -> None: if self.repeat_queue: await self.queue.put(self.current_sound) - async def skip_audio(self) -> None: + def skip_audio(self) -> None: """ Stops the currently playing audio file if there is one. """ - await asyncio.sleep(0.1) if self.is_playing: self.player.stop() self.is_playing = False - async def stop_audio(self) -> None: + def stop_audio(self) -> None: """ Stops the currently playing audio file. Resets the playing flag but leaves the queue intact. """ - await asyncio.sleep(0.1) if self.is_playing: self.player.stop() self.is_playing = False - async def pause_audio(self) -> None: + def pause_audio(self) -> None: """ Pauses the currently playing audio file. """ - await asyncio.sleep(0.1) self.player.pause() - async def resume_audio(self) -> None: + def resume_audio(self) -> None: """ Resumes the currently paused audio file. """ - await asyncio.sleep(0.1) self.player.resume() async def clear_queue(self) -> None: """ + |coro| + Clears all audio files from the queue. """ - await asyncio.sleep(0.1) while not self.queue.empty(): await self.queue.get() self.queue.task_done() - async def pause_queue(self) -> None: + def pause_queue(self) -> None: """ Pauses the processing of the queue. """ - await asyncio.sleep(0.1) self.queue_paused = True - async def resume_queue(self) -> None: + def resume_queue(self) -> None: """ Resumes the processing of the queue. """ - await asyncio.sleep(0.1) self.queue_paused = False - async def get_queue_contents(self) -> list: + def get_queue_contents(self) -> list[str]: """ - Retrieves the current contents of the queue. + Retrieves the current contents of the queue as a list. - Returns: - list: List of paths of audio files in the queue. + Returns + ------- + List[:class:`str`] """ - await asyncio.sleep(0.1) return list(self.queue._queue) async def queue_loop(self) -> None: """ + |coro| + Continuously checks the queue and plays the next audio file if not currently playing and not paused. """ try: From f5532d8dd28500c828c4dfbe10a083ddbad533c2 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Wed, 10 Jul 2024 01:43:00 -0400 Subject: [PATCH 11/14] typo in example --- examples/music_queue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/music_queue.py b/examples/music_queue.py index 5bc5b432..57e6d673 100644 --- a/examples/music_queue.py +++ b/examples/music_queue.py @@ -6,7 +6,7 @@ class Bot(commands.Bot): def __init__(self): - super().__init__(token="TOKEN", prefix="!", initial_channels=["sockheadrps"]) + super().__init__(token="TOKEN", prefix="!", initial_channels=["CHANNEL"]) self.audio_manager = queuemanager.AudioQueueManager() self.song_dict = { "song_one": "C:\\PATH\\TO\\FILE.mp3", From 23ba3939a51449f9cfdb1ded83bc64263129f7e4 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Wed, 17 Jul 2024 22:47:45 -0400 Subject: [PATCH 12/14] Changes for PR review --- examples/music_queue.py | 1 + twitchio/ext/sounds/queuemanager.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/music_queue.py b/examples/music_queue.py index 57e6d673..19121189 100644 --- a/examples/music_queue.py +++ b/examples/music_queue.py @@ -50,3 +50,4 @@ async def close(self): if __name__ == "__main__": bot = Bot() bot.run() + diff --git a/twitchio/ext/sounds/queuemanager.py b/twitchio/ext/sounds/queuemanager.py index 267a226f..a4686e36 100644 --- a/twitchio/ext/sounds/queuemanager.py +++ b/twitchio/ext/sounds/queuemanager.py @@ -1,6 +1,6 @@ import asyncio from twitchio.ext import sounds -from typing import Optional +from typing import Optional, List class AudioQueueManager: @@ -23,7 +23,7 @@ class AudioQueueManager: Path of the currently playing audio file. """ - def __init__(self, repeat_queue: Optional[bool] = True) -> None: + def __init__(self, repeat_queue: Optional[bool]=True) -> None: """ Initializes an instance of AudioQueueManager with an empty queue and default settings. @@ -34,7 +34,7 @@ def __init__(self, repeat_queue: Optional[bool] = True) -> None: """ self.queue: asyncio.Queue[str] = asyncio.Queue() self.is_playing: bool = False - self.repeat_queue: bool = repeat_queue if repeat_queue is not None else True + self.repeat_queue: bool = repeat_queue self.queue_paused: bool = False self.player: sounds.AudioPlayer = sounds.AudioPlayer(callback=self.player_done) self.current_sound: str = "" @@ -130,7 +130,7 @@ def resume_queue(self) -> None: """ self.queue_paused = False - def get_queue_contents(self) -> list[str]: + def get_queue_contents(self) -> List[str]: """ Retrieves the current contents of the queue as a list. From be2bed0a64dd313f3aa4949d93e3cbbdb054af34 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Thu, 18 Jul 2024 00:00:22 -0400 Subject: [PATCH 13/14] removed duplicate __all__ --- twitchio/ext/sounds/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/twitchio/ext/sounds/__init__.py b/twitchio/ext/sounds/__init__.py index 24ff1b87..c25e2fb9 100644 --- a/twitchio/ext/sounds/__init__.py +++ b/twitchio/ext/sounds/__init__.py @@ -59,9 +59,6 @@ ffmpeg_bin = "ffmpeg" -__all__ = ("Sound", "AudioPlayer") - - @dataclasses.dataclass class OutputDevice: """Class which represents an OutputDevice usable with :class:`AudioPlayer` . From ca49519fd7b025120d548fb0988cb95e432e6747 Mon Sep 17 00:00:00 2001 From: sockheadrps Date: Thu, 18 Jul 2024 01:11:15 -0400 Subject: [PATCH 14/14] Fixed docs --- docs/changelog.rst | 24 +++++++++---------- docs/exts/sounds.rst | 3 +++ examples/music_queue.py | 4 +--- twitchio/ext/sounds/__init__.py | 4 +++- .../{queuemanager.py => audioqueuemanager.py} | 2 +- 5 files changed, 20 insertions(+), 17 deletions(-) rename twitchio/ext/sounds/{queuemanager.py => audioqueuemanager.py} (98%) diff --git a/docs/changelog.rst b/docs/changelog.rst index bd9e0458..90d11aae 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,18 +5,18 @@ ======= - ext.sounds - Additions - - Added :class:`Twitchio.ext.sounds.AudioQueueManager` to manage a queue of audio files to be played sequentially with optional repeat functionality. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.add_audio` to add a new audio file to the queue. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.play_next` to play the next audio file in the queue. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.skip_audio` to stop the currently playing audio file. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.stop_audio` to stop the currently playing audio file and reset the playing flag. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.pause_audio` to pause the currently playing audio file. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.resume_audio` to resume the currently paused audio file. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.clear_queue` to clear all audio files from the queue. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.pause_queue` to pause the processing of the queue. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.resume_queue` to resume the processing of the queue. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.get_queue_contents` to retrieve the current contents of the queue. - - Added :method:`Twitchio.ext.sounds.AudioQueueManager.queue_loop` to continuously check the queue and play the next audio file if not currently playing and not paused. + - Added :class:`twitchio.ext.sounds.AudioQueueManager` + - Added :meth:`AudioQueueManager.add_audio ` + - Added :meth:`AudioQueueManager.play_next ` + - Added :meth:`AudioQueueManager.skip_audio ` + - Added :meth:`AudioQueueManager.stop_audio ` + - Added :meth:`AudioQueueManager.pause_audio ` + - Added :meth:`AudioQueueManager.resume_audio ` + - Added :meth:`AudioQueueManager.clear_queue ` + - Added :meth:`AudioQueueManager.pause_queue ` + - Added :meth:`AudioQueueManager.resume_queue ` + - Added :meth:`AudioQueueManager.get_queue_contents ` + - Added :meth:`AudioQueueManager.queue_loop ` 2.10.0 diff --git a/docs/exts/sounds.rst b/docs/exts/sounds.rst index 23734828..27be7590 100644 --- a/docs/exts/sounds.rst +++ b/docs/exts/sounds.rst @@ -185,3 +185,6 @@ API Reference .. autoclass:: AudioPlayer :members: + +.. autoclass:: AudioQueueManager + :members: diff --git a/examples/music_queue.py b/examples/music_queue.py index 19121189..e035d1e8 100644 --- a/examples/music_queue.py +++ b/examples/music_queue.py @@ -1,13 +1,12 @@ import asyncio from twitchio.ext import commands, sounds -from twitchio.ext.sounds import queuemanager class Bot(commands.Bot): def __init__(self): super().__init__(token="TOKEN", prefix="!", initial_channels=["CHANNEL"]) - self.audio_manager = queuemanager.AudioQueueManager() + self.audio_manager = sounds.AudioQueueManager() self.song_dict = { "song_one": "C:\\PATH\\TO\\FILE.mp3", "song_two": "C:\\PATH\\TO\\FILE.mp3", @@ -50,4 +49,3 @@ async def close(self): if __name__ == "__main__": bot = Bot() bot.run() - diff --git a/twitchio/ext/sounds/__init__.py b/twitchio/ext/sounds/__init__.py index c25e2fb9..d269c042 100644 --- a/twitchio/ext/sounds/__init__.py +++ b/twitchio/ext/sounds/__init__.py @@ -36,8 +36,10 @@ from yt_dlp import YoutubeDL from tinytag import TinyTag +from .audioqueuemanager import AudioQueueManager -__all__ = ("Sound", "AudioPlayer") + +__all__ = ("Sound", "AudioPlayer", "AudioQueueManager") logger = logging.getLogger(__name__) diff --git a/twitchio/ext/sounds/queuemanager.py b/twitchio/ext/sounds/audioqueuemanager.py similarity index 98% rename from twitchio/ext/sounds/queuemanager.py rename to twitchio/ext/sounds/audioqueuemanager.py index a4686e36..9c5b0906 100644 --- a/twitchio/ext/sounds/queuemanager.py +++ b/twitchio/ext/sounds/audioqueuemanager.py @@ -23,7 +23,7 @@ class AudioQueueManager: Path of the currently playing audio file. """ - def __init__(self, repeat_queue: Optional[bool]=True) -> None: + def __init__(self, repeat_queue: Optional[bool] = True) -> None: """ Initializes an instance of AudioQueueManager with an empty queue and default settings.