diff --git a/modules/__init__.py b/modules/__init__.py index 480d4fe..dcfa4bd 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -3,19 +3,32 @@ class PlayerModule: Simple passive observer, this allows you to send the current track the your RDS encoder, or to your website """ def on_new_playlist(self, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): - """Tuple consists of the track path, to fade out, fade in, official, and args""" + """Tuple consists of the track path, to fade out, fade in, official, and args + This is called every new playlist""" + pass + def on_new_track(self, index: int, track: str, to_fade_in: bool, to_fade_out: bool, official: bool): + """ + Called on every track including the ones added by the active modifier, you can check for that comparing the playlists[index] and the track + """ pass - def on_new_track(self, index: int, track: str, to_fade_in: bool, to_fade_out: bool, official: bool): pass class PlaylistModifierModule: """ Playlist modifier, this type of module allows you to shuffle, or put jingles into your playlist """ - def modify(self, global_args: dict, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): return playlist + def modify(self, global_args: dict, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): + """ + global_args are playlist global args (see radioPlayer_playlist_file.txt) + """ + return playlist class PlaylistAdvisor: """ Only one of a playlist advisor can be loaded. This module picks the playlist file to play, this can be a scheduler or just a static file """ - def advise(self, arguments: str | None) -> str: return "/path/to/playlist.txt" + def advise(self, arguments: str | None) -> str: + """ + Arguments are the arguments passed to the program on startup + """ + return "/path/to/playlist.txt" def new_playlist(self) -> int: """ Whether to play a new playlist, if this is 1, then the player will refresh, if this is two then the player will refresh quietly @@ -25,5 +38,19 @@ class ActiveModifier: """ This changes the next song to be played live, which means that this picks the next song, not the playlist, but this is affected by the playlist """ - def play(self, index:int, track: tuple[str, bool, bool, bool, dict[str, str]]) -> tuple[tuple[str, bool, bool, bool, dict[str, str]], bool] | tuple[None, None]: return track, False - def on_new_playlist(self, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): pass \ No newline at end of file + def arguments(self, arguments: str | None): + """ + Called at start up with the program arguments + """ + pass + def play(self, index:int, track: tuple[str, bool, bool, bool, dict[str, str]]) -> tuple[tuple[str, bool, bool, bool, dict[str, str]], bool] | tuple[None, None]: + """ + Returns a tuple, in the first case where a is the track and b is a bool, b corresponds to whether to extend the playlist, set to true when adding content instead of replacing it + When None, None is returned then that is treated as a skip, meaning the core will skip this song + """ + return track, False + def on_new_playlist(self, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): + """ + Same behaviour as the basic module function + """ + pass \ No newline at end of file diff --git a/modules/active_modifier.py b/modules/active_modifier.py index 323719f..4e98c68 100644 --- a/modules/active_modifier.py +++ b/modules/active_modifier.py @@ -17,6 +17,9 @@ class Module(ActiveModifier): self.playlist = None self.originals = [] self.last_track = None + self.limit_tracks = True + def arguments(self, arguments: str | None): + if arguments and arguments.startswith("list:"): self.limit_tracks = False def on_new_playlist(self, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): self.playlist = playlist def play(self, index: int, track: tuple[str, bool, bool, bool, dict[str, str]]): @@ -53,20 +56,21 @@ class Module(ActiveModifier): elif len(self.originals): self.last_track = self.originals.pop(0) else: self.last_track = track - last_track_duration = get_audio_duration(self.last_track[0]) - if last_track_duration and last_track_duration > 5*60: - now = datetime.datetime.now() - timestamp = now.timestamp() + last_track_duration - future = datetime.datetime.fromtimestamp(timestamp) - if now.hour < MORNING_START and future.hour > MORNING_START: - logger.warning("Skipping track as it bleeds into the morning") - return None, None - elif now.hour < DAY_END and future.hour > DAY_END: - logger.warning("Skipping track as it bleeds into the night") - return None, None - elif future.day > now.day: # late night goes mid day, as it starts at midnight - logger.warning("Skipping track as it the next day") - return None, None + if self.limit_tracks: + last_track_duration = get_audio_duration(self.last_track[0]) + if last_track_duration and last_track_duration > 5*60: + now = datetime.datetime.now() + timestamp = now.timestamp() + last_track_duration + future = datetime.datetime.fromtimestamp(timestamp) + if now.hour < MORNING_START and future.hour > MORNING_START: + logger.warning("Skipping track as it bleeds into the morning") + return None, None + elif now.hour < DAY_END and future.hour > DAY_END: + logger.warning("Skipping track as it bleeds into the night") + return None, None + elif future.day > now.day: # late night goes mid day, as it starts at midnight + logger.warning("Skipping track as it the next day") + return None, None return self.last_track, False diff --git a/modules/modules.txt b/modules/modules.txt index b34b7d6..ada229e 100644 --- a/modules/modules.txt +++ b/modules/modules.txt @@ -12,19 +12,32 @@ class PlayerModule: Simple passive observer, this allows you to send the current track the your RDS encoder, or to your website """ def on_new_playlist(self, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): - """Tuple consists of the track path, to fade out, fade in, official, and args""" + """Tuple consists of the track path, to fade out, fade in, official, and args + This is called every new playlist""" + pass + def on_new_track(self, index: int, track: str, to_fade_in: bool, to_fade_out: bool, official: bool): + """ + Called on every track including the ones added by the active modifier, you can check for that comparing the playlists[index] and the track + """ pass - def on_new_track(self, index: int, track: str, to_fade_in: bool, to_fade_out: bool, official: bool): pass class PlaylistModifierModule: """ Playlist modifier, this type of module allows you to shuffle, or put jingles into your playlist """ - def modify(self, global_args: dict, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): return playlist + def modify(self, global_args: dict, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): + """ + global_args are playlist global args (see radioPlayer_playlist_file.txt) + """ + return playlist class PlaylistAdvisor: """ Only one of a playlist advisor can be loaded. This module picks the playlist file to play, this can be a scheduler or just a static file """ - def advise(self, arguments: str | None) -> str: return "/path/to/playlist.txt" + def advise(self, arguments: str | None) -> str: + """ + Arguments are the arguments passed to the program on startup + """ + return "/path/to/playlist.txt" def new_playlist(self) -> int: """ Whether to play a new playlist, if this is 1, then the player will refresh, if this is two then the player will refresh quietly @@ -34,8 +47,22 @@ class ActiveModifier: """ This changes the next song to be played live, which means that this picks the next song, not the playlist, but this is affected by the playlist """ - def play(self, index:int, track: tuple[str, bool, bool, bool, dict[str, str]]) -> tuple[tuple[str, bool, bool, bool, dict[str, str]], bool] | tuple[None, None]: return track, False - def on_new_playlist(self, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): pass + def arguments(self, arguments: str | None): + """ + Called at start up with the program arguments + """ + pass + def play(self, index:int, track: tuple[str, bool, bool, bool, dict[str, str]]) -> tuple[tuple[str, bool, bool, bool, dict[str, str]], bool] | tuple[None, None]: + """ + Returns a tuple, in the first case where a is the track and b is a bool, b corresponds to whether to extend the playlist, set to true when adding content instead of replacing it + When None, None is returned then that is treated as a skip, meaning the core will skip this song + """ + return track, False + def on_new_playlist(self, playlist: list[tuple[str, bool, bool, bool, dict[str, str]]]): + """ + Same behaviour as the basic module function + """ + pass ``` Each module shall have a python script in the modules directory. Each of the modules need to define one or more global variables in order to be seen by the core: diff --git a/pyproject.toml b/pyproject.toml index 4009eaf..23da189 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "radio-tools" version = "0.1" -dependencies = [] +dependencies = ["log95", "unidecode", "libcache"] [tool.setuptools] py-modules = ["radioPlaylist", "radioPlayer"] diff --git a/radioPlayer.py b/radioPlayer.py index 4d96c5c..ae19e50 100644 --- a/radioPlayer.py +++ b/radioPlayer.py @@ -206,7 +206,6 @@ def play_playlist(playlist_path): if track_tuple is None: song_i += 1 continue - logger.debug(repr(song_i), repr(old_track_tuple), repr(track_tuple), repr(old_track_tuple != track_tuple)) if extend: max_iterator += 1 else: extend = False @@ -294,6 +293,7 @@ def main(): try: arg = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else None + if active_modifier: active_modifier.arguments(arg) while True: play_playlist(playlist_advisor.advise(arg)) if exit_pending: exit()