mirror of
https://github.com/radio95-rnt/RadioPlayer.git
synced 2026-07-29 15:29:14 +02:00
some updates
This commit is contained in:
+12
-27
@@ -34,18 +34,14 @@ class ABC_ProcessManager(abc.ABC):
|
|||||||
class BaseIMCModule:
|
class BaseIMCModule:
|
||||||
"""This is not a module to be used but rather a placeholder IMC api to be used in other modules"""
|
"""This is not a module to be used but rather a placeholder IMC api to be used in other modules"""
|
||||||
def imc(self, imc: 'InterModuleCommunication') -> None:
|
def imc(self, imc: 'InterModuleCommunication') -> None:
|
||||||
"""
|
"""Receive an IMC object"""
|
||||||
Receive an IMC object
|
|
||||||
"""
|
|
||||||
self._imc = imc
|
self._imc = imc
|
||||||
def imc_data(self, source: 'BaseIMCModule', source_name: str | None, data: object, broadcast: bool) -> object:
|
def imc_data(self, source: 'BaseIMCModule', source_name: str | None, data: object, broadcast: bool) -> object: """React to IMC data"""
|
||||||
"""
|
|
||||||
React to IMC data
|
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
class ProcmanCommunicator(BaseIMCModule):
|
class ProcmanCommunicator(BaseIMCModule):
|
||||||
def __init__(self, procman: ABC_ProcessManager) -> None: self.procman = procman
|
def __init__(self, procman: ABC_ProcessManager) -> None:
|
||||||
|
self.procman = procman
|
||||||
|
self.tinytag = tinytag.TinyTag()
|
||||||
def imc(self, imc: 'InterModuleCommunication') -> None:
|
def imc(self, imc: 'InterModuleCommunication') -> None:
|
||||||
super().imc(imc)
|
super().imc(imc)
|
||||||
self._imc.register(self, "procman")
|
self._imc.register(self, "procman")
|
||||||
@@ -57,7 +53,7 @@ class ProcmanCommunicator(BaseIMCModule):
|
|||||||
|
|
||||||
if int(op) == 0: return {"op": 0, "arg": "pong"}
|
if int(op) == 0: return {"op": 0, "arg": "pong"}
|
||||||
elif int(op) == 1:
|
elif int(op) == 1:
|
||||||
if arg := data.get("arg"): return {"op": 1, "arg": tinytag.TinyTag().get(arg, tags=False).duration}
|
if arg := data.get("arg"): return {"op": 1, "arg": self.tinytag.get(arg, tags=False).duration}
|
||||||
else: return
|
else: return
|
||||||
elif int(op) == 2:
|
elif int(op) == 2:
|
||||||
self.procman.stop_all(data.get("timeout", None))
|
self.procman.stop_all(data.get("timeout", None))
|
||||||
@@ -71,28 +67,21 @@ class ProcmanCommunicator(BaseIMCModule):
|
|||||||
|
|
||||||
class PlayerModule(BaseIMCModule):
|
class PlayerModule(BaseIMCModule):
|
||||||
"""Simple passive observer, this allows you to send the current track the your RDS encoder, or to your website"""
|
"""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[Track], global_args: dict[str, str]) -> None:
|
def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]) -> None:
|
||||||
"""This is called every new playlist"""
|
"""This is called every new playlist"""
|
||||||
pass
|
|
||||||
def on_new_track(self, index: int, track: Track, next_track: Track | None) -> None:
|
def on_new_track(self, index: int, track: Track, next_track: Track | None) -> None:
|
||||||
"""Called on every track including the ones added by the active modifier, you can check for that comparing the playlists[index] and the track"""
|
"""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 progress(self, index: int, track: Track, elapsed: float, total: float, real_total: float) -> None:
|
def progress(self, index: int, track: Track, elapsed: float, total: float, real_total: float) -> None:
|
||||||
"""
|
"""
|
||||||
Real total and total differ in that, total is how much the track lasts, but real_total will be for how long we will focus on it (crossfade)
|
Real total and total differ in that, total is how much the track lasts, but real_total will be for how long we will focus on it (crossfade)
|
||||||
Runs at a frequency around 1 Hz
|
Runs at a frequency around 1 Hz (depending on other plugins it can be rarer, but never any faster)
|
||||||
Please don't put any blocking or code that takes time
|
Please don't put any blocking or code that takes time
|
||||||
"""
|
"""
|
||||||
pass
|
def shutdown(self): """Ran while shutting down"""
|
||||||
def shutdown(self):
|
|
||||||
"""Ran while shutting down"""
|
|
||||||
pass
|
|
||||||
class PlaylistModifierModule:
|
class PlaylistModifierModule:
|
||||||
"""Playlist modifier, this type of module allows you to shuffle, or put jingles into your playlist"""
|
"""Playlist modifier, this type of module allows you to shuffle, or put jingles into your playlist"""
|
||||||
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track] | None:
|
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track] | None:
|
||||||
"""
|
"""global_args are playlist global args (see radioPlayer_playlist_file.txt)"""
|
||||||
global_args are playlist global args (see radioPlayer_playlist_file.txt)
|
|
||||||
"""
|
|
||||||
return playlist
|
return playlist
|
||||||
# No IMC, as we only run on new playlists
|
# No IMC, as we only run on new playlists
|
||||||
class PlaylistAdvisor(BaseIMCModule):
|
class PlaylistAdvisor(BaseIMCModule):
|
||||||
@@ -105,9 +94,7 @@ class PlaylistAdvisor(BaseIMCModule):
|
|||||||
return False
|
return False
|
||||||
class ActiveModifier(BaseIMCModule):
|
class ActiveModifier(BaseIMCModule):
|
||||||
"""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"""
|
"""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 arguments(self, arguments: str | None) -> None:
|
def arguments(self, arguments: str | None) -> None: """Called at start up with the program arguments"""
|
||||||
"""Called at start up with the program arguments"""
|
|
||||||
pass
|
|
||||||
def play(self, index: int, track: Track | None, next_track: Track | None) -> tuple[tuple[Track | None, Track | None], bool | None]:
|
def play(self, index: int, track: Track | None, next_track: Track | None) -> tuple[tuple[Track | None, Track | None], bool | 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
|
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
|
||||||
@@ -115,9 +102,7 @@ class ActiveModifier(BaseIMCModule):
|
|||||||
The second track object is the next track, which is optional which is also only used for metadata and will not be taken in as data to play
|
The second track object is the next track, which is optional which is also only used for metadata and will not be taken in as data to play
|
||||||
"""
|
"""
|
||||||
return (track, None), False
|
return (track, None), False
|
||||||
def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]) -> None:
|
def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]) -> None: """Same behaviour as the basic module function"""
|
||||||
"""Same behaviour as the basic module function"""
|
|
||||||
pass
|
|
||||||
class InterModuleCommunication:
|
class InterModuleCommunication:
|
||||||
def __init__(self, modules: Sequence[BaseIMCModule | None]) -> None:
|
def __init__(self, modules: Sequence[BaseIMCModule | None]) -> None:
|
||||||
self.modules = modules
|
self.modules = modules
|
||||||
|
|||||||
+3
-4
@@ -1,7 +1,7 @@
|
|||||||
MORNING_START = 5
|
MORNING_START = 5
|
||||||
MORNING_END = 10
|
MORNING_END = 10
|
||||||
DAY_START = 10
|
DAY_START = 10
|
||||||
DAY_END = 18
|
DAY_END = 19
|
||||||
LATE_NIGHT_START = 0
|
LATE_NIGHT_START = 0
|
||||||
LATE_NIGHT_END = 5
|
LATE_NIGHT_END = 5
|
||||||
|
|
||||||
@@ -51,7 +51,6 @@ class Module(PlaylistAdvisor):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.last_mod_time = 0
|
self.last_mod_time = 0
|
||||||
self.last_playlist = None
|
self.last_playlist = None
|
||||||
self.class_imc = None
|
|
||||||
|
|
||||||
self.custom_playlist = None
|
self.custom_playlist = None
|
||||||
self.custom_playlist_path = Path("/tmp/radioPlayer_list")
|
self.custom_playlist_path = Path("/tmp/radioPlayer_list")
|
||||||
@@ -104,7 +103,7 @@ class Module(PlaylistAdvisor):
|
|||||||
logger.info(f"Playing {current_day} night playlist...")
|
logger.info(f"Playing {current_day} night playlist...")
|
||||||
self.last_mod_time = Time.get_playlist_modification_time(night_playlist)
|
self.last_mod_time = Time.get_playlist_modification_time(night_playlist)
|
||||||
self.last_playlist = night_playlist
|
self.last_playlist = night_playlist
|
||||||
if self.class_imc: self.class_imc.send(self, "web", {"playlist": str(self.last_playlist)})
|
if self._imc: self._imc.send(self, "web", {"playlist": str(self.last_playlist)})
|
||||||
return self.last_playlist
|
return self.last_playlist
|
||||||
def new_playlist(self) -> bool:
|
def new_playlist(self) -> bool:
|
||||||
if self.custom_playlist and self.custom_playlist_path.exists():
|
if self.custom_playlist and self.custom_playlist_path.exists():
|
||||||
@@ -128,7 +127,7 @@ class Module(PlaylistAdvisor):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
def imc(self, imc: InterModuleCommunication) -> None:
|
def imc(self, imc: InterModuleCommunication) -> None:
|
||||||
self.class_imc = imc
|
self._imc = imc
|
||||||
imc.register(self, "advisor")
|
imc.register(self, "advisor")
|
||||||
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: object, broadcast: bool):
|
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: object, broadcast: bool):
|
||||||
return (self.custom_playlist, MORNING_START, DAY_END)
|
return (self.custom_playlist, MORNING_START, DAY_END)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from . import PlaylistModifierModule, Track
|
from . import PlaylistModifierModule, Track
|
||||||
DEFAULT_CROSSFADE = 5.0
|
DEFAULT_CROSSFADE = 6.0
|
||||||
class Module(PlaylistModifierModule):
|
class Module(PlaylistModifierModule):
|
||||||
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track] | None:
|
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track] | None:
|
||||||
out = []
|
out = []
|
||||||
@@ -10,7 +10,7 @@ class Module(PlaylistModifierModule):
|
|||||||
out.append(Track(track.path, track_crossfade, track_crossfade, do_cross_fade, track.args, focus_time_offset=-track_crossfade))
|
out.append(Track(track.path, track_crossfade, track_crossfade, do_cross_fade, track.args, focus_time_offset=-track_crossfade))
|
||||||
else: out.append(track)
|
else: out.append(track)
|
||||||
return out
|
return out
|
||||||
playlistmod = (Module(),1)
|
playlistmod = Module(), 0
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
# This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from . import ABC_ProcessManager, Process, Track, Path, Popen, tinytag
|
from . import ABC_ProcessManager, Process, Track, Popen, tinytag
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
import subprocess, time
|
import subprocess, time
|
||||||
|
|
||||||
@@ -6,13 +6,12 @@ class ProcessManager(ABC_ProcessManager):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
self.processes: list[Process] = []
|
self.processes: list[Process] = []
|
||||||
def _get_audio_duration(self, file_path: Path):
|
self.tinytag = tinytag.TinyTag()
|
||||||
return tinytag.TinyTag().get(file_path, tags=False).duration
|
|
||||||
def play(self, track: Track) -> Process:
|
def play(self, track: Track) -> Process:
|
||||||
assert track.path.exists()
|
assert track.path.exists()
|
||||||
cmd = ['ffplay', '-nodisp', '-hide_banner', '-autoexit', '-loglevel', 'quiet']
|
cmd = ['ffplay', '-nodisp', '-hide_banner', '-autoexit', '-loglevel', 'quiet']
|
||||||
|
|
||||||
duration = self._get_audio_duration(track.path.absolute())
|
duration = self.tinytag.get(track.path.absolute(), tags=False).duration
|
||||||
if not duration: raise Exception("Failed to get file duration for", track.path)
|
if not duration: raise Exception("Failed to get file duration for", track.path)
|
||||||
if track.offset >= duration: track.offset = max(duration - 0.1, 0)
|
if track.offset >= duration: track.offset = max(duration - 0.1, 0)
|
||||||
if track.offset > 0: cmd.extend(['-ss', str(track.offset)])
|
if track.offset > 0: cmd.extend(['-ss', str(track.offset)])
|
||||||
@@ -35,7 +34,7 @@ class ProcessManager(ABC_ProcessManager):
|
|||||||
try: p.process.wait(timeout=0)
|
try: p.process.wait(timeout=0)
|
||||||
except subprocess.TimeoutExpired: pass
|
except subprocess.TimeoutExpired: pass
|
||||||
self.processes = alive
|
self.processes = alive
|
||||||
return bool(self.processes)
|
return bool(alive)
|
||||||
def stop_all(self, timeout: float | None = None) -> None:
|
def stop_all(self, timeout: float | None = None) -> None:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
for process in self.processes:
|
for process in self.processes:
|
||||||
|
|||||||
+1
-2
@@ -11,8 +11,7 @@ def _parse_args(text: str) -> dict[str, str]:
|
|||||||
if "=" in line:
|
if "=" in line:
|
||||||
key, val = line.split("=", 1)
|
key, val = line.split("=", 1)
|
||||||
args[key.strip()] = val.strip()
|
args[key.strip()] = val.strip()
|
||||||
else:
|
else: args[line] = True
|
||||||
args[line] = True
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
class FSDBParser(PlaylistParser):
|
class FSDBParser(PlaylistParser):
|
||||||
|
|||||||
+8
-6
@@ -25,6 +25,8 @@ def get_jingles():
|
|||||||
if not master: master = jingles.pop(0)
|
if not master: master = jingles.pop(0)
|
||||||
return master, jingles
|
return master, jingles
|
||||||
|
|
||||||
|
def chance(one_in_n): return random.randint(1, one_in_n) == 1
|
||||||
|
|
||||||
class Module(PlaylistModifierModule):
|
class Module(PlaylistModifierModule):
|
||||||
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track] | None:
|
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track] | None:
|
||||||
if int(global_args.get("no_jingle", 0)) != 0: return None
|
if int(global_args.get("no_jingle", 0)) != 0: return None
|
||||||
@@ -34,26 +36,26 @@ class Module(PlaylistModifierModule):
|
|||||||
out: list[Track] = []
|
out: list[Track] = []
|
||||||
last_jingiel = True
|
last_jingiel = True
|
||||||
for track in playlist:
|
for track in playlist:
|
||||||
if not last_jingiel and (random.randint(1,3) == 1) and (track.args is None or int(track.args.get("no_jingle", 0)) == 0):
|
if not last_jingiel and chance(3) and (track.args is None or int(track.args.get("no_jingle", 0)) == 0):
|
||||||
out.append(Track(track.path, 0, track.fade_in, True, track.args))
|
out.append(Track(track.path, 0, track.fade_in, True, track.args))
|
||||||
jingle = primary
|
jingle = primary
|
||||||
if secondary and (random.randint(1,3) == 1): jingle = random.choice(secondary)
|
if secondary and chance(2): jingle = random.choice(secondary)
|
||||||
out.append(Track(jingle, 0, 0, False, {}))
|
out.append(Track(jingle, 0, 0, False, {}))
|
||||||
last_jingiel = True
|
last_jingiel = True
|
||||||
continue
|
continue
|
||||||
out.append(Track(track.path, track.fade_out, track.fade_in, True, track.args,focus_time_offset=-track.fade_out))
|
out.append(Track(track.path, track.fade_out, track.fade_in, True, track.args,focus_time_offset=-track.fade_out))
|
||||||
last_jingiel = False
|
last_jingiel = False
|
||||||
return out
|
return out
|
||||||
|
|
||||||
class Module2(PlayerModule):
|
class Module2(PlayerModule):
|
||||||
def imc(self, imc: InterModuleCommunication) -> None:
|
def imc(self, imc: InterModuleCommunication) -> None:
|
||||||
super().imc(imc)
|
super().imc(imc)
|
||||||
self._imc.register(self, "jingle")
|
imc.register(self, "jingle")
|
||||||
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: bool, broadcast: bool) -> object:
|
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: bool, broadcast: bool) -> object:
|
||||||
if broadcast: return
|
if broadcast: return
|
||||||
jingle, secondary = get_jingles()
|
jingle, secondary = get_jingles()
|
||||||
if secondary and (random.randint(1,3) == 1): jingle = random.choice(secondary)
|
if secondary and chance(2): jingle = random.choice(secondary)
|
||||||
return self._imc.send(self, "activemod", {"action": "add_to_toplay", "songs": [f"!{jingle}"], "top": bool(data)})
|
return self._imc.send(self, "activemod", {"action": "add_to_toplay", "songs": [f"!{jingle}"], "top": bool(data)})
|
||||||
|
|
||||||
module = Module2()
|
module = Module2()
|
||||||
playlistmod = Module()
|
playlistmod = Module(), 2
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from . import PlaylistModifierModule, Track
|
||||||
|
|
||||||
|
class Module(PlaylistModifierModule):
|
||||||
|
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track] | None:
|
||||||
|
playlist[0].fade_in = 0
|
||||||
|
return playlist
|
||||||
|
|
||||||
|
playlistmod = Module(), -1
|
||||||
@@ -34,7 +34,6 @@ class Module(PlayerModule):
|
|||||||
def on_new_track(self, index: int, track: Track, next_track: Track | None) -> None:
|
def on_new_track(self, index: int, track: Track, next_track: Track | None) -> None:
|
||||||
self.counts[track.path.as_posix()] = self.counts.get(track.path.as_posix(), 0) + 1
|
self.counts[track.path.as_posix()] = self.counts.get(track.path.as_posix(), 0) + 1
|
||||||
self._save_counts()
|
self._save_counts()
|
||||||
def shutdown(self):
|
def shutdown(self): self._save_counts()
|
||||||
self._save_counts()
|
|
||||||
|
|
||||||
module = Module()
|
module = Module()
|
||||||
+6
-17
@@ -4,32 +4,22 @@ _log_out: log95.TextIO
|
|||||||
assert _log_out # pyright: ignore[reportUnboundVariable]
|
assert _log_out # pyright: ignore[reportUnboundVariable]
|
||||||
|
|
||||||
def load_play_counts(file_path: Path) -> dict[str, int]:
|
def load_play_counts(file_path: Path) -> dict[str, int]:
|
||||||
"""
|
|
||||||
Loads the play counts from the file generated by the play_counter module.
|
|
||||||
"""
|
|
||||||
counts = {}
|
counts = {}
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r') as file:
|
with open(file_path, 'r') as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
if line.strip() == "" or line.startswith(";"):
|
if line.strip() == "" or line.startswith(";"): continue
|
||||||
continue
|
|
||||||
try:
|
try:
|
||||||
key, value = line.split(':', 1)
|
key, value = line.split(':', 1)
|
||||||
counts[key.strip()] = int(value.strip())
|
counts[key.strip()] = int(value.strip())
|
||||||
except ValueError:
|
except ValueError: continue
|
||||||
continue
|
|
||||||
return counts
|
return counts
|
||||||
except FileNotFoundError:
|
except FileNotFoundError: return {}
|
||||||
return {}
|
|
||||||
|
|
||||||
class PopularitySorterModule(PlaylistModifierModule):
|
class PopularitySorterModule(PlaylistModifierModule):
|
||||||
"""
|
|
||||||
A playlist modifier that reorders tracks based on their play history.
|
|
||||||
For every pair of tracks, it gives a 60% probability to schedule the less-played track first.
|
|
||||||
"""
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.logger = log95.log95("PopSort", output=_log_out)
|
self.logger = log95.log95("PopSort", output=_log_out)
|
||||||
self.play_counts_file = Path("/home/user/mixes/.playlist/count.txt").resolve()
|
self.play_counts_file = Path("/home/user/mixes/.playlist/count.txt")
|
||||||
|
|
||||||
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track]:
|
def modify(self, global_args: dict, playlist: list[Track]) -> list[Track]:
|
||||||
self.logger.info("Applying popularity-based sorting to the playlist...")
|
self.logger.info("Applying popularity-based sorting to the playlist...")
|
||||||
@@ -69,12 +59,11 @@ class PopularitySorterModule(PlaylistModifierModule):
|
|||||||
count1 = play_counts.get(track1.path.as_posix(), 0)
|
count1 = play_counts.get(track1.path.as_posix(), 0)
|
||||||
count2 = play_counts.get(track2.path.as_posix(), 0)
|
count2 = play_counts.get(track2.path.as_posix(), 0)
|
||||||
|
|
||||||
if count1 > count2:
|
if count1 > count2:
|
||||||
playlist[i], playlist[i+1] = track2, track1
|
playlist[i], playlist[i+1] = track2, track1
|
||||||
|
|
||||||
i += 2
|
i += 2
|
||||||
|
|
||||||
self.logger.info("Popularity sorting complete.")
|
self.logger.info("Popularity sorting complete.")
|
||||||
return playlist
|
return playlist
|
||||||
|
|
||||||
playlistmod = (PopularitySorterModule(), 2)
|
playlistmod = PopularitySorterModule(), 2
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import glob
|
|
||||||
from . import log95, Path, PlaylistParser
|
|
||||||
|
|
||||||
_log_out: log95.TextIO
|
|
||||||
class PlaintextParser(PlaylistParser):
|
|
||||||
def __init__(self): self.logger = log95.log95("PARSER", output=_log_out)
|
|
||||||
|
|
||||||
def _check_for_imports(self, path: Path, seen=None) -> list[str]:
|
|
||||||
if seen is None: seen = set()
|
|
||||||
if not path.exists():
|
|
||||||
self.logger.error(f"Playlist not found: {path.name}")
|
|
||||||
raise Exception("Playlist doesn't exist")
|
|
||||||
lines = [line.strip() for line in path.read_text().splitlines() if line.strip()]
|
|
||||||
|
|
||||||
out = []
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("@"):
|
|
||||||
target = Path(line.removeprefix("@"))
|
|
||||||
if target not in seen:
|
|
||||||
if not target.exists():
|
|
||||||
self.logger.error(f"Target {target.name} of {path.name} does not exist")
|
|
||||||
continue
|
|
||||||
seen.add(target)
|
|
||||||
out.extend(self._check_for_imports(target, seen))
|
|
||||||
else: out.append(line)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def parse(self, playlist_path: Path) -> tuple[dict[str, str], list[tuple[list[str], dict[str, str]]]]:
|
|
||||||
lines = self._check_for_imports(playlist_path)
|
|
||||||
out = []
|
|
||||||
global_arguments = {}
|
|
||||||
for line in lines:
|
|
||||||
arguments = {}
|
|
||||||
line = line.strip()
|
|
||||||
if not line or line.startswith(";") or line.startswith("#"): continue
|
|
||||||
if "|" in line:
|
|
||||||
if line.startswith("|"): # No file name, we're defining global arguments
|
|
||||||
args = line.removeprefix("|").split(";")
|
|
||||||
for arg in args:
|
|
||||||
if "=" in arg:
|
|
||||||
key, val = arg.split("=", 1)
|
|
||||||
arguments[key] = val
|
|
||||||
else: arguments[arg] = True
|
|
||||||
else:
|
|
||||||
line, args = line.split("|", 1)
|
|
||||||
args = args.split(";")
|
|
||||||
for arg in args:
|
|
||||||
if "=" in arg:
|
|
||||||
key, val = arg.split("=", 1)
|
|
||||||
arguments[key] = val
|
|
||||||
else: arguments[arg] = True
|
|
||||||
out.append(([f for f in glob.glob(line) if Path(f).is_file()], arguments))
|
|
||||||
return global_arguments, out
|
|
||||||
|
|
||||||
# parser = PlaintextParser()
|
|
||||||
+29
-1
@@ -1,3 +1,5 @@
|
|||||||
|
from modules import InterModuleCommunication
|
||||||
|
|
||||||
from . import PlayerModule, log95, Track
|
from . import PlayerModule, log95, Track
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ udp_host = ("127.0.0.1", 5000)
|
|||||||
logger_level = log95.log95Levels.DEBUG if DEBUG else log95.log95Levels.CRITICAL_ERROR
|
logger_level = log95.log95Levels.DEBUG if DEBUG else log95.log95Levels.CRITICAL_ERROR
|
||||||
_log_out: log95.TextIO
|
_log_out: log95.TextIO
|
||||||
assert _log_out # pyright: ignore[reportUnboundVariable]
|
assert _log_out # pyright: ignore[reportUnboundVariable]
|
||||||
logger = log95.log95("RDS-MODULE", logger_level, output=_log_out)
|
logger = log95.log95("RDS", logger_level, output=_log_out)
|
||||||
|
|
||||||
def load_dict_from_custom_format(file_path: str) -> dict[str, str]:
|
def load_dict_from_custom_format(file_path: str) -> dict[str, str]:
|
||||||
try:
|
try:
|
||||||
@@ -84,5 +86,31 @@ class Module(PlayerModule):
|
|||||||
self._imc.send(self, "web", {"rt": rds_rt, "rtp": rds_rtp}, False)
|
self._imc.send(self, "web", {"rt": rds_rt, "rtp": rds_rtp}, False)
|
||||||
logger.info(f"RT set to '{rds_rt}'")
|
logger.info(f"RT set to '{rds_rt}'")
|
||||||
logger.debug(f"{rds_rtp=}")
|
logger.debug(f"{rds_rtp=}")
|
||||||
|
def imc(self, imc: InterModuleCommunication) -> None: imc.register(self, "rds")
|
||||||
|
|
||||||
module = Module()
|
module = Module()
|
||||||
|
|
||||||
|
# This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
# distribute this software, either in source code form or as a compiled
|
||||||
|
# binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
# means.
|
||||||
|
|
||||||
|
# In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
# of this software dedicate any and all copyright interest in the
|
||||||
|
# software to the public domain. We make this dedication for the benefit
|
||||||
|
# of the public at large and to the detriment of our heirs and
|
||||||
|
# successors. We intend this dedication to be an overt act of
|
||||||
|
# relinquishment in perpetuity of all present and future rights to this
|
||||||
|
# software under copyright law.
|
||||||
|
|
||||||
|
# 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 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.
|
||||||
|
|
||||||
|
# For more information, please refer to <https://unlicense.org>
|
||||||
+1
-1
@@ -7,7 +7,7 @@ class Module(PlaylistModifierModule):
|
|||||||
if int(global_args.get("no_shuffle", 0)) == 0: random.shuffle(playlist)
|
if int(global_args.get("no_shuffle", 0)) == 0: random.shuffle(playlist)
|
||||||
return playlist
|
return playlist
|
||||||
|
|
||||||
playlistmod = (Module(), 0)
|
playlistmod = Module(), 1
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
# This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
from . import PlayerModule, log95, Track
|
|
||||||
import os
|
|
||||||
|
|
||||||
_log_out: log95.TextIO
|
|
||||||
assert _log_out # pyright: ignore[reportUnboundVariable]
|
|
||||||
logger = log95.log95("Skipper", output=_log_out)
|
|
||||||
|
|
||||||
class Module(PlayerModule):
|
|
||||||
def __init__(self) -> None: self.playlist = []
|
|
||||||
def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]): self.playlist = [str(t.path.absolute()) for t in playlist]
|
|
||||||
def progress(self, index: int, track: Track, elapsed: float, total: float, real_total: float) -> None:
|
|
||||||
if os.path.exists("/tmp/radioPlayer_skip"):
|
|
||||||
self._imc.send(self, "procman", {"op": 2}) # Ask procman to kill every track playing (usually there is one, unless we are in the default 5 seconds of the crossfade)
|
|
||||||
os.remove("/tmp/radioPlayer_skip")
|
|
||||||
def on_new_track(self, index: int, track: Track, next_track: Track | None):
|
|
||||||
if next_track: logger.info("Next up:", next_track.path.name)
|
|
||||||
|
|
||||||
module = Module()
|
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
|
||||||
|
|
||||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
# distribute this software, either in source code form or as a compiled
|
|
||||||
# binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
# means.
|
|
||||||
|
|
||||||
# In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
# of this software dedicate any and all copyright interest in the
|
|
||||||
# software to the public domain. We make this dedication for the benefit
|
|
||||||
# of the public at large and to the detriment of our heirs and
|
|
||||||
# successors. We intend this dedication to be an overt act of
|
|
||||||
# relinquishment in perpetuity of all present and future rights to this
|
|
||||||
# software under copyright law.
|
|
||||||
|
|
||||||
# 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 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.
|
|
||||||
|
|
||||||
# For more information, please refer to <https://unlicense.org>
|
|
||||||
+2
-3
@@ -281,9 +281,8 @@
|
|||||||
function handleMessage(msg){
|
function handleMessage(msg){
|
||||||
if(msg.event === "state"){
|
if(msg.event === "state"){
|
||||||
const d = msg.data || {};
|
const d = msg.data || {};
|
||||||
applyProgressState(d.track);
|
if(d.track) applyProgressState(d.track);
|
||||||
updateDirs(d.dirs);
|
if(d.dirs) updateDirs(d.dirs);
|
||||||
if(d.playlist) { playlist = d.playlist; renderPlaylist(); }
|
|
||||||
} else if(msg.event === "playlist"){
|
} else if(msg.event === "playlist"){
|
||||||
playlist = msg.data || [];
|
playlist = msg.data || [];
|
||||||
renderPlaylist();
|
renderPlaylist();
|
||||||
|
|||||||
+31
-42
@@ -15,13 +15,14 @@ MAIN_PATH_DIR = Path("/home/user/mixes")
|
|||||||
async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: multiprocessing.Queue, ws_q: multiprocessing.Queue):
|
async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: multiprocessing.Queue, ws_q: multiprocessing.Queue):
|
||||||
try:
|
try:
|
||||||
initial = {
|
initial = {
|
||||||
"playlist": json.loads(shared_data.get("playlist", "[]")),
|
|
||||||
"track": json.loads(shared_data.get("track", "{}")),
|
"track": json.loads(shared_data.get("track", "{}")),
|
||||||
"progress": json.loads(shared_data.get("progress", "{}")),
|
"progress": json.loads(shared_data.get("progress", "{}")),
|
||||||
"dirs": {"files": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_file()], "dirs": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_dir()], "base": str(MAIN_PATH_DIR)}
|
"dirs": {"files": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_file()], "dirs": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_dir()], "base": str(MAIN_PATH_DIR)},
|
||||||
|
"rds": json.loads(shared_data.get("rds", "{}"))
|
||||||
}
|
}
|
||||||
except Exception: initial = {"playlist": [], "track": {}, "progress": {}, "dirs": {"files": [], "dirs": [], "base": ""}}
|
except Exception: initial = {"track": {}, "progress": {}, "dirs": {}, "rds": {}}
|
||||||
await websocket.send(json.dumps({"event": "state", "data": initial}))
|
await websocket.send(json.dumps({"event": "state", "data": initial}))
|
||||||
|
await websocket.send(json.dumps({"event": "playlist", "data": json.loads(shared_data.get("playlist", "[]"))}))
|
||||||
|
|
||||||
async for raw in websocket:
|
async for raw in websocket:
|
||||||
try: msg: dict = json.loads(raw)
|
try: msg: dict = json.loads(raw)
|
||||||
@@ -33,13 +34,10 @@ async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: mult
|
|||||||
key = str(uuid.uuid4())
|
key = str(uuid.uuid4())
|
||||||
imc_q.put({"name": name, "data": data, "key": key})
|
imc_q.put({"name": name, "data": data, "key": key})
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
result = None
|
|
||||||
while time.monotonic() - start < 1:
|
while time.monotonic() - start < 1:
|
||||||
if key in shared_data:
|
if key in shared_data: return shared_data.pop(key)
|
||||||
result = shared_data.pop(key)
|
|
||||||
break
|
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
return result
|
return None
|
||||||
|
|
||||||
action = msg.get("action")
|
action = msg.get("action")
|
||||||
if action == "skip":
|
if action == "skip":
|
||||||
@@ -80,23 +78,6 @@ async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: mult
|
|||||||
result = await get_imc("activemod", {"action": "get_toplay"})
|
result = await get_imc("activemod", {"action": "get_toplay"})
|
||||||
if result is not None:
|
if result is not None:
|
||||||
await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"data": result, "event": "toplay"})
|
await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"data": result, "event": "toplay"})
|
||||||
elif action == "request_state":
|
|
||||||
# supports requesting specific parts if provided
|
|
||||||
what = msg.get("what", "")
|
|
||||||
try:
|
|
||||||
if what == "playlist": payload = json.loads(shared_data.get("playlist", "[]"))
|
|
||||||
elif what == "track": payload = json.loads(shared_data.get("track", "{}"))
|
|
||||||
elif what == "progress": payload = json.loads(shared_data.get("progress", "{}"))
|
|
||||||
elif what == "dirs": payload = {"files": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_file()], "dirs": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_dir()], "base": str(MAIN_PATH_DIR)}
|
|
||||||
else:
|
|
||||||
payload = {
|
|
||||||
"playlist": json.loads(shared_data.get("playlist", "[]")),
|
|
||||||
"track": json.loads(shared_data.get("track", "{}")),
|
|
||||||
"progress": json.loads(shared_data.get("progress", "{}")),
|
|
||||||
"dirs": {"files": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_file()], "dirs": [i.name for i in list(MAIN_PATH_DIR.iterdir()) if i.is_dir()], "base": str(MAIN_PATH_DIR)}
|
|
||||||
}
|
|
||||||
except Exception: payload = {}
|
|
||||||
await websocket.send(json.dumps({"event": "state", "data": payload}))
|
|
||||||
elif action == "request_dir":
|
elif action == "request_dir":
|
||||||
what: str = msg.get("what", "")
|
what: str = msg.get("what", "")
|
||||||
try:
|
try:
|
||||||
@@ -142,21 +123,23 @@ def websocket_server_process(shared_data: dict, imc_q: multiprocessing.Queue, ws
|
|||||||
Headers([("Content-Type", "text/html"), ("Content-Length", f"{len(data)}")]),
|
Headers([("Content-Type", "text/html"), ("Content-Length", f"{len(data)}")]),
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
if request.path == "/favicon.ico":
|
if request.path == "/ws":
|
||||||
data = b"Not Found"
|
if not "upgrade" in request.headers.get("Connection", "").lower():
|
||||||
|
return Response(
|
||||||
|
426,
|
||||||
|
"Upgrade Required",
|
||||||
|
Headers([("Connection", "Upgrade"), ("Upgrade", "websocket")]),
|
||||||
|
b"WebSocket upgrade required\n"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
data = b"Not Found\n"
|
||||||
return Response(
|
return Response(
|
||||||
404,
|
404,
|
||||||
"Not Found",
|
"Not Found",
|
||||||
Headers([("Content-Length", f"{len(data)}")]),
|
Headers([("Content-Length", f"{len(data)}")]),
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
if not "upgrade" in request.headers.get("Connection", "").lower():
|
|
||||||
return Response(
|
|
||||||
426,
|
|
||||||
"Upgrade Required",
|
|
||||||
Headers([("Connection", "Upgrade"), ("Upgrade", "websocket")]),
|
|
||||||
b"WebSocket upgrade required\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
server = await websockets.serve(handler_wrapper, "0.0.0.0", 3001, server_header="RadioPlayer ws plugin", process_request=process_request)
|
server = await websockets.serve(handler_wrapper, "0.0.0.0", 3001, server_header="RadioPlayer ws plugin", process_request=process_request)
|
||||||
broadcaster = asyncio.create_task(broadcast_worker(ws_q, clients))
|
broadcaster = asyncio.create_task(broadcast_worker(ws_q, clients))
|
||||||
@@ -179,6 +162,7 @@ class Module(PlayerModule):
|
|||||||
self.data["playlist"] = "[]"
|
self.data["playlist"] = "[]"
|
||||||
self.data["track"] = "{}"
|
self.data["track"] = "{}"
|
||||||
self.data["progress"] = "{}"
|
self.data["progress"] = "{}"
|
||||||
|
self.data["rds"] = "{}"
|
||||||
|
|
||||||
self.ipc_thread_running = True
|
self.ipc_thread_running = True
|
||||||
self.ipc_thread = threading.Thread(target=self._ipc_worker, daemon=True)
|
self.ipc_thread = threading.Thread(target=self._ipc_worker, daemon=True)
|
||||||
@@ -192,10 +176,6 @@ class Module(PlayerModule):
|
|||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
def _ipc_worker(self):
|
def _ipc_worker(self):
|
||||||
"""
|
|
||||||
Listens for messages placed in imc_q by websocket process or other modules,
|
|
||||||
forwards them to the main IPC layer and stores keyed responses into shared dict.
|
|
||||||
"""
|
|
||||||
while self.ipc_thread_running:
|
while self.ipc_thread_running:
|
||||||
try:
|
try:
|
||||||
message: dict | None = self.imc_q.get(timeout=0.5)
|
message: dict | None = self.imc_q.get(timeout=0.5)
|
||||||
@@ -215,8 +195,7 @@ class Module(PlayerModule):
|
|||||||
"official": track.official,
|
"official": track.official,
|
||||||
"args": track.args,
|
"args": track.args,
|
||||||
"offset": track.offset,
|
"offset": track.offset,
|
||||||
"focus_time_offset": track.focus_time_offset,
|
"focus_time_offset": track.focus_time_offset
|
||||||
"global_args": global_args
|
|
||||||
})
|
})
|
||||||
self.data["playlist"] = json.dumps(api_data)
|
self.data["playlist"] = json.dumps(api_data)
|
||||||
try: self.ws_q.put({"event": "playlist", "data": api_data})
|
try: self.ws_q.put({"event": "playlist", "data": api_data})
|
||||||
@@ -239,9 +218,13 @@ class Module(PlayerModule):
|
|||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: object, broadcast: bool) -> object:
|
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: object, broadcast: bool) -> object:
|
||||||
try: self.ws_q.put({"event": "imc", "data": {"name": source_name, "data": data, "broadcast": broadcast}})
|
wsdata = {"event": "imc", "data": {"name": source_name, "data": data, "broadcast": broadcast}}
|
||||||
|
if source_name == "rds":
|
||||||
|
self.data[source_name] = data
|
||||||
|
wsdata = {"event": "rds", "data": data}
|
||||||
|
try: self.ws_q.put(wsdata)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
def imc(self, imc: InterModuleCommunication) -> None:
|
def imc(self, imc: InterModuleCommunication) -> None:
|
||||||
self._imc = imc
|
self._imc = imc
|
||||||
imc.register(self, "web")
|
imc.register(self, "web")
|
||||||
@@ -258,6 +241,9 @@ class Module(PlayerModule):
|
|||||||
self.ipc_thread.join(timeout=1)
|
self.ipc_thread.join(timeout=1)
|
||||||
self.ws_process.join(timeout=1)
|
self.ws_process.join(timeout=1)
|
||||||
|
|
||||||
|
self.imc_q.close()
|
||||||
|
self.ws_q.close()
|
||||||
|
|
||||||
if self.ws_process.is_alive():
|
if self.ws_process.is_alive():
|
||||||
self.ws_process.terminate()
|
self.ws_process.terminate()
|
||||||
self.ws_process.join(timeout=1)
|
self.ws_process.join(timeout=1)
|
||||||
@@ -266,6 +252,9 @@ class Module(PlayerModule):
|
|||||||
self.ws_process.kill()
|
self.ws_process.kill()
|
||||||
self.ws_process.join(timeout=1)
|
self.ws_process.join(timeout=1)
|
||||||
|
|
||||||
|
self.imc_q.join_thread()
|
||||||
|
self.ws_q.join_thread()
|
||||||
|
|
||||||
module = Module()
|
module = Module()
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
# This is free and unencumbered software released into the public domain.
|
||||||
|
|||||||
+18
-11
@@ -54,6 +54,7 @@ class ModuleManager:
|
|||||||
def start_modules(self, arg):
|
def start_modules(self, arg):
|
||||||
procman = None
|
procman = None
|
||||||
parser = None
|
parser = None
|
||||||
|
neg_modifiers = []
|
||||||
"""Executes the module by the python interpreter"""
|
"""Executes the module by the python interpreter"""
|
||||||
def timed_loader(spec: importlib.machinery.ModuleSpec, module: types.ModuleType):
|
def timed_loader(spec: importlib.machinery.ModuleSpec, module: types.ModuleType):
|
||||||
assert spec.loader
|
assert spec.loader
|
||||||
@@ -82,8 +83,12 @@ class ModuleManager:
|
|||||||
if md := getattr(module, "playlistmod", None):
|
if md := getattr(module, "playlistmod", None):
|
||||||
if isinstance(md, tuple):
|
if isinstance(md, tuple):
|
||||||
md, index = md
|
md, index = md
|
||||||
if isinstance(md, list): self.playlist_modifier_modules[index:index] = md
|
if index > -1:
|
||||||
else: self.playlist_modifier_modules.insert(index, md)
|
if isinstance(md, list): self.playlist_modifier_modules[index:index] = md
|
||||||
|
else: self.playlist_modifier_modules.insert(index, md)
|
||||||
|
else:
|
||||||
|
if isinstance(md, list): neg_modifiers[index:index] = md
|
||||||
|
else: neg_modifiers.insert(index, md)
|
||||||
elif isinstance(md, list): self.playlist_modifier_modules.extend(md)
|
elif isinstance(md, list): self.playlist_modifier_modules.extend(md)
|
||||||
else: self.playlist_modifier_modules.append(md)
|
else: self.playlist_modifier_modules.append(md)
|
||||||
if md := getattr(module, "advisor", None):
|
if md := getattr(module, "advisor", None):
|
||||||
@@ -116,6 +121,7 @@ class ModuleManager:
|
|||||||
self.logger.critical_error("Missing process mananger.")
|
self.logger.critical_error("Missing process mananger.")
|
||||||
raise SystemExit("Missing process mananger.")
|
raise SystemExit("Missing process mananger.")
|
||||||
if not parser: self.logger.warning("Missing parser, advisor-less will be forced.")
|
if not parser: self.logger.warning("Missing parser, advisor-less will be forced.")
|
||||||
|
self.playlist_modifier_modules += neg_modifiers
|
||||||
InterModuleCommunication(self.simple_modules + [self.playlist_advisor, ProcmanCommunicator(procman), self.active_modifier])
|
InterModuleCommunication(self.simple_modules + [self.playlist_advisor, ProcmanCommunicator(procman), self.active_modifier])
|
||||||
return procman, parser
|
return procman, parser
|
||||||
def advisor_advise(self, arguments: str | None):
|
def advisor_advise(self, arguments: str | None):
|
||||||
@@ -183,6 +189,7 @@ class RadioPlayer:
|
|||||||
|
|
||||||
def _play(self, playlist: list[Track] | None, max_iterator: int):
|
def _play(self, playlist: list[Track] | None, max_iterator: int):
|
||||||
assert self.procman
|
assert self.procman
|
||||||
|
running = True
|
||||||
return_pending = track = False
|
return_pending = track = False
|
||||||
song_i = i = 0
|
song_i = i = 0
|
||||||
def get_track():
|
def get_track():
|
||||||
@@ -204,7 +211,7 @@ class RadioPlayer:
|
|||||||
return track, next_track, extend
|
return track, next_track, extend
|
||||||
|
|
||||||
def check_conditions():
|
def check_conditions():
|
||||||
nonlocal return_pending
|
nonlocal return_pending, running
|
||||||
assert self.procman
|
assert self.procman
|
||||||
if self.exit_pending:
|
if self.exit_pending:
|
||||||
self.logger.info("Quit received, waiting for song end.")
|
self.logger.info("Quit received, waiting for song end.")
|
||||||
@@ -213,18 +220,17 @@ class RadioPlayer:
|
|||||||
elif return_pending:
|
elif return_pending:
|
||||||
self.logger.info("Return reached, next song will reload the playlist.")
|
self.logger.info("Return reached, next song will reload the playlist.")
|
||||||
self.procman.wait_all()
|
self.procman.wait_all()
|
||||||
return True
|
running = False
|
||||||
if self.modman.playlist_advisor and self.modman.playlist_advisor.new_playlist():
|
if self.modman.playlist_advisor and self.modman.playlist_advisor.new_playlist():
|
||||||
self.logger.info("Reloading now...")
|
self.logger.info("Reloading now...")
|
||||||
return True
|
running = False
|
||||||
return False
|
|
||||||
|
|
||||||
track, next_track, extend = get_track()
|
track, next_track, extend = get_track()
|
||||||
while i < max_iterator:
|
while i < max_iterator and running:
|
||||||
if check_conditions(): return
|
check_conditions()
|
||||||
|
self.procman.anything_playing()
|
||||||
|
|
||||||
if not track.path.exists():
|
if not track.path.exists():
|
||||||
self.procman.anything_playing()
|
|
||||||
track, next_track, extend = get_track()
|
track, next_track, extend = get_track()
|
||||||
prefetch(track.path)
|
prefetch(track.path)
|
||||||
i += 1
|
i += 1
|
||||||
@@ -250,7 +256,8 @@ class RadioPlayer:
|
|||||||
i += 1
|
i += 1
|
||||||
if not extend: song_i += 1
|
if not extend: song_i += 1
|
||||||
|
|
||||||
if check_conditions(): return
|
check_conditions()
|
||||||
|
self.procman.anything_playing()
|
||||||
track, next_track, extend = get_track()
|
track, next_track, extend = get_track()
|
||||||
prefetch(track.path)
|
prefetch(track.path)
|
||||||
|
|
||||||
@@ -266,7 +273,7 @@ class RadioPlayer:
|
|||||||
|
|
||||||
class RotatingLog(io.TextIOWrapper):
|
class RotatingLog(io.TextIOWrapper):
|
||||||
def write(self, *args, **kwargs) -> int:
|
def write(self, *args, **kwargs) -> int:
|
||||||
if self.tell() > 2_500_000:
|
if self.tell() > 2_000_000:
|
||||||
self.truncate(0)
|
self.truncate(0)
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
return super().write(*args, **kwargs)
|
return super().write(*args, **kwargs)
|
||||||
|
|||||||
Reference in New Issue
Block a user