diff --git a/modules/advisor.py b/modules/advisor.py index 39fc33b..e283b11 100644 --- a/modules/advisor.py +++ b/modules/advisor.py @@ -14,7 +14,7 @@ _log_out: TextIO assert _log_out # pyright: ignore[reportUnboundVariable] logger = log95.log95("ADVISOR", output=_log_out) -playlist_dir = Path("/home/user/playlists") +playlist_dir = Path("/home/user/mixes/.playlist") class Time: @staticmethod diff --git a/modules/fsdb.py b/modules/fsdb.py new file mode 100644 index 0000000..426281d --- /dev/null +++ b/modules/fsdb.py @@ -0,0 +1,85 @@ +import glob as glob_module +from . import log95, Path, PlaylistParser + +_log_out: log95.TextIO + +def _parse_args(text: str) -> dict[str, str]: + args = {} + for line in text.splitlines(): + line = line.strip() + if not line or line.startswith(";") or line.startswith("#"): continue + if "=" in line: + key, val = line.split("=", 1) + args[key.strip()] = val.strip() + else: + args[line] = True + return args + +class FSDBParser(PlaylistParser): + def __init__(self, ref_dir: Path) -> None: + self.logger = log95.log95("FSDB", output=_log_out) + self.ref_dir = ref_dir.resolve().absolute() + + def parse(self, playlist_path: Path) -> tuple[dict[str, str], list[tuple[list[str], dict[str, str]]]]: + if not playlist_path.is_dir(): + self.logger.error(f"Playlist path is not a directory: {playlist_path}") + raise Exception("Playlist directory doesn't exist") + + global_args = {} + if (global_args_file := playlist_path / ".args.txt").exists(): + global_args = _parse_args(global_args_file.read_text()) + + out = [] + for entry in sorted(playlist_path.iterdir()): + if entry.name.startswith("."): continue + + if entry.is_file(): + real = self.ref_dir / entry.name + files = [f for f in glob_module.glob(str(real)) if Path(f).is_file()] + if not files: + self.logger.warning(f"No match in ref_dir for: {entry.name}") + continue + args = _parse_args(entry.read_text()) if entry.stat().st_size > 0 else {} + out.append((files, args)) + elif entry.is_dir(): + real_dir = self.ref_dir / entry.name + if not real_dir.is_dir(): + self.logger.warning(f"No matching directory in ref_dir for: {entry.name}") + continue + files = [f for f in glob_module.glob(str(real_dir / "**"), recursive=True) if Path(f).is_file()] + if not files: + self.logger.warning(f"No files found under ref_dir for group: {entry.name}") + continue + args = _parse_args((entry / ".args.txt").read_text()) if (entry / ".args.txt").exists() else {} + out.append((files, args)) + + return global_args, out + +parser = FSDBParser(Path("/home/user/mixes")) + +# Claude wrote it and agreed to unlicense this + +# 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 \ No newline at end of file diff --git a/modules/playlist_parser.py b/modules/playlist_parser.py index 577c10e..fed97aa 100644 --- a/modules/playlist_parser.py +++ b/modules/playlist_parser.py @@ -54,4 +54,4 @@ class PlaintextParser(PlaylistParser): out.append(([f for f in glob.glob(line) if Path(f).is_file()], arguments)) return global_arguments, out -parser = PlaintextParser() \ No newline at end of file +# parser = PlaintextParser() \ No newline at end of file diff --git a/modules/web.py b/modules/web.py index 0f9279a..512ce16 100644 --- a/modules/web.py +++ b/modules/web.py @@ -55,7 +55,7 @@ async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: mult result = await get_imc("activemod", {"action": "get_toplay"}) if result is not None: - await websocket.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 == "get_toplay": result = await get_imc("activemod", {"action": "get_toplay"}) if result is None: await websocket.send(json.dumps({"error": "timeout", "code": 504})) @@ -65,15 +65,19 @@ async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: mult if result is None: await websocket.send(json.dumps({"error": "timeout", "code": 504})) else: await websocket.send(json.dumps({"data": result, "event": "toplay"})) # Yes, this is not an accident - await websocket.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 == "skip_next": result = await get_imc("activemod", msg) if result is None: await websocket.send(json.dumps({"error": "timeout", "code": 504})) - else: await websocket.send(json.dumps({"data": result, "event": action})) + else: + await websocket.send(json.dumps({"data": result, "event": action})) + await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"data": result, "event": action}) # broadcast elif action == "skipc": result = await get_imc("activemod", msg) if result is None: await websocket.send(json.dumps({"error": "timeout", "code": 504})) - else: await websocket.send(json.dumps({"data": result, "event": action})) + else: + await websocket.send(json.dumps({"data": result, "event": action})) + await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"data": result, "event": action}) # broadcast elif action == "jingle": result = await get_imc("jingle", msg.get("top", False)) if result is None: await websocket.send(json.dumps({"error": "timeout", "code": 504})) @@ -81,7 +85,7 @@ async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: mult await websocket.send(json.dumps(result)) result = await get_imc("activemod", {"action": "get_toplay"}) if result is not None: - await websocket.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", "") @@ -270,7 +274,6 @@ class Module(PlayerModule): module = Module() - # This is free and unencumbered software released into the public domain. # Anyone is free to copy, modify, publish, use, compile, sell, or diff --git a/radioPlayer.py b/radioPlayer.py index 36aec7b..f07aaae 100644 --- a/radioPlayer.py +++ b/radioPlayer.py @@ -66,7 +66,7 @@ class ModuleManager: try: future = executor.submit(timed_loader, spec, module) try: - time_took = future.result(5) + time_took = future.result(3) if time_took > 0.15: self.logger.warning(f"{module_name} took {time_took:.1f}s to start") except concurrent.futures.TimeoutError: self.logger.error(f"Module {module_name} timed out.") @@ -223,7 +223,16 @@ class RadioPlayer: while i < max_iterator: if check_conditions(): return - self.logger.info(f"Now playing: {track.path.name}") + if not track.path.exists(): + self.procman.anything_playing() + track, next_track, extend = get_track() + prefetch(track.path) + i += 1 + if not extend: song_i += 1 + self.logger.warning("File does not exist:", str(track.path)) + continue + + self.logger.info("Now playing:", track.path.name) prefetch(track.path) pr = self.procman.play(track)