major internal changes (might be reverted)

This commit is contained in:
2025-12-12 21:20:38 +01:00
parent 06b983d3cd
commit 2d31f0a907
8 changed files with 53 additions and 80 deletions
+6 -5
View File
@@ -7,11 +7,12 @@ from pathlib import Path
@dataclass @dataclass
class Track: class Track:
path: Path path: Path
fade_out: bool fade_out: float
fade_in: bool fade_in: float
official: bool official: bool
args: dict[str, str] | None args: dict[str, str] | None
offset: float = 0.0 offset: float = 0.0
focus_time_offset: float = 0.0 # Offset according to the duration
@dataclass @dataclass
class Process: class Process:
@@ -23,7 +24,7 @@ class Process:
class Skeleton_ProcessManager: class Skeleton_ProcessManager:
processes: list[Process] processes: list[Process]
def _get_audio_duration(self, file_path): ... def _get_audio_duration(self, file_path): ...
def play(self, track: Track, fade_in_time: int=0, fade_out_time: int=0) -> Process: ... def play(self, track: Track, fade_in_time: float=0, fade_out_time: float=0) -> Process: ...
def anything_playing(self) -> bool: ... def anything_playing(self) -> bool: ...
def stop_all(self, timeout: float | None = None) -> None: ... def stop_all(self, timeout: float | None = None) -> None: ...
def wait_all(self, timeout: float | None = None) -> None: ... def wait_all(self, timeout: float | None = None) -> None: ...
@@ -72,7 +73,7 @@ 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]) -> 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 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:
@@ -132,7 +133,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]) -> 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
""" """
+15 -14
View File
@@ -18,8 +18,10 @@ class Module(ActiveModifier):
self.can_limit_tracks = False self.can_limit_tracks = False
self.morning_start = self.day_end = 0 self.morning_start = self.day_end = 0
self.file_lock = Lock() self.file_lock = Lock()
def on_new_playlist(self, playlist: list[Track]): self.crossfade = 5
def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]):
self.playlist = playlist self.playlist = playlist
self.crossfade = float(global_args.get("crossfade", 5.0))
if not self._imc: return if not self._imc: return
self.limit_tracks, self.morning_start, self.day_end = self._imc.send(self, "advisor", None) # pyright: ignore[reportGeneralTypeIssues] self.limit_tracks, self.morning_start, self.day_end = self._imc.send(self, "advisor", None) # pyright: ignore[reportGeneralTypeIssues]
@@ -27,8 +29,7 @@ class Module(ActiveModifier):
if self.limit_tracks: logger.info("Skipping tracks if they bleed into other times.") if self.limit_tracks: logger.info("Skipping tracks if they bleed into other times.")
self.can_limit_tracks = self.limit_tracks self.can_limit_tracks = self.limit_tracks
def play(self, index: int, track: Track | None, next_track: Track | None): def play(self, index: int, track: Track | None, next_track: Track | None):
if not track: if not track: raise NotImplementedError("This active modifer does not support advisor-less mode")
raise NotImplementedError("This active modifer does not support advisor-less mode")
if not self.playlist: return (track, next_track), False if not self.playlist: return (track, next_track), False
@@ -50,17 +51,17 @@ class Module(ActiveModifier):
if len(songs): if len(songs):
song, official = get_song() song, official = get_song()
if self.last_track: last_track_to_fade_out = self.last_track.fade_out if self.last_track: last_track_fade_out = self.last_track.fade_out
else: else:
if (index - 1) >= 0: last_track_to_fade_out = self.playlist[index - 1].fade_out if (index - 1) >= 0: last_track_fade_out = self.playlist[index - 1].fade_out
else: last_track_to_fade_out = False else: last_track_fade_out = 0.0
if len(songs) != 0: next_track_to_fade_in = True if len(songs) != 0: next_track_fade_in = self.crossfade
else: else:
if index + 1 < len(self.playlist) and next_track: next_track_to_fade_in = next_track.fade_in if index + 1 < len(self.playlist) and next_track: next_track_fade_in = next_track.fade_in
elif not next_track: next_track_to_fade_in = False elif not next_track: next_track_fade_in = 0.0
else: next_track_to_fade_in = True else: next_track_fade_in = self.crossfade
if not self.originals or self.originals[-1] != track: self.originals.append(track) if not self.originals or self.originals[-1] != track: self.originals.append(track)
@@ -74,10 +75,10 @@ class Module(ActiveModifier):
if len(songs): if len(songs):
# There are more tracks on the temp list # There are more tracks on the temp list
new_song, new_official = get_song(False) new_song, new_official = get_song(False)
self.last_track = Track(song, new_official, last_track_to_fade_out, official, {}) self.last_track = Track(song, self.crossfade if new_official else 0, last_track_fade_out, official, {}, focus_time_offset=-self.crossfade if new_official else 0)
next_track = Track(new_song, new_official if len(songs) else next_track_to_fade_in, new_official, new_official, {}) next_track = Track(new_song, self.crossfade if len(songs) else next_track_fade_in, self.crossfade if new_official else 0, new_official, {}, focus_time_offset=-self.crossfade if len(songs) else -next_track_fade_in)
else: else:
self.last_track = Track(song, next_track_to_fade_in, last_track_to_fade_out, official, {}) self.last_track = Track(song, next_track_fade_in, last_track_fade_out, official, {}, focus_time_offset=-next_track_fade_in)
next_track = track next_track = track
self.limit_tracks = False self.limit_tracks = False
return (self.last_track, next_track), True return (self.last_track, next_track), True
+4 -3
View File
@@ -20,15 +20,16 @@ class Module(PlaylistModifierModule):
if int(global_args.get("no_jingle", 0)) != 0 or not self.primary: return None if int(global_args.get("no_jingle", 0)) != 0 or not self.primary: return None
out: list[Track] = [] out: list[Track] = []
last_jingiel = True last_jingiel = True
crossfade = float(global_args.get("crossfade", 5.0))
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 (random.randint(1,3) == 1) and (track.args is None or int(track.args.get("no_jingle", 0)) == 0):
out.append(Track(track.path, True, False, True, track.args)) out.append(Track(track.path, crossfade, 0, True, track.args, focus_time_offset=-crossfade))
jingle = self.primary jingle = self.primary
if self.secondary and (random.randint(1,3) == 1): jingle = random.choice(self.secondary) if self.secondary and (random.randint(1,3) == 1): jingle = random.choice(self.secondary)
out.append(Track(jingle, False, False, False, {})) out.append(Track(jingle, 0, 0, False, {}))
last_jingiel = True last_jingiel = True
continue continue
out.append(Track(track.path, True, True, True, track.args)) out.append(Track(track.path, crossfade, crossfade, True, track.args,focus_time_offset=-crossfade))
last_jingiel = False last_jingiel = False
return out return out
-17
View File
@@ -1,17 +0,0 @@
from . import PlayerModule, Track, Path
def format_time(seconds) -> str:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
return f"{hours:02d}:{minutes:02d}:{secs:02d}"
class Module(PlayerModule):
def progress(self, index: int, track: Track, elapsed: float, total: float, real_total: float) -> None:
if track.official:
data = f"{track.path.name}: {format_time(elapsed)} / {format_time(total)}\n"
Path("/tmp/radioPlayer_progress").write_text(data)
def shutdown(self):
Path("/tmp/radioPlayer_progress").write_text("")
module = Module()
+20
View File
@@ -0,0 +1,20 @@
from . import PlayerModule, log95, Track
import os
from typing import TextIO
_log_out: 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()
+3 -2
View File
@@ -1,4 +1,4 @@
# websocket_module.py # TODO: focus time in track
import multiprocessing, os import multiprocessing, os
import json import json
import threading, uuid, time import threading, uuid, time
@@ -165,7 +165,8 @@ class Module(PlayerModule):
if key := message.get("key", None): self.data[key] = out if key := message.get("key", None): self.data[key] = out
except Exception: pass except Exception: pass
def on_new_playlist(self, playlist: list[Track]) -> None: def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]) -> None:
# TODO: add global args to data
api_data = [] api_data = []
for track in playlist: for track in playlist:
api_data.append({ api_data.append({
-31
View File
@@ -1,31 +0,0 @@
from . import PlayerModule, log95, Track
import os
from typing import TextIO
_log_out: TextIO
assert _log_out # pyright: ignore[reportUnboundVariable]
logger = log95.log95("PlayView", output=_log_out)
class Module(PlayerModule):
def __init__(self) -> None: self.playlist = []
def on_new_playlist(self, playlist: list[Track]): 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)
if str(track.path) != self.playlist[index]:
# discrepancy, which means that the playing file was modified by the active modifier
# we are playing a file that was not determined in the playlist, that means it was chosen by the active modifier and made up on the fly
lines = self.playlist[:index] + [f"> ({track.path})"] + [self.playlist[index]] + self.playlist[index+1:]
else: lines = self.playlist[:index] + [f"> {self.playlist[index]}"] + self.playlist[index+1:]
with open("/tmp/radioPlayer_playlist", "w") as f:
for line in lines:
try: f.write(line + "\n")
except UnicodeEncodeError:
print(line.encode('utf-8', errors='ignore').decode('utf-8'))
raise
module = Module()
+5 -8
View File
@@ -28,7 +28,7 @@ class ProcessManager(Skeleton_ProcessManager):
result = float(result.stdout.strip()) result = float(result.stdout.strip())
self.duration_cache.saveElement(file_path.as_posix(), result, (60*60*2), False, True) self.duration_cache.saveElement(file_path.as_posix(), result, (60*60*2), False, True)
return result return result
def play(self, track: Track, fade_in_time: int=0, fade_out_time: int=0) -> Process: def play(self, track: Track, fade_in_time: float=0, fade_out_time: float=0) -> 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']
@@ -216,13 +216,13 @@ class RadioPlayer:
return return
playlist: list[Track] | None = [] playlist: list[Track] | None = []
[playlist.extend(Track(Path(line).absolute(), True, True, True, args) for line in lns) for (lns, args) in parsed] # i can read this, i think [playlist.extend(Track(Path(line).absolute(), 0, 0, True, args) for line in lns) for (lns, args) in parsed] # i can read this, i think
[(playlist := module.modify(global_args, playlist) or playlist) for module in self.playlist_modifier_modules if module] # yep [(playlist := module.modify(global_args, playlist) or playlist) for module in self.playlist_modifier_modules if module] # yep
assert len(playlist) assert len(playlist)
prefetch(playlist[0].path) prefetch(playlist[0].path)
[mod.on_new_playlist(playlist) for mod in self.simple_modules + [self.active_modifier] if mod] # one liner'd everything [mod.on_new_playlist(playlist, global_args) for mod in self.simple_modules + [self.active_modifier] if mod] # one liner'd everything
max_iterator = len(playlist) max_iterator = len(playlist)
else: else:
@@ -230,7 +230,6 @@ class RadioPlayer:
playlist = None playlist = None
global_args = {} global_args = {}
return_pending = track = False return_pending = track = False
cross_fade = int(global_args.get("crossfade", 5)) # TODO: get rid of global_args usage in the core and instead just store fades in track (that would require a pretty much rewrite of the active modifier tho...)
song_i = i = 0 song_i = i = 0
def get_track(): def get_track():
@@ -273,11 +272,9 @@ class RadioPlayer:
self.logger.info(f"Now playing: {track.path.name}") self.logger.info(f"Now playing: {track.path.name}")
prefetch(track.path) prefetch(track.path)
pr = self.procman.play(track, cross_fade, cross_fade) pr = self.procman.play(track, track.fade_in, track.fade_out)
[module.on_new_track(song_i, pr.track, next_track) for module in self.simple_modules if module] [module.on_new_track(song_i, pr.track, next_track) for module in self.simple_modules if module]
end_time = pr.started_at + pr.duration + pr.track.focus_time_offset
end_time = pr.started_at + pr.duration
if track.fade_out: end_time -= cross_fade
while end_time >= time.monotonic() and pr.process.poll() is None: while end_time >= time.monotonic() and pr.process.poll() is None:
start = time.monotonic() start = time.monotonic()