From cc861e2869dce13a6515eb2719f5df5fba911296 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 10 Apr 2021 22:56:53 +0100 Subject: [PATCH] End of Day WIP on marker support --- player.py | 51 +++++++++++++++++++++++++++++++++++++--- tests/test_player.py | 2 ++ types/marker.py | 41 ++++++++++++++++++++++++++++++++ plan.py => types/plan.py | 14 +++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 types/marker.py rename plan.py => types/plan.py (87%) diff --git a/player.py b/player.py index e24b80b..6082f25 100644 --- a/player.py +++ b/player.py @@ -20,7 +20,9 @@ # that we respond with something, FAIL or OKAY. The server doesn't like to be kept waiting. # Stop the Pygame Hello message. +from logging import exception import os +from types.marker import Marker os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" from queue import Empty @@ -36,7 +38,7 @@ from mutagen.mp3 import MP3 from helpers.myradio_api import MyRadioAPI from helpers.state_manager import StateManager from helpers.logging_manager import LoggingManager -from plan import PlanItem +from types.plan import PlanItem # TODO ENUM @@ -74,6 +76,7 @@ class Player: "play_on_load": False, "output": None, "show_plan": [], + "markers": [], } __rate_limited_params = ["pos", "pos_offset", "pos_true", "remaining"] @@ -402,7 +405,48 @@ class Player: return True - def ended(self): + def set_marker(self, timeslotitemid: int, marker_str: str): + set_loaded = False + success = True + try: + marker = Marker(marker_str) + except Exception as e: + self.logger.log.error("Failed to create Marker instance with {} {}: {}".format(timeslotitemid, marker_str, e)) + return False + + if timeslotitemid == -1: + set_loaded = True + if not self.isLoaded: + return False + timeslotitemid = self.state.state["loaded_item"]["timeslotitemid"] + + for i in range(len(self.state.state["show_plan"])): + plan = self.state.state["show_plan"] + item = plan[i] + + if item.timeslotitemid == timeslotitemid: + try: + item = item.set_marker(marker) + + self.state.update("show_plan", plan) + except Exception as e: + self.logger.log.error( + "Failed to set marker on item {}: {} with marker \n{}".format(timeslotitemid, e, marker)) + success = False + + if set_loaded: + try: + self.state.update("loaded_item", self.state.state["show_plan"]["loaded_item"].set_marker(marker)) + except Exception as e: + self.logger.log.error( + "Failed to set marker on loaded_item {}: {} with marker \n{}".format(timeslotitemid, e, marker)) + success = False + + return success + + # Helper functions + + def _ended(self): loaded_item = self.state.state["loaded_item"] # Track has ended @@ -459,7 +503,7 @@ class Player: and not self.isPlaying and not self.stopped_manually ): - self.ended() + self._ended() self.state.update("playing", self.isPlaying) self.state.update("loaded", self.isLoaded) @@ -656,6 +700,7 @@ class Player: int(self.last_msg.split(":")[1])) ), "CLEAR": lambda: self._retMsg(self.clear_channel_plan()), + "SETMARKER": lambda: self._retMsg(self.set_marker(self.last_msg.split(":")[1])), } message_type: str = self.last_msg.split(":")[0] diff --git a/tests/test_player.py b/tests/test_player.py index c5ac8ae..9283826 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -349,6 +349,8 @@ class TestPlayer(unittest.TestCase): self.assertEquals(item["cue"], None) self.assertEquals(item["markers"], markers[3:]) + # TODO: Now test editing/deleting them + # runs the unit tests in the module if __name__ == "__main__": diff --git a/types/marker.py b/types/marker.py new file mode 100644 index 0000000..7c166ff --- /dev/null +++ b/types/marker.py @@ -0,0 +1,41 @@ +import json +from typing import Dict, Optional, Union + +POSITIONS = ["start", "mid", "end"] +PARAMS = ["name", "time", "position", "section"] + + +class Marker: + marker: Dict + + def __init__(self, marker_str: str): + try: + marker = json.loads(marker_str) + except Exception as e: + raise ValueError("Failed to decode JSON for marker: {}".format(e)) + + for key in marker.keys(): + if key not in PARAMS: + raise ValueError("Key {} is not a valid marker parameter.".format(key)) + + if not isinstance(marker["name"], str): + raise ValueError("Name is not str.") + self.name = marker["name"] + + if not isinstance(marker["time"], Union[int, float]): + raise ValueError("Time is not a float or int") + + if marker["position"] not in POSITIONS: + raise ValueError("Position is not in allowed values.") + + if not isinstance(marker["section"], Optional[str]): + raise ValueError("Section name is not str or None.") + + # If everything checks out, let's save it. + self.marker = marker + + def __str__(self): + return json.dumps(self.marker) + + def __dict__(self): + return self.marker diff --git a/plan.py b/types/plan.py similarity index 87% rename from plan.py rename to types/plan.py index 0e9f2fc..4cc4e9b 100644 --- a/plan.py +++ b/types/plan.py @@ -12,6 +12,7 @@ November 2020 """ +from types.marker import Marker from typing import Any, Dict, Optional import os @@ -90,6 +91,9 @@ class PlanItem: "name": self.name, "filename": self.filename, "length": self.length, + "intro": self.intro, + "cue": self.cue, + "outro": self.outro, } def __init__(self, new_item: Dict[str, Any]): @@ -108,9 +112,19 @@ class PlanItem: self._artist = new_item["artist"] if "artist" in new_item else None self._length = new_item["length"] + # Edit this to handle markers when MyRadio supports them + self._ + # Fix any OS specific / or \'s if self.filename: if os.path.sep == "/": self._filename = self.filename.replace("\\", "/") else: self._filename = self.filename.replace("/", "\\") + + def set_marker(self, marker: Marker): + if not isinstance(marker, Marker): + raise ValueError("Marker provided is not of type Marker.") + + # Return updated item for easy chaining. + return self