0
1
mirror of https://github.com/radio95-rnt/RadioPlayer.git synced 2026-02-26 13:52:00 +01:00

some improvements in the core

This commit is contained in:
KubaPro010
2025-11-13 15:37:48 +01:00
parent 5fd56bfa41
commit 26122a374a

View File

@@ -1,10 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import time import time, types
import os, subprocess, importlib.util, types import os, subprocess, importlib.util
import sys, signal, threading, glob import sys, signal, glob
import libcache, traceback, atexit import libcache, traceback, atexit
from modules import * from modules import *
from threading import Lock
def prefetch(path): def prefetch(path):
if os.name != "posix": return if os.name != "posix": return
@@ -19,18 +20,16 @@ MODULES_DIR = Path(__file__, "..", MODULES_PACKAGE).resolve()
class ProcessManager(Skeleton_ProcessManager): class ProcessManager(Skeleton_ProcessManager):
def __init__(self) -> None: def __init__(self) -> None:
self.lock = threading.Lock() self.lock = Lock()
self.processes: list[Process] = [] self.processes: list[Process] = []
self.duration_cache = libcache.Cache([]) self.duration_cache = libcache.Cache([])
def _get_audio_duration(self, file_path: Path): def _get_audio_duration(self, file_path: Path):
if result := self.duration_cache.getElement(file_path.as_posix(), False): return result if result := self.duration_cache.getElement(file_path.as_posix(), False): return result
result = subprocess.run(['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', str(file_path)], capture_output=True, text=True) result = subprocess.run(['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', str(file_path)], capture_output=True, text=True)
if result.returncode == 0: if result.returncode == 0:
result = float(result.stdout.strip()) result = float(result.stdout.strip())
self.duration_cache.saveElement(file_path.as_posix(), result, (60*60), False, True) self.duration_cache.saveElement(file_path.as_posix(), result, (60*60), False, True)
return result return result
return None
def play(self, track: Track, fade_time: int=5) -> Process: def play(self, track: Track, fade_time: int=5) -> Process:
cmd = ['ffplay', '-nodisp', '-hide_banner', '-autoexit', '-loglevel', 'quiet'] cmd = ['ffplay', '-nodisp', '-hide_banner', '-autoexit', '-loglevel', 'quiet']
assert track.path.exists() assert track.path.exists()
@@ -69,17 +68,16 @@ class ProcessManager(Skeleton_ProcessManager):
self.processes.clear() self.processes.clear()
class PlaylistParser: class PlaylistParser:
def __init__(self, output: log95.TextIO) -> types.NoneType: self.logger = log95.log95("PARSER", output=output) def __init__(self, output: log95.TextIO): self.logger = log95.log95("PARSER", output=output)
def load_filelines(self, path: Path):
try:
return [line.strip() for line in path.read_text().splitlines() if line.strip()]
except FileNotFoundError:
self.logger.error(f"Playlist not found: {path.name}")
return []
def _check_for_imports(self, path: Path, seen=None) -> list[str]: def _check_for_imports(self, path: Path, seen=None) -> list[str]:
if seen is None: seen = set() if seen is None: seen = set()
lines = self.load_filelines(path)
if not path.exists():
self.logger.error(f"Playlist not found: {path.name}")
return []
lines = [line.strip() for line in path.read_text().splitlines() if line.strip()]
out = [] out = []
for line in lines: for line in lines:
if line.startswith("@"): if line.startswith("@"):
@@ -93,8 +91,9 @@ class PlaylistParser:
else: out.append(line) else: out.append(line)
return out return out
def parse_playlistfile(self, playlist_path: Path) -> tuple[dict[str, str], list[tuple[list[str], dict[str, str]]]]: def parse(self, playlist_path: Path) -> tuple[dict[str, str], list[tuple[list[str], dict[str, str]]]]:
lines = self._check_for_imports(playlist_path) # First, import everything lines = self._check_for_imports(playlist_path)
out = [] out = []
global_arguments = {} global_arguments = {}
for line in lines: for line in lines:
@@ -113,7 +112,7 @@ class PlaylistParser:
for arg in args: for arg in args:
key, val = arg.split("=", 1) key, val = arg.split("=", 1)
arguments[key] = val arguments[key] = val
out.append(([f for f in glob.glob(line) if os.path.isfile(f)], arguments)) out.append(([f for f in Path().glob(line) if Path(f).is_file()], arguments))
return global_arguments, out return global_arguments, out
class RadioPlayer: class RadioPlayer:
@@ -126,11 +125,14 @@ class RadioPlayer:
self.exit_pending = False self.exit_pending = False
self.exit_status_code = 0 self.exit_status_code = 0
self.intr_time = 0 self.intr_time = 0
self.exit_lock = threading.Lock() self.exit_lock = Lock()
self.procman = ProcessManager() self.procman = ProcessManager()
self.modules: list[tuple] = [] self.modules: list[tuple] = []
self.parser = PlaylistParser(output) self.parser = PlaylistParser(output)
def shutdown(self): self.procman.stop_all()
def shutdown(self):
self.procman.stop_all()
def handle_sigint(self, signum, frame): def handle_sigint(self, signum, frame):
with self.exit_lock: with self.exit_lock:
self.logger.info("Received CTRL+C (SIGINT)") self.logger.info("Received CTRL+C (SIGINT)")
@@ -172,7 +174,7 @@ class RadioPlayer:
time.perf_counter() time.perf_counter()
spec.loader.exec_module(module) spec.loader.exec_module(module)
time_took = time.monotonic() - start time_took = time.monotonic() - start
if time_took > 0.2: self.logger.warning(f"{module_name} took {time_took:.2f}s to start") if time_took > 0.2: self.logger.warning(f"{module_name} took {time_took:.1f}s to start")
except Exception as e: except Exception as e:
traceback.print_exc(file=self.logger.output) traceback.print_exc(file=self.logger.output)
self.logger.error(f"Failed loading {module_name} due to {e}, continuing") self.logger.error(f"Failed loading {module_name} due to {e}, continuing")
@@ -204,9 +206,7 @@ class RadioPlayer:
raise SystemExit(1) raise SystemExit(1)
def play_playlist(self, playlist_path: Path, starting_index: int = 0): def play_playlist(self, playlist_path: Path, starting_index: int = 0):
assert self.playlist_advisor try: global_args, parsed = self.parser.parse(playlist_path)
try: global_args, parsed = self.parser.parse_playlistfile(playlist_path)
except Exception as e: except Exception as e:
self.logger.info(f"Exception ({e}) while parsing playlist, retrying in 15 seconds...") self.logger.info(f"Exception ({e}) while parsing playlist, retrying in 15 seconds...")
time.sleep(15) time.sleep(15)
@@ -235,7 +235,7 @@ class RadioPlayer:
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 return
if self.playlist_advisor.new_playlist(): if self.playlist_advisor and self.playlist_advisor.new_playlist():
self.logger.info("Reloading now...") self.logger.info("Reloading now...")
return_pending = True return_pending = True
continue continue
@@ -253,18 +253,15 @@ class RadioPlayer:
prefetch(track.path) prefetch(track.path)
self.logger.info(f"Now playing: {track.path.name}") self.logger.info(f"Now playing: {track.path.name}")
for module in self.simple_modules: module.on_new_track(song_i, track, next_track) [module.on_new_track(song_i, track, next_track) for module in self.simple_modules if module]
pr = self.procman.play(track, cross_fade) pr = self.procman.play(track, cross_fade)
ttw = pr.duration end_time = pr.started_at + pr.duration
if track.fade_out: ttw -= cross_fade if track.fade_out: end_time -= cross_fade
end_time = pr.started_at + ttw
if next_track: prefetch(next_track.path)
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()
[module.progress(song_i, track, time.monotonic() - pr.started_at, pr.duration, ttw) for module in self.simple_modules if module] [module.progress(song_i, track, time.monotonic() - pr.started_at, pr.duration, end_time - pr.started_at) for module in self.simple_modules if module]
elapsed = time.monotonic() - start elapsed = time.monotonic() - start
remaining_until_end = end_time - time.monotonic() remaining_until_end = end_time - time.monotonic()
if elapsed < 1 and remaining_until_end > 0: time.sleep(min(1 - elapsed, remaining_until_end)) if elapsed < 1 and remaining_until_end > 0: time.sleep(min(1 - elapsed, remaining_until_end))