From 3951a4b4270fbd318694918df54c53b2a9130891 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Mon, 21 Jun 2021 00:22:29 +0100 Subject: [PATCH 001/156] Add fader live tracking to player --- player.py | 41 +++++++++++++++++++++++++-------- ui-templates/config_server.html | 5 +++- ui-templates/status.html | 4 ++++ web_server.py | 2 +- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/player.py b/player.py index 3f76b9d..db05d47 100644 --- a/player.py +++ b/player.py @@ -81,6 +81,7 @@ class Player: "play_on_load": False, "output": None, "show_plan": [], + "live": True, "tracklist_mode": "off", "tracklist_id": None, } @@ -597,6 +598,16 @@ def reset_played(self, weight: int): return False return True + # Tells the player that the fader is live on-air, so it can tell tracklisting from PFL + def set_live(self, live: bool): + live = bool(live) + self.state.update("live", live) + + # If we're going to live (potentially from not live/PFL), potentially tracklist if it's playing. + if (live): + self._potentially_tracklist() + return True + # Helper functions @@ -605,7 +616,7 @@ def _potentially_tracklist(self): mode = self.state.get()["tracklist_mode"] time: int = -1 - if mode == "on": + if mode in ["on","fader-live"]: time = 1 # Let's do it pretty quickly. elif mode == "delayed": # Let's do it in a bit, once we're sure it's been playing. (Useful if we've got no idea if it's live or cueing.) @@ -652,20 +663,28 @@ def _potentially_end_tracklist(self): self.logger.log.warning("Failed to potentially end tracklist, no tracklist started.") def _tracklist_start(self): - loaded_item = self.state.get()["loaded_item"] + state = self.state.get() + loaded_item = state["loaded_item"] if not loaded_item: self.logger.log.error("Tried to call _tracklist_start() with no loaded item!") return - tracklist_id = self.state.get()["tracklist_id"] + if not self.isPlaying: + self.logger.log.info("Not tracklisting since not playing.") + return + + tracklist_id = state["tracklist_id"] if (not tracklist_id): - self.logger.log.info("Tracklisting item: {}".format(loaded_item.name)) - tracklist_id = self.api.post_tracklist_start(loaded_item) - if not tracklist_id: - self.logger.log.warning("Failed to tracklist {}".format(loaded_item.name)) + if (state["tracklist_mode"] == "fader-live" and not state["live"]): + self.logger.log.info("Not tracklisting since fader is not live.") else: - self.logger.log.info("Tracklist id: {}".format(tracklist_id)) - self.state.update("tracklist_id", tracklist_id) + self.logger.log.info("Tracklisting item: {}".format(loaded_item.name)) + tracklist_id = self.api.post_tracklist_start(loaded_item) + if not tracklist_id: + self.logger.log.warning("Failed to tracklist {}".format(loaded_item.name)) + else: + self.logger.log.info("Tracklist id: {}".format(tracklist_id)) + self.state.update("tracklist_id", tracklist_id) else: self.logger.log.info("Not tracklisting item {}, already got tracklistid: {}".format( loaded_item.name, tracklist_id)) @@ -865,6 +884,7 @@ def __init__( self.state.update("channel", channel) self.state.update("tracklist_mode", server_state.get()["tracklist_mode"]) + self.state.update("live", True) # Channel is live until controller says it isn't. # Just in case there's any weights somehow messed up, let's fix them. plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) @@ -986,7 +1006,8 @@ def __init__( ), "CLEAR": lambda: self._retMsg(self.clear_channel_plan()), "SETMARKER": lambda: self._retMsg(self.set_marker(self.last_msg.split(":")[1], self.last_msg.split(":", 2)[2])), - "RESETPLAYED": lambda: self._retMsg(self.reset_played(int(self.last_msg.split(":")[1]))) + "RESETPLAYED": lambda: self._retMsg(self.reset_played(int(self.last_msg.split(":")[1]))), + "SETLIVE": lambda: self._retMsg(self.set_live(self.last_msg.split(":")[1])), } message_type: str = self.last_msg.split(":")[0] diff --git a/ui-templates/config_server.html b/ui-templates/config_server.html index 7934ce1..6c9ee7f 100644 --- a/ui-templates/config_server.html +++ b/ui-templates/config_server.html @@ -44,7 +44,10 @@ {% endfor %} -

Delayed tracklisting is 20s, to account for cueing with fader down.

+

+ Delayed tracklisting is 20s, to account for cueing with fader down.
+ Fader Live means if a BAPS Controller is present with support, tracklists will trigger only if fader is up. +


diff --git a/ui-templates/status.html b/ui-templates/status.html index 94cc3da..162e854 100644 --- a/ui-templates/status.html +++ b/ui-templates/status.html @@ -10,6 +10,10 @@
{% if player %}

Player {{player.channel}}

+

+ Initialised: {{player.initialised}}
+ Fader Live: {{player.live}} +

Play {% if player.paused %} UnPause diff --git a/web_server.py b/web_server.py index 521f6f0..b6b95d0 100644 --- a/web_server.py +++ b/web_server.py @@ -100,7 +100,7 @@ def ui_config_server(request): "ui_title": "Server Config", "state": server_state.get(), "ser_ports": DeviceManager.getSerialPorts(), - "tracklist_modes": ["off", "on", "delayed"] + "tracklist_modes": ["off", "on", "delayed", "fader-live"] } return render_template("config_server.html", data=data) From 639b82adf939e175646201c55a29d3ee3be4585b Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 22 Jun 2021 18:20:18 +0100 Subject: [PATCH 002/156] Add Controller support for fader lives. --- controllers/mattchbox_usb.py | 14 ++++++++++++-- player.py | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index 2f4c714..5f22777 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -58,6 +58,10 @@ def _state_handler(self): self.next_port = new_port def connect(self, port: Optional[str]): + # If we loose the controller, make sure to set channels live, so we tracklist. + for i in range(len(self.server_from_q)): + self.sendToPlayer(i, "SETLIVE:True") + if port: # connect to serial port self.ser = serial.serial_for_url(port, do_not_open=True) @@ -85,7 +89,13 @@ def handler(self): ) # Endianness doesn't matter for 1 byte. self.logger.log.info("Received from controller: " + str(line)) if line == 255: - self.ser.write(b"\xff") # Send 255 back. + self.ser.write(b"\xff") # Send 255 back + elif line in [51,52,53]: + # We've received a status update about fader live status, fader is down. + self.sendToPlayer(line-51, "SETLIVE:False") + elif line in [61,62,63]: + # We've received a status update about fader live status, fader is up. + self.sendToPlayer(line-61, "SETLIVE:True") elif line in [1, 3, 5]: self.sendToPlayer(int(line / 2), "PLAYPAUSE") elif line in [2, 4, 6]: @@ -121,5 +131,5 @@ def handler(self): self.connect(None) def sendToPlayer(self, channel: int, msg: str): - self.logger.log.info("Sending message to server: " + msg) + self.logger.log.info("Sending message to player channel {}: {}".format(channel, msg)) self.server_to_q[channel].put("CONTROLLER:" + msg) diff --git a/player.py b/player.py index db05d47..34fedf3 100644 --- a/player.py +++ b/player.py @@ -600,7 +600,7 @@ def reset_played(self, weight: int): # Tells the player that the fader is live on-air, so it can tell tracklisting from PFL def set_live(self, live: bool): - live = bool(live) + self.state.update("live", live) # If we're going to live (potentially from not live/PFL), potentially tracklist if it's playing. @@ -1007,7 +1007,7 @@ def __init__( "CLEAR": lambda: self._retMsg(self.clear_channel_plan()), "SETMARKER": lambda: self._retMsg(self.set_marker(self.last_msg.split(":")[1], self.last_msg.split(":", 2)[2])), "RESETPLAYED": lambda: self._retMsg(self.reset_played(int(self.last_msg.split(":")[1]))), - "SETLIVE": lambda: self._retMsg(self.set_live(self.last_msg.split(":")[1])), + "SETLIVE": lambda: self._retMsg(self.set_live(self.last_msg.split(":")[1] == "True")), } message_type: str = self.last_msg.split(":")[0] From 8d98410a6ab9ffe574b97a8bf01f09249fcdc964 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 16 Jul 2021 23:56:58 +0100 Subject: [PATCH 003/156] demo pydub normalisation --- player.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/player.py b/player.py index 3f76b9d..bb42f86 100644 --- a/player.py +++ b/player.py @@ -34,6 +34,7 @@ from mutagen.mp3 import MP3 from syncer import sync from threading import Timer +from pydub import AudioSegment, effects # Audio leveling! from helpers.myradio_api import MyRadioAPI from helpers.state_manager import StateManager @@ -436,12 +437,30 @@ def load(self, weight: int): # TODO: Update the show plan filenames??? load_attempt = 0 + + if not isinstance(loaded_item.filename, str): + return False + while load_attempt < 5: load_attempt += 1 try: self.logger.log.info("Loading file: " + str(loaded_item.filename)) - mixer.music.load(loaded_item.filename) + + + + + + def match_target_amplitude(sound, target_dBFS): + change_in_dBFS = target_dBFS - sound.dBFS + return sound.apply_gain(change_in_dBFS) + + sound = AudioSegment.from_file(loaded_item.filename, "mp3") + normalized_sound = effects.normalize(sound) #match_target_amplitude(sound, -10) + normalized_sound.export("{}-normalised.mp3".format(loaded_item.filename), bitrate="320k", format="mp3") + + + mixer.music.load("{}-normalised.mp3".format(loaded_item.filename)) except Exception: # We couldn't load that file. self.logger.log.exception( @@ -456,10 +475,11 @@ def load(self, weight: int): continue # Try loading again. try: - if ".mp3" in loaded_item.filename: + if loaded_item.filename.endswith(".mp3"): song = MP3(loaded_item.filename) self.state.update("length", song.info.length) else: + # WARNING! Pygame / SDL can't seek .wav files :/ self.state.update( "length", mixer.Sound( loaded_item.filename).get_length() / 1000 From cb425689a650a62a055969206ad22d936254df70 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sat, 7 Aug 2021 16:52:57 +0100 Subject: [PATCH 004/156] Add requirements.txt Several dependency versions needed downgrading from the latest due to breaking changes. Add a requirements file for the time being. --- requirements.txt | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4af8acb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,29 @@ +aiofiles==0.7.0 +aiohttp==3.7.4.post0 +async-timeout==3.0.1 +attrs==21.2.0 +certifi==2021.5.30 +cffi==1.14.6 +chardet==4.0.0 +charset-normalizer==2.0.4 +httptools==0.2.0 +idna==3.2 +Jinja2==3.0.1 +MarkupSafe==2.0.1 +multidict==5.1.0 +pycparser==2.20 +pyttsx3==2.90 +requests==2.26.0 +sanic==21.3.4 +Sanic-Cors==1.0.0 +sanic-plugin-toolkit==1.0.1 +sanic-routing==0.7.1 +setproctitle==1.2.2 +sounddevice==0.4.2 +syncer==1.3.0 +typing-extensions==3.10.0.0 +ujson==4.0.2 +urllib3==1.26.6 +uvloop==0.15.3 +websockets==8.1 +yarl==1.6.3 From 443467377c76f012d581dd1270273c083ca707c3 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sat, 7 Aug 2021 17:03:31 +0100 Subject: [PATCH 005/156] Add a few missing deps --- requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/requirements.txt b/requirements.txt index 4af8acb..88e13e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,13 +6,19 @@ certifi==2021.5.30 cffi==1.14.6 chardet==4.0.0 charset-normalizer==2.0.4 +future==0.18.2 httptools==0.2.0 idna==3.2 +iso8601==0.1.16 Jinja2==3.0.1 MarkupSafe==2.0.1 multidict==5.1.0 +mutagen==1.45.1 pycparser==2.20 +pygame==2.0.1 +pyserial==3.5 pyttsx3==2.90 +PyYAML==5.4.1 requests==2.26.0 sanic==21.3.4 Sanic-Cors==1.0.0 From 6cbb8948eb5d649e379ab401a8f4ecd15af383d1 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 11 Aug 2021 23:24:09 +0100 Subject: [PATCH 006/156] Add requirements.txt version freeze. --- build/requirements.txt | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/build/requirements.txt b/build/requirements.txt index b01b08a..c8da374 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,16 +1,15 @@ autopep8 pygame==2.0.1 -sanic -sanic-cors -syncer -aiohttp -mutagen -sounddevice -autopep8 -setproctitle -pyttsx3 -websockets -typing_extensions -pyserial -requests -jinja2 +sanic==21.3.4 +sanic-Cors==1.0.0 +syncer==1.3.0 +aiohttp=3.7.4.post0 +mutagen==1.45.1 +sounddevice==0.4.2 +setproctitle==1.2.2 +pyttsx3==2.90 +websockets==8.1 +typing_extensions==3.10.0.0 +pyserial==3.5 +requests==2.26.0 +Jinja2==3.0.1 From 883b877fd86fb7d19fe6298da4fe901bc66ab1fc Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 11 Aug 2021 23:25:13 +0100 Subject: [PATCH 007/156] Removing top level requirements.txt for ones in /build. --- requirements.txt | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 88e13e0..0000000 --- a/requirements.txt +++ /dev/null @@ -1,35 +0,0 @@ -aiofiles==0.7.0 -aiohttp==3.7.4.post0 -async-timeout==3.0.1 -attrs==21.2.0 -certifi==2021.5.30 -cffi==1.14.6 -chardet==4.0.0 -charset-normalizer==2.0.4 -future==0.18.2 -httptools==0.2.0 -idna==3.2 -iso8601==0.1.16 -Jinja2==3.0.1 -MarkupSafe==2.0.1 -multidict==5.1.0 -mutagen==1.45.1 -pycparser==2.20 -pygame==2.0.1 -pyserial==3.5 -pyttsx3==2.90 -PyYAML==5.4.1 -requests==2.26.0 -sanic==21.3.4 -Sanic-Cors==1.0.0 -sanic-plugin-toolkit==1.0.1 -sanic-routing==0.7.1 -setproctitle==1.2.2 -sounddevice==0.4.2 -syncer==1.3.0 -typing-extensions==3.10.0.0 -ujson==4.0.2 -urllib3==1.26.6 -uvloop==0.15.3 -websockets==8.1 -yarl==1.6.3 From b90330ff578b4c540ebd0cb66d51bf1605897796 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Mon, 16 Aug 2021 23:29:58 +0100 Subject: [PATCH 008/156] Fix failing to return when no shows. --- helpers/myradio_api.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 8e8c7a4..6621327 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -156,23 +156,22 @@ async def get_showplans(self): payload = json.loads(await request)["payload"] + shows = [] if not payload["current"]: self._logException("API did not return a current show.") + else: + shows.append(payload["current"]) if not payload["next"]: self._logException("API did not return a list of next shows.") + else: + shows.extend(payload["next"]) - shows = [] - shows.append(payload["current"]) - shows.extend(payload["next"]) - - timeslots = [] # Remove jukebox etc for show in shows: if not "timeslot_id" in show: shows.remove(show) - # TODO filter out jukebox return shows async def get_showplan(self, timeslotid: int): From e696e2237aeff3595c799ad8a051b43eb79fdf22 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Mon, 16 Aug 2021 23:43:09 +0100 Subject: [PATCH 009/156] WIP Normalisation in filemanager. Needs race conditions fixing. --- file_manager.py | 156 ++++++++++++++++++++++++++++----------- helpers/normalisation.py | 49 ++++++++++++ player.py | 21 ++---- web_server.py | 14 +++- 4 files changed, 180 insertions(+), 60 deletions(-) create mode 100644 helpers/normalisation.py diff --git a/file_manager.py b/file_manager.py index 34b818d..c853f06 100644 --- a/file_manager.py +++ b/file_manager.py @@ -11,6 +11,7 @@ from helpers.logging_manager import LoggingManager from helpers.the_terminator import Terminator from helpers.myradio_api import MyRadioAPI +from helpers.normalisation import generate_normalised_file from baps_types.plan import PlanItem @@ -28,19 +29,20 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): current_process().name = process_title terminator = Terminator() - channel_count = len(channel_from_q) - channel_received = None - last_known_show_plan = [[]]*channel_count - next_channel_preload = 0 - last_known_item_ids = [[]]*channel_count + self.channel_count = len(channel_from_q) + self.channel_received = None + self.last_known_show_plan = [[]]*self.channel_count + self.next_channel_preload = 0 + self.known_channels_preloaded = [False]*self.channel_count + self.last_known_item_ids = [[]]*self.channel_count try: while not terminator.terminate: # If all channels have received the delete command, reset for the next one. - if (channel_received == None or channel_received == [True]*channel_count): - channel_received = [False]*channel_count + if (self.channel_received == None or self.channel_received == [True]*self.channel_count): + self.channel_received = [False]*self.channel_count - for channel in range(channel_count): + for channel in range(self.channel_count): try: message = channel_from_q[channel].get_nowait() except Exception: @@ -53,11 +55,11 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): # If we have requested a new show plan, empty the music-tmp directory for the previous show. if command == "GET_PLAN": - if channel_received != [False]*channel_count and channel_received[channel] != True: + if self.channel_received != [False]*self.channel_count and self.channel_received[channel] != True: # We've already received a delete trigger on a channel, let's not delete the folder more than once. # If the channel was already in the process of being deleted, the user has requested it again, so allow it. - channel_received[channel] = True + self.channel_received[channel] = True continue # Delete the previous show files! @@ -80,7 +82,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): except Exception: self.logger.log.warning("Failed to remove, skipping. Likely file is still in use.") continue - channel_received[channel] = True + self.channel_received[channel] = True # If we receive a new status message, let's check for files which have not been pre-loaded. if command == "STATUS": @@ -96,44 +98,114 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): # If the new status update has a different order / list of items, let's update the show plan we know about # This will trigger the chunk below to do the rounds again and preload any new files. - if item_ids != last_known_item_ids[channel]: - last_known_item_ids[channel] = item_ids - last_known_show_plan[channel] = show_plan + if item_ids != self.last_known_item_ids[channel]: + self.last_known_item_ids[channel] = item_ids + self.last_known_show_plan[channel] = show_plan + self.known_channels_preloaded[channel] = False except Exception: self.logger.log.exception("Failed to handle message {} on channel {}.".format(message, channel)) + # Let's try preload / normalise some files now we're free of messages. + preloaded = self.do_preload() + normalised = self.do_normalise() - # Right, let's have a quick check in the status for shows without filenames, to preload them. - delay = True - for i in range(len(last_known_show_plan[next_channel_preload])): + if (not preloaded and not normalised): + # We didn't do any hard work, let's sleep. + sleep(0.5) - item_obj = PlanItem(last_known_show_plan[next_channel_preload][i]) - if not item_obj.filename: - self.logger.log.info("Checking pre-load on channel {}, weight {}: {}".format(next_channel_preload, item_obj.weight, item_obj.name)) - - # Getting the file name will only pull the new file if the file doesn't already exist, so this is not too inefficient. - item_obj.filename,did_download = sync(self.api.get_filename(item_obj, True)) - # Alright, we've done one, now let's give back control to process new statuses etc. - - # Save back the resulting item back in regular dict form - last_known_show_plan[next_channel_preload][i] = item_obj.__dict__ - - if did_download: - # Given we probably took some time to download, let's not sleep in the loop. - delay = False - self.logger.log.info("File successfully preloaded: {}".format(item_obj.filename)) - break - else: - # We didn't download anything this time, file was already loaded. - # Let's try the next one. - continue - next_channel_preload += 1 - if next_channel_preload >= channel_count: - next_channel_preload = 0 - if delay: - sleep(0.1) except Exception as e: self.logger.log.exception( "Received unexpected exception: {}".format(e)) del self.logger + + + # Attempt to preload a file onto disk. + def do_preload(self): + channel = self.next_channel_preload + # Right, let's have a quick check in the status for shows without filenames, to preload them. + # Keep an eye on if we downloaded anything. + # If we didn't, we know that all items in this channel have been downloaded. + downloaded_something = False + for i in range(len(self.last_known_show_plan[channel])): + + item_obj = PlanItem(self.last_known_show_plan[channel][i]) + + # We've not downloaded this file yet, let's do that. + if not item_obj.filename: + self.logger.log.info("Checking pre-load on channel {}, weight {}: {}".format(channel, item_obj.weight, item_obj.name)) + + # Getting the file name will only pull the new file if the file doesn't already exist, so this is not too inefficient. + item_obj.filename,did_download = sync(self.api.get_filename(item_obj, True)) + # Alright, we've done one, now let's give back control to process new statuses etc. + + # Save back the resulting item back in regular dict form + self.last_known_show_plan[channel][i] = item_obj.__dict__ + + if did_download: + downloaded_something = True + self.logger.log.info("File successfully preloaded: {}".format(item_obj.filename)) + break + else: + # We didn't download anything this time, file was already loaded. + # Let's try the next one. + continue + # Given we probably took some time to download, let's not sleep in the loop. + if not downloaded_something: + # Tell the file manager that this channel is fully downloaded, this is so it can consider normalising once all channels have files. + self.known_channels_preloaded[channel] = True + + self.next_channel_preload += 1 + if self.next_channel_preload >= self.channel_count: + self.next_channel_preload = 0 + + return downloaded_something + + + # If we've preloaded everything, get to work normalising tracks before playback. + def do_normalise(self): + # Some channels still have files to preload, do nothing. + if (self.known_channels_preloaded != [True]*self.channel_count): + return False # Didn't normalise + # TODO: quit early if all channels are normalised already. + + channel = self.next_channel_preload + + normalised_something = False + # Look through all the show plan files + for i in range(len(self.last_known_show_plan[channel])): + + item_obj = PlanItem(self.last_known_show_plan[channel][i]) + + filename = item_obj.filename + if not filename: + self.logger.log.exception("Somehow got empty filename when all channels are preloaded.") + continue # Try next song. + + if "normalised" in filename: + continue + # Sweet, we now need to try generating a normalised version. + try: + self.logger.log.info("Normalising on channel {}: {}".format(channel,filename)) + # This will return immediately if we already have a normalised file. + item_obj.filename = generate_normalised_file(filename) + # TODO Hacky + self.last_known_show_plan[channel][i] = item_obj.__dict__ + normalised_something = True + break # Now go let another channel have a go. + except Exception as e: + self.logger.log.exception("Failed to generate normalised file.", str(e)) + continue + + + + self.next_channel_preload += 1 + if self.next_channel_preload >= self.channel_count: + self.next_channel_preload = 0 + + return normalised_something + + + + + diff --git a/helpers/normalisation.py b/helpers/normalisation.py new file mode 100644 index 0000000..9622c3a --- /dev/null +++ b/helpers/normalisation.py @@ -0,0 +1,49 @@ +import os +from helpers.os_environment import resolve_external_file_path +from pydub import AudioSegment, effects # Audio leveling! + +# Stuff to help make BAPSicle play out leveled audio. +def match_target_amplitude(sound, target_dBFS): + change_in_dBFS = target_dBFS - sound.dBFS + return sound.apply_gain(change_in_dBFS) + +# Takes +def generate_normalised_file(filename: str): + if (not (isinstance(filename, str) and filename.endswith(".mp3"))): + raise ValueError("Invalid filename given.") + + # Already normalised. + if filename.endswith("-normalised.mp3"): + return filename + + normalised_filename = "{}-normalised.mp3".format(filename.rstrip(".mp3")) + + # The file already exists, short circuit. + if (os.path.exists(normalised_filename)): + return normalised_filename + + sound = AudioSegment.from_file(filename, "mp3") + normalised_sound = effects.normalize(sound) #match_target_amplitude(sound, -10) + + normalised_sound.export(normalised_filename, bitrate="320k", format="mp3") + return normalised_filename + +# Returns either a normalised file path (based on filename), or the original if not available. +def get_normalised_filename_if_available(filename:str): + if (not (isinstance(filename, str) and filename.endswith(".mp3"))): + raise ValueError("Invalid filename given.") + + # Already normalised. + if filename.endswith("-normalised.mp3"): + return filename + + + normalised_filename = "{}-normalised.mp3".format(filename.rstrip(".mp3")) + + # normalised version exists + if (os.path.exists(normalised_filename)): + return normalised_filename + + # Else we've not got a normalised verison, just take original. + return filename + diff --git a/player.py b/player.py index bb42f86..5808c23 100644 --- a/player.py +++ b/player.py @@ -34,8 +34,8 @@ from mutagen.mp3 import MP3 from syncer import sync from threading import Timer -from pydub import AudioSegment, effects # Audio leveling! +from helpers.normalisation import get_normalised_filename_if_available from helpers.myradio_api import MyRadioAPI from helpers.state_manager import StateManager from helpers.logging_manager import LoggingManager @@ -428,6 +428,9 @@ def load(self, weight: int): if not loaded_item.filename: return False + # Swap with a normalised version if it's ready, else returns original. + loaded_item.filename = get_normalised_filename_if_available(loaded_item.filename) + self.state.update("loaded_item", loaded_item) for i in range(len(showplan)): @@ -446,21 +449,7 @@ def load(self, weight: int): try: self.logger.log.info("Loading file: " + str(loaded_item.filename)) - - - - - - def match_target_amplitude(sound, target_dBFS): - change_in_dBFS = target_dBFS - sound.dBFS - return sound.apply_gain(change_in_dBFS) - - sound = AudioSegment.from_file(loaded_item.filename, "mp3") - normalized_sound = effects.normalize(sound) #match_target_amplitude(sound, -10) - normalized_sound.export("{}-normalised.mp3".format(loaded_item.filename), bitrate="320k", format="mp3") - - - mixer.music.load("{}-normalised.mp3".format(loaded_item.filename)) + mixer.music.load(loaded_item.filename) except Exception: # We couldn't load that file. self.logger.log.exception( diff --git a/web_server.py b/web_server.py index 521f6f0..60ab571 100644 --- a/web_server.py +++ b/web_server.py @@ -1,3 +1,4 @@ +from helpers.normalisation import get_normalised_filename_if_available from helpers.myradio_api import MyRadioAPI from sanic import Sanic from sanic.exceptions import NotFound, abort @@ -313,15 +314,24 @@ def json_status(request): async def audio_file(request, type: str, id: int): if type not in ["managed", "track"]: abort(404) - return await file("music-tmp/" + type + "-" + str(id) + ".mp3") + filename = resolve_external_file_path("music-tmp/{}-{}.mp3".format(type,id)) + + # Swap with a normalised version if it's ready, else returns original. + filename = get_normalised_filename_if_available(filename) + + # Send file or 404 + return await file(filename) # Static Files app.static("/favicon.ico", resolve_local_file_path("ui-static/favicon.ico"), name="ui-favicon") app.static("/static", resolve_local_file_path("ui-static"), name="ui-static") + + +dist_directory = resolve_local_file_path("presenter-build") +app.static('/presenter', dist_directory) app.static("/presenter/", resolve_local_file_path("presenter-build/index.html"), strict_slashes=True, name="presenter-index") -app.static("/presenter/", resolve_local_file_path("presenter-build")) # Helper Functions From 55e18eebb9b86ace642e0ca3edbec5653cce97dd Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Mon, 16 Aug 2021 23:50:23 +0100 Subject: [PATCH 010/156] Fix aiohttp requirement --- build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/requirements.txt b/build/requirements.txt index c8da374..1d8b09f 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -3,7 +3,7 @@ pygame==2.0.1 sanic==21.3.4 sanic-Cors==1.0.0 syncer==1.3.0 -aiohttp=3.7.4.post0 +aiohttp==3.7.4.post0 mutagen==1.45.1 sounddevice==0.4.2 setproctitle==1.2.2 From 5e364f0814ba8ee2bcac2630da61d38b46f76c1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 22:50:46 +0000 Subject: [PATCH 011/156] Bump websockets from 8.1 to 9.1 in /build Bumps [websockets](https://github.com/aaugustin/websockets) from 8.1 to 9.1. - [Release notes](https://github.com/aaugustin/websockets/releases) - [Changelog](https://github.com/aaugustin/websockets/blob/9.1/docs/changelog.rst) - [Commits](https://github.com/aaugustin/websockets/compare/8.1...9.1) --- updated-dependencies: - dependency-name: websockets dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/requirements.txt b/build/requirements.txt index 1d8b09f..17487be 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -8,7 +8,7 @@ mutagen==1.45.1 sounddevice==0.4.2 setproctitle==1.2.2 pyttsx3==2.90 -websockets==8.1 +websockets==9.1 typing_extensions==3.10.0.0 pyserial==3.5 requests==2.26.0 From a8b6036227e2a4dfa653c58e16e96e8b9661aafd Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 17 Aug 2021 00:14:18 +0100 Subject: [PATCH 012/156] Include nunito font locally. --- ui-static/vendor/fonts/OFL.txt | 93 +++++++++++++ .../vendor/fonts/nunito-v16-latin-200.woff | Bin 0 -> 22368 bytes .../vendor/fonts/nunito-v16-latin-200.woff2 | Bin 0 -> 18200 bytes .../fonts/nunito-v16-latin-200italic.woff | Bin 0 -> 23280 bytes .../fonts/nunito-v16-latin-200italic.woff2 | Bin 0 -> 18844 bytes .../vendor/fonts/nunito-v16-latin-300.woff | Bin 0 -> 22936 bytes .../vendor/fonts/nunito-v16-latin-300.woff2 | Bin 0 -> 18764 bytes .../fonts/nunito-v16-latin-300italic.woff | Bin 0 -> 24120 bytes .../fonts/nunito-v16-latin-300italic.woff2 | Bin 0 -> 19608 bytes .../vendor/fonts/nunito-v16-latin-600.woff | Bin 0 -> 23412 bytes .../vendor/fonts/nunito-v16-latin-600.woff2 | Bin 0 -> 19248 bytes .../fonts/nunito-v16-latin-600italic.woff | Bin 0 -> 24700 bytes .../fonts/nunito-v16-latin-600italic.woff2 | Bin 0 -> 20356 bytes .../vendor/fonts/nunito-v16-latin-700.woff | Bin 0 -> 23200 bytes .../vendor/fonts/nunito-v16-latin-700.woff2 | Bin 0 -> 19088 bytes .../fonts/nunito-v16-latin-700italic.woff | Bin 0 -> 24576 bytes .../fonts/nunito-v16-latin-700italic.woff2 | Bin 0 -> 20192 bytes .../vendor/fonts/nunito-v16-latin-800.woff | Bin 0 -> 23576 bytes .../vendor/fonts/nunito-v16-latin-800.woff2 | Bin 0 -> 19464 bytes .../fonts/nunito-v16-latin-800italic.woff | Bin 0 -> 24960 bytes .../fonts/nunito-v16-latin-800italic.woff2 | Bin 0 -> 20648 bytes .../vendor/fonts/nunito-v16-latin-900.woff | Bin 0 -> 24024 bytes .../vendor/fonts/nunito-v16-latin-900.woff2 | Bin 0 -> 19796 bytes .../fonts/nunito-v16-latin-900italic.woff | Bin 0 -> 25124 bytes .../fonts/nunito-v16-latin-900italic.woff2 | Bin 0 -> 20792 bytes .../vendor/fonts/nunito-v16-latin-italic.woff | Bin 0 -> 24472 bytes .../fonts/nunito-v16-latin-italic.woff2 | Bin 0 -> 20000 bytes .../fonts/nunito-v16-latin-regular.woff | Bin 0 -> 23124 bytes .../fonts/nunito-v16-latin-regular.woff2 | Bin 0 -> 18972 bytes ui-static/vendor/fonts/nunito.css | 126 ++++++++++++++++++ ui-templates/base.html | 2 +- 31 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 ui-static/vendor/fonts/OFL.txt create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-200.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-200.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-200italic.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-200italic.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-300.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-300.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-300italic.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-300italic.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-600.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-600.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-600italic.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-600italic.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-700.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-700.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-700italic.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-700italic.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-800.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-800.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-800italic.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-800italic.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-900.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-900.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-900italic.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-900italic.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-italic.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-italic.woff2 create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-regular.woff create mode 100644 ui-static/vendor/fonts/nunito-v16-latin-regular.woff2 create mode 100644 ui-static/vendor/fonts/nunito.css diff --git a/ui-static/vendor/fonts/OFL.txt b/ui-static/vendor/fonts/OFL.txt new file mode 100644 index 0000000..4950a58 --- /dev/null +++ b/ui-static/vendor/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014 The Nunito Project Authors (https://github.com/googlefonts/nunito) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/ui-static/vendor/fonts/nunito-v16-latin-200.woff b/ui-static/vendor/fonts/nunito-v16-latin-200.woff new file mode 100644 index 0000000000000000000000000000000000000000..13fb56466d880d99d397acf381e13f3d3c1815b5 GIT binary patch literal 22368 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 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-200.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-200.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..58b6c6182317e831e05dd1c97cc5d1ece0cf82ae GIT binary patch literal 18200 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 P=Y43csqeX6dt`10$gbL& literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-200italic.woff b/ui-static/vendor/fonts/nunito-v16-latin-200italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..b132f8c45efb5c4a39bd0093271f14e0a6e2e355 GIT binary patch literal 23280 zcmZsCRZu29v@Hw_&fxCu?(Q(SySohT4uiY9yL`Co2ZOu2ySqDF{!{niJe*XmN@dmB zot^A-CAB;4CNC}y0t)h7w5~yr{;MkxzuW)A{MY*5MM6|e90UZ^|C^KfhBbKp&o2oD zdF5|z90UY%4Fm*h8lrixPC{8#7z6~m?3*_M0r}Bo*P>J)ugt&<0s;&7ZKw1N1A{a? z4r3cb`)|(RTUYn33-~Q-_Se|enGgg79`#$J{*59$3ix$1dvlv_&icELVGt06s;#NV zV{=2NZ(B6hZw=J{2co&PhuJsB3j)Hc2LfWX4h1XVXklvj?So?0w}$;cU`@h?S$vb< z-28W&O{kvba z|N15T4?lDfhiwgQOuspm?{Q#$>x|ej<<9KwoSeUh@a^-D@3{dVrOYr6wRbfAw$zBKSZWn)@o5Jd2vm!a&qH14o?h-;WQ+)^u}cN6vBHgL5m)V;sc(x9ca z4X-G(w%{?`PHt?=q;&a+bajFYp!}YKMep9Wf1(gV6}qn$O)yc4g5vt$Y<*?zmUMVx zZJD@BCI~&=lY`;XljkQH3o`y*fow3USB98ugLg|@;a^)aZ>=AClHIz8R*6byV$7Uc zAj>p|l$CVav(PVq6C*-d_$^KC)f+xIQ1Km}V?9L%-AbhkU9r2%}aP;&PrB4!bVhxWDR%*Jk*{99z<; z(xmF50+jao?NaQiMB?XNjIY{gvdWB??v{>_UM(X}evXBj6JfVg{ft^2!H{n#O_i=f z-iF`CYTVoXvNHg~NhT3sf7G_K&zKbg2?h%W31$FF>;EPXyKV4213#|~wtN2l6Eq-- z6Cp&@v4;n8Y!!EVd$#|+)LcB2oL$f@uLgVq0Ea|rPvoJ4`uHTyX^cI-`gfWW&bfyv znxaSJUX=ZrXe|<1L%V_`C$QL4dd0u zgv5FZNVg9RQ7ScLN?}^jga==!mB(G%ZWNpK;XomCf-JfvPk5(o>_RVbXvMBN-MY}85O%nByJFE0E+d8!Gmn={j z)HL{(ws3&aCG&Oijko$^ubeZxS`KQ%80KT68=Q8$bmO+C8v?X~gtK3sEPfimy~EM_ zX~qwmdqy1JpXXPh$@m3{qS=kn(jK$(cysEOPeDwFz_WnZ7mxXK-rmym+taMpg7*zZ zzZQ#iXu2i2cPm!DqoC$ta*~F%pb#ECpo7`3zl7{P1>;aNHaQ)YqPyQBBmw05o0a_!dpEenYRTk76)y#0uEsNG>el-3mJV4g)}DO#<_#pagYYufTYR5m>A z63-`Lwwm{ambOK@>-$>c;whLi0OC*SX?FNwzSdl|+-$R^#-ueIYqPc@c8*sB>p*m> z>;_#WDTL$s@Q1}(J^Qh)8x4|sHl)y4o}yz#&(^7s(^$-HID@r-Oh;lb;NB%%1xWb zVIyTjYdnkmD%ljSg@h*@8)hhyu=#vMqX8O>SzE>t@k>>qSSDIa%tmn@2P{OEDkHk! zrK}DS9=!@sYo?nRoz`F}-E?8m{HJk^V)X)t?2SvNXJ_UxjFNL!Q7R_JafnlBBz$Ji zq?D3t;bb&>$NMLSXd7(degS1G=cq2SDt&aqSi$5$b#otEwdzSbrZW&nwVmp84^Af}y)uy@i?`BGm9~QbPh4AE<%|5{Bu-W#4(pOARgk(m!)Gm+%?3EH0-EB zV$!jX52i;}FnNvJ5H=zUToyJ8Wbw>MR%9x&qDAjB@A|uD+Dsm^NKeUAGD#;!Sx&62(!@CWv2aoFJeYF z$}G~uShxH+$-)!n=1DW$-k?vgy+H0KsZAOryRfApWg>jUr-;;nz}>CgTZfZ9?HC*G zW;JlYHVb4}h-e=#*(nPAwd0y>(*o{WmQxQqEG_GrAqpK!k4~bDYp+Ay$z)_via0lx zNwTaf*M73R?@#dHBqedpgCun!)ddxG5jyGdhIy8i%2VcE#+`aV`K+}b6f79GbeQ(bH4RY&y#lWH!@B1Ogh%1ATu zk?83K^1UdwO}G&<(qtuOs-grnbS%>ZO?<9{1Z~fsXv!K$WT-AG4yr0DlBR6Qi~r8_ zZU7v>LwGxnp&|b3{8;ZZY5kW~FW;Net@leiKh8sj3D!kX<*JX;=!8#)A)7Os+EuNy zN)M28%kb}W`FG5W34dCE<#Pve&j~?53{t?seQl)4eSY;kAAZGt*}@?s4m%^0K7C;* z{F)SA9CF45+&zC02(pXTpd$j9%b4jQPc=Ti7^0?};}j!KC<{43Jc=oPeLuHK{ioO7 zZ)BVF-tig1KircOnl=b5rV9?84I(`}mWmPCTxO$8i77jwNm+deSEU;?BaheP8<&R- zDm5K?Zidn@&~%dL&kus5+F!_pI4xz34*+>{8*codC)3V7_p&9MZ>qrXC0gA!7N+KWh>vCYuS4OawWz`^6V^43pA6Sxg8Und1U$en6reNM3O?& z4_W5?RN90jh7`W~jmV7j3<4|L+?l_zj}Xe*Br-%cvA;xq7d;U#>O$K&XeM5Jrh8B< z5$lUOVuR2^3{=w=(U};^1onoKp-f3p+FgNCBCLF>@@>A9_f#F9+MmoYS<13K9(qOU z)s=5V>etfb`VkLWpJ!qO8l)77F;<_NW4(rW2>ZMJMhrWc{!ckVOVhE?$-_=(opPnz z641S7F?t#&fsj$AXUVU1h>3oMgytV3{Rf2HL$|o zd0@ocpDdye3lezq4vp$wUSR}NKDBsjmjpU%dML?EHBV>}OV!4VbWD#yN3CA!zpC=u ztbjTHU*aKRUB76W9Rpx8++`@*bh5Q(tda-W%9Li)Jc2nu1cNQIBr-9hOCK_(1ud0G z9jAj#GZ^!uL?lsuYbTRGRM`knHL3-YVXr8^sb}u4n+(Z=CRz_aP#FDknrh~+@Zu@~X z#G;>Jk;w$5DGjF*IRI@m)wHUx=#b+p?A}<*c{NKF4`#xI$2^$dk7g6d$orG7d1F}^ z;j_`m=050w7pp^Y)tddBduo<)6zF3f`sG6zY@Eqz1l&CuY+Jq60_ymk$}LraY#nbO++HZu5M{ zEQFyWLVoD(7$W`@n43BsVQ<>8u-F6UUE`OZ{X@5$-?do(P<_C&#$sb<2KzZQe1mj8 zD~w1VS#S~d)48N9g%0=MJ9jDRmAI+0k_dWg-&%oGL#O-F4l+4$X_re*ZdJ*MMVSo& ztRlr6$+xYil zfy0@VP0t?Q+w@4_uMHM&U7ZN-w{~btgmpo)Y+qEKF#zZ* z!?Ta7c5W^d_n4%U*Ig{_s~#*Toyi)_=~0!b3?3i$ZP9BYqo}gXT1&;Bv0XijwE&h@`)tA#p47kb23BNL zw=#L};fp9t6&2*MafhS1{NlQpI-tEnf~R4C&BjsLd7hsinEd?1jn&@)AZe1TF;6xZyR~A>(sBKcX-uA;GYeVje=J^`&Z_^(EV(W|W?}Uc{!sH%z`^lV(>P7X6y(T@g^tZmvuE>|^mHbZW zp6;;tcx_yI#w~8I0{Fw118)Xq`-%A(dHad$thMavIR0xM+uY3U%eyGMc(zp5~DeQKY) zZ5RKtUcw@T_^Q~4(3Vv-+nFGI=$}uinI<<3GL@{9vFJ|;S*D9mCl(p_30V*jou4SO zcfThBejubqpCMNFjsEc?jq#+voOME}+wT#0t-ZYl+VJ$chwuX$5%A6mekK>h(%k>m z{$OA+i!l1mj)H!DfgrfAPI&(`z>#SG@@~-5tFjO>NV1k}HfbhnFy(1ZT#mtm#0|x> z*cDz)dMQ@tHtTYOJJ9nQABO{e0Ruyao?B(TqTh3|sut_d)&m8aZ>m{4GF}cjw!Awd z8BcYx|4!{p5&sG=Yl{&bMTGbl31!T=OO$Kyi%4;sZ=aq1AFS)(uX#x9Gq8v3 z>)Tx(LT&F|uyvQIuChc}!XTvXS2+IA?x=uaVcB5P0)e|YeozQ=rdT9wVIGk5kgX$C zG5nuIl*4L+F%)-kXQz!KDRE(?N)H7fI(+I?mdKq7Fc8lO*Ad9OYmqIwM12=uaAW2{ zaWPxxclxcQ`z@PaiUPitH<+EKE={cbPndGVf~yMh{q*so-y%QUy!$0wt4IG?bFt_V9%GQ~jg* z;J2pb&Rk;gNt{zuq(t$l{mRzCh=R80H@sTt`^fEe!iQ6vRJcL`aku@cOJ)5xxQ$xL)-4_gg5aa#5~=Kqnop9bww~K3~Z|Uv8Xm^Q;QX_UxDgk(RsxA z5Ek3ebl$w_=O_4sLD*}^K`PAYVXy&~8tD0yxec_6h}-Q0JT0!coyNZJfJW*Jt<&r6 zo+CSgc1LnI!ou``#V+FL?(5rur7Wz;lH}UTVq}R;rK|MK}KM#0iY17xb25X)C`h5R$mz2HPXBD_3hcsoCEQ~0#_7`V8<2wl_l3} zM)H%-_VEU6G=p~!4y9g=*G|ro9!m;#=l#nZM{Z#!0d=(W@eT9l(v0~q7ABt;6d%*P z?r~n{s8h}ZuU1`svn9BXo-9$)0O4u1bvjksgSk}pP)K(k-1Dv4vc%Mtb>P@YdP3`< z+rA*BNmA{`xl_?VB$DGAfy-L5%f(Xb1+mRm5kYt}i6GGANj@$eyR3Lx#ynziB8j0v zdb;KhK&HgPzh^(6M~t1t6&bzY9VUGZi$=%Wi$*4X4zhz+7_NKpac!WR%rCe3Bn>u5 zVC(mDLwNoe3BphV6U~bXB|=tNMR~-sP&a8Fxdk#-4YJzA0ee05-n=*Q=A5j%@ILrx z-I3nSA<(YPddwx7+RhUv7M{TK^O0xD<;vFDvj*)`Cuz!2tM6d;OyA*hj%GquOZ2t` zITXL^)1w8l6XSvCH_v-(V$MqHq)~k)O2PAlTH`bPaZ>?CjJ6^sz3-@_8uW+1 z0%4&=54|OTncsvL%cR$g4FPG&`f&2;@x(2)`UO032EJ$~G*SnDFH$+d83VBnvxWwE_f<#U3lR@! zj<$X#afyPI0El{^?|u>DVQwC(x=7N|_eBAYGt|5pE_Z{Vd}**=&uYc$ig$-12o!1I z%_4GHsUE@huVID5V@_e&;JnC_T5liKN&kkE#&+EBh?&Z@f`+(;yun{k;m^qCf}Yz{X3ePR?8lR7r-|7gIhi3 zTqrRL>mE8c^p6RjC_kjkrTvZ2${7Z~cT&TRK6VeRz>tgQb8mQDG)6=Q2h@~~JI%qJ zZ@>!%R&FT_d`T2XAg^FrmN~5+Xq#!^*zO3beXn&`M-;47J)|7oQ550JZmLPw^Xpy~ z;-DRe=t*znwJLZNNeJmK4t?i5q0WG~x$Ez2<52IQET%;CT%y_DP=GXZgwsAa0%ivc z-8Lj@^ZUYID@{@UYiLs;rYJo4_|tKu4J~wZ``gDQlP5kq1isC>x8)6#mMl$jNMg8@ zP4^3nlV#`InXz2Aw-JeI_sxqSesRdX*H)5nXdm0RLfek|>W}ZJ7hPoDleArQ6WzVX zuU!UWsH}f^F6xDk$p!IR61V+PK*6ShOKD1is=iM7DT9`*vOC|dB^ds?TGC!(vCJOW z->E990=6(81#pX7)iGsr4MujrQ=Zq;n+0&nFD%k5Rn)+Ccb)ZXr#I7`thfMZgKP4w zo=SKS&1ign=C6nL#w_-P^_9jAH7K41(NU|2$$N-hz$AuiND7Aydzaj~)n`8zqNr~< z+-)`6zvSWz09(&0erz~E{>ttweuf2m#NQNP2}d-W^^RZpvcBSST|05{^{?LY2M#4m>!-JVZ7D- zrnA9nc_A3y0ni`<8ZSxf(ATRdY(s-&?o_7J&lRb^Gk&G(F_11+p_i#n!tcP(%4zS1 zv-H#eR_aRd&f9YXe|Q6wE=46%egkG~yYXa32(a$#cfHuGA?0z`E1N+TT*SbKry>oC z>VT;%1H)g#sz%H3R`EJGlE)9M$BaZ3bT~NZp$6bgf6H!cRC~hN9i`K~O~qXB@pm!$ zx*9z%nk92|PS5~-Ae}ydL$Oyl9a2)PG${o|6^G<&%|X4ND{Vbs+2wF`Q47vKo&e)k zbp&m%&`=ERvJ@-|%i^bYYsPRW}1n;Ij(z1BX##CWqk=^@P zG*Nr~$J}CQt3c2(JN?#4QU8*1a6`xJhl{dW7#U&E!&Bz`Nk*y)tB8n-X2Un#mgS#- zM%cl+2Jq&L@20wh~>*rOV&rL;vT#;RR*3 zMUnPXp+JE`L{s;`tnIdcx$y_$b**J_Yf%Lo9TK<&Yw(i;;1mNt0B7G{9X*l*AZpPm zJ{Jx9w7d51d(j&qB*ESA8g)b|REup2bCM=+wg?rl$)xfM}{)B4}#yJPWiAwdTT{Q=mLQpx2O)Q#Q> z3BdYu5AaTay6VC%gMbJQ>1W|Uw=#GiEKj~Zf~3;T+jKtG=I&9%)sjd(6v=?R71T6u z{wV{LD#QUtaz!o4dhm@0+23|#{Loa6={{)rOr518(r~34!!l(y)rE*HGIU4G=6}36 zZ?Fn^#sYfpj7|8WE(D2#F*Be#e8f0i4cP_T`G?#Z&U+5=HYRw@k zE7Lg^fN-}9dg#`s+g4$bu!9D5ulFNA}LuLO>QBxG-|>KZ=x~T8zTdh3fKN!4DXwn=buN9 znl$A3oRNE#Y+HYZI*txIak~S!z(kTa<`bKZgn5VK*%W9h#o_U&k`|d)8!`B?W)CC3 zo2!Xo1!?m1_0|7fG)$AOkGVLpVaqRO-&q?!Gu<~4+qXoFwy*d0 zN7a@Od9;h}0r}R8sae4C}C^0pgT=D)1NE#W{VXc}F5@!;<{-;*G(eZSzfAh{UnH0*1HwFHoe;ibyYXeF97GQm4=#o6gsz5R|(7x-}i= zd{ZpDZ--;N(3KLGJTd+3K?IO5$v^s;a#RfvcD zP2pIWw7~QbY>_7rwoXpC^-*rON-}BmB9^tKS&8XM!9nQ!j(&0~Wm4$9M1&F2rf%yW zZG;ivk4Mi|Uz;8Cx|}R49aGo)UYCpBdD-ynEA2z2OGmver~KzYYeIy&PZI50={8+$ zr#GjLqQ!x~FPY@r?T_o}7kfa6t?Cd_{-Qf8)@eF-c%n?Nvy+|*ghM61RO`&mkzd8h zfAxE=(u>#Bhi85x4DBg9(^0QTf(+3@s_3gPC~P3YCy|SMig{S{T^_CbOc8Vd zz5p*(9ag55?vZvX7*B0DnFMA$T2Qf)>hx2aKt|sc|7!f!(WoSLdT*MRj^aP+kOxfS zOo{DlxaDhq7guwg*K#$NKI7It_r)#+PYkx7Nl*$umaJ z^#w{6;$I2p3gaQXHED)&^kO_hLcp50MSas^l>ds(NJS9oNsudG;Kt&fjI`pe57Mn1Ma#OM# z*oIAb#IZ+LBRt3Dzb+EXbOfuBlWIDzCc*P1fZ5u3`eIH@@!xl{I~y4gbe%Kjf6S+D9ybDy5uQkqm+M{ z-JpzjZ0LlKNs1Gl%IT9jM8bNUW^gg0k(LbVAfDummWzSKQGmdhCVFo4dfQWL4mITX zXG!s&7txy~Gw^Ya+|{?naK>T?A4q`!6I9`bxmdB0h-DS>+KayPY9#bltX>l=8Ss5< zNQ>fnpK?ScG{OFhU*-8q?uoP5M(9{g1DqVw9{SN}fI8dSXR65}NbmMh#tPxZV0H0r zr=Wj^b3#xXklikYr6hj*p4^wN6s^F@YUZraWjSri3)Xun(KX{DAcVk4v%p?sOYLLm z`Xj}LYsh!-ljbHBscBxhz88QV(930Bh&3QZDopJLEzFZwhT4!gnGrv)RvhU%31ND4 zFcMrl(dbWzcfXk`N%2YK*>tcwv--{>7beD+P%7EgbX0qwa6_M0CEP=U08DTQ#kLzb zh0~K9c+@5&^eR6(!@%;2hK{8%sFG<;;SH|UYb4Gfcb#FJe9cv+;VT+8S_u*gI@;i` zmWIEdd&Bl5drq75WDY2QbogU>9<2bC7+)U*Dw1;_-2>swnO7g>M&VWk_~o}}j2du!>MHuCm$28+bBFor1vA3hamm?n9c_#~~Ag4M(E z9Y#w2xD8OZm#v+cSoPD_Istk^a_OiUjxZ`j)-#FXI?lLH_S1K49uZbkl+qkr;r+M~*xq+9O~H zXIQ^;8BwV*yCca%6~GR%gZOa|{#>u zIcN$NAxRo7#k^rtuV~)hi4_%RDub(hTG9}|AE31hv+`P@C zm?2FwRzg;T%t*@qB=fU0Q0wYBMU6)D-@(GuOX42XJ00!GM0|XpiGFF9Y~()?D$+3M zNHFKA5yQsz5X1xdeYuse1n59d%7-747q?i;5YeF&wbOdzpc#gKxhzn&yBd@(>deCF z*)YywE11CT?pzF%%9Qj>^#!VAstz7Yu=gLI{pm}gR(=?`KRRUc;)Wg4uuzStZa2!uA69u$g3NQoeRwo$fKj zG%Q8KY7L_NxR;y?#f~8y?8*3Ow;%?KYI7r-H#&-o|evNbyULjc=W-Gif zT(2dF$AFYDxsB$^JsJh7libFrD_hz#K~^y{-X^wX_h&HK7*S`fxr}EPoU~VE8Qxf( z`!{<3=ru|Gx|}F|;5&k5r_80rkKGB^0zvq2mkGfyUpZYNwK$iljJqT zg3-2T_!L7i=C&RA;Na^+uzv@&Qda>UzdhhqsoXH^TA6tR#gz`Z16ksnEphi6M3a-? zqMH&?5kN1x6X-cic^NG6QKGjFD1(}oGje&1i$A40T7p<;iW>SYHdsh)g~e}21$9A~ zOC5SEf<{rw?7tVpKndCznFs^~nfn!KS%FM_geefi2q?>t{kwRtBX*bkS+J$BuuGI6 zJ$t{a=sry_8=)=ZxI;G}q7rcp9=e5c3b5ED!B*nSfuUqSsycmQFh`6q#8Htenzc7e#16?3E!DdBFAUmcidp=I|3h^TLNF*Vr_2l9+#Kc zrhAD3O=UDr-FKJaAR^sI^eyo4ILor@U%5gWhiSGcfTYR2wQF_De&F=hpixkmr$zC4 zS8Wt{elsbLN5*8Cc4D?6%dxU4wnA$E(dZO-G6SoZH`0q{LKcS`!_sWO^~dCRys?+- zy7lY+nQIPr>rwp~n0ER4idEtNJ}=aE-vZq zZ6wE*y>$wv#oxB~a+Yyzf&ox({N&DC)Q8s<4`{xfZ9iz?;#Npr>0J|tB(av{&ij%k z`*fHDQU(&u$%zQGcorcljQ)~1naiH~#@$bgIi6IQCV1$KA5tbb^+lxYYDf-|B03%J z{Z(B$l%ggqt&^W6Hyqovz6rw32eBx)Sw^2<B7;elgs!G%9)Y}+1X77+sEE=se(nttfDA)Lo+W^-l$MdhDdlvb|_S6=V=CXLnXS>YkX3p3n7 zGryQ)`%LilQ1j8wc?}Ta4iilwopOxj$ZnX4yTbNV+DSX)iu+!YI_;ipWTQ)l^W&^= zjz{AJ-0$w~kD6I|=H*bi@9txbI7JoG7_q3NeiyqOlrb~LC-&NR?BFuc`~k67rGdM8!%JO zR~H#{&RNz<@Ua=d_HMU)7em%14sR*IR9SOniLq=0U*H^Qg=I;FTYbJzyfxWIR=Vw$ zBQ>VhF%|BGc$z01=Y$%21OyBaX}cC=pONG??GA*Hm>%4aL0nvY8E_v)G7Jb zud&(&bLy;QxiDdBaQn&a^Ih}aPB_2vV|D?NIDYKI{hM8!py7)5&gy?Po$*FhTfW&Z zKJM}k0t22A27EI0?^PDkhK+v_Me7Dp@0@-MrlDNJ{9SVj0?DNrdZ7_EYf?3g{Q0b% zueyr*(5m2gK@*XqJN?sQINMSgYP~8_7x?CNNbcl?4X&D^in%SrX%HMqU{xb9zgfq@ z;aZG3^?kvh*q9IUmHerNJv>4EZ$rrzy>AE6N+)OA!#HEF1w#51?oKxMyj*ea&++7a z#H*cWv=*fdk0koH{E%U0X! zBaA4vLVc**dIT9G;sAM(O`)5r{S;K+r`N9{~y zA*7O*O*8%gKwTQyo=iNkQ5Iy`WPP(&qw6CHW)SWh9ZDVO{Sc3Z(TuP=Nhn^m58K-v z3(Z^L@lRJcHd(kGlueEmY%SkrSl-6xIB2J`+YefkRyS7~y7liT?xXOm?N%4tE*kYS zFLHG*gLsa8aW{Zo7M<=}pC2TB($c|5L5ztsBW#=3%$tN+{*3OJnJmE?R9e&?f&9Wd zw;^X*Ed1s~p`((gX>0#*{LZ-)DIy24xi9dnXda6n)i{{hEZrWHL4!f zr;OhAB-uma_6XT9tq|15X;0l`jIYV&F!cUF^&=l-A+8z`Ev zcU48*5epqep62$I*hnecg$B6FtG;n=r#QQ05)CI%iqK)a#u)mr8|_!Dku#dKAUmUp zsL5O@XagoB_Oq&ex%L}2t)C$w8nm%je1RaP+eBJUzdv{ji!%ME;oXZX3w0($DHOE9 z%9s@ARMtcnDoLlMM9pDd;A6AeNJ|&VP5D^s@tmrp3l~08PN6OH7q6!S&@(KKu?FJs z#~Kkpok>sX64#ns)@&M5^3wv4uXFV(E4F6SR}!o+ZDgC#RugQT(Qbo2_e{`!eBb+a zb4Dt;@1u&D(Jl|~cR`{r3FkBn-A(>_w&{Bfq;mM8cLHU|y3ORD8N$#9DAVmzkl>O- zX;Q{$&A8_yBBr5JN3yHqNG4OVk@m7D&xS9CEM`#i;K%%dssERNhdrEAv(A050iB?G z^8)|jgbBgy@lU$&r3%v%y8;D7!m0z3$yPd%q*3?^Tu`1}06K2Zv8qUK92=i@Un?V2 zwY<2R@v~zwr5)n+>A*Ds!qP5wsQhwfc-*zR3h0)tTVE0g!mjHz*mb$8UT+uf5PIXZ z^hw`*93q9`(}JC0eT@Gz3)_yE_V3zbdh#FnR9%&izjJS=(qofpJJOe{D0WW2Xj|d^ zkVNl|=6@)X3dCGX5K0C9**{H242FL9aC{&;sHk;yNzwtPd^{Fs>RdqLRGX{oH;*hp zJ3eSF-i>i!xkcyzVf|8cd)*$h-N3s=Km29#u2nUBh&IklrpT#?;%I zDtz`&FV0W@eEJE2^_(^@2>gS8hSs`b{Lr|2??On)A&ms)X4U9>c8ldw8e4%|^j<6^ z5J_Q<5lN!6;Z9#uutHfyeJ%*tOGpYx-gBeZd0?zdO$O=V3LTsQGj;s9-dT*C>}$Gr z+LkooK7O<#+lT;^RRdDKf965;C<+*Dd+AM3ZriZa{PZ`p-?zvB?N*(Iwi8KB)O)8? zxo*%yJS*zyb!<(ta-o$c_klnw&@>8Quz1EX@LbU)f#~pisSg7l~Vdk;T$kF5`2vyvoa%9m?W}_VAA* zCTjU#r6|7!cG4E-aY%VP2|-nsYbv^6eu2SiDXN;*;1Rl83Cq|et-Eg{pv zp@5yxPGrgwV&``U;tm1X_YiQa!emn3+FhEdwg>}gcs`ha4j#4=#$b6e$|Bmv+Wn6n zmdz4Hgp5?OpElUugu|76hkf-RoeL|o>xPf;Zuq}F=_c2lODI~Z3?1DmX}qv;Pq$80 z6AJfE1~+pu&J9F>)FDkK8Q^WV6khF5tifA~e|Jv>s%-x@zzv{t!ybhnICVxEm0!wg z1lBVl)it5pNkhwU!)i>OjpUF{OfaG74Vj>yN%;#olzB_OnZOKoyuPBwPECc^O&%_G z{eAxzA2jt(>oo#qoxlQD@(`Wnq5)5wk%fCYF2l<5vZ7`-sa9z#48%rEK%^#sojG(> z7_A_QA^89@FFSdcL|^CxD>Npz%^qYm`mv{UdvDk+K1XU3`J&t*+gzfA%#GH$na;W8 z>hxS=d5L{%rM)5Y{VdJST;8G&>lxycSV{?J1fe$DzKZ$h5yLK;g4dB1 zg;=fhw=5}7GHGiac%)6ZQO;^U)ve_3S=upUU!U6?0KkjfrF$Pk9M(wi{sg7bRQO2} zG@lXB7cKQ{MUT2)4NcQopy!rNeh2V@$AEKC4>i?Os{zJ1G>8$yq{ac)gKsUm7-^uF z_Y#>x`PMCUdzLGV%x1y`3#~J{vyewo+?or4Y-?F^?4+lX@4QAlT(aoGR{_=j=Na#; zL0$Asn&~^Vxf)RES-1O}meN?ONHO$ux+pQ5{FGQFvv1*`J@rYOD~^AJm}yO~_ef?h z!;9?DD&=>A;`hb=2sZUzymRr#^NuqhQ7Mu4`>m}Oz z-cuBx!n@f?+h45K(EgJ8dFjeThZUl2rq+?PTUeP|nC&E2x>0(v zPK=~Q$za&mm~)6}yfQ{0&CQHXLs|;*i9fk{gHhu0BCpZOtc98tGEc#drz723#cL&k zNe1ZZ84;le(I!M06;4v};%@G$mPdY5)3Zzkaee7OelfFchAF~o!>)UGas+&ITG}a) zJR^vakk{2R8*ov14~T+96OtljHW@i+wZFm{lqcw!vQ%&6r1hCIYeJ=DgC~v5n&EP1 zU`Yns(%^xD(37kf>T7!@QsK?2NoG_0%_y&_8|IDcL#Z2_!-8!kO6bchTJ-Ct5~)r( z1KEq-&-9fe90TVxle#70fXBaigVEAjjQ8gVsEV_=jne{ZQG<9?eacK3@`>O%G_m9< z#C&kSxJw2Yf43E`iw^y)c?TsfW_HfXdpxIDv9Q4LM2-9$RT`%)SQ&8IG_hR>mLHgmKUvVCLxzHUfPb~Uyzt=>3VJSf8RWLMT+*AgUDx&3D8n;GBR^(Ze=-O`+10KVjE24^Nb5 zoPQNpC&fayH6;@{L2| zjl+WHho^vN!1@)%tTqHO@J=PXL8H{BZZ%vz(zlubR-KX`I&!GJh(se!ZxFIb{j7kZ zsPlM${l_ZLE67Rq<8G=2L!Z0+o&Qr0elz}7 z0c!$DUv_h0O@b?$7z{dupuPNvwn8uEV`Uo0Fz=&GPQ)12QCEWsVlU z41L^3C!$x=eBB**pz)t8olu~U{(?fXTq{`}!{wf>5>Vov&-IJFBURHp(c{HS~ zgysJQI~v60TbEj94uworHl^waK4Ksi`}+mX!Lz6njiD z77rsqaGzu2c{Z^;o#GHj0HF5(y8F-EvsCbRcMbM;jdhQar_36vB?^IqK(5R^%$O}( znk5f}d=f}KPpQUJEZ2A@MJIjH9SjbKNg3O)-!EFyVC`HcGh5p)Sz<6W7_F|34hDmy zy}x!Qli5)>6#P|gv8!t_*R$B&y{LqOBX#qc?0jt@7?eOBj@6L+Wst~E>*rfq=Ie%t zXDbawt81de!SVc|zP>}fz)pFDfFO4-p5VICFgo+WM&LP^DKSg}dC0Z??J;bER}1Hk{j}w?t2dGGBa&f zOGyhyin4>O?*UITPG18Sudge#HFPEYR&aK1bTPvn3~Bn3*piS;QQJPdeYnqORV|DU>}r8y zA(n$UkIxV*ZvyQt?D7NW5dko!87>K^eSb;@tN^GOP9Z@k=h0OaEos(w<14=9qNKF;SC+L{;QUQ1LPPDn#HF8U;Ie=j-A z-Z~e4drdg}u%#=HwZWLC=!z;TDy03A_PQfS8{?WKsP)lTzkz%& z{*@vs8Uj=xp4-ca%+8^y!j>wszOc5or`g&v&rM*Sn_6Dd?#|Y1rl~PeS3?(6*Vt1; zIml^FH9^@nb3bf&qd<8PMfU?4z~o{dS0%6fsUSPO{qfRk4?YeJs-|jM@n8PZy-H41 zoDhCU7A5U!dVp3ReG9=SVM;Uf;`iM4_ZkRDZ1JmD-OO(j92ciq-XhgA?j zv-tXNTH){3VMVq0I{L95)OhsUe*0UB7X6yv{ytlyK2UKBbRzj8R;$1v86-|1UZDFD zk~u33Um}01-)0p#y{H3sU=i6w^LripfvSEgh@7WJ7{8?ssfHl`@O_eGDv$p_GX&*_ z@0Vpmecym0nevxEuNb2Cxi1hkB!B6PvMH#aXYXKaCkT0wicD0GMhn9Tgz|%gB4D0w z395pZ*v62+!+Unx7L5i~$E4sD~o-??ZIvKPi8s`mzb#j|btk1ji7I`8kg}oM=4%k=HA;0V(9z@Lq6&v{~EVf*}v5`E~Z}VglV!Jm0UU;H-mN+Qz z4kOqu=r;e&Z-}NY{tw0Hml*G*@osFeK71nSgZ!M7Nj1_kLY^n<1^-w)Yy{zVx*xp7 zEWFGMfmZV4fcPUpw}fwfU9h}#=00-hVmzFn*-pmA)CCX$i6rKUdA7?>BT*EHv=W58 zup<@)R978M#Nz9DTKTg)ExOo1#NwlbrN0+t+#US};qEA?t0kzejK0TjKd5NVi2=vn z?)G;$1BX#9nqq#+AmTaTcsPf!2YE0$hd2Zt4p!(F&k}MHb{-lFiA7mTmR1_pLapd{ zFeK7e(jhEB$d~?cc|XYpnGr#--Lpwi6K$rFMDioH|8*rUx4VNP^`M#nQNYfpw?cC z%YiWL6=bLqG|4Z#lvh+;xVQK((mf#^KBov~aoFn%!u`eFI-(rtY_$MYpJc(WXp4*APi)3W8r_4vuTw#W^i|s7Hq7xbkd(R|M};x{Xwy11^%Skidqy#wznP_ zBZvLtJ(CR>=Q5=eeC{F_1f4T={J4&Z_PS#C^!8h~vqR(1>B9KX(5cb!q2fn3ZAP@J zb@ecJ1GCW<(2GA*Z(ufYyPf`h{;h4gkyy%u= zSDbeE$J;+~YWV6mA3po=a@+2{_Fi1ID?7cb@Az=%gjey+sp>urljN=)loJhZCdoco*^wFY^9?Ij|8z1a+)-qF6%Og@{Zr8|BdHAAz!_9~Q; zi{)0q+D=-8eqw9}A?fBqCb;qNVm!^J&OPxCi24~kvto<7BUEkyapn# zJ)Ux1A&&`@x$kCGJglTga0Ev=AJSD(7ylW9+tE3d%pD#6N)>+(XBym{FSv_{3HYbX z*6i?+TIKj?Snpk zL*-q?Lv8tszI~3mw`@^3_SLf8pOH2B_|Tymjd*DJlikzlRO`Op!R7|J|43iw-Z*WJ z&ZJ3uvTgSb=|jE#*4;T-*XE1HK2aSZqC{&+jip<=s(TxDWRo+IXid*T zTeMoSB-OI%m3(#gwmps0v2Y!^fc*^vbsc8XvUZlz)-ik}Ryaulo|Fz;$ETjgzX)_na4LQ9BX6`zc&mX&UcJ|KYe17@P*)7-a z>*&~b{g#Q+dB@>PFFl;x8j<;+H`W{7zI*rfXkSbV@^Wmels|UYO!;@TpN5X5>nEHa zlJio>6*nC_c6BT)g+$p59Ne{VENCgRA%$bevkspzqR&?tLWjFRlwd~Cuty}g;Bys- z$r3NK_oRCy@swHuOl5#66@YD9zm7sb5lc15*MWDNspb#mC3FS95 znnn+O9eE=0AHvpRMqV&Fxm@dOBbwhQbgS9g*=vs~afkjSTlM2i1mayaE5M zRiSrVf46HF)isk4=18RpilHnz_hI&`To!Fo=o{xTfCzUZkAMUZI;ZfQ>_O-*L#`ZSk{!n4)c$~W(-#NC1Nn|CW->C8EA z-mTyGPDc1}h#Tg7Ug64bD?a)$6yiERBzkrJSQl6PZHVi?%dnu^u%17_LD+`71HR%9 zAgf!&TSGPs6wj+ZfZ`X(=QBRjXBJ-)q<9>H2jP1BCFCQB^fB&ft*Du$+~Jna67Hat zv$pAv1!7L+tiuhV4f){8M@)W85+jX;bb6$*aU`A1<+7Q49v)1RmO`?z(3Bo+O!u|4 z^wGcneKFWS6XlJsPNKhh&fBjCF82PGoWb48neE$P^mk@pLt6TlS zpu@Mbew_-8O37jV>2(uz1K`Srr$1CW?_#s5t2z9Y>(j2gXIG`i(*u&AWWC@U0m3*>~=a2}Z z%W-5(=CwKJORAIAUnYNeC6+4vPxD4k;nQfCzU}7Xb8#2l?bpG{WKXJ2F>@e)D27vLDs5kqw@CQ*JD%(RP>HDiucv z&K9xn<+Y$^_8K85i)RUpK?(OL8kXMt774uSBdQprtMM>Skp5R$rPqHf1vabrqGMO) z@GB)iRom!j8~saUGKqLLixY*`)&l*jYpJhqp?_qKm*6?{Df|SgM$L?ZT7(eYQY#2B zh9#%42=LBCI?+JOL>Sy9pV$ee+ND=!v|{EjzaYSC-HqXRRdp~NS9l-&+7M1SU-3>H z=*mayqp3syuYD|`eG_^FzqNvEE*i6PX3muhdK1S?`@Cu6oBWoa@yy#+$IuaEpgMFJ z?dyXPz#gh^45Dvp5$Ex6SD{j&>!lUE$ur)J6>7*G+}#(wVW|*c?@6o1~b-Cx}V-z4iGq6<#THLGLLi46Ak!|x-?x$ovW&kLcXJv?JWj4x>^_ZZu&vWM(dj+)tQduzuPJu2@LlmG`W)D6z} zGTDa_T_W~8kj-s2 z1~@U2;Y!^wL)2!w{4&y=bhRANRIj*4P(tEaSx!- zMQU(LcAZm$lOhkZQsdU$P2QoM;17&+c1@|AKgjzJ;`6(=`7sXl7e)sH7+X{GWEH49 zdlB~plOG-bHQB+&-CSkaF z-}G4sivO_s8>q!ULIJ`l3P2|-L>|Gwn8k-dJTIBD)ER*1`@1Alk-PlG8v|bYYNIWL zC~I8x1hg&Ig+E&V6I>(C>Hq)$0RR911DV^Q-nNKe4?Oh%4+H1`0002n7p2tz0002n z;)nPCg8u9UDg+V$0ssa80ssI20001Z+GAj3U||0IZ$1M9tNg#Ff6c5~%r6+`F(3g( z4gj&?2YUc`+I`LgtX)AE1mOAi)VBL<`(4zwZQHhO+ctyRwjI>Axt*LmE6JCc^~x87 zs0&D)663snsusbj7NfT5CajcG*_a9CgGF6JE%hE2yowQKCW*Jctg3NeR@jXyD5@U7 zW_piW$w`$3HB|@L$YRb-Hp5S$#N zA=82YIgKzO_E1$AK`J9N0SSS>vcXGv!yh-|DHx|4xUI*6gk#AGt5FVtJQHjxfY9U^ zlb9A{ixMj3`LFwYvn=l^0XO9gdqVO9#f@U9DZh|f*khbRPPG>n+065xvXm3%Sc;&M zltVSu09B|Zl_oz5pz>6gI#Uq&$q=|uDau8eDIXOe7mB28l!>ZScPdFXDof>~=G#zF zvL@VvrMjXq7u}7nCL6B9jS57k8e}H%jjbpxTlEL_3Mv!o$Pv`yAM*(G2?2@%`iRAj!{Mfr_Rt`iJpC|6@J{@0aGLAvBERAv8=Tp%^*h4T_PY)I)!q zqi=xe40#Yc-)}`OQ8H5oDnZ@J8pRTo-jpM%XC*sTBwt0w9JDjGqncWPFy)74Dx>~x z1fqm#G|EXAGzI@*{^tu^$XWcbTfRtkKyRS$)RQz#q%G7gs;`hQI72hJ-;ueIF3`BB z-W$+s^>{${)x-2=U0T=v_Xzk8Nowwd0001Z+DySifaE{`0KnKDt24Hw-rpB2*CS2rYyz!T{lhNQouI0pdd`BxOonrGe5|X{NMPW@VE+U;d#~QPwKYb@_Cw zbbs}Q^p*8Z^j-DC^t%n5p`2m3VX|Sq;i@sev9j^H@v-r}@wchIX|Cy}*&DIgt54H?jYuh$Eus5}zaD*K-9mhZrWCcY)WzYz81Ovc$Fb}K+d%$UM3%mqB zRG`MyJZc%Wy*f?3>@+z8&L++!F51=7HO{rm&Aa=#=eoDM?|Mj2+B4Df(Hr*G_YU@s z_HOpx_R+qiFPE>Fuad8iZ;5Y%Z;$W3AM;E8%>Nz>G!Fa-4i3Hu)e7|t?F!uqbK!d7 zsp0M6+mTdcSY%7&Z8UfERBT9GjF*XDPc%;=$q~s5$*(Ciqw4?w z0RR925ddBQ2LN9H1pop7Bmi0fmH+?%fe)X9=k=Hij$x?O;kVd=C;}ocaM^iB5W}8 z^5RL`2FL0x+G(N--%r0g=WV0X+cO$}J>&NqE8eYNV+VgO`v)rCzCjhU?7G$%Ilu3! zfMk?N(CW$_Tbr%ZeU0whW=CE7&o@6u{4w599W$zq$xTG%W5=NGp7ZmJF>?nw8eu4S z+H8XZY#aapMc=R8UX815+p}%kwvpLhG~2dqW@elWHNzrSA25OX+Q!deqzPmILU=9{ zo-&Nn4Cf4+2_=jXg!7jOM)Hgoyd;t+qKV-ZuX)2;Vu>T31QHoV67P7=Xg;uovz#ND zVp2#YjdU{j$S20|nM|_CCWrsz%9Iw2WjqrY$0R1QkCwDzGOhVX8>TRg=}e_9?PyO2 zzA%GX%$6B5=_qqL(U~rEWe(k#%X}6vkNqs0`ZItc zZZJ?5vg983WhHBNvj-Pkal;)CJn_OCAB?h*t?Xnk2RX_~206<`u5y#RJme`adCNye z`N~iJ3Q!=osG^!03^?M8A15*6AVD19A*-;*0%!6Sge`UqVhDK@;G|$KaFJ#_;xU)F z%oVP3oDao^Y8#Ap}JI(zMFj7YXlmi11zW(vy3ymF~uvTfj+G9Q1IDXnX^+uzw z*IInGwUbEY9qd>TT1E61j*eN6P#e`QYbP}^qX0rDREO~Dy-hDnyT&v|92Ll5#qRt z#Z^IJy*2QHGcfGd2yGkePaN&^RKqKIl=;$3j^)7ZP?g@~2T73&oa6M}We~D2D+CGn z|JJTZ0ov3>G!J8rLfac{TDC6=AAnv}Ow-a$?I0YE*2~{ukNv3}$-dj7GEu&L5s!WB S9E^lM(Z+?=Pj#0tR{#L7sUf2P literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-200italic.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-200italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..343c03a404e18f838424ce991f232aee8738e020 GIT binary patch literal 18844 zcmV(}K+wN;Pew8T0RR9107;wx5dZ)H0ITEx07*9h0RR9100000000000000000000 z0000QY#Xys9ECmxU;u>z2!SLCpDhsx3W3)!fwv+Hgd_j~HUcCAh%y8q1%zS;iAoHC zRvUzv7mA&KA==yyOq1LCd!9@;7v1e_w^m~gX9k6h1K6DXx3d5Lzb7|jh-{&;Bid5gffHIu3rlbt?1ZS(zq2Nq_Zm^lT^*k)ugqZSc~i%M%O z;0ZeR^k(*7P_$ixo>gFlWf5cnokBEV+78e*b55lCC=(D)vJ@p#By2Dmv(}+&U8VlJ z`hVxXV)@&D`_=m{Dr~W;XAvYwh<|0w+uOsOQktMak!6-aCKO(|M%0^z0W=8!h3oGgJf9-#K-}bsq~|f7TYtQCOlwTWue>~Y`JLC^?%pkK_YUO%P{4tpD1f9$ zh#CVR)ks-Jp)~;ILRwBMog!RF%5s5Qr&D@=_43O6{qp1G?>CcYCh+`ouWH_TS?gx_ z()mCGn1Dto?SAd1P)@Ane+u3dtbhfp&aJ^P6s#veQc$k{@AQ>g$H_)HK`kpPE?|ol zB7fZ{-){}OV=NXxEkCE(1Hcl33TR;os%5j&v%4SFgD{-qjl;^2k_;MNNGwhx424GJ z$A0#qpf+#X)qW`C*IRz>Re5)rn;>#c5vDFe$fe`E|HSUN+yDRnnY!v{ZQAF(99fc# zk&$F%r1SKDkFn)2*8SU5kdQpuceVJN-xYgepJoRPidYLxlT@}ZNf49 zq)L;cNwZdM+6}YJT00$d*il3#9&HhTL_iWC1&|6z1Ed2o0NH>XKqa6SFaofHo z5#^XDDt2(NhnGXN9HHeHEvDZW2EtGn2@7?u3a^B3hkn=cFMS!vP)5T+iSe2Jc%LK0 zQ;c}@4)HBD;9q#)+PTo|i@d&#&hlf^%zbX%VLyN^Rq#p5Rs2G}^0)1ke)ctO&eltI zAsM&wS{%MX`K@HBB|$o}6g-phdVh+E%SI)Y-3CoA=I5>3v8gt#<;HPd6Nt(hX$h$4 z0jSn0d%DcRu&8>OaxmV%k~}g`>N+|fL88gV`E5ibHm@@w*RIZa*v=YRDQVh!R1*uj zY?Dh#I67-BIcF)nTBX!BY8B~R6|ZKt2x$ED3#HS&@Hz)O6m5PI!6)bB(7r5}n;m$` z2I$0$BsH_q9%ipe5dfNq(}K-)KsusSl{>u7V9}OV50Q_cfLyqEq7E07@t{>0KkVu( zK{(iN?)A+x7>7acy3S@~0Tuu!qFvgw9uEERc$%!Eu5bl~Ui4KFK4U81qhL|=zO`g}HJE2BLn+CnLY&e8Xz439#lC@Y=pAvw?Yj>d{;d*vt zii49@2<`9tRo9E9$9zoB}g1=7#)zMmXKvX(a>waIh8fM6V`&~ z2t@$`Hqw}*0QzBMfOts$p^Wnpg8zjJ>?Wh0+Q23CG~bE1B1N{*L7|f_t4bqEdK1AJ zCphNejXy;OZRrtk>)h{7&Bm`egh@>8$USNl&w=^z&AEB5jIoT{Kv7W*fh$5*;(ZJL zw(?8t4&G$&Pl37kC^g#IYDTg<%l01liOZ0-zo2FEPexjn0zK=wzG=pFgJ27(T~XkreU*5Z3yohln}ofsIyvrfqa8Wt-Mx~V4(#k* z*Ey&`M&87rfvbc+LU#mYOl+1>^PPbqe%DTZ4Ui`Bods!dqp%nI;5)7)^PvQS9H_? z*?llUv+tnNzbC+q2P$^9$2o63K@q}W_UgZN!aW8qf zafej{*(p5P!`Z_w25`-EUeLddRyXspqqM|+!R~AKs`9!@nq5QP{wy#7hQYD_N(ekY zx2|d{jr_V7(1K72( zY#JkM#z*+PKEd)8E5kQ71ozb^LFT@E$bzER7oh0M>$D+PEZ<2BXQ4oJ7$i9w9 zA@H78g*LW|&1`9RJaK%O%rPM}yl-p;&Mh6|#?69L+ z9Cuy2x89oMy$>e)?1!lc;s@TngJH)2lKq*}`q77t#%$=AdIDF(@YN5YO^qJq2WZsh zL-q7c7~?|+i_$$3px9nlC@F^ro72KqQ^CGmnlnu*TxbA5CBHqT4t2J0R4v8;?q1`c zX$WY2^$odmFEO`(tObtM&V5t$H z^eK5LGiyo65#<8qh8p*$GMK^h zopkiw4|;#)^c(IqMoi{ofH}B7m4n^U0TCQTr<410hy#TJ4?s{1r8vevoAup!!k<<7 z%B9+7cwMcR)Z>eiUA=1@B_?ry=kV@>xQqj8jJ|JAe+O5zI4X?WrSwN zh6D{6Ns_|yElQOQ2BC)tI~3s! z<8Z3FyEYYZLJhq*)jX);J-~b`$}D9!u^~DkFqCYPVW=ErH@>o3h{JZs+gpGJ2Bk+2 z;c}>E55oVDM&diQ*!sDM2lPQRXP5rwa(=017N$@c3HCDm4gc?)xnUFt*E2sqD}ddZ z;1A?20OVIs0XPhR0&MEBT>{wFFJci6%Lt&(o!D>+;#sIid%h3*c#BHTC+EkRa2A}9 zv*Wxu2^YaNb6c%4p^i{rp#T8tI36Tc#XgLo^20&p2!2exu zUc31G!t-qbc>c_I$IVt~@!a{@)u)%Ao&x~@K7d5`fxOCmVF2pbp7A$duoVal1#+`?LeEAWPIBLIR z-Z&i~K!ixqV#Q04C`qPlIdbJGP@zh-8g=S5Y1X1uhr@n4;+31Wlew?c1HXKtz-J>} z^4bYs!oxQ&t+F}OBs(66#}}nXwzZJh%plytk|(;!-1_Q z?D_EI#haM3S|I`j2@@<-xF`d~>2H9HKdyhpV&p4Ps7RS&PnBvUHAsUtmD)Kf*R9J1 z7oBm=d1o1Rrt=usW8uz9et~$pJMKQ~`*RyH=!DbWp6o2c#H*nPL(S5gxv~d)J1h-+ z=@TREwg08(%h;H#3n1Xm4}!?EDF&{d;cwo0_GVdnibs;a`-p_}KT4OBt$sGvf+s-j zcTiz;6$De+QH4OQ+6o65|8ip|W?aJ{B}P|dtZ`+&l?V?eLdg*}X^dV@I%#QPbWAtS zS%ghj9-rMgCkWMbgF4zor{L0m30+-VgaIvZkzsm7dQjI#^Noy!|0+0N^0r&jeh%6J ze$QLwa0q+vJQkY#hH-OwG%t+9(aXruQ;>qzr7=rZ-$6NPi*FHu|K`b&ROwDp( ztpqM)tv>s6Qp}AoR{&A917)mD-mjuhTK5w3TcbAIv=tQ;Y1!i)%}FY%fSI@zKAo*i zId_5T4!jmsUx6B$iACm>sY;n=b83u|-?R=TfzCUCX9vFMXFpt~Xz1hw2K#sYv;C8Z zv79`3)FBj%G>3tKI1c8HS`7N+B)C#Jh1IR#gbFTnyx1;`9^L$jiCkArtzI$SEHec} zdy(_}K!qc!pfMGInhbntPL&&u%3 zu^H!_@nC3nn3tih$ft4kQm}b-6pZ&ipdk^ANCKLY1EZ3HmSkW|@?cyFU_y#uQcAt6 zP}k3i&z>E4l^VWf3JSGrBP;gn^=s*Rf1!TA*%W4IMiU=k{^w*By)ex*GsDa>bId%m zz$`LL%rdjWcw{#qYrzUO3nO!xG`i&toHzmbM!asd1vVp_sq5H=$f&v}Gn$sPs84>9 z8^gg4JX42EyJ;(#4)BBh~yVzUKg;9;_LyKbKHw3^7 z8@g2Xbll9h$^m3eMye+OJqqumL)53%Dvt2NkkzDHAESs&T#?$jz%ym&N&e)(AGsv{ zv#GdSyX%fr$B*B%g(mkQcZ9C+W!P0TQn1M$>L0KRdz#0$+N^^9X1!}nW{WiI=V-iMH$D6F^#>pi6nzOR=JAg$)uuO zAkkr3GD^lF5=OGXZliQuk*O??KqM`sS49jLO(c+l>7rgmOO^?QA$aBnQ<%6VD4}ah zU+;e&C7TKi0Sm+)3_deVG@(vLr<2|KTGrGW*u-M?lL$pAf|`5@C*jmF?Lo ziAZOy4R;K@(w>i2k)R;LF^Eb}F$yu@?Ik-W2%)CL-OS=^4e2pQ!E_W2rEG}midc^7 z%Gy790cP1>HBY=z3FN`2}^y2-S5dqC_aQ!3Fr<_=?x`>%&N}_w~Gv6;}rBh(8yPct~}h>6&7h;vu{;@hgTsqq6Qvj^;W^lVlJzZrtYC|&nmy4oC8^NV&>k~I544a`601}; z<&cE(N@qqC*-kdv$swUFxMIz^vj`h5#xbE_olpR(N6nFX0a|T)ih^xxHgg<&m-Id2<%T}L zy`@%sg|&EDG#4bAnJC1bPR40U?NrznaeNsnmbMoN0@)z*`HE$i00{V5?UD&VNH$HoWZgBzLMK!s;mn#qCd8J{tKQTpJsF_~2oGcQI$K~%PB}KD4>~4x{ktXHXOxJA`&!nK!vOGxK;+PnJqU*nfd=|spQ=X6$wlXZ z-BjR3VY1P4QqCG>G8qpW37YPPiI6-e!b@07V?#sc@-gZMy_AspKe{v$PVkQwby3 zk4T<-RUq4?=2lCKv>u(?@70dFk;l+TBble$U zt8>_9{6u~&O`~KSAA{9h9E9OVaGx!2AOh*#L(hW{0hV;EyWoJ)LBUn&wP?tOX_^x-LZq#* zR+DD32e~fuN!-hZ<}#TBY^NKh2w^6NubMdAoIBP(Qlxc;qp7Ux7LWG~lsQ+X8Vsu* zsYEp!hG>w0?4xl9t5Ud=k)BnE7loyz$5SG7VT_O6DQCICbK*&6SR3%E;LBAT;-ae< z7U?O-Mj|Z$8$7~j2@(%lDU7>Tx-tk1hP|{PlWIoaRc_V*Q#g#l!)OdWPM01* zUaoH8Z&|xQM={nUS9*d{@@pa1{cLN1F?m#($pu~@PuhBg2zF+V$^e#e{hu-v+X%^* zf|6pi2ysesp+YYLw#P$hS9rw)em(kwIm+(iP;=>|lwmzaItm@9z>N$!iAPfNT~}BK zkOCcmcBW9tjL|z7(8(6<43R=2+*Fn0*bLU|}>t%+q#>*Quw zpIq{NM~PzD3C0E&)_lvJiUs_d>)lD?i~Ey9?)czj%8LNgDFnyQK#-AS^~fEYPXW?N zM19yr<0M~+He#v{MC!T-WfY5_oDJqPi@x}r#r04p$j4(AWpdV`Y5y-$qSnTY`#a%e z-Z<+sGVE-bh-sC)$zEE@mifO~aAjm-IMkNEs;@{d>XOH-CBfPt=G8tSoZcdg)belT zLltKP(OUMLF@~pfzZcc@9x2Cbu5XrgD!0;}eHGK-RcAdWDG{0tYUw0%UrnUUw^&6ulF}jn#}UqoprKe3TXi(W#AEFhDwLNgOREr~x~y;8-Kdv|GIx&O_$~MJG*i3zcB49kH{Y3aSa#m6IWz-XZ`UBhq@1VnN zBw37iWEeq=ZDcQ%91KHB2qwbVI-07I#DxZ;E4ZPQr$lv%msjHI916wI*~J0LsVmv=ef-w) zOAE-Sdrg|rVSg$R}NL!gZ7gadX`(Uwr$G3=rVgQv(4rafqmLZ zKqXw%%|%*IELiOz%?A|g9JjeXP7RRM)co~$*S-U<`K>iKh|KU&ZoWkPSag`;2(sxz zbBB(wQk}z*juXMpOWRCs##0aO#aFqyY++5yxJz;tU7*t=Imw+onhGP)cr`2dk2|G} z5l*(Rl0pOs?V{<2qkZ7B*>QNt3fnBoVCg3N1Ri7hYZq`(qE;lT;FITv!y^kSXKsi) z`cEn=aIwrl8IFhfT5ULZp}=cp@Z*mmv38r0gd$OF{kgp;g2sJ;=0X(I3tyaV_MK|h+w^A&LEQ?z@N}^eta4*T+2kZwdN0lW zqAwI7a)P&}g`P~WgLERT^JM#6h<&q^cQTq(o*sT1Qrv0%Ur0au5SDWmc+M|-GisVXtz}(3nJlEt{Y^-7yR&JHPK?zimBfm3@ohXs3TdZvsm7dcnrYYT!c><#XbO)EAXz(}bmD8z2NzhY{p0{@>U(;0XMK3dYL(P_$pt?r1s8zN;J(Md}gr z4J2mZ%wenBer^v2wHR=x%1msqgu-n($Rd%s4I3JCxe~c0WKPQ=76BW4hS8I~4MXJa zmV}8XbE<;jpSPlM!AJhAi8Ey$2$9=*ltqB8S2y<6cCtu>{(9qJ8*EJjwcRXkJ#=Df z->x$LfoSh4L-=^1m4*;lySCmk*S>mWyd&Yfv&k94oz@p>6{ha6@@5py+Pa=2ei<{o zX=QN$qWDdFyS}@;s$u2G$Oi&COBH^%uBocr6S0PF2)z(M|INr`5a0U@!RfqD+0D+m z`%9294=3*hD~0tR+;>jt=xpD&UFsiLlWw-RKTPD_dvA8b$=AEU2LT+-``P4II@@a_ zWy!_}ybSS#|Na~eoABSueXQWD2xM#OKdESp2Am;h<+Cad)V`Ma=Q*YJ1|`X`ly~`T zVxu`ssHK>&L65)CRNf`E7L9Tap0jIi?rYA`Xe+o%o3v=(crB%t91Ry086%wn@Lvev zXjuPO)Np37E%IOEW;WXiT{|)b>@MCF1B(<-62}uG-Q6oXiDRY{pS})EXG!a)*0I?T za`fU9e-4QYogW$)+kr>#Xxp&&1S=gvbd2tI(O_p8z1;;plX!x&ucvsd6Nl-p2ppUw zk{rDq1-+AeE8_C3AaF%Hz8VeRpq>?=Bp1-g6SFk^lb*hbA=_g`*``DJe?Z7wnfYn| zcn{7|9x2E!zJR(J*F`?LgT-cIhhIi?&X92EuVBv-V)u}pvjBg_!P`*K&U}*ZS!LK| zk0Q(ps|~bae|uX8{gM+V31aR#beS-VO(KC?w?=uVKu4b3-ZLSsY9ZX_C@f0T+&5Lc z%k85~ZaMt?7^Bzz6IU7>L1R|)2oy(dL{&t(%Xn5I?EwfZ!D0-7tG?KIw@KG|M;}xO zOSXiJ)SyB9(HYR0t2FhjS5~zUZY?lZIRUCIS2@vTY;tJfhbt+_xE2-bB267NKGnLm zpoe3KS_QqG8wfdGK6O2|l0a$E@6h_1zA7c7Q5UL|8o3V2n!oAt4V_cz=7}9ZOyqM^ zmSjA0@&-+L_7M*i`~ezn=Jm$>+6uK!s#)qSgd5M7NmT;AlzxF~VnTVOAW%G%wAeIJ ziEMePYPEZ<=E^tAuXKs&s~caFefyefS=Xri^>1{aZ{?p-9Zm^f>D}6jYNeT@jwzH4 zPIpPbp(<-{wI&T(+u4!uYS(rK1CA0?R_o@>gXl&?d89MOvy!Ne`?62FQxsGRLt8^7 zloF#v8-V~0^Q@bI0jTac(pwYrrak@Ptls$_$J0q`+Ex(I6zYU!o~`_0QCrO#TW@7N zR^iH&Tfz>c8<(7`Q0TQ`i8xlE&T7#6%S46R3Ts~}$6ai^zB2HwF=`QX8r^}Q(-1Sh z4x^{H+XS)Bk_&UMm&^3QU>sY=r?3IQ!6#9t*FN%grp)))f_Z3>){S&Um^k|kXHA>LxaDa`EU7P^%eI_1M+6bJhk`Pt z%BGrJRi@E<+%?#Gb7m!hd>UonkiDu79jmZ;b%Nb1IG7=b?F`$KY%Wz8K4uX=4;&^LRNwdbHUezzAk5L#rrMjqIw^Zcgoz8cKhulU1)`%Eyud-s7T3l{>EWv{yzOl43l5fex*`f@-r`{r3Xmlp4ADmw) z2yOM2P)ZDvZ%!+;Woh05;Hc|Q3Yvs0XkajfCqY)RY10A;%=17V&mmkMF`kHu>c@^If3{UtOu)PC-_xg8kH+VPY}!xHOR z2m+N+a#6enjU1tSj=y!{B!EO_`i{JHE`O)iR(!lTMMj_mftk0DPx1Gldz;?=WX$cO z_hFdr6VG3JDZB@U=^ecJ=9IUNespYl-TfP~Bl$fG==$;Sw!KPzWB9=tfW7giOpJ5R(WHLAmRm8I&oK@*=zydUWO(IreEH(N@$T-ZG z@cv+9$X`_92)Fgu7cUo(A70Eab1DE@f_j`v}XEC6~KinHH2inrMYIL{p{%~igrL|dnsM8NN z_kaGz>CMdppS^k7b#U|0%w)WF)b}Ef^5VY4qe$36M&rhH+li&)l~V|a@5syF4vknf z&LXR%k_B1PQWhTR;^`uF5xZKDaK|3}`L+`CCV&7`H<0t-yh?NPivqe@UIL0e%EF+_ z?c)9k_b9@k%q`b=tOjX4Yt1>e>fEE<`8rLICv-_H!A1kVL=#jR^firg-3kZ^1a=#! zIEZ)Cq0gr=6o>o6{^Jm*>}A7VF`a5WRltI(lDQ8KsI<4P*5)g-sY->K z$Qd|%7Ov}JlPf5`QCwol^2$7kG@P%D*c`@Whu$KBni6_tkFPTCw0Cu%+pnw_eZDzj zd%VzI=jn}G9!?vi*aw1ET}&p6X{?1EEd~py3#-JHY7uC)bA{T7N?f54o{7cOerYyX*nXLTWK;>bcvmXm7u~Qb4Hs?ShvBW zvsvOpQtC>5C8+c$3u6XXJQ*t0M@^(y^FUd7aa`|j8i|z|YKyGjLX~x*-!?a@By81| z71{L3W`oI~1G@be3Tu#eo`cIZ2Cqv&s^$AFV(|(+Qr(=-aSF2?mUnCCv8o8Q5Y zkZ@$cC3l;^*!X;kD&Im8Kuv2Q2@3!`kc5M@SdQ;8Nlj^(U7QTl&X?ejM=zVnY}IFs zWre;+*N;3!!jk~OOe)hscw|3`dSh}t8+;O;GMMOiemwMo?_rpec{=5zu(UImEmV&B z1_H_$`75QV&HpbwOC@oT&N+YxDN}w)nnMveJd8l-(n^qCCT?Bs+mF0ma4a>)OxSxs zPCZxe$&i=|F~D6g^E-KXJq)@Hk@W(Tcn|yY?rG|@yfag5(J_#Gvz=`^pIYoJS!Pdl z2*+X`W9FXFIYr(=&IeR|un8FvTXNV+gBg$DnvI+jl0pL-g5gMB#z3<4-c-a>Tfc35 zS?xRfTQU|p!TzOtTi*UuollvG(pk_fO}mp3b5E}Q`Q5;;~CkXfN^&0 zt0{w{MT|`~2-H^Euqb&^gQ9@0KXpR*qMlYjt%qC90gzs{hLo^okke-*K z9)A4~Kbbx%jgKx{+>+D1h2~7fg>m>&9A1IPDd`2_18md5xylboSiAzq7G}>8Hy<3G z9-MhsFZ}ufYkrvb(}UGBSe)fQ(97^t{F63JKiaD$;oj1;C;Z00X*w2H^cE<2VoPV{?}T{LRULmo&5OnW0?($N`oS)UnUX- zf-;4qL?{i&r4oNooc-|NpKn^b*QI6<2^s5Btvzr4+5Bg@p|7~F4Oai)jffuBOCWA- z>3;LeW>|S~uV4JFJQoC*Eb+y0;(5gM z6`WZtGKtVP*V{kR^#C_Xe#X`OVdgK#9mM&}4x<8xxPnW*- zV)_t*_7&_7I>Rmej7j@v2zEXLQ|&`LlK~kmp$bM6uVU+2MA(H)Om#5*-~DFQ64asb z@g#O2CiFNfV;Rp}SpqHl1W_s;Q_Y&v67cV@VEFk0x_|ozKmM=-Lytf3W%Bu+jIRz2 z1)A{8#hn>`p)kXL*E7OFbAh3xRqz8g!R%;;>go%PgK3d^0v0xli0jToFPEqS z@39ZITV2Xl@t4zmp$m>+jzAyN=^C(x1;lVVbQbyeS=g2S<3EkOnC4N{1*KwMkU_p1 zYM3pk<3yOKYf!`zA}-=qlX0+j5DCg&Om%Zjt+I;!(S-)<`Fn1fcn@WgvVRsB3(RYV zOsE4;gxR+x4MOJ9&o3f}ps-u;C-B_|*<YX0MWLrteXV1N7MJe0;f_FD_^9(gmhmn}z`ZMSY z>WHKsR8M0|5U9@W@ zVu8D)D&*4i)*6ds7U7H2Dl%Z_r_jmOHf{8?DjvoKmxtr%WS+nyP!x}U85{|DZ>S;bF;}L5GF6&1T!1hqHBx#%8a)b zRE~Gr8x$@M*9eMg-;Jt!3%mkOzFV^?OQ{W3i?p&ZEn`7xU)tjaK%B;rZ$k0`M>vn` za&5@z?#RtpHt&Cz$1kscHtr0St;@Cwe);0_d8egkp`JCWL_)R3<5jIr#8<1l-d8vt zUytJTsMf?2vw2vSen~VY>9<-T9F0Q6V%;lI5W8c%?bR(qwY4qX)f_v0!v+)Z(#bBm zD^w9G2_;dyz+hEE3tF`+Of;5Q3}&Dymgt6U&~eDd08^n|IpFhr>4`_E1ZjXwP(6#CW_nI+BG}!Ih@d)&0vZR#Uhch#m&~= zvu1O1XIaeau9@rFje%u`OR797ZT<#<3hXv~^!{KrHGdp0F+|1zls``Yb#d{J>$t0G5xDnG>0L|FxydrLy1s%1=21wVP&M-Mu}!J$86m zdiMWK+8xo(opY-%&!)Hj(}YV7b#LB`Kp}6omToDV&S@!INkjdv|1^~1pg%4n>b{rB zZHDtXKjPoQ+A93n^xsYSMMx2s0lbmkT*~)i4obAAvXcKQW}r^$OdZs*ONAe^c*<7o@1V}qL5cQJ z8}JSQZ1BadVLgDxX68P6#Jeyl;D!B;i%`qeJ?cL7ALXwc>HqJO9UHyY6Kc7-N9mK3 zyZ$y?Em!xbdzC&rFDp5~F36+Sm(+4~kGfC!3+g$Eu+|%aTCVO<`s~)k(8z$)VU7#O&iWmo<89SBjO4HGI5>0t!(8r7i zmVp?+9MK6N*~89_g_39APBJBBdu@{od^n&F4qm7~g|a==gbj&Pz7eV0dN(cL3EA$o zj}N}ko~gD-S+VDjmGPNjoz7?8%6!*`lF}2}Lc^|8UZBr3C2m0k(uJeB*=-neg9~~- zPx8rP5tv;e&_DfizqQgE+A7Be&!~a^2T7F-oi-tp>znCb>~gsOfL&LywcREm295U| zX-u;Aj8L4sujfcYX#hr=eK4cZv9_lqm{WkI=1vI%;T9-9w-Yf&OXrtUSQ$f+BKg?b z%zrZwD^^bmV#Z{y)F;-^npfTnhS#*po)si7jXBIxW|Ff{TXrYSXJFxrt;*JJF{_p- z_FI;!<5v4@I{IvTT&?xC3XE?;o>u1iHtfi?uFy2Yx}WS|sI3s-1nvPs$`gq2PD$Z4 z5WFcIT+MuAwJnjtLEQt$v&)VNaE`8sVT)=-9t;C8;8T6|gO~i~jaFHplL^A4)4GA) z>P8>q6`O|9=>sBy#qb~$AphVJnc=>ptWnuHpAb#-ckur3QYPVtz1w<#`89c#K!YohNW;${uDC9{9{Qhw8nx;IzeZ@E)9vIEQ-` z)tDIbEklWzgE=E9<>wJmv=N%Ft_4Mz1>!4npp~0Zy8Ou+wqD}Ha7O`_0q4Jvso!!qKHRRG+lj-=Ci9$b$>14*_)(SSy8 zx&AjeY3$0xQ+D{q&7=u+JdU(DI#Q-Sbf=yI zKIg@jl!r5@7ZCvQy&?)1N7)aNOuVP%p|mvZr~MA_;`2}MKfixI?QZ+qa$e?H z5_pbT-HdlU>AA#0sx4D9(VXkM*##c6gMj963~6 zVGBH}tq^hpb?#1N>ZBC?FL|d@96NK7MU=0Ba6;EhQ_uSJXnX7FX?F6j!s^NP$RN%H z^<^?bPjX=nz;qJkJU4YvaePZLyGE*3Kt>EA7%%fo$CtxuA7+kswNeUXcLFA6Wd0d1 zi-#M${<3lNZJQzQVbKxEmX<>p_~{ubPXaWZ`nln%G%2 zHQ@NLdRjltruDK}7WSxZW@P^&EEf6PB6XXwh!2@F@ch{Trww)wL-sQz^^%?(JCE9b zpa$LrqXYjYRa`f{l)bCAZE>Z9O$v z?+Kg`hlb)F5$G9w-(W>nj(|HDTSphLk;ZI}AbZa&%qms82SSXPO1siQKQP!Ee&KpWQv943G0;}AXPVxV%2-Pup;Q3Od z24NtHo)=aD)2SAY@%Qk4atpi`R+}nfwxQ(-^}1s)Q8f+752=hXEuNHWhX%iUkU1d9 z3Z?vdNwHU>dZk>qjdNkg^TaftCb2KE6xbR{rJ2`=4!DuV0D2AY5`*p=;R5bV%oZAN zD@kB}300q>gy3D^h~7A`INq>Hnw4fc z-8Ki_cXsAX@_uG91Jk1O1F(b4&KQdwUan%!>QQRKKJ1Ig(Gy2J-Po~EOx2C%co7X9E@iD)d6~#O97$Z!SYQr&dKW6G zsnv1B0o#l5Zn7%`Z=!~h!z5Ba<73S3*w1nMDt)j&uOQOKBqz0dy96MX7Nj0RdBPE&v zTAs0+B#$X_}7+x_XGuSJLkJUFe**Yv z_s@DLLAX5+3e=1WhMalCLC{|XGI!4DxVND((Up!{(GgP4q3DgtI3-gwU%tlFmj42wlhQbx57M!gjzhO9&y`-Gz> zBqf3cjTj^bPdyKB@^FY&0nTWzL>#YDir$MXElNFSVvk{=a+sAiIU#?^*(26K%3`Vg$p~ zT5U&LD}p;zb}my4_sQo5%K%M^?_I z)LQ*}p5|o0{HGL1ozw3Jg2WFPK${3N^ym{l*Z%ya@?<%^C`*Vo(0D$UZi-DFBW;` zE>yApBc1_m36l0MkRs~iMj#|82_~SAuGE&8ho_%1i@~r78(H+tFpkLv)bbQUcmrdr zJ(&x8>-luj6e5WV4=KI*=5PQ9B~SrA+OC{6;TI~Ylty}ztz9q_t`Hn;`P>QnFN2dX84N^JbQiVsoD)ap5}Bm? zms!?mL?MT4O;VhAhX_RwKh(-l!T0(0x`%*Hq6K!Y{J@5E+74=p{-d zynumEQ0cwiXFTecvA?ZKkEz+&$@adxN92rv{ek-5@GZ$B^}W13m~^(q`6rxU=@{dAukriU`;LF-?E70(3W zcpdB;F3lMx!0Fi`H&a(gzIgt@ee3qf>Z@EVpf>dq$|zJKj^WtWn18@@p)Cr759WIa z8C8OA%mzTZ$w2|}Rts*IYuf^}Z>PgzR=?A{6LYJwD>Y#d26KHNE&lXBZy-c=d`UD& zQ0aG&P0`XhMW|MOE9a|cKf|jpzx77@bUrSo^`m)6Q3~;w3sVH{VFs$3!N5nThgbA7 zb4h_;A$T92>@g7E%;2wEZSnuUAVTl3`z-hX=yqEks6xy0?&mHv_v7d& zWRn$pbH{7gG|&UR*OS85va@@gorV_lm&kJDXO#>>TQyWn23Bj6GmeZ{dbUQ@$(vKk zFBCz?%B1QpbH_2yn5es2G)1DwVrn?Hj5>5%#8{J|Sd^4B!0 z4?iYc_q`tmP<#{s5U6Jt;cfN+M{6;Cx!n&jSx&lm^)}wI;kYv2Y%y^UNl|T`#7|kb zI^s*G67{a+ek1Zx1$}Hm^6!nshcv>%e|QA;T@pG~-%CE(LIX*yBoQL~yr&a4hO0|> zZ|aY|Eta+jSKqd&XsAT>!xT)dxMUIS%IJHX#xZ8~1SGNvDd=+cP%buNqpjdB@Hz?{KZPdMaaJ(_H>@<`2Q6 zNK)i@mf@mbpxXtaT;ST2_OTM>81JS~T%yD`(*RN=oC`P6($w(iTrx%|j2?AH2PQ6l zv{h&Gq!d%#6@(>*}XD{eySRE3Z>@qvgq9_Uz_{Kazqy7QP)K6Z%jLOvD9ubQIw zxC^1(^HgJoAgUWNRWPu0GLJj%kyT+RYh_iVVm$8LL^s+R-Jvx$(_?yuWw};ze>P&XsHXvzx4*o0{p2 zJ1wg6ujkSWt_Z1eMddN2Q(o0Yg2Gmw+=!0Q%k0+%y1qS@D&xg5Ush~7Ni`|?FvXx^ z3b(wHwcHpsp4nZ3C^K#QI74Dd2hn>8A;Zvvd6Cu;7(p_Q_{sMdwnB|C2rADo4_3Eq? zDT-9Ik75iebGkun9Z>6D-<8f17A*#bSK)NwcF8&nG1OqI46|zmzaobmoRrEmy#920 zgsfVq*Y~r-^1>%dIO$ZBi3*lk?qSTDtF&I}|H^?d1ysN=0sk4$2C#nBuSGc zPmwZJYSV?A=DsH$c<5KLx2`F^d_klmL~L%J>w|4EzpQP%yiIBK~1 zi@H-FdW%J-96pFFeH3>GLJrE^Au?nxN~8~5ba_zZcF_3vQP>#dia|g@)&#^55MYo6 z1zA%N5D-J>X+LT`4k2J|dGGwodQ1?7AV1k#&#RSlT3hw~!@i8eg7}d!)*0+EDC? zOv_fg*NiFCdlF`)52lNL0P%;JWz)g(<#Fag{##*6%;|FI9Mru*%KXYuYz jxA;n4k^{eFqh{rmzw-ZY^z+|Is`wA~j>iix00000DiVI& literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-300.woff b/ui-static/vendor/fonts/nunito-v16-latin-300.woff new file mode 100644 index 0000000000000000000000000000000000000000..f3915d59021dc4d263a5538c240553acd8ded89b GIT binary patch literal 22936 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%0D=##00aO6 zc-nQ4HC6;b5Csc&VnQ<99TGgjHAdIn_d0tFPQZzXsbF2+SatXRW56f@2Iw0c11eDE zK4z#?xu0o%syx6dzf~S=(hTv+UzLZM>ASA-G~0b&oRK6&nG9iqgvc?=ChKglEj^q0 zcD*G}LPd_G(Y@*<5i6gl=frAGWzu}scwWxgQbvcY*@AjisQX}5SCTkpN!o(M_$DAF z)MU*knoe72JNvqR&quzcyzY0;-C~tpyf;TW?i?3Vvv;M*`>NNw8ha|U&2FQdEx@~8 zeS}mcB;Q3r#VY%bO~;PD6@6cP8!z-ct@<0)zsT~5ziph8SINpT*QD8?^*b)P{s;aU zP7444c-myaLsTUI0EW@;&#sr<%eGx(+qP}vjO|&CZQC}_7`4u>ZS7C@Vgdlomod^B zG5{gGl?ksI!$rn&iJgQJ#yG+$C4%w1;T`XZB#LNa_`pX#@tIiSh$n$WCXmDzzA}+- z?BX(4NT!k$Qb{A748HS&N&F;}EV9X=oLrgGj>$}A8dI3TbdJ!T4$Pz@WprW|bC}C) zI@5)&bmJHESinM=F`w=-rw2XhMQ;|-hs7*q8A~|Ia<0;sD*Dl%0c>I*gIK{TRIW*jGoV?1XA_E_LdzJjpDjuDI^j{=+&%r&ml zh8Mi#1~<9IZO(FzM?A(H8%!0VP=zU+wLHX95v*gsB5|Oa8tz~ukO1~DniCW%3M+!K zW+Us_!e+LzM$w94BqfSfoZ^+BL?tO%DN0qE(%DX1w(*-H{_vM#Whhfw%2tkYm8X0a zsIa9eve;@M2%;zs@vQiZxbJXx*~WT`q&YL)oHpd=fmBsw_3kTsBRhv<0|APcBjFtk zuFA0%@!icyN+P{HUGI7&tMx{wFaeW{R5XrlS&U*=l(h0bU&&n3Ia0uXdXIA7G@81SkgvB7FVh!xtJmJYlWUc(uoRv~m2j zb?S{qU$3?JY-=Zx$~)MxAhe3;FB~1S9-%g>UDi%&VnzXkPR6O(Z$aW-o%Jx~WE`z_ znxyqkmamn?$U=T`iY(t_%cJ#G(_S@)w6(}u=`~ubhSIj~UT<#x0P5yD#Q*>Rc-ms{ z-obDJ(32c!AP_`R7(O%Y-JNQS1Sup^i3ABm*BS{)C2Rod0eXZW7V24cL@KP_teUFs z1^;&zp%LP^ip5nyVZAl*f-^Ae)(CAI>`xr+^i;zud6fCmOpfKi?NF88;P3<5Yj@HZHV2}N&9Lc`h bqB2pweG!j+>>P}QKGDX7)=zbpFjoKoyAGt8 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-300.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-300.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..c06ead49993b16c10ed53ea2c66d754132ac8dd6 GIT binary patch literal 18764 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 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-300italic.woff b/ui-static/vendor/fonts/nunito-v16-latin-300italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..c53445bc686c8182e8f43155bd4cf7d8f00fccb5 GIT binary patch literal 24120 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 Uee4{Jgg(*6h1O4XmoQfV0HyBDS^xk5 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-300italic.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-300italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..41699367282688fa2c416f6d33b0fdff709849fa GIT binary patch literal 19608 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 bqB2pweG!j+>>P}QKGDX7)=zbpFjoKoxtYHY literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-600.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-600.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..1b7b39bbec682e4ed7678625a44a596e36738fb4 GIT binary patch literal 19248 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 X+Zp}8S@)4^&MLpkZ1pV500000bv!AK literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-600italic.woff b/ui-static/vendor/fonts/nunito-v16-latin-600italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..c718176c8e7ef9750a9885b749ea4ae5d7ae6883 GIT binary patch literal 24700 zcmYg%18`EnCa`WGMRquAy>aM+N?Omr2+Q(g9 zObh@7_(=vn0HpuQy3rr|f0_TR|C_``M8yCApnxAv<_9*Q`C#AT3i8T7+#~=1MFRlf zDjg!7?usj`3IPDnFF(8q007)=-=g$aUYVW=0D#r`@l*PNfe|&Hwy~|D!w*;cqpSPT z1xd)71sc1#5CQ=3KYjr99~9!dAkdjPnA`qvod5vDC;)&^wLSB6Vs7aChV4IKUBjeX{E$D~e|7lv0}@C>2o4Kd z7mpwACnhij007Daxlbl-V{iPU(@gu}Nd5zjqJ)gCp~sI#`{!B!g#Q6p2XWHQ(AM;a z+xV#m`j1YD1x4n;!QR;g0ML2(v8R9HjZ>r;CptKp{`l$-{`fWjUuTHjrdK5lzXQ)Aw&u(yH}iC%-t~FzSy=*Z8)Kvp2Ca^hDpp?TQtfgt27L@*P7Uf95t}M&y+Bt03`Fbq+4;?x^s%-nlSGc&WJ&XDvJ zPX|xl!zaQl zA73a}lh^vY@Y=_Z-96*a^=x>4`Cf$s{B8%C4>Y=^-+Pkq*ivm0>tMR36c%lH0CLWShy;{!pQSpG`kd^ z4J$;!V82O2KhuqKqS515wMdz;OeQO|fy_v4Q(Ow=+=O#sTd<$BfOd=g42ZPENg&_Z zFOAo8!LV~UA6-9$g~2ud$s-##w(<28TLNQL^v5)|GJ0<9s3YGWnTAq+jL0i&uJn>oYJ6(>tH^2V-PsgIX2CKYN<-ooBAd^1(-);UQ%88*x|WvCECz z9@}HCh8hya{-NKeBT^Up&{z92AA<6OC9{r+jzP`qe6%U35szyI6K)NVle0!CY>%;F zb49fPiL>OroHx!SxkgrwIZrod?xuqd!E)S)=#1XH!4Q#bMlr1Tbq|UdOUI}|I`5Jl zoNNAblgYi)4HUK~pE&Y;gBzAcn9i}sNTmMl(pC6%wP=su*Rkqm8~R3)@-EiT(C2uX zqwILN@+tMNjAtonO7QQGm`t9m*+g|BvZkwNENC}+^2sjPcCwmGy*3&CIpV+6M3OtI zT=`tJsKh46rW+pRK%MDj#WW-oZKqCx)xw33+~^zfs?pTe4t�^_pH>SC(!SGv57v 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 PKGDX7)=zbpFjoKo`Y9p+ literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-600italic.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-600italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..121cdac02a974ee1711805fa446d0bebbe7f42ef GIT binary patch literal 20356 zcmV)RK(oJhPew8T0RR9108fMf5dZ)H0J5Y208bzQ0RR9100000000000000000000 z0000QY#X>z9ECmxU;u>z2!SLCpDhsx3W48zfxSfwgd_j~HUcCAh%y8q1%y%uiAoHC zRvQ_fHEf$k!GrSvMCjiBsYfBOaS()oGbh|wX147d??LFAuGcJ5^Rh6-mx#N@2_w|ykvtV@L!RF|&*=&_3y4L+fq5nkC z5h)ixX=L%}ERD#N7x_yjbNCmL|Ett#gv}D(9qQZK7PfKNvHB$Mp#FRE3|LN&v*Qt4*xlOT(xH#(NsKhJOT&wW9KK?{#aK`>?{ zW`jCiSxa?)y2woXZv!y5O2;G@$s&u`niY$L5gR>XBgz87%D%v;*V8M{xKz)(R{zYp zyHDqnGtvssD*!UcfS+{)RHo>dnkFyRaVV-WvS;so6dIL^2h>E0C6I8DQBf91c_3wu zB7e`^xt9+lNQkeH6PruR>nROsFd#%Fi7LWlB)vOajW9_j-gKb?L@hFbBv z&;=OViPtXUrULl)dvzrjRl*94GGRYEKnFPX?k4QpkR~XdeOuI9)CyGfmyR2-8dT_f zwY{e8ZT3^qZwV2VQ+kff&hS~{a;zBF;ksf-AG?lyF+{)00Dk|zOYfbzGw=QPKbsYi zLr9AP7^eUV)gG!fPAsaNYvrz1X#(8E2iSrZhGEOCrxwX?0sZ#!K=R-Z|Non=zVzKH z)kvag!_aev?zQ@}d~4ID*SggCAgg-!m0rE~s)SS}fshalV)zFH{aX^_Uy zOdxMEUG@tYMVkc{?ckv`39KTRjb+vmj~pgF3=%}z*FJZ*sv`E`9@St|6e1x65eXqk zkj%CJ{oBv{_n>Y{pTwXb0#drH*;jtI+_DU(Gr1|Xwbal+Ldy0_0mB1U04JoOLz1OR z6_To0t5ns-Nj1m>sRkR8=8r!R28@6Nf*EX*R0I?*rfm!u01<(={aA#;H*Csq0qpUB z+A4rOE$v+cFa*GnZr3l?)&LOHG6GgGtoULj5YCB4Huc>Z?`kGf)A&Zt@#3D{!Zt%( z-rD2%6vT&BG7lfWfS{1Dh;BXl^cyhAkabpDZ-dRY+wY*mPCDf@qzH&m0=S$*u6!N3MJ>ZUqVzE74VzVb^Uu?GoSsIW&);oS|{nIp+sI z00000000000000003HcDJ3A{Y>$2KHPT049ZVO~XR_|}$QhxfrCXuQ?>%qT7=fOsO z0|7?8mDGTSL&;cF#$VPB*k-BHgr3*B%ol4(Cq05E%m#L@z~5~uPtOTsx)~%_sMGcs zCab9Fj1SJ9TD>4Y(2d)_Gq633RX#zRyz)d~c$Nw%(&N?Yp-pQMYPX5Lp2!I@B-d>i z0f-?(f?G{f!BBHqdjPv7s1UY6GUN)QAPxhZ6T)bW#0Q;Ws&kFQY8$le5U;g>TK#jF zb3iobeE-@{c?|YY;Fj@D4=Uy3lelryTWe)&O=nfO`D;~eqZB%U>6oj|E|U|NS$fH- zNO*a6+GB5`FC(S@!{d^zv><JJObzffW8tah1P?2&8uyB9g!_AM5~aMCLZ@f zKp;c3tw$}I>6^sp{y)Yt}+9BBKWImJLeP(WmW|?ck@*34Y*%VCn zfvZ;22*`%4PO8}M$<10fFDJH79VDj2g4W`p{(&v65>dZgZ8-rD2@{Q>m%DkrDVE-} z39u``NVK6BVg9bIm;*!`iVX}pciLiJS?7(kjxGcD0_ZkVQ|znX<-^*DkwIuSCQtCdRTNVe5hBRe8dRLbr^mVt4#PE1t)QMmGw*rUe3mb?@+ z`7l6|shwQ1sj5%{N>N}fT%!dy;$9Hw3Q`CfH9b>i%gt|r`m#TQiKf;mUnGO@6trB5r6@0^%o-e(0uc9if?NpVwBP4 zk#i7A(tXx2Xbw%KRX(O2O6b0xBs^1#Pn6rtwi2*ZzGo%g6V8%E;U;QD(UepZVGl=> zkD?AS;(9(sTH+>P$Rw!LP5MmMaMub-UK*G8N*f%2QqPH1^q}flWmJ%LBOw^BCm|4O zSzUz2ik$Tw#8~iz?#K2Cs@h473$HEu^aRI+W?q@X|KOQUTH16$d?9(flud!yj7w4! z8IfeBL9MoSg*!@54iXOhTbn5==whAM+Cc6SL8_ z9~@m_@yb1(R{KGD19H={jCxziTZdINJn;6Q;HL2!-=2MZZzaPuN4w3HU>U%3sj4G7 zu!{nhHPr`qF2_?~Wq+Fc0oKhtfd_FzLoo+n7vJJDp>v!lJ&ZP^q}3vPN40)%4?Ovom`B>KTmngj%zPcvp9NiCD)qQ-mNV3d6N2J>7L<$lpMgQ-I&$Gxk>be#$fwfHs#vnEK;VQXhw15Sd~)dke=GcOSf!3RUKzlDl@@LlQmMzjMD zSq<#(?LqYb($;7~PHo&J3`YYa90(+zVqD`s1_ay$8rn`#5^YAwLSAt^Ph&0r!wmet zE!Z2df|kNRJr0&5Ua8bn8ZXeALJfuo!mSYxDxt6u(u1E{43vi{u3`JT7hdi%@+jlV zGuO@n(wW*HhAx&jXwKj3k3Ov{*IB%q=3TY zXALKTA6GE)uCyYUA%zLWRlV?tu>s^gA-G?(4+SQOwP8dLBVcIC;X1EO01Z4Un8Fit zAf{-yJM!a!!%hews$Z`Ks~})<$QcEK79SDtVeJ|KMDchQEHv?~gXZwmCINm7j3QEm4Zp-o_Z(tq0@YF> z3o`?~#qZe$OlJkgk>HYCSki@tS6;~{3vZm&=(4jE&X17}4yWPE6~kcAYv^aFKmM(V zBYkUuQSkL^T@)BDXCzP%dNVJbf;1`h9})-{9K9N*wI}k?Nud4G8RySd^#<{inx565 z{9p8Tfa0g!x|Blc)1Kv3PECX8j)^$24If+<6FX~@6oXrlEXQ(XG*qy4c9PJrLI^{pQC!0jFibBB586pgB9{*U zNGuIbMZ6$!hX*S+Skexg^rlrgpiWEdXdtPDh1*m4mObeZFW8BZCV_!;jqagRo_fE* z)lI{q`g8P5Hm9zig48qDvdbC_CA&N#hK6JCo#&b)O^H-U)(E;1Or8_+V30#7F9~|A zK*3^CdYgy=0=(A7RWfo%78wKb89Q8mm;TNv>VT2H@|1VLY+yS|<==tmTMdT6w8QwX zPaQN1S-$|i3y8iv0Zf4d3E+wmaOi=X$Xik@K_N(hf$YKz^&i^G&%<*zALipJOzKI! zDH)}tG?ak~q{68LsyDS+tME!Zh8Ll@(}wKFG@lwK7{}(?141eSzwtD-gKud7JY7i?)&WW(~D2f00LM5?C}GLx0(xm9^$2@`+pw$ z-WFHvcGo)}T(#c;yF9YNQS0ro(IziE^V~M?AuMcMd|teXh$;9`QlV&ASlRfob7&)2 zh*04U+v|uoP6n_-0uu|XGA?c&VHF}ORjJV=u0^YM3Eg`1>NC!$pALEDx~*=z?~Vt4 z`Q#sbHrxfT9rGp9H!rQQGUTm~c0{w>2p6rj#yZ>4UlxxP@*>{z`J?_{^py*VHKt)KcVP;1mnV~C>2RN-Zj<& z+5+Oa$$*4jN+5uWCM7ViWm^IV_51AL+>F$)NZw0g2DPLnPcZy6C2%Y-)}aTvoMtAM zqZbs5D|V_f>%-ZdE7YlA$(HsFofweg_B6Yi6HAB++{2qLnoc6J$;u7&gyU_zz~Ec% zwaGy@0KexfaHr$%Zl==2B7>aE8{yo2gbwFIpOg|zgdRQFIMbv#q=TLuFQW;?iE)Rp zgqI~nAz4E$m;`6N>clp{960O%c@B_%C#>ig($Z?>NjRa>(T$&QlDNwHpg_-TGl+h3 zz$J^8AVC8H;X+TKvvm-xK(u14ot%tH(5!Ao;!8_ zhmhm*^)lAAWJN)7kf#JeBSMp-ZMLKnLef~pFz6|EGSwU-yrl=;d~%6=HhL4>1y5z- zdF76K^YI~N?5cY{ycZI9)Sn%*T?C8;hF8r>jFU8b1iAn?Vgl}}OjfK0aT5P*gip}W z@^l>I8Y;qA4L+AEW*mXS(w6brN<6t^0_bxCBp^B?)u(SUZ_FgY_C7h7R%ig-3N#Bc z67t9Od{(+MXJ$mi4a93>V4UoV$0WacL#a%J1l|QBVhs_Ah7qZT6PZR3xkeI&Mk78N zohUU1QEALEUvLmM1s}cnFeFHj3a@`Qx@0yqG>1-(G;i?bGIo!p37CV#@6r$Ny>QyS&Da3 z%c~O@=F5gDZ--3U$WO$)VF>JJ>3`-dt9jhhNhi3hTxm92dvvWCtD`lol0yfDWnkZK48oeZN0yO7K~0?~k}qG7CJm-$(UYjecvRAeIq>}SZ) z5XtRA0(qK}{2qV=*2M{JG(h+I92MHAGN=hyTP-v$k(tZyT=%B0Y zC>i#&P85;SE+kNxrI75a~ztA1(#w66yu9vex#9AkL|~_YfuNZ6Ljmfk@wBh~f?XLQ&8^6eR}$dxe4a zD;Z=UGT1<3$Qh1?p`mCP7K(=9p=cOk0~u)`GRi<=G{P}3CKLl>LoqNe6a(XJAQKEk zCK_mR5|(UWdRqp5H4{00&yKCJs#1Wl06TA|A=_HWq4sdGQ+p)+siC8xS7oe(=X%@jI)Ad-A22l^+PEam%ebpyU znR@O;?lVPKbvf$K32UCV5>?GT<20#{;s>u2)%268N}55Gh|-vGg03D&zuI+IE6-C2 z%-5Etsc%m7;mN=y)t6&MPUllCE0D99Q>!{QdQ`KiYH%@at1OkNP*_ye{aH%okX01Q zFU{37X}IZMpiH&jpmX_8HM)BIb56b6#>A2bOB_g2#Bu%iozse)m{9NL*(mTAFn~R8 z3~I=?QzrgTJzB*uf01R{fVvi+ldP~bzl)+1udc*;DYZgJcC87u%(oUdvOYJxPE;9rpsiawf1q%N_Tjxq(;_?d@>f)=>U z$_%SypfV*(sY);mYq9&ud_2+?ckp2WClbLHZ9pC-j{k6_Ly@l)&e_M@X(N} zNiifFH$jV&Xh6qxew`)lhIK*P%%EKb;# zn)R*mMKiYD!-t!SV9$VeXy&t{WWIBX2jy)IrQ7*-_BsPIw_SM1c0UJr|IBr&mx&2l zvVJFACX1|BD#RJeQr`{c>y6NG%5pn#??@a3OUOSW`$+s$WEJ=g6~-|&WQ%_@ytG1- z+uCWiNi2_NCC;iwH@5pAtLQYwvHQ;4 z2espFZxk0NyLl^Br?#jTT{$J02|vC`Y@+Ha`tJm1Jt_Ch7djd;pV+xgEsQPmk*bxA z+l{@D=zT?|XAgBut^fI9htH;z>rFepnTf$OaM3csQ` zizM6TxOv>Mmsn&4xE4EWn?&g>wc7Pm_c{};Y`-RyAb}x$H?i%#xE?uG1}eLY^J%!_ zdy&M2rC=)4I!PJ{+y82e8Y4%+O^#uZy9_2(T81l~JG3F0dTVB^jsG$Z$gXf(b(`b@ zj5e~#BP^CWAF_2WcND>hJC;*;8D2k6Lu4d)1*%-H`<#(a$_^44cec8lAfC&7M`KQf z;%|^UZm5(~1<_Ao#j}Rj#a*PX-oPv4;Q*H7$QJkPFIq9n%NxvJV~L5iDJTP?vH?(E zY+e2fAPc}fi*fT_wF2_u>!1?-SQMjxS;aLtXiVt3gZF2qr)0F6^aH$oV#6vk)Z<4% zFY-vs_O$&~Kf(H;Q9nOa^RfmW*?in1$=B3r1l63!Ll$w*P3t<*i!;aO@9;)9m^}3v z^jB45ri%KxMdc5>Mv0a!1vhu(vf}A3AoJImjoc3@e&K=B#%cZJmAknuCgEPI+)Pth z$ig~jFJE`-NG{Eb)ksd3`%D&P2HOEKMGHcL9YaZW)EM?#@~8*Nm}z)ljsvC?I?c!< zn#Uj>Am;4JB_j<0vHU?wqUa^q+yZ-9iTN8r;|);Xo@J1n3UMUL#s*=dOmDo>ppGzH znv56MRa#XApXU?M!4&sf%ASOvB5`*WcB^j)O5CBvPWp1wlpdF$Manf=aF_$gX+p*! z=L^%?WH-c8-L!t2*MrOoI4y|P=CmSfl{~#4TSu7j6~%wku3*Wao}3Oq#95$R=%a8& zWF=Knuc{pKMlbzwpY0|b8&-{I4aF)z`M|lI4g3KshbP%)j->e*Q$M-v+#DvsZS}Ru z)O1x7T8qPi6Vo~mxWYWD^;vR=MAnd|MtRiFHk_gJi0Z|p! z5T9Ybao8>>9Im;+)LeZXl(tCV8k~@)U)Qn^RX@Bb@j!7p`b-rl=SZP6X{@04L0g?C zdI$WNKiPd`2s)alpbc79wWY;LZ$+Zh*ko-EcQtVV8HD0D#`e3Hrvj zjaHL}H4mtho79x=E_Xa@j+LE01xD%rscjj`HknlRtRb38NOeQw0{Ja~_)k`W{c>bL z|E_t@zssB36F&0I#C3YjYzmU?2025!%>K84L}F55$tFUaLpF}ZM1WD^kZlT9&^hFd zGazNr%7rA|#Y&HdPrrR7e%IsMkiD1%_1j{U-S;Xj@xA9AlT30KR($jQc60yXJJY_2 z>8_w)p3r*^z)e~1@CNlr-woO$t_bj4MC56WvBsq-j|{2s!F4?WtO+a)$K5?~!(D8E+BHA{J_^{k1mnrp<(58U{8vWo81SV10iD0xW=|ANrh+rqkq$VbH@Ip-0w{u8wk}6G%ptn3iYI+*iavvt0e-@?! zvY{u94BLICBGcRVwCQSjt8Uz;Qk91Sb&DxB4qm^e#JmAJFbj?x83l`PsYxD>vE9N9 z$86WVS^6vyzvOOwAe7BxpG-kHvEkRkqNq$ek`0iJy0=iYdLHY>pdY7Jj;F0dK@MVR z+3asRkduFc3s1<@YL`ZG8>nWm+8(n@*RzL6cHN48s3JwS!+(u#`ScM#Z$z8UeDSE` zAbjw+XJm%=6!-p}!d{{KN}+|QHGwv-Cb=xZXQRHk0`kRI(M608t_KOO0H=>4b>hGd z$c$zzdqskrqzR)gVM&7w_20*k7&xNijYGYM?BjsmI$*4o3r03Q)thY^Kvu(fg5OgA z<1e2SrXrg?;;oeV19K=^GVL7gK|GncOefgsofV@~S1RvD0GUH2uD(~7FOjPC#{-n3 z`W7p!O%4nENO4OC_KCLkp%afL)&#|CCMbF#pzKmqS$bQ|PY;Lg(&>O8;M4|-6t#Az z=7-^70VD@4wX#e}`Wm=&zwxv93uaEog5MAwB1_#|BY;9jx4Up|`M5+&Bg=-Uu)q1=$35GMXUh7TcS*KxJ+x+iK)ARz zxxBb>d2m(JkYBT~ys4+HcaRw`nzNpbTeu|jE`*+Zi^d_Nt`FhT{#J7rNb3NY`I?$? zTZbwh8MzuIr2YbfA2L{0_K0m3uxx>;Y*zjFiqW0%{*pFAdy$z?P^9sp#J*ln@q+5E z?$&m?G5enC^e~qU-;UUeiYw^v{Mmy|`O+FdK}}(FUm%vPWxSbdO2=0~zPF~TTO?o0 zOrK-PnPQCsFwds?Zfr^cJ-RJ}IsBq76P&buofM%bsjXLXi(}C`fVu{Nn(v54JH@C$ zI}4ar%ehqnf;FW+$SwfTz9vkEL93sL{L{F^PG$pUo;q5S5Ee{Qh2=aUwcv9UsU}_A z6KiSZ!rVPOFYB*5sYn~Mwxx}OY*kvX=Z~Ar$s-(CGGf2oCawe^tJJZ0D+^Y(kOy$R zsu3ZIRN^DH(O^THco|zVdV!JYX4{O~fxwB)ZC#bZVe5?8xb2eVo({iiNp+$r9`Pi7 zhwU0$cpJ7JTsyBP@bQtB*^C4lhU>!6OZ{7Qt$%~fe#mfqvgf`#N}>StkftWwCArbj zAFD~u8k~BX@@JQ-^<%f!URr2YS~l~l0qB~-nu=~IYP>Y3Y?GN(lcC20wRF)Qf0E<- zYbdfP%v;OD!=F5{wcF0C|B}N_4W&*_u}q7u|5vL_;d2GU-RMD&l^y=I--q_%I6vw`|S9Enr+ zFFWHw9I3Qd6iR7cU0xh*2KoJVgEcx{Vp9$fe#mGPvwpk1wuldhkPG)M9q zil+7n`e&48B7DxO`BVjf`}NN7#LWe;De?Jv5y&G8VTACT?$t*QQQ!d9!k!xYA`EM& zDixR|r&AYIkI4d08HVA_HCnpH&qx=FeZiM8_Q9-r;oBix=^Dj%l zu<5DKn+w(xve%9EE&1<|Kh;?P0NSPDK=#UVCK900qjNvJBA!oy;A-M|5+1ILszJ6@ zh0l!1<5SU>k=t0epPi+b@+s)gk<9ZzUn`xXXFI2u7MK)MUqxuOxhY3}{QO$?t}uk% z?(fU1qCM^uAR-#&Y&Rp{A}Q0)(TST~Bp%bMzYg?slSDevD+S4%zL$0cQj zvxAZ*H;K!z-V^~90TH<)CPXDP(b%vERjr{t+TrKO{0$v3k7|0GtI{bksi;wnzSyXe zn3dFMp}xdeoC)k~AE=cR8g!m|p^0XOY_Ko<;`rDnp+(k0QngrHXt%&~=D%C@Q+vTC z2bB3M(ACfp^{S?~yPR^rNW{uYO2jjxwOwLViQWEbPylv{&9Xj8iBb8(Bn4PfD^!*j zI(5ArI*Zs%Y0RBmVjoc=>++B>*7rFC{D=5Dnf3n00V$&!&xkO7P^=E?i9LpjW^u7g zUtSeZ>$Pe|f?xL%B?Q2RE@nR8k3-^H1wxC3%E+Rw>H0t{#O3BNZ4tQpc5JmW!@|k`DRRX z$gh@G>T$-G+D}LGrU+ql(T5A$HhlCAr~wKeezx^g zn&Q(W_Y^SG7|sc}9>o|g!ueTf$_?x$QPE>?)s-4F@TwyDw zZbL|-aky$h&5UpJ- zN>^54A|~gP|DT_bVY0d{qzBi+Kl@}5gRDd_O|`Oxc@>KK!~DB6ZGBEe>iwf`J@qU5 ziWoM_Mp5nd3Qe~dS!P#%8sN$ElIGD{DiBhB8yfvDKau$lfXwTnE-|W7Lwov3K1X)9 zsea+8coyNy9e%DXP_UpTWxu)-%G3@4GNGx^be;IGP-%)#-zLS#%X>!G_yA8lIR!dy zU{jYBptWuw-%3*M0PP{@6ZX>MqwL2fFjdk=;eS~P(;N_RMW#xYp6-F4!wg>f%u-^I z?xMigU#Nsl{8DI5xS2D7ej&u~1<9MAj$NH1GEk64oet5$X6-4xh$5gQv>C`1<`YSw zY=(X(xOZq=Us>2H5j>piq@v+k(AM`s>1|%lOJIq&F2_szznnute1uYgS`u?#!@SZ2 zhXk96?4|xb>mUaj%*sA4#Ef~qMZ54TV750VBkvPSCaUSrzyoVb;>!8ieHZ9R4$&q;$$0yh&Ap$ z*8dTAR{>d2w`SGGui97xqd3G}&y3`nNh`1jW7uOq?_V+*hEv=N8#Z@Pi%6tdg0fpI zB76p%MR~f{X1mKG!e+vmD}jCiBs@#|{-W&a6}81?Di#^!Mh{4oFXlQ8WhEs7yV_D& z6UYte)Gn_2~xm%(&N2YkxetFfzo}x@YW;eQ0i}`|C`D0{r{E&FG z8gO=-u7Lc;vUF5SY0=D>1V#K^|BX7)>D<1Ovy2wrC%~5R$V@jvUr0ZyG?Z9oAr?6C z;H!9Bdf^#D+(n>hs7Uywk{?!=cSO#Swn8OGOuM5fXS}u zLkmn_4<$nQ)}J=G&~#-m;SG4Amr@#_jdIt1c78E$l7NmFMU5|eP$hR{}p%!-v4$$ z1ys~-G!q7tb)i!^zTCH%%|R%y*k>#jzATCmFc6=j3UwiU5%0Oo=tEX$Ucam5KItUL z^$|YbDNNB_MxW#=yz-)=Xi}jb2S87-=GBbB?0|ya{Zf74&N?WfU8CPx!loc6Vs-iT z9lC!+g;|9aSfHr2;5V*5_YG#U4aO5#8Eg|@p*JWUpYLkPhXxcfcPqbCKtqnA(d66T zrK0GH0AC!|I!inIi;NOra&X@GX^tyPdRs)*BySFs^AfkxRnxmJdt%>su|^q?3Coq% zqV9IR)yLJdX*RyxY?N~iY^qftHyKN21C)`XZn{E57fC1MdLZE2*M?}iNO?d@oTVv~ z3mG1h^707RCN>x&y#l+gmL<1Kbhd`WcN9*gr8GK8gkEkbFD()p#A=Mo8FupoIAdjwcRsm zoH4tGr@u>@RTyXH?W&t+AKr#9z7zD}o!R17*E+z4gM7^K`y8x{=xB*{kH!yzWv zSj%X(gSvcTv9UNOS86V(3ooGIpaB?tZMwD;wCM-i#T9QNrC8DxBFMx6m*s;)a4zKo zL5w8ezJ9HqlaV&*xiQ+J;xf<^vWP_bP76_aJKd8+X3&~#6<=X2xBjD^f^&eD@ zAV04ZjBWF)R-jf+Ua44{esc0Y@Vx3`^N+mG(VX`5Ts;LE#!kDO%|36(eB>rwCKrHB zkR9L->?lTHC}ftKuB8hNoPbu7bR zWW1%npy$-#>5np0{-W~q0`XrLEt}{#7UvfxF?(mX>$(EM+VnWTDkV#<{bbh7TxLXT zE^#RGGT*Y+S4S-WuH1_?UdP~ab+e48vf6qI8 ze#m|hP@ImVV6kKz4uZu(3)~H$*Yn`v;_fpASO^aHl2DkxtO`%T;K+C!1c!l!I#^nH z1UDGFw!rT_TE|%ug|)pxjXxHx*pLuk=)Z5rGw|1=f#E?ckxm(Q@UESLyd*mrJGw6J zqLYscS*2)1CGci#(S{^Jsw!i$jI1Jabui2|FXlaBo#U$g##({&Fp^f>$x}<=vMR zGH)zH*a?PoilXUK??Ud~yO&4sBK*kkKAIl@8|@QJV`g3Urn8Hkx13`yiNHp9OgQ%( z3HoUtcW}{AgDqfWUGj$bVMU`+N7lpn;ZfPr!*r4Rxlp?FT>1OyS+^FsyGDLEd154T zW90iUFJBhGe#OAgWXrb)qyu*(mOl;xf{6~%a9;(&{FU6r(_467xQ|LLr^g&!vcx#a zp0he6D|!BMHLrfA#cVzeII?BS*mrpzcgeIC-eva@z|CETIbEdd?O&14;bH2>lY6xi zuY(g!CJr4k4Z>zkAYM=QUf6C<5O2`a;naLdK8LN$Jz}{W+5_zm4iaw2Pq3th3AJ1! zDIEt$*Fk^NlnqDFzgaWS3qZ>u!$K-z3c}m_U;V*#*IYti4#^bZB zM(#$oiNpH%Jf`hGv|s!;j#%VbW@$A+!KwexEXTzP3XSCoDNCROS0YVdU%;+6MS4V* zl$1Kc3bvNbtK_kN`Qd7ZFGY8+v;jQ8a~?T`^u$?=*ARpUDrPHW@EC@BvF=#SpruO}yl!f@kkC^#3~3|}71n(YnB zg*L$lvt0f%Icf3*%*7wGKk$ojQ&i|_VC3H@G4(G69FhxO0WAV|f|`4q6XGa7JCDTr zEBpOV%23DvxOWDeoC{qBolpHMN7ev>|AZ<^2cWApknULsZ$5Y$ctuXmY`8ZcG8NQ0 z<3-DEB}A}f=D$zKtL;J1X@Wa9>SG)VXd3A0%-SR1soOy&4qFb8NA7-gxmWDlk-u|r zR~#a%4YOw_1JC6gDA-p|WwxTX#Rmmd_uh~>expqyLVHZNNLRgn+> z^MbYeH#|5OlzVggrX`!dD)fGHWyU&@;UK&big|*yu`p>V6HAP|zahFkkc#C@{-$fM z>IpN;J$JD)32TIH{k)aXu3+q4vj@3yTVxAz?w<9+`VJi>z~`0%ehBP2aGnTXS9;_K zec#yEbk+9C&6$%9?ghvvGAp4Ib}|SY?m@^%kOl=(Arp2iUwIHZ90X2eaJl8%`>Ee_ zgLMDYx*PBU<=Hbqyz{*m7-VfhIkXjyQT>9(ofkMH_hzytVRrU0@Ep!O%s~zAq@V7a ziu(EJ>*XuvL#aEvSB2Ct*dIj=$=iJs`7He?vPvD%CdH_B4gCppV7=Dq%QBK~b%;?_ zn<0Az~)E!%->{*v})5c$Dz7&nI^{`}2&0FQ&IYn?Atm4moW6fpUw!6y1NVfi5p7 zPlvV>8FF+vG`;=D2CFM%xA6>>r8=uSY`0^?tTP;uFw7@1SpGwtUgdhUW%zVU%lWBw zC7XMCHx<|Q9*D%YcLaq)L6b^!d)yu;hg%km1+9v%n69XbV6CpFvBp!bIS^W<%rLI5 z+BI{LwKlo6GP0|yJGgCKsNLK}UF3YMi>Y&eNn&Zm)Uc|hhNTlwy>b=yTq+~h8MMm9 zA+O=y#nTcaK`!=I@CuDytA@`lwX5$<+BTKwxx0;u2n8TEVYAL0R*olRH;h+UZ}tdO zGX?$J0?x33a)pI=FH}f8WYV%TJ%&?iI`59}yHQ%i^s&OpXS>8EbyKs-^cQ-LD)Sk! zl1^{U+(H|ed7k6uUq7L&LYmo$`=uA;BU_|Z^7&`dA0Kd~v?w&W-Q(mKfzhh{U{y_al zR8Oq7v#+kMv$K|{1t0$2_R#>aat*x5Xfqj&&LYI>!Mg(=Ra{!6hX&<+ceA2Qwof1& zHd*yb{@qnC2Cv4t24=$ZL1pCnegaB1g`IN0(5cruL-8NstU-AN>Olbv;)vYa^)RG| zar@s3K^9|>^82havLAD}Um9$uXb*xV!X5#ul1YX(A}}lxY&yJ7C~kEy#>XPn;`LxMAn(f^W2dW=_!smpl4%rGt;zgd^uIvm znb$>~1#8>nX*=yfr}#oN`Qe$2SH0_N_^^}r!t~(StVgf`2p7Z0%4KANjVKbzr4+n% z`uP2lHj%kl9-}?PXXBpY{aE;?*X_I~4JY^^A`yl}9K4tyViUv+ys&Qm^$ibIAGsDU z#(*J0?v>|1t!P|x9=17p2QVwW+wyo?;AH?HAs`;g7&P09m6B|vtdBGEoYi?R}@CyHe7(+>Ke(Q1L*TG0_8yN$V zLSB>QUi;^Syao8td2^o~09%w8uG6|ajOXPeu{t4dB!lzy5 z^Hi<=H3x%c1QWkZK14lg>0XpW0@8hH59@Cps~mq7x~2MG8o2~L{eAaZN#{8R?C4=# z>tUAgs=W!B`^w}mOO{-}YQwSSbJ*{{oP6Z!jT}MLMnFE3B$>owvq2&?8N_0fNrZ30 z;~Me!MjXCLlx+YWJmGp3E8}6F?->I{sb|mVICH4vwO+=hgJoOK z7;LsdmX}mA7q_m6gWOpWo5gxETQcGlPUSRC=M2u|EY7}p4!2bX^OW}0jq<4zdSJ?@ z53AdP-E8Z9ZJRx|_8!Q#qis4h?bP`$9M16^&htDj`6VZCnU`_3=T!&$K?o|`g8`e^ z%oeut|JVO@3#@+kUAS8R(91{__S=hhgHp)@(4Y%aSkvD?ZP=z+zxFlU#y#i8Q1Rt6 z%cbq;Sv41tAiv8KtY}><`?!Z=I`?txCL`p5_zx6BKTvQ9IL|yN%cbsyc_A`n>}eh| zN$z#$2g?OXaEl>h&)^s|o4%W7t`yaXb+nlDxe$Ycp9<>ElY0xU3C=r2{>i+fJ&YfZ z{st|F2qDYO+De>mH)(4UViJ*{t&;)TK0<%yG`_fMCi-AR+>bX5r!*)OmWyHaBD~CC zEqEWY4Plu%g-#~~?!=J*N*!@dW)M14zI&cG-pOHf@&f&wbK>$gan5ol(=ktrz!MJ5 zGaQKwgFqMs;jyW6LXwybXe$-b^4mX;J4}` zFwveMjb@s<9gBjf3$X4h8zQg$okGcNMJM%aS)1m3bKx7OC@$wo(?%(-;4x}^JSc>D z;eFEKg`66VeM?ZkNr@lx(xR!3N4Z7Wjuw{JIU|6Q@XOL7^02gw(+!G@-BRwVQZfV< z?0)bU-kMqvSP(x0IbAYgfEnCGI5wSWeF>OorgZ+TpL%7Diyyx&F5jLyaFFWSphAdR zHev|MkCzaEPv+QS&wVs4=$GaxGAB!kEZ^bpOaVgImPHl^o@*O3edrQ3)=fJ9)^ye| zj0+ezS#03WZLx%pcmgKMZ`V{=5{IrWa9iI>yYMP(av1AbqHc`tt zVrGy|2t1RD9A?}Bxit);-k@4VcJLhAoM}TVLWE%>=Zpg}#L?mhEUJ@wz(Wtkkm&NL zU%$ccgtp4lD6maQzzSC&2m27@&YVm`B>j0c*Rc#;ZC%Ox=rg!pz~ekE{gBM&EX3mm zm<^o@BtBsoOxKwhb)Ss85Vl?Y?93}lq|0wjO=8O|kPA~{AFB?bPjw-XV{Sv4Du7Pj z_c5DN+t^`>Gu`t#aV1vd0wPfO<+2dS(lH7wLsL@7FtgfAtUZ~Lf5j`a zlN_Hs8Fn-%g74DANF)I>?zV~BKo1x8IVtTfB>ob!6rs%1F6&~pH^G>CoiM~cM#&F6 zzgRdxky6`(B;z20?4negxgmb_hzO>3{t#ujAgI>BsQ68fr)EpCx4QZr?z zb@+Hm`AaZnYL|kAR)Z13xbZ;luQ`^>7;bVLCr!^rx@#)+@l0~ayV)b(*baxjZSwee z3)|GyINjYw6qr;NQDCvcb5xRMHF}&Z6}afiBHYYp2KO3bW~uom@1lcd9tz|Vl>{Hm z?OfE?#raa?2HH)^#Wpp~Gl$xlp>iCq)7m4->7tc23QY#^Q-lf=%sx9k_ z{j}aJGj$7t6M0}fax=zSyEOvy&6{p!Ho?LC$}_RG?pyAicc$)<>FR>;6kMcLCc;8}0;O(DKMKpQ7|LlW$$Le@ujWyWZO!A8PhUXcZD*k-NF8j9$T`KX|iis@)|N5TY9hv1?Bot4d(f&-ByBD6;I*Yd zRi(}}okICfQA43t{T#QM$Xy$TsS+~7h0%tY8=8P1Z}?i|dKL`VrY%b?A@0(fwhh8+ z(d^n?p4!$|Wzf3VC?g0Kav%wM=J}E>L9EJHK~%yNK|qh8rrLdlX7*z12&zj*%?=hK zYaY1<&M#Hi7p5(@7?X~U1sgTH!SNc7HPgDwExgl;Oci7X>MS0M9wh|>4K?g(gpufw zWo7%CUZhy2=1(geHJBz2qO!wGHgLONm~uHcyPM)weU`r%PF~plKuUg%YsVUMnl!}L zPN(u+?kMxfY^S4Z>(Ophd1xFpBS%Xcyr<0Jdy*SVzDnY@7=IV)+!fKi)KI{x_@?VEQ~jJ;Ez5?uwzVWi;&nZ{ z=LWiN-q+@xsoHJl1H#pLd%wFMSHot$sms7~tSp`Eu^;8Q;SELb%LvRPm^};N#zsPU z)PnZ_EckZ0oHagtBf}+zoISHDgSst-`I19zcMfw$%;UEKI1k70X;tJs+Wb&e1YYlm zqv!{pp*18z$2I7e)VF1xM2dt9UTDaY4BX8B2v1{(diNobK?I6y3U2){J`ScvOh9`P zmb@pPfE^1Ci#>!qO(Nd~J@jFt9d^dw7a>E}H3RL7Z>X(M=%-#1@{NCoe9EVvd~3B| zpW~D(Bl|*B7n3-AlFU@Xb4IPGPo{`~Z9gcq6cU~zkraA<_6a5%eg0v*G4L8kNWBi) zGuJz09Mm&0Ylw@Dze8_02MOkr}Bfm_Bnd_iVrZ`;l1vt%qc@R_|`Z+*NIkQUXc?hNDO>7)qV zLT`uxPCKjzPMHloimsx=_MG!(4OvFwBb#lpbl2Ny(&1c;Fr~;%Plk(kZ0lwINhv=;|_yUCcaa zT+!<6t&JoG*T>0&r0p*j>9&2MdJvc{x6vc}b7}P(w)h=z^Axk_* zQ&3@9G<_V`jO8b`TE=_lj^~f+R@9so4|ph*-NW^jVQ!BPcF7vE*>9U59PQzBflI~4 zeE8FVaoVJz1e=LnTgbVf4`>@tjmfxU{K^zbsi?gHLBpc<6PVlp3gTt>$J^H?Dq`dZlDK6yJA^g~BxL}F_gA)ekiC1M2l!=0Bn;kgyhn~51P~5>y0-0^yxD(+UpVS?>zr+KFKoR5~ zyUJ%o?+nJmEY^Rxxded*eFft6O zdqiN*2;S!%0!o4>C!EK%y4<^ zbh@0nM+V6;;^jEfxShTUJ=@R}Ecs{gsG^+Bm$^*hUck1)2Cw`yT@(3^DUhaS$&y1G z7H2sxzOW&iX2Yj7g&%`zQc1;otexl|!Aqc9x4F>E_8tH0SAqn~SQq{nepdbC7A zPXHXl&FCAMrr-IYkS|juT}FlSCx-KiK>XgC1UD8!u-hs5U{m5pEHXtx?uCf(3rJOt z)BTw+_F9!*qp$k%d9%QF~(FFz*}W%=-JruAH&T*)BY z%R?zUnj1)GwpYF6=st6~R;h}A7_MnU<0fe>L%!amrcAV!=Lq(oHXhr81g z=O^qsv+Gont~{H;+?yeL=Xnv+|-q$6P_eHe?cDQYa;PC`kak)Zg0F^M7 zQMju*?l2dHr*k_AK>QB*-|EL?g~?fOb`MN~CBA{FAwf2iQmiJZ zL#0A@&O55|py!&In;E)tG+6gL5NSqMfc2=av_+l(>fN6P&Fxm-2t$|8^|qWcM?fj| zubb*&9H7n=Lb$i8n9z=AC?!ujW=@jlmuGQMhJ~A4Ix6%v(EzKd$Bj=1_-ffwvQdDN zDVEKAn>ud0scei!-3VBI>x_vr58xdxb{`_ zb5P1uP>#-1?tF#Sd{P8k6fBBiztKwY2AZ!Fdkz|b=_@oj4Sf`9>ZjAwW9P>+12T`<+7&t2cNcV@LO594eA4C&Ex7&%wp9LEj?4m&h$jqpV1|698LX zlx1wb-V9#pJI6+NxVt)!%kJs^=`^-mr&T0aX!Ux9P5;(E+@nyr$iY;s$nLc@Db?ui z1rw#W{YCGOlz$t~J@eEPk4`sNdLHgd1d#7kD&xF{IXwoC%_{m zvz)jisb;(VtrrV~?Rq)1b+Iq^QE-Qr<#01SKP7`BGi1`~p%eOu@G(4xMn+^yb0#RD z{!weY8wkHK{a*bBp=aG}c8H>conN`$kdJ>uA~~e?M3)QadlvV;b6@04ex#sBXzZ}J zl0bI`%`@S;wfsMgf&hQ_e-L~o*0Rp#0EreH8cgZWZ2TLI2`|s`k>w!(PW*yEnxym& z2~joe&Ui{W+_2gcL5ahTt4RakIe4xbs)m;?-{6diB$or6canqLTz|27K4v;M8cl`Z zO|yG@L>?yvu4C)!BBDLqv!gVcEOg205*V{7BI2=g;#|uJxv{Q1e1n6I3ujRwq%aUJ zK?hO{frO7-4N7T)GK^m{QTC2^`Tz!dsL=G$eOkTe$8$~6$0K~~;2_V4{NutN(ZjBl z_kVQXy_(gLANkMVmp;HxN7vlZcJO8YKfb?j{;62p$ivWJ00A!jm})JukUGn|eyR6g z!_keu3Yh1WJajyd>UuWvpViWuo^Sj=XEJx<{_0tB%pC?H!8lRP=E|7Fnb}GAvPEsz=S+V2LR*(+T#LbgB}8)0U=004Q8Xft&*~X z-P?I~bFZ-|=J16)AsTee1qu(h4JXB=>WRL0B8J1&X&F-lpLs7Z7bSf+$WdEP{j3(J z+ImhVewp=*E#jf__qLq()gbvoO3-oyU1D_}=n|{zhL8~|K>)$@3O)Ue!!bbA3wJig zb{Jp<43H0*pnz&m*?KwvrU#<}5U8OP_`64}4GPw733Hl#+9@0B{#)x}(EN>(>nEU$ zSb<*8BahFKu@TLey|D(VNh(E-)*#5l!(lWb_)8zq8~qHQtH?pw~gC< zyY14IY{=)jfB-SHPBN7m1EW)HIdJFD76c6F>MR{ z=_eHw+D~I?>NgKhQ2WJ8>M%~1xF*ed5Cw4X7lh)Hp<(w~dL_j5NFvJ7C8`BtI=igH1|QOr3a zS!CQmgv!1|{Dd|TAlkUW_V!evftXog?q+t#*QpDm>TsjtD>u@HPtbLJfox_DX*arX zV@B^kh%q!U!-X*Cl-Z};e$(k0RABhPq|zl9U1Db8%j&W#uDW)i-mkpB&cv>}p^NKg z*k-#xUj$vk_j}{EJG%N#m~asyeG(NeL0qdgEfU&ol*lZ};=U9e&MtJaULx21{b9MnEqbR2ggM)WMP zl;`D$*`<8{t1PF875!sC42(fBIEE1N;K_?O5itoV894}$bMO}+P>?o)g)~*XdG34Sfrp+J zDonTtk)lM4;f#0H0d%#sm3=D()>wI;-jyBG{MwS`RaqGc%=qwPU3uyB-b6Qtcz>R4 z8fvRcYbwgB>ijO5O9cqKmi{a;0pr;1RSD0 z3c^QK&xTl&%moO~2lv+Fyz*<#!p!Jm921;_)Qbv!1g*BK;z%F`)ya-F^7 zZKpBVi3l^`{9v%{AK+la2OS!JExg@1vj|HnG}GvQ2J}_1;hubl@viJ3V>r~9hsyoq n^!+onZ6Uq*qJ0RZ6d;;&EU7rOe<*g6JQdcPVdqF-Iz zug+go%Gl4q*%1!_0DARTqwK(GqH3t1^__$O*!oW0L+rtk_%c)jr4w>LC*1)Wcd%!$Nol|{*u33*fHK_gIY55#O%W!YT^gA`kPqhGJu>94o>7t6pysmQMD z)I&{xe*oZs``$*fVpBOn<3MjAW3fui^*;?&M6IA|lPUT|Q&S-%H1uxBGIyX+UC!>T zOU1Rg@wGIA^1*#xf&?Evc0myFzzba0u}1tr`(@Gi_8wH?e9-{8`DEbu3h@$~sa<5{ zswYE8xs6oTPF~+e15A1ey13n{gmOQq-UN+0b6qd@gLV3d zIh4ZV@r{7}vVd7p)%i;Ua0w`{Mr<@_4Hr~|4q1jeZ|#7BT# z`|@=he*?H(C$_3Hi@M4}W@#)ikupTx*?+Zeql6TCsE!1JApdD%vyD);Ma2@TBLh+( zLphJ}%jd+4hm9Rw5zkJQ zh6{5-L zq=o-G@y_@CDLpZHSCQ(t?UIA9(ESQx=^%4BD?Yw1)J=$N5hxl*`LTxUo;yF1rTtXl zBrftb6*4J*WU1UpGxy>O?c0lM0B)qdxCowu=>m$}=T&EW#~p{rM!SYtK$s(+$APG| z7x?y1^v8G}S^@`td6{62)k3(M-_QsC88_C2p|7g-sR7@&aG1AH#d?eCeBPl>Q8omH zO*M%EBc7~TlN8w>IoF%MLJ7ez)<&-Lhgucrx+`roXFkz@t)>p)HI1gFO?FL~~UG%wAzH{A9}N#1)YYidbmdf}{KX3HQNqJSWH z|I+vO9RGwBXTAHYzIn38T?O{EW;=y#y@jVot^V{cJtq(f8!OMzFcUN8#J>A`lGjOx zcHf&CZ!c%M2-S7Y-BVIxPHS^o?j*EFt8H~0P1dQ~gqcuZ;=;}7uYIG{$#CjH96oB; zr{i4V13$=Qk#By@<$?K#bnHBD^Y;bjSWR4~lbz;ZXOjx%oHVzZerM?WR(2B0d-eQj zf*n9qP-cY;4voZ+z^>-cd0j!e&XI_EdoNXDz%fCy=*GfhuE-~$&fq+-8nLfA_J6D@ za>iA8r|u1z9MZ!g0ArMrq$}u*WiK^MOa3Q?w81&} zg9XAtK;K)3Sa5BhlPGuR?_!o0LVnfAH(p6ade0hUA}}0aUYMfH2$ngz?)7xn+qg*g zxl-s8fb-qG?JVG=*v?$!FwZIfqYPcmDT#ah@^Kg#IeHmC;NQie{j*UYLv$-KP4RbONe1b2mTG5h>nGdVXnMZL7pDEIO|M%b1Dj6wOe4$Z^#v}( zG^ogtjk>Et$zxoeRtFLb85~}^gs<5J0XRBUuKs(0Y1l}-K6s)<5dzj~qn!Frsoe?n`!1}LXrMqERV{M~V#7fKG^TCAU# z3vX*9m!2g4QAH^o1?M%0g&BJQg4pmKxUL)M>OmT;5A=@r_AU+~_4W4dLg%3$pgE!` zp~vz80R?b~;tan$yf_%C_V(ryjeoxFc!GkWC0b&HeK!nl< za9y{W5CBlp008K1qup2@YrflRvEtlVCUMBH+GwuIxKcls%KXCfBO0X$16L!KV&aGX zC(j-y?Z)h6&s&b>m8Kf6n!=XIh9Dorgl{p5eILS~r&@|-9@L&Elqz2QI$ztsaf z4?$6CG&t}kq<^QtC^3qD&y$_RKV@Q)ILF}$`0}^Ei{nLnn-bn8bY(;V9|!IwJY^_g ze`o*R?(9G#+KQuD8Mu6x5iB%VaDa>W91;4~enYBh3F{-%p^xP+71M?uA~j>5W`d-1 zzg^wgbVOmYFbBF}l9V(1VUmnD2yjrMys+9)qRKy|B}EkhYKh5)Mf3`S^U@2e8}}Vj zo~N`KuM;rd_pur8r<$K71s#DXrWJmPs>Y^Ksfeul0VWTu+LeDTn)W+JT9%IM_A2>? z)$C@4a&m{25yrw}QL{@V2a(L%P-DbIN%9Qjh4IQL=tl8sIP6F98tx#-3aaqLNKT4& zN{WhNM$Adey%*Yd<*Z%D&^B%(BRscxF`gF^I&bS9-ghNCpVu}%Y{#@yOv{1_RbM4h z@!xhMRu@*a>*^PkZUC2NVZXJ-?>jTV16c&r;}YOn_y-C)oB{>)qm?TC^E3SV_S5o1 z1BC!L>WD$~@&hIdGtIv|;)qcGaQI`x$0BG$0O!u=%18rtuKM-E8ad+_D;Iu-RzL=z zB}dWsyKa>Vh{u5_f>m1oTzD3{@i=`#@?J<{<^b$X z$rk0<^X=r;^>Kq@O^3FNo&+QWwb47gdt%x5p?wjzA@1SD0-HOBTwT19=N4pM6guCV6n_TN&2$9kn`658-wGFvICAf}wm)s;e zUGdg*ch1rcpo0FzuJ8qmO#ClXLTHl?)#`pOepn-J!KNzyjWEcH3_q+#Y#`T110AXWgrAk$2d z478|{r}SAqGkGHW*+8Roy1YmMF+@>~B$B5pD_-(Olspi^7;A_^ihUuJQ!H6(ZJA%?4lRW!O*pU-F9*CZ3e4b>;yE;vNPpXN~m?MULgEx@Dk$bk^S!i&i-v3o~ zxv?{y6(p0Ni2L>a*G$eP%c|+;lXoSje4diJ7*z(pp4fnfeN*o%S(ImTk3Y_+{uZa-D1kN z@+1y+%{BOkNYb%R9M#e4+B}J-J5)T`%-mC10ktF^eW6T@JJGG^Y8dLu@B{Gy&X=u=W{?G_fY zp6n{1-`UM?d8y*?>G8l0jT(H;@>P~O zx%C?+tebxO5#$@t$z5y*vG$z!GFyZNlgXzO=8d8ob15-tm09LS3eKGM`bCUsS4p+k zHe|tB{RdY-MS68Bz2^arfb2|Rel9ad7_t)#`_;@5)dM^Z!6?Gx5USEuk}1|{AhT%qvCS8u$7kAO1YNT%*Sex+9ulJ z+q%Cn{DppolW(jQL+C0=fR?Xg@mOXGp!%d_J7>A8%IdImq1?hF65$7xuHW(ScJijyjG^T;o`F zUU7EikOu>*?&XaqQm%33OOsfYPo@CLAN1!lIPdI#B%cHUlCQ+AD;^(T;Uef?E9i#r zHVmVWJNiZ;w;fOndoSpQ{kXkda~n4?T;r!c<9G`{ng5D^Kqxr_dyHct>_F3Qo2_As zbea|GH-a>~uPH~I{d*nCd}8h;EYXzj zCj&o)BqV}iX-b1!(}yrg6p-qA`{GLglgyu@c3Ex(+Z zrsKqCf`WE_BT7ApPWk_VO^LdIs~#A)@F9wJr@5YYK&(6L<9)BazwNT(>~{_3DQ|?u zzQ_kj%8#LZ^wapFWi$@g|4)zi=LZ1Rbv;l8#1+#7A?powFj-kbUtNMBC@_q+RymUr zbbM1*D}>$1*x2~^VD?UHH#IE{eY{HLh1S&I2@=;d;bn}I(Pn!R6VOlmLv&Mu2Nudz zmyZ?*K)@Fr%wJR*fZvupLe!StH{?WB*B72m8(T4`Q+D$28|1b=SFphFi!+V=U{EMX4}mu#+m18&?0$^rm&b#QDj$qzgk zlZ8C5)jNkNe`Z)w-)G;S#oV#-R3-`&=!Fo1Y{^bv>|;kdcdq!Eb>fJrYdB)cpU50B zO(b-{%z{iV8TM_#_@bJK0~elcwY0UZ8Ow?W#E4bbaC7mVJG>k0>o*BZAla#vkP542JV7%2Qk|Ymb6`N1`vk zcmfE`9Btf-EMDY3wOpNl>)EC_57|jYz9e3-mK!Rzk%fuF^}Gtk%Cz!G{5dYm;9+Wa zRV-y85pt3MRIhTiamssk7lnz-NuC5(oFpJe?tZprqC+CiU&}^R*l@)%Wf7f*x+K-R z2#w~-?1d090+cv{7iv75;7SObT#vmyDXcK2RftvPs z>3+S&ZK3Vfwq^aZ5dF^}glg)!SNmO%@);lsJ0P8BAQ5k*c56OkkzFlP*xerxcZwjm ze1l!Y;{Fg!3S`8b`G6CxAjhY_tSLQBRz&ow8G9|gy_Rt)z};k`JVUFx7ILxnHsIezc3fA4SkE{Tz|>fNek zUnWRcLt7Hath&gMy$GMclSsC#m2^OQa6mpL1UeD)16S-HLC8Bj5(f;^DU>K~?i zh~QlH-y7F`N`eXM2WD{E50ud6Kc><^I2xeR)9O7VWl{ zENuC^{Bij*>yqkK>=Uz4#Z%F&y(^sS`hkm-(|_q~*AwuQvHvw0MRRR;+mr4l=^nE( z2)OZ+x1(a}EjN0Bju*%R1+PqpeHZdadsxqQvivHpl#L4jRqEVJ;!r1Dmm zhNd{~wq$=h4&Gq^@i>ySKpPY;h6rT`YGaSH_z{>K6MfP)p{;todE5gI zc|%REft(;U>afr;6ESi{Hd%}VSt4RM2BOG%Zd4T0Ax^YdhHjhbn+&>$7=B_nqH20G zWy~8qZK-|2I8CE!$I=Zvppx^jQPKj#8Y3@?W-@b)fRK<7(Z`GlP>=mCb}fR=hJ`=85alV zJ5dLIDD_5Rd+o4rE#Dj~WTs;OGOWa8X1FBEBq$WF?;uja*oCEcfT@Bl@kx70W;@!J zd>!%87H*_fqSdRHEvjj(SZs27Sc%!+pkXH^ylgDguE&Ljog@R5$N=;@o6%%HO_7FT zuO(98qH@PdDo9kpNWQLK{tj zD8{pGzH}w?%XcN)?WS&In~}#m=B>?hYaMxFrRpK)^A*%wn3TI&@8E#cruF>Np}KWH zv!N!xO^sdkotDkx1p@$e=W73(=2IB;rPcMtqm$;FSpC3)24~oU)ayqK6?TuAg%n^? z;_zV^nqDSn>kbdLy<&X?aUr;~nI!=N*!|yOe4AK{43@$1RRV1)QCAi&a)rNgEV1YC zqX@!{b8(nYwffV3wn@&$Qe6r2>r09bp#GB`;}kVi_yGYh8UqZ~hcc>MrO=rk=a)R` zMgxh&G`>Rt&;5-Rs#fDw0j!qGmVl%14sX5HQu)=6>E5%$bo0JcY?UL0kZ5Ycp|Yv=5cilkcwSL@rjc0GyAf!H~j!CCrbCrlt%O|ilUZG4zvCm80Jh#*fY)SZqK#(b&b%-&o^w(SlhaYxm0Se;xRf9;r0L(bT? z+PKV4g#*;RmkPs;@1IqC%hwZP-ajKs%DnT?FxBT&Jx!gigJrsY{0oe0FcUlH?DJg@ zXZ?j1LRM-tdEE1O6Chxd2KEFid2*kE7P~z(C^6C;WqME`C+7Cdx&r`&#C_SeW8I>6CbA%va&g{3VGF%+w(~*tE{O2Z?EK)?Mg#+fJu=0u68s}I z^JW*H=O~^>%3-1%U#TRDE)f+lUF;g$Y*Sf|W+T|h&vEs$2Md&DNJEQe1AM;?$==CC zjl;_Ss!zq!#a2g&0|s36MZuXMyr?UCGXdd!VXg~a2Rh%h=qMjYg}Weoa5Wl<>1O{2 zTct=wQx>@gdlV%BeTQ%+R6(EoCqd?&Axx9xR?CMbZSoD*r$6V3Wpsa2mdosMU*;k# zF>Yvw1VNxJ43-oz49R;THCr;ZGm)J3ME!fGCS~3})L>`YBEilVi%DJ?!N zwVY+@(bPL%-&mE#PgUFK}uoJS_xwI&q&}neA5U{YTK|Ijy8&+ps4R(mmI}0;7W6Nxm1Q$r^8jfl}tye*wHpu%xhNw|o6D0V zDbQiTl$4k4w(cS;6Pdk--wOy8D}81CI78;mItLGRVy;V>Y3zRI1Ktylva}iXZ;KGr`DrLL&4=#G7_*G#hY_=oJz=gv&2qES*^ma$v8AC?)jW z6yQ_n%;z|AmJJJNUow}4V}NcsjM3ttTS+Rw`lLUJP^~HvqIE0B~Ld!Jdmne&!tDNSA9!eEL%UQUDYKn z9q_oo(3#6c>N?9*%$4J1LJTF2N+<@kg%%kp8kN43j%)EL!45`F4dW;sb^m(=K}uG5 zvUI_Xt>^4ivUFLuva9G2S$t-Gz?5#3IMZ?BFyURj3SX-iRzzL^*Hs5_+q_(Mo%918 z6%^`#sS!f619limX4ftv5FS|4Z&(Bd{@bd{7Bhb4D69q+)o9!}23e=JYX7DPUfEXO zb^hvT7d6vO;go{Zu-2Jji663J75>_=5O*LyIX!`xV3Fgqu=@JCIy%^Dbb0!?xHrdF z+RQ#%uNpuvF|NCeNhsIShvldQfKV4GveWs|&3dV|VO^uWbzQA29F`NDb|6B}G+(vA z22(;RLiVO4{Ro!r4;V?X$47p4Kj)k@C8wP*7z@L5ZF$*<3AWsA8~1d+n|5%D%x!EwB5c=q0!>!Oj4>yd zW=7}xw`je|<}9&%X@2g@t_Rfv_+#|8+1x3kETScG;&l|BQRK4>uCM((R+Wa^T3A<y?=6jbMG&Ko0yRILrguSn!rzg3#&N{cPr}#3sABR8owF`#sYG? zaYm{0{E-b~{HMkr^!ZysN&-)w>PB$W@ zy)hsT-%o+52&n4wW`$caS<1O{qmE#NbHM`evv$dkIj$r;6y4wL_9cSDXmomW&K*%$ zZ#E#bYQ1|Re|$4Xmzw~1u$g#$r(%=2dbGaeBXT-&@mVHram^HygsD^&l`>3xe;S09 zwFJW5&gn6X!l)6^f*3|dIl1(Dcz3;zIyN;`VTQSIiN*~zJmIF*sK+1V&&^rc-L#(FqAQh^}O|bVl%|lP(tXq?yH?mvc{|%HMr_c-=MxB92Gl( z4G01~|3w!(SRmwA3dK@_ES|yyB$a|W84oJ%*$2*^8PBG%HT76>;6T3<$Fx`TrKQd2 zsN6$FRQ!B6Y*ZyP@j5DLZg0Dalw?srv20{1de=hq-yBPiBv4;d!^|1IWE|f*UWlx> zwLAkVpd2yjp+Fa(hqmkd>^Z@nzBVs4I?33v){5<;3A+fH<@>)Z%zeip0qVU|{#|p?DxY2;Q+-r%QJmFOB+ww~Cap zF!0RufVriQ3O5Dra@zW0c&c)>WFo4IF6c)7(o^=PB1=iQ%NRD>aC;`?weoJy_`$t^S}`4Y%2Fm{zf1)@yZX*O`&Wv+SU4Hoo)>fGOF^l*@EP)JTE$n0em0_}u)2!GXG|r<$QAbmWhOw~cz^DuKQ; zi8gr)?6&sC);c>i8nzrg!#*xm3>H=nmzkZ!vf!XKJeF*u9&)FOqVYVs#P8KcwyQhc zJ_6N_CuR*KFKKIE(QmS-jz9VBUb(%CqTlh~7^Cx}5O`eSBG8ne59l=*PD#9r9ZDsc z`A_5{NRM|S4}Zz$4kO+48~hges=Mp216y(wRF0GsH#c*zFp!tWf7Lz zm;Q1QVKA-RXi`MOo0&(MM9U+PdNjy5C6e7J{fJV=$pr@(=-^Z@lBKvfadeCyg-{{`4b$|H>d&IX3tQzf0ZPouJk`e>WwMbtrvesTG$3zE zlURJN)~rK965t45eDC%Z;99wWiZ5ArESN+8)+Gg5(>+isB_iH1BuiRb)fRtGQI{!A z@XZ{N`BTsL(RTIS0S-RyzM49nL*zOR%93vIc9ySQJ6*u^Tq{UQUoWzSlzl=8(-=`X}e=WJ{f1a005Z4eZyR5I4(;L#kJE>`g+m zNo!h#fs2hj>)Vx*VDXTp2H%Ydg2cYzR6|2i@q~u_$;Rh1b4BAs!L0(+K63Esj{(xQ zOXm$8eJ=X+Gw)|jqzc*HpCXGsQ2)-26JykL$M=8}u223)hZW1e>hxm93L$1c*!<(m zO*QuKlE({{@38V`oYGRSi?eiiouiSx+SJ_2FFmcJqT8TgxmP9PcsI0(9QM9!Wu3x zH=T`Y&18Rvb`z2m_|xtoKJ+(R5KuBZ8&{}L9oGQe{Utgmex4B;TXXiy>leqK%1(`< zEeqCu;Lf*H5dvixn*xnU#QuKFxKUB{zTLfaY?RXCc{B?Bo@p`~e5K(q^ao$EAd*;F z33-6&3@!vnXZ{2XR^@*A*r>5XPpu>o(vAEZJg<&2h5UU*$nW2BN|M0yz2nut$`bfJ zCXI7lCqP@*89L4bTTAj5PteJg)IcQh3oHz-i#HE$7fS^;TAgP4e}2}mU9h*+b_m#A zq^{VfJ%j8JjwVI#LKhYngwY#)r^}dpa55pHsNxV@M@bZm516UlMujKs+P&Ooci{9G z5K-i6Q|T#C?9&7zmy^;ak{Lv05n1NWfhEJViV9j)C&`y7+5IkPV82V&)|SeKR2l#l z_q_IGeZj45s{Eg>M2WCIIH;r3Ly4xlWgvvHIn4Lid?+MCtJRT7QFV}|ND5qW^yTS( z?>u|Zu1cFR%?P*r)$wyr%ZCh@fWjH{2Cd1$9D(gCT`)xqxG_}37wxYZ9_alb8!xbK zNrH;|PK<-NFiRGVfq9A-!5s7C3YH_mo)~#htdh;*H?T3*{!0pgoCqb*mG|alCd)Dr zFEmr*+-x~1!^T)ogemkB`?B4*i$Cp89Y2B6!1HYJ3Y)mD1a9P%>I6jCLAk4BO#Ot9u?d2 zyV4bN@&9m({54Hg)NPnA5^h8G85$xQxWJ4$v3xM*BDvYhu9Sen37!~uwGeRjV6fTI zcN+U{*ukz|P!~OI$yM6XuUzJ%N)ei?RjI!t^E)V~l1KRyY3H^cOXEp6;kAtp-KMoe zWn>d3LtNMhHaK$hgvi=7tP}DSB}{f2-tMEi7RyaF3S(j33lE9Az0l=-UIj&XE)fh+ zfUlmVcAla#!l!-qg5RaxblJ#eM+4L*H3r=W!4w!0i5jRFvfwpe&MVNYsUCE(a_ofy zOH^`FKa+Fm9FQk#+sZzNAr)nE*l2ox&E>WP02a@1X%!3+Qa>3I zgpg@l(}J}wG2LXc!G%N~$MuQ5zce#y$PQfe4}HA7*s4{G#P{p&>8Y;g3#nAyXeaNk zxX7(nK2jJpPTA5iVI66$)g65~YCF}=GqEC{!qy!y38=UU|CtvCI@}%SrlvfLjKMn+F-+!`qkgHIDv$Pk>OK!>|nXXv6j*( z!HYS;%7uov{1rQQaZRb$3&-`qFd_Q!!u&VYZPJdc@FT->oxU%ai6QwAMwiZ0YFetx zad)WeMAmMK&I5Tnmx`ti9%svH9(J{mn4^T7&rua1Kn&4Z#j2?%B=K0yLfT3RAMch)x1Zu__9@A0$Y%h3a95|nZa;oIzRF)gVN<=3>qDY;T527h-qw10 z)?d?E7l?JL1UNAlHJ$h26SO`1HCmkXb=X-GboTiKt7~uZ?c=}ENbBqyjZ!&+ONoQSioW48{UWrj$WMdCTNV`o0N zq-&m9efY4Br%)vD#yPbPrc^ndAaVof3O=2COZW|{+$7nm&VN#ib=(t3az)!@`GRlp zY>JnBgogtk1T*zf$}d&_6?vK)Oj$LD2+2r?7ahd$*P%rX8q67H9zSjoR*;@3=`jUBg?s9+-cv>#WdXAc-Pe(@rfpDK>50mO$1IwnK$In8x3kvX} z4U(b31bZ<+L!Yu^G^kX%N+r>)KcnUto8!p^Und1Pe>1aHFn8kL;v61UsW(yo3@ z_b|L&@FH?+tVd5ddi3Xfar&j}@!BLC z#6_pIgzzLFbLTgXv2Ubh7a$PzEAM{Df*+NJ?Dn}wrw#ft>6V$pNa@Hr66!_DhuPA^ zD?AB-hoJRSUPK@(pS3hoVeOz`J+(eM*KA?CK(ky$t)5;kpNTRcM;9)jHm|Y3SS;N7H)ARJxLw!62`lp;zmOI2TZlXi7 z-*{W`{KV+{vUW`tO$gRf<{)xE*BacB%Aqx(mfC}t@;eEl``s0shi3cEI|h~IFoXw> zp2Qz1x}^o{s#aFwzn>A%CK#qb5fPSMF*0r_#2I?8XfkDm56PdIa^%RAfPp?DPAN`} zs~9vXSD;kfFRPcc-#HGRzy^JGkCOKL@uhkzqIY{%OU8D3N#1h=P5qB8f2b+&umJ$K|Ak&G9kV zCwH?5_k8HBHWW6t#zEMTmVY&75*F`X>@s8)v1@(MyEDTRx_W!1BRr)#c<)@>`6-A} zvxwTpL`A2uUkNt(R&4nuLgkdzPssF5!uE-nk_$7Btsz;by}v<@s%)m)GRUOs_7ea3 za>7HU?TC%?z-#CS*&NUFdazCBfc+k5kZ?dm5?JK{J=te~>Vm~kO1MhdGm07csETBc zjELx7lZ{v?{H{&M!6t-s;Z&)z{o(4Bh7Bm#5EB3CU5irFhyD&PxXJm88C6>ZO=;!} z!V9QNw8Dh%1N>=+rqpD%_{M4%WCSrS(i;zD>L|YsuqbJv5*d)aT^Z5XiE{;2tvUy~ zvD;E;$KpjToSFzc@};Oq4`PuoN2YDs5M_GS?b;nLqAD z>t1M6cF=?Hc1YhUh#C z)TTMvo{3qQMi~?8@&NuQamuOFbIK6Xwh{$(H=$BtVlQ47?*svYk@?XneZOzOwWE%$ zKM53c>H!9pX>RtnoYYug9DD(9+=7jGc^d5dPRNzcUwwVN^9rQ;gF3!xtEZFb6iyHG zwmp0K7@?T(9|4}a#=6W>Py;jD+26)9GB|W))gP)I>D8^BvmCc+2gFx=VF~4*ylYeL zs+)phX_044CUXi*DadAjewl-yM?*sS;6Bj0PWQ)_JF2YEwfk#}hJmBRXxGg(Q~cwq zLlqfd2Y+fhTL2yYx&qZ~FT)Q>Z!MmM`;D!kMNJy{M`3xf5Nb7BS0F{mT-pK2)nHd+ z3)xImcLb0Esjojpb^Ya^_R{!*(c6*E`!l;Ucq2mU@)WG55@in*thIW!4o#hIt@TA5 zt!^f622G7-;NM`Z%B7i*dlGjYdH%J9l@^^Wu=U}(>wPe6dW|6k+?Jw3gN4(VGk=-B zbeU#x4A})tfF}5B154v!Ckze6N0}ysGVL)fi=^!7$ZTlVpsZCkIjvjZ^ba5!t|L>2+bgHF%S|jD3)L`js8Ao;prZN$|L`zB zjj;H#*bXdBn3Emr+zcN2%W*cW%H0@>&VKAs^;?pQBXt=&=0%#(h1t#hat&Dd0kcu_p~fZg zt+bF)&H6EA5`q8K@?-f} z7=K%uDjqyKu#I-}n2mie}BFYUBWa^2s{6OXg9_AY~ zB3O5N6izHA1*PEWJ#WpV;Yo4aNOUmPI)pCKW%VOd-ThAb(FZLF^5SL4`w6Dt%-E>v zixRe;uT>CA(7<3Bh@epCNpW3UsNdM+8gIj#VdId2J+|8&bG`8s=haC4(w)N(JBgVw z;KD~$CP`Rgw=^N5Dnl2QY3~6yVs-;A+n@)O7OUg`m^bD--fDOWg88AaXFJbT+gh4> zXM5gPYu1P;{x_beP#|kgjs5V6sD4PbGF8g86aTFsYmW9r9u15;8-S$jGDU!vk#+G+=U@Q1(oaZ0+avhmAtV`sFZHZY%u8d>GBlqT!c5F+rJIaz<1_2!!eyAM1La@#h@=w%;~%L zpD#pj4A}1tjTrJO5v}^meq0B!_=_B(5(T6zX)&vqk1wA_?rZLomXOL5o?zU6;Oi4y zx@>GuR8~B?dW;^H4heR%S-BF$eq}M61?MW~$)|T9uLUeOgZX+#>IRIT}GXc{MwQ)hJwPvS{>@craBA2KW?mxj$TQWK>UY!lrk3lHcc z_Ij(HJXEy|wxlxbGm{oIju|>{#ZPRVGc8*gdM5Bix`G_;FA52{Xz_W$26|(!i-d23hmkUfKo6n+xjo0hp$zBS0yNH zR5@o*$2A8}CCG;=oqheWB=tWx1?_61blpOk4lO`~WQZWok~Z1ZJrMS{-poY`XeTO= zV{VmN)zuBsgBCru*Nn)3E!E&s?hN6XzPi|2i|Vy`^=lQN+^WjZQ9W%>kFxHdU&y>8 zBD}R>0qYY-Q?_uxM1NJU85dV8(|31|6fTZFWfl0J0QwmP<~);8${B%;jVdz(C`lI&wYsA{@Zpp2Sj=(JzfP)ff^7y9gWHvXM%}(av3#$aY zlQg(hsEzh?`9y%|xG{_g0V?xAJ3sZ$FgigU9Kz|elv@J>&3yDCx@(1NKrD>P#c0?` ztB2aSZO~@dN*nC^X4}g9?ActaOTpHjxXe4#{@||ex{1=*@sC}zkE)&QLQF6S8GmSX zwY#QJyK?U(*IWv-PMp$31Y05NbT~t`Y;3?LXI{1S8tW`oSu?t@-;s&&4wpDmis!6e zao5_dm)qeQ!pII;sI}muPNO@N$yP99V!@cFrk25=CaF!V;>5@*P7u$deO9a%FAo3^ zt^zHOt@L33%?sTKXyDOiy;><1vS9Vo>@Y3%({pGVPutYgYfrDI8@+Gmzm&k(vo#EI z@0+yjg;{qVd5LdC@cV@lAtibq@xS;;X;;dv_ar50G+f`{zFP44gj1N@M$cEfH`F7w zfRym)T=6}YTd(s8f-f1R$7^p5MxD8s$?0--ru=TVKOU#Y>5jwgFo!eFXav!jg%Fvf znSL6jHDJz&l`M>2uhwsxm?LNIg|SM7d@hxUhQaw(jGG}`wP*zikgDnpeTAsJmP1gr z@&YxqG9@7p5K7nGFmyB~>BWp3?nXzOw|fEs&+S-8?V*SyhraC-Jl+EbZV3BeNM?t} zIZ`|7^$5Oihh!$Pj%RFTqZ!C%Qi&LS8-UYp zwb1HKDrc?Bt2dQXU*Q~_G#^eY>%@ib=}P4^e73*Qst@@*LEGzJ?@#%{oc;CNlnbX# zlLrfhsY2oJU73L3dE}9dKjZmZ201v1FnS*NSwFm(hxQ2b(Ee#f4-Lj0C0XJ{GTl4I z-eRrLi`sf*O9;zH>y#o}8^>=-S-y-;{lsr05o;q9!Zt&AQc zIlK;;FJj{vIAq2q&Hy%^_R@<>i$(nAV)3y8{LwbnqBe058vY;angbr2K?hBMR4c-A zwrbk8Lzk=T0_SnzW8RR9GyA}NC=sgl+l2BaOsEB6Cz;G^oe3{-zU1Xj;!8ST_A+PE z$GKkq5*O!9IA1yszyw@#CA^#qt`T1xt~nDv;)EbN2lPw;Jp;&yT*yZ&(GjKu#ON5J zYeY{g_w+iQeu1qN6HGYWCc$ksv8r32;Is24m)Qw4He#c>uaZr#(ZZ-gFjy>&a|kH& z#$xdY@Ev~u4vkM!8vOb-C2}WlDF8`;{}^L3{f~A)ha>w=Wb(iU`cA;#K<$OsZ6hDFr>OUE>ik zV&z4AeDRec=r9q#xKyf{ow1nt)vq?kV@~sXg<@Ht93FYZqWiRQ5bk$RVn478`*jn$YjG?L)*ubyx)zeU-<%&A^ zu-9af+DUIS6)QSjF#z~Xywv%Q+v9Oq!FO^;T*;Kn16H%7{!paD84|_!BO9#);Fv+s z_qJ1n35Er00rF6bR;Q9Drji1t=%b~@*8l}xUOrQPN1;%DxC||fLYonCD-uwSjXLa9 zcR(O~C*cSe$qz}I|ms`9F z7OsSMExto?Lc8mrme)aRo@qUP)K7H%h3`sPskS48Zd}&t-{Uym+qk?^{d-zXDnjt_ z1#PVkJ`{C~XQO3@fm62GJT^QyHS#l75Fe3q<*Mj=(>OwUh#~zFazZ~uyqdQiLmRqt z05>ANky;iej0^ z*itd84WF>3AT73-+`4=BR{BY1vq|_6|9JqDWD-&mIW0rROj>-|wFeJgbIrkn*KQr3 zoa}a{rn-xFT>xV5!P>>01}kC;1b=4T{V}@3EQ&2U4>nlHT!0C52GOgK-VZbUSb$Au zBDLSFW@6xgU{RYbJDPrDrYkDiXzKen77u zQJzA|L*1$sn+?Rgfd1_dEr1&mo0*3Bx@LOA%!aX%p~1pHG9C%hwQn(@DLl2b&8w@j+UwZLHy*7};sMe~t za>E0|i5Pv&3awA+D|uU1(IB&eAESZ!aEy#VM^5ofmOwt%iLAXb)S_dm$)LVgsh5XQC6rR!o-TIx)@u94f88n-ulsBXzq`Jt0~xYa zPyeP=B9EW7ln{aV^8}HB3lO28mf-rSx>tueJh#HD)s*0KHX62C&?>y@Vy{+X1n;GV zR(AZ93Ct+~x%XVgfQ`eulY+?+6~pV})mAirYOWoRT5Y38CXcUC;A;|y&+KnB_Mch5^X`os@7}rdo{bytIezfE>kfvxL6_MU5VF}u zx7*0(+ySe}85psooyMm1-JZG>Hoe}6kPWMsDY|@+AOSg3<<1t<#6we4{=_AzYnW>3Rt69%y zljEuJeq!1Fjl{|W{fj@5uNgoStl3p2ol!}My4ZE^vO?< z5^j@o$gX5qR?O8}d1S|SPUACG49r8J5!63Tc|?1ZbwNtg(Mg|^3wqo#*MymM?jYHO zUQZrk-n8l?ahP?$VcLV9=s4O3^`9L!@SI(W=WOWfi+O1A-gx%3+pVWUu~?{0Hnm%= zb~GG{w%RoJ^abc^f;^0j&oC0D_1;KaYU|_U6J!%5J+yLwgi(+)FJ-(rB_GT(%%y#H zdopWXXR`Q%q;jvA^cdw=IQ@1V?f)O($v1LlErz2eRupHhYzg{#(;S~o+3loa*V+Bf zSICe)_s}Pg!IQ7z3_v59VQw|38L1LB%L*No*Tx#v7C|4s&}{Jr%$qiu173@HwYOCE zl5I}EO=mxK%C58doo9+gp#Sqo9G&81WJA7x!^(EpX_UzBbNG~HX3*%Qnq3#H>%?em zO|7;jHj)e_lc8WLMdGn$e0XMNINpqnq=Ntn2GewmZ8C+vOddfobcEueSn&Zw^ne4M z5_*JbwwdB$AQYKEH-R|J-3heNQLk06Bfv|f*D3c=qnqe9Ep!Fy@zL5~8*egD|JtZx z%>k(t9YYOgmSM7!1gIY7a=*>t)ae72q%R$f3Sva34^u#}41ieFEr#`aGL79ru$J`r zJt0ZP@FP&CKZ3FhmBjIl zoD7gZsgNd_g6gzK0P?g7*-4J#Hvkum=&TBP*|`=zi97&Vq!22DI5&DdcOR2M`G2eY zF-rMol0BSzx$M~uv0IV*B;sj)6#(c?gz@#FhZw!f1W0wS(;Um%=gmer#Bs;m37Lr3 z*!?aHZTuSAc$?M+jiyi$Ai8K9{i0;Z8iMHUygeH;J2}pVU4HvDB9RkrXk#CF6up`I z1ai>au|t(2X>649oF+LaBALuqXI8MS36zSV)pjA{+QxA%Lqs@~%^ngW2G@RQYb|*K zeVqFsk}p!hqg0#I3LcOTO6pY$U&$PFr3M0c+Ktacv>ibJ1kmd4Z`-zQ$F^;I6WiwM@3M<++w0i2%}%{}6LWD-)l6Hw zK$g4%@4r!3^((m+4dhbf8Q!tK(~o5fH26gpT|u6*MFSD^g?>a&>AvWypUFz=Jk=eI z}}d0S}sPs>ei1Z!0&R)j6CX_yEAp=lFl%t9ofNGXC zh6=>}iCScwU&RzBrgQxr#K<|!@%pW-hO%F{!$Y+|fQ&-Abc6dB;v5p?IW&+GRb)3v zxq?O;-?^e0{6rUc^NeKbPI(kZQB*-Wly3e@6r&MH1r%m>ABv`Mil6`r{Lgv6%)xFw zMD0a^nr{fi8)SV?-u>3a2?+8q{=1-wID}B%F^gYoN-6=&(Lp0fluINiP zVm(R>FZ3IAN52s-;9~5#*|R*Qm1};2Yd$Y zh3Uq4+D*Yzfa^d20Kko^=-LL=CbgOL*|u%lwr$(CZQHhOo4+>$00@H)pbr=Urho-t z4cGyWAbF4x$Q9%PYDQ(WHrgERjP^%IqX#huRvGJz^~XkI)3L?aW!#Pz#K+(#@vDS| z@DfQPH&L9ZOw=dV5>GWgO*hR^tyfz?J5)PSJ6F3>yH)#6S5P-vcSLtVZ_rEn0s1le z8Tuvq4f;L$6NWK{M`V=DPL81fRf(!cwW7LFgQyd9X?hfWgnmwcqW>~_hGS%=7SoS8 z#C%~(vcuR(>=pI_`-c6IL1eVb=w_6SU5sZS73$hRqvI$d!^CBe{7bl3{Ek!KLtvYK?>wFuq<+63L?XgStX7&RP z=qT;j2pI@r6y|~@VGYjXLH_Kl zfA!kf!m%v;IZ$GhCS*}LES z#AomczPy<(@YnD^3JeXr43-JD3@!>D37JAQLgPbQLwCZ#aF6h!@a;$-vLd=FrpDUF ze#M6+T#3<%%ZcAfT{4)glN_1ck$jgjr4p&qsUE4lieD+MY*KbBN0hV5HRZnYLiwot zQe$d4wSziMJ)btF2c#FJPo+=&2g_8T0{{R30RR925dc&G2LNRN1pop7Bmi0fmH+?% zfDf$z1ONhf+I5h%RRlm3MKA7z#GB#nkl+cf@w&TjI}|`jK~zD=#r=#u8~Z>naXM)0 z>;rD$4Yx5Ud&TW$?q0F;6!&kqlMx=?a2Gc`zTs|0+D_i^FiUMwb}3M#MVS-{l2i$> zz#Q`|iLO*)t|!#UlhQ57&M3R4bt0H&TeoVmpk9%WiL17vY=c=_0{5!c2s1;(WDZpd zwgg#X#6@{C74`g=+s41$z2nFdWsw7+EP;ZFgYt_A1{p*PV zd_8eO_%HpWsl3RaQ8zKenq$GS?7nIDMTyDB|9QcCHLQiLLIkTVuZm~z4P;%d9tr(Z z4mf%O=2}hQ0001Z+GN2)R3!iahSBfOu9t21vRz}_wr%5#?OBa&+cwV_wa%_>?N9e& z0szhDG13|`0HM5<39lK$MaFW8orDq2I3g$|lJUIZ9q)-Eni!0H;3J>-Oe}H4lRzR9 zNa71$naDSGahWS5Q%MS`q>)Yr-}%8Lev(NR+2l}8u1smiWTrBWDa>FxM`%w6X3~)| zIx&km%w;y6=|We!@r!vZV4=*IPj{KqgP!!FH;d@QVwSRuB^+frSLsU?{pimCHZhPv ztY8(ZS;=6AFqC0baF5}#kR?xeDl1uYkVClOiW}~D;E5OB_~0uW*~(7#a*(5(WRSC5 z>lDB;1D?j-wK!FP40Sz?LgaJqV@aH^c94DA#JZA&;Sl~>)g0aPp5sV^_ z0-O}WHLlZ!7rf*KH@U@a&T@`NJjNUwOckmyg)4%!Jj7CwtYg2TaG;tR?hr^20qkKk zCn!`jR)k>9M%J^1&1_|jVq|0_C5ly?;+3F8B`H}cN>!TD*-l%w@tY$4@Rwp`C{tO= zR*rI&r+gKtu%#)o*lHjMq9_jWtoVz#?{Ih7#(IjRIWygyHst4lR8?g4?kjsEJBMQf z0g9I+;T;UF%CQ#l-OWi#BE39a?|LPx^+u;K0h5eWG>&aqjAB=mwDLY*$z0MpvRu|v zKVWn@Qaahw3JBaMRnl&1fNH5X?G2L*JZI$>%~=Sb2tiJ!js}(FgOs6sVlV^YKLdd> z5HSM@GmtQ229$wF8HkvHgdd`M)MKr)HfCC{cAEPiV5E)&CWxNUueJDWYbTM)JJ_)xw2J62938VBp*E^r)=p|-MgfFQ#;MtFLE>JW z^)TgR9IbYmr1eggua(8fLVj_IEZ<|xqxDtOUNwibwa8lOHCn5N(zfniZ*Kko5am0~ z0001Z+G6nD!Egf5lN@Lu5JXWJJ~Qpzoob5&DI`*f1PMgf8VN}yYyj#3dW0Yr>REO~ zDy-hDnyT&v|92Ll5#qRt#Z^IJy*2QHGcfGd2yGkePaN&^RKqKIl=;$3j^)7ZP?g@~ z2T73&oa6M}We~D2D+CGn|JJTZ0ov3>G!J8rLfac{TDC6=AAnv}Ow-a$?I0YE*2~{u ikNv3}$-dj7GEu&L5s!WB9E^lM(Z+?=Pj#0tR{#L!3+H42 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-700.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-700.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..243810e4594c4c858d043e9dd3562bfd3b1adeff GIT binary patch literal 19088 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𝔔r>VIw5E?pF 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 D>w0-m literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-700italic.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-700italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..31e8f499b34627da8f6a4b7c99eac45d925b8d40 GIT binary patch literal 20192 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 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-800.woff b/ui-static/vendor/fonts/nunito-v16-latin-800.woff new file mode 100644 index 0000000000000000000000000000000000000000..d415e7de4553d653c17574ff0e52f67f20670a7f GIT binary patch literal 23576 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°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 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-800italic.woff b/ui-static/vendor/fonts/nunito-v16-latin-800italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..d987d1e0e64f9717efd84ace7cc56e2c4bcbfaa7 GIT binary patch literal 24960 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

_(*R|6)M^EM{ISECG_xxtg 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 W5s!WB9E^lM(Z+?=Pj#0tR{#K5wwe0? literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-800italic.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-800italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a206080faee452b66a1fa3a9031a3791036e673c GIT binary patch literal 20648 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 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-900.woff b/ui-static/vendor/fonts/nunito-v16-latin-900.woff new file mode 100644 index 0000000000000000000000000000000000000000..00a55dc7bade514e36cb273f6220dc88e9c1c6ad GIT binary patch literal 24024 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%0D%v!00aO6c-nQ4wN(T_6h$xY z#DrwHJ0y65YmBbDZ#!EC6;Kf|H&~Z5_MeRbqXZbBZ*UB#K(+2;hDx>WXPTdCJ-{lz z)q1eWGsG)@)q0qjzUyi|&34}xXCz5cCPSDYA#%*J$vPWsTRqEsyWWx~p(969bgw%} zMCB9xoT%n>Cgro@dFz}lB|2oy7Bs6v-v_IvlEjfEX$unLn}Ah9PgXwBblO7O*{8bS z-66`Osi2zJbGKM!7w^rHi95%IRdaTwDf?>HyQ+99v(0Hy&KA&_#~dMD2`k@4LB}fl zj!nmoxfOF?e2W))o>u*h>R)8#{B3d0desruxk^$tsD8&K*Zu8Ak-AL^7T?yyHDlL=!_SANa^8J`+bg2_%xl1d{o}S0?g}U0mi0DO8e58b;E| z;5$E<#7{EGBAXn_$(1SXn9NkBF@+gS=LqfTz)U()Mki)5hq=tAGhOIPH-0gX1uT>q z^XV>gdeD6=PG@vq96Skz$OMVh!w12H7gm+5QZ|03hpsn7P90C zPh}-*4sr+=Tyeu44?OY08y|dSBU{?gQ-Flrf@~DmWNm>l6CA?6b@8V!yN(%B7i-N<^+X`#)=TE*~ogfu$ir_QH)|4 zNr~bVuLLD3Ny$o4s?uasI@@W>HhxpYAO2FT3}q@y*~(F_@|3Rv6}B`*7F!JjK@`Oy zo)v!)_Z{vo+gMMLG-sxp(}w&!kgAHT-hE|nWan^fAVBeQB)o&cRXNrozPmX|Nu-yj z>s_y8wch9yCSa10ipH@mi&5-~l2+d5E164LN0!Ta>IaN2M@lDqS^0Oi0ygs*>m_(EfcC#+Q(ul87vHjbaRPQB6S>$Mi2ZS5pd zc?UZdgjNy#g`;EEBh*H<%i2jz%qW1+$v8FpElAv}vmU0LjHA^~leFH+^0l%US;#L= zk>z`Ad9=Q2+N&?v{G8jA5c-ms{-obDJ(32c!AP_`R7(O%Y z-JNQS1Sup^i3ABm*BS{)C2Rod0eXZW7V24cL@KP_teUFs1^;&zp%LP^ip5nyVZAl* zf-^Ae)(CAI>`xr+^i;zud6fCmOpfKi?NF88;P3<5Yj@HZHV2}N&9Lc`hqB2pweG!j+>>P}QKGDX7 L)=zbpFjoKo)K!nm literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-900.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-900.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a01f10d65072cc36da27ebff1f0f4fedbcf82f23 GIT binary patch literal 19796 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�?lv-Rhv@Skn#ImzH$ICTWsJR0NpXUXhw7f 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 VkA3VMjD$YX#)Z~Tb(b(#007;QEqDL` literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-900italic.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-900italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..41cb13d72cbea219135ce720a98f0836f68fb016 GIT binary patch literal 20792 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 H0RR91rl}Hf literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-italic.woff b/ui-static/vendor/fonts/nunito-v16-latin-italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..c8fd3dfbec8ec6fe71e90ab692dc6b4f6615f7d9 GIT binary patch literal 24472 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 t)6z}tARLa?%imy+{iz(uzT2WQQNDcp literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-italic.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..7d21ce4f3bb3994c165149e3f053ba24c6467978 GIT binary patch literal 20000 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 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-regular.woff b/ui-static/vendor/fonts/nunito-v16-latin-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..a1cfbe29c5274cdeef7bc1f135ff8da25bcf0e76 GIT binary patch literal 23124 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 literal 0 HcmV?d00001 diff --git a/ui-static/vendor/fonts/nunito-v16-latin-regular.woff2 b/ui-static/vendor/fonts/nunito-v16-latin-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2f9cc5964455b8f5ca989db989250dbebb1a5f66 GIT binary patch literal 18972 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 - + From b1d9ad8c93b55202a63a108ecb8a2456974720da Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 17 Aug 2021 21:59:48 +0100 Subject: [PATCH 013/156] Fix normalised files being incorrectly named. --- file_manager.py | 9 ++++++--- helpers/normalisation.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/file_manager.py b/file_manager.py index c853f06..1e5e54f 100644 --- a/file_manager.py +++ b/file_manager.py @@ -83,6 +83,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): self.logger.log.warning("Failed to remove, skipping. Likely file is still in use.") continue self.channel_received[channel] = True + self.known_channels_preloaded = [False]*self.channel_count # If we receive a new status message, let's check for files which have not been pre-loaded. if command == "STATUS": @@ -112,7 +113,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): if (not preloaded and not normalised): # We didn't do any hard work, let's sleep. - sleep(0.5) + sleep(0.2) except Exception as e: self.logger.log.exception( @@ -181,8 +182,10 @@ def do_normalise(self): if not filename: self.logger.log.exception("Somehow got empty filename when all channels are preloaded.") continue # Try next song. - - if "normalised" in filename: + elif (not os.path.isfile(filename)): + self.logger.log.exception("Filename for normalisation does not exist. This is bad.") + continue + elif "normalised" in filename: continue # Sweet, we now need to try generating a normalised version. try: diff --git a/helpers/normalisation.py b/helpers/normalisation.py index 9622c3a..b8eec49 100644 --- a/helpers/normalisation.py +++ b/helpers/normalisation.py @@ -16,7 +16,7 @@ def generate_normalised_file(filename: str): if filename.endswith("-normalised.mp3"): return filename - normalised_filename = "{}-normalised.mp3".format(filename.rstrip(".mp3")) + normalised_filename = "{}-normalised.mp3".format(filename.rsplit(".",1)[0]) # The file already exists, short circuit. if (os.path.exists(normalised_filename)): From b3ac0a83720c42feb88eb364b41ba4a14a0f9de9 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 17 Aug 2021 22:04:20 +0100 Subject: [PATCH 014/156] Fix comment. --- helpers/normalisation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/normalisation.py b/helpers/normalisation.py index b8eec49..900ffee 100644 --- a/helpers/normalisation.py +++ b/helpers/normalisation.py @@ -7,7 +7,7 @@ def match_target_amplitude(sound, target_dBFS): change_in_dBFS = target_dBFS - sound.dBFS return sound.apply_gain(change_in_dBFS) -# Takes +# Takes filename in, normalialises it and returns a normalised file path. def generate_normalised_file(filename: str): if (not (isinstance(filename, str) and filename.endswith(".mp3"))): raise ValueError("Invalid filename given.") From 27974ab3928b0b5dce27bfbe7220fc03d2318f4b Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 17 Aug 2021 22:24:28 +0100 Subject: [PATCH 015/156] Optimise preload/normalisation idle states. --- file_manager.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/file_manager.py b/file_manager.py index 1e5e54f..ed6d482 100644 --- a/file_manager.py +++ b/file_manager.py @@ -34,6 +34,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): self.last_known_show_plan = [[]]*self.channel_count self.next_channel_preload = 0 self.known_channels_preloaded = [False]*self.channel_count + self.known_channels_normalised = [False]*self.channel_count self.last_known_item_ids = [[]]*self.channel_count try: @@ -84,6 +85,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): continue self.channel_received[channel] = True self.known_channels_preloaded = [False]*self.channel_count + self.known_channels_normalised = [False]*self.channel_count # If we receive a new status message, let's check for files which have not been pre-loaded. if command == "STATUS": @@ -124,6 +126,11 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): # Attempt to preload a file onto disk. def do_preload(self): channel = self.next_channel_preload + + # All channels have preloaded all files, do nothing. + if (self.known_channels_preloaded == [True]*self.channel_count): + return False # Didn't preload anything + # Right, let's have a quick check in the status for shows without filenames, to preload them. # Keep an eye on if we downloaded anything. # If we didn't, we know that all items in this channel have been downloaded. @@ -151,10 +158,9 @@ def do_preload(self): # We didn't download anything this time, file was already loaded. # Let's try the next one. continue - # Given we probably took some time to download, let's not sleep in the loop. - if not downloaded_something: - # Tell the file manager that this channel is fully downloaded, this is so it can consider normalising once all channels have files. - self.known_channels_preloaded[channel] = True + + # Tell the file manager that this channel is fully downloaded, this is so it can consider normalising once all channels have files. + self.known_channels_preloaded[channel] = not downloaded_something self.next_channel_preload += 1 if self.next_channel_preload >= self.channel_count: @@ -168,7 +174,10 @@ def do_normalise(self): # Some channels still have files to preload, do nothing. if (self.known_channels_preloaded != [True]*self.channel_count): return False # Didn't normalise - # TODO: quit early if all channels are normalised already. + + # Quit early if all channels are normalised already. + if (self.known_channels_normalised == [True]*self.channel_count): + return False channel = self.next_channel_preload @@ -200,7 +209,7 @@ def do_normalise(self): self.logger.log.exception("Failed to generate normalised file.", str(e)) continue - + self.known_channels_normalised[channel] = not normalised_something self.next_channel_preload += 1 if self.next_channel_preload >= self.channel_count: From c2141c773db4e807fbcd4dbdda732c4685cf95e2 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 17 Aug 2021 23:04:42 +0100 Subject: [PATCH 016/156] Fix error animation --- ui-templates/404.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-templates/404.html b/ui-templates/404.html index 0f7204b..5c3bae0 100644 --- a/ui-templates/404.html +++ b/ui-templates/404.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block content_inner %}

-
404
+
404

Page Not Found

Looks like you fell off the tip of the iceberg.

← Escape Back Home From 12616794024a17592cfbd2db584eddb63bdfa9b5 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 17 Aug 2021 23:05:43 +0100 Subject: [PATCH 017/156] Fix UI being 0 width on small window widths. --- ui-templates/base.html | 48 ++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/ui-templates/base.html b/ui-templates/base.html index dd44710..93b8c39 100644 --- a/ui-templates/base.html +++ b/ui-templates/base.html @@ -24,30 +24,27 @@ -
- - -
- -
-
- -

BAPSicle

-
- {% if data.ui_menu is undefined or data.ui_menu is true %} -
- - Status - - - Player Config - - - Server Config - - - Logs +
+ +
+ +

BAPSicle

+ {% if data.ui_menu is undefined or data.ui_menu is true %} +
{% endif %}
@@ -56,7 +53,7 @@

BAPSicle

-
+

{{ data.ui_title }}

@@ -77,11 +74,8 @@

Woops.

{% endblock %}
-
-
-
From f28f76fbc7362b6fc5918f4d588d04cbccd9e715 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 17 Aug 2021 23:54:24 +0100 Subject: [PATCH 018/156] Get sanic to log to file. --- web_server.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/web_server.py b/web_server.py index 60ab571..ccc1d7e 100644 --- a/web_server.py +++ b/web_server.py @@ -1,8 +1,8 @@ from helpers.normalisation import get_normalised_filename_if_available from helpers.myradio_api import MyRadioAPI -from sanic import Sanic +from sanic import Sanic, log from sanic.exceptions import NotFound, abort -from sanic.response import html, text, file, redirect +from sanic.response import html, file, redirect from sanic.response import json as resp_json from sanic_cors import CORS from syncer import sync @@ -11,7 +11,6 @@ from jinja2 import Environment, FileSystemLoader from urllib.parse import unquote from setproctitle import setproctitle -import logging from typing import Any, Optional, List from multiprocessing.queues import Queue from queue import Empty @@ -26,7 +25,57 @@ from helpers.the_terminator import Terminator env = Environment(loader=FileSystemLoader('%s/ui-templates/' % os.path.dirname(__file__))) -app = Sanic("BAPSicle Web Server") + +# From Sanic's default, but set to log to file. +LOGGING_CONFIG = dict( + version=1, + disable_existing_loggers=False, + loggers={ + "sanic.root": {"level": "INFO", "handlers": ["file"]}, + "sanic.error": { + "level": "INFO", + "handlers": ["error_file"], + "propagate": True, + "qualname": "sanic.error", + }, + "sanic.access": { + "level": "INFO", + "handlers": ["access_file"], + "propagate": True, + "qualname": "sanic.access", + }, + }, + handlers={ + "file": { + "class": "logging.FileHandler", + "formatter": "generic", + "filename": "logs/WebServer.log" + }, + "error_file": { + "class": "logging.FileHandler", + "formatter": "generic", + "filename": "logs/WebServer.log" + }, + "access_file": { + "class": "logging.FileHandler", + "formatter": "access", + "filename": "logs/WebServer.log" + }, + }, + formatters={ + "generic": { + "format": "%(asctime)s | [%(process)d] [%(levelname)s] %(message)s", + "class": "logging.Formatter", + }, + "access": { + "format": "%(asctime)s | (%(name)s)[%(levelname)s][%(host)s]: " + + "%(request)s %(message)s %(status)d %(byte)d", + "class": "logging.Formatter", + }, + }, +) + +app = Sanic("BAPSicle Web Server", log_config=LOGGING_CONFIG) def render_template(file, data, status=200): From 7095c6425c52e09d9e9666343bd86ed5ccdaf119 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 1 Sep 2021 21:27:25 +0100 Subject: [PATCH 019/156] show current tracklist on status page. --- ui-templates/status.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-templates/status.html b/ui-templates/status.html index 162e854..a67f86e 100644 --- a/ui-templates/status.html +++ b/ui-templates/status.html @@ -12,7 +12,8 @@

Player {{player.channel}}

Initialised: {{player.initialised}}
- Fader Live: {{player.live}} + Fader Live: {{player.live}}
+ Current Tracklist: {{player.tracklist_id}}

Play {% if player.paused %} From 6c80a8c192be8edc420c8fcaba34f1bfdcada700 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 1 Sep 2021 22:25:44 +0100 Subject: [PATCH 020/156] Fix logging to render escaped classes etc. Improve colouring --- helpers/state_manager.py | 8 ++++---- web_server.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/helpers/state_manager.py b/helpers/state_manager.py index e91fbd5..6e9dee9 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -168,7 +168,7 @@ def update(self, key: str, value: Any, index: int = -1): # Just some debug logging. if update_file and (key not in ["playing", "loaded", "initialised"]): self._log("Key: {},\nnew:{}\nold:{}, ".format(key, getattr(value, "__dict__", None), getattr(state_to_update[key], "__dict__", None)), DEBUG) - self._log("Not updating state for key {} with value {} of type {}.".format(key, value, type(value)), DEBUG) + self._log("Not updating state for key '{}' with value '{}' of type '{}'.".format(key, value, type(value)), DEBUG) # We're trying to update the state with the same value. # In this case, ignore the update @@ -177,11 +177,11 @@ def update(self, key: str, value: Any, index: int = -1): if index > -1 and key in state_to_update: if not isinstance(state_to_update[key], list): - self._log("Not updating state for key {} with value {} of type {} since index is set and key is not a list.".format(key, value, type(value)), DEBUG) + self._log("Not updating state for key '{}' with value '{}' of type '{}' since index is set and key is not a list.".format(key, value, type(value)), DEBUG) return list_items = state_to_update[key] if index >= len(list_items): - self._log("Not updating state for key {} with value {} of type {} because index {} is too large..".format(key, value, type(value), index), DEBUG) + self._log("Not updating state for key '{}' with value '{}' of type '{}' because index '{}' is too large..".format(key, value, type(value), index), DEBUG) return list_items[index] = value state_to_update[key] = list_items @@ -191,7 +191,7 @@ def update(self, key: str, value: Any, index: int = -1): self.state = state_to_update if update_file: - self._log("Writing change to key {} with value {} of type {} to disk.".format(key, value, type(value)), DEBUG) + self._log("Writing change to key '{}' with value '{}' of type '{}' to disk.".format(key, value, type(value)), DEBUG) # Either a routine write, or state has changed. # Update the file self.write_to_file(state_to_update) diff --git a/web_server.py b/web_server.py index 833a5fd..cbec8fc 100644 --- a/web_server.py +++ b/web_server.py @@ -1,5 +1,3 @@ -from helpers.normalisation import get_normalised_filename_if_available -from helpers.myradio_api import MyRadioAPI from sanic import Sanic, log from sanic.exceptions import NotFound, abort from sanic.response import html, file, redirect @@ -7,8 +5,8 @@ from sanic_cors import CORS from syncer import sync import asyncio - from jinja2 import Environment, FileSystemLoader +from jinja2.utils import select_autoescape from urllib.parse import unquote from setproctitle import setproctitle from typing import Any, Optional, List @@ -23,8 +21,10 @@ from helpers.device_manager import DeviceManager from helpers.state_manager import StateManager from helpers.the_terminator import Terminator +from helpers.normalisation import get_normalised_filename_if_available +from helpers.myradio_api import MyRadioAPI -env = Environment(loader=FileSystemLoader('%s/ui-templates/' % os.path.dirname(__file__))) +env = Environment(loader=FileSystemLoader('%s/ui-templates/' % os.path.dirname(__file__)), autoescape=select_autoescape()) # From Sanic's default, but set to log to file. LOGGING_CONFIG = dict( From 0e040d2d2280b0fec3250a47ffed865715e1823a Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 1 Sep 2021 22:26:40 +0100 Subject: [PATCH 021/156] Fix controller to serial "None" option as None correctly. --- controllers/mattchbox_usb.py | 18 ++++++++++++------ web_server.py | 4 +++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index 5f22777..00f20aa 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -38,6 +38,7 @@ def __init__( # Allow server config changes to trigger controller reload if required. self.port = None self.next_port = self.server_state.get()["serial_port"] + self.logger.log.info("Server config gives port as: {}".format(self.next_port)) self.server_from_q = server_from_q self.server_to_q = server_to_q @@ -57,10 +58,13 @@ def _state_handler(self): self.port = None self.next_port = new_port - def connect(self, port: Optional[str]): - # If we loose the controller, make sure to set channels live, so we tracklist. + def _disconnected(self): + # If we lose the controller, make sure to set channels live, so we tracklist. for i in range(len(self.server_from_q)): self.sendToPlayer(i, "SETLIVE:True") + self.server_state.update("ser_connected", False) + + def connect(self, port: Optional[str]): if port: # connect to serial port @@ -71,8 +75,10 @@ def connect(self, port: Optional[str]): self.logger.log.info("Connected to serial port {}".format(port)) except serial.SerialException as e: self.logger.log.error( - "Could not open serial port {}: {}".format(port, e) + "Could not open serial port" + str(port), + e ) + self._disconnected() self.ser = None else: self.ser = None @@ -89,7 +95,7 @@ def handler(self): ) # Endianness doesn't matter for 1 byte. self.logger.log.info("Received from controller: " + str(line)) if line == 255: - self.ser.write(b"\xff") # Send 255 back + self.ser.write(b"\xff") # Send 255 back, this is a keepalive. elif line in [51,52,53]: # We've received a status update about fader live status, fader is down. self.sendToPlayer(line-51, "SETLIVE:False") @@ -108,7 +114,7 @@ def handler(self): elif self.port: # If there's still a port set, just wait a moment and see if it's been reconnected. - self.server_state.update("ser_connected", False) + self._disconnected() time.sleep(10) self.connect(self.port) @@ -116,7 +122,7 @@ def handler(self): # We're not already connected, or a new port connection is to be made. if self.ser: self.ser.close() - self.server_state.update("ser_connected", False) + self._disconnected() if self.next_port is not None: self.connect(self.next_port) diff --git a/web_server.py b/web_server.py index cbec8fc..32f9446 100644 --- a/web_server.py +++ b/web_server.py @@ -164,7 +164,9 @@ def ui_config_server_update(request): server_state.update("port", int(request.form.get("port"))) server_state.update("num_channels", int(request.form.get("channels"))) server_state.update("ws_port", int(request.form.get("ws_port"))) - server_state.update("serial_port", request.form.get("serial_port")) + + serial_port = request.form.get("serial_port") + server_state.update("serial_port", None if serial_port == "None" else serial_port) # Because we're not showing the api key once it's set. if "myradio_api_key" in request.form and request.form.get("myradio_api_key") != "": From be885d87e4b8ec85141d2468f7aa1d54fa5ea7a8 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 1 Sep 2021 22:32:23 +0100 Subject: [PATCH 022/156] Fix API debug prints --- helpers/myradio_api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 6621327..7f3703c 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -19,7 +19,7 @@ from typing import Optional import aiohttp import json -from logging import INFO, ERROR, WARNING +from logging import INFO, ERROR, WARNING, DEBUG import os import requests import time @@ -44,7 +44,7 @@ async def async_call(self, url, method="GET", data=None, timeout=10): func = session.get(url) status_code = -1 if method == "GET": - #func = session.get(url) + func = session.get(url) status_code = 200 elif method == "POST": func = session.post(url, data=data) @@ -234,13 +234,13 @@ async def get_filename(self, item: PlanItem, did_download: bool = False): ) # Check if we already downloaded the file. If we did, give that. if os.path.isfile(filename): - self.logger.log.debug("Already got file. " + filename) + self._log("Already got file: " + filename, DEBUG) return (filename, False) if did_download else filename # If something else (another channel, the preloader etc) is downloading the track, wait for it. if os.path.isfile(filename + dl_suffix): time_waiting_s = 0 - self.logger.log.debug("Waiting for download to complete from another worker. " + filename) + self._log("Waiting for download to complete from another worker. " + filename, DEBUG) while time_waiting_s < 20: # TODO: Make something better here. # If the connectivity is super poor or we're loading reeaaaalllly long files, this may be annoying, but this is just in case somehow the other api download gives up. @@ -248,7 +248,7 @@ async def get_filename(self, item: PlanItem, did_download: bool = False): # Now the file is downloaded successfully return (filename, False) if did_download else filename time_waiting_s +=1 - self.logger.log.debug("Still waiting") + self._log("Still waiting",DEBUG) time.sleep(1) # File doesn't exist, download it. From 1de449fe0c9aca78f8164406c74b78ec7c16f4f7 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 1 Sep 2021 23:23:50 +0100 Subject: [PATCH 023/156] Fix tracklisting start timer not clearing. --- player.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/player.py b/player.py index d901963..a349beb 100644 --- a/player.py +++ b/player.py @@ -665,6 +665,7 @@ def _potentially_end_tracklist(self): if self.tracklist_end_timer: self.logger.log.error("Failed to potentially end tracklist, timer already busy.") return + self.state.update("tracklist_id", None) # This threads it, so it won't hang track loading if it fails. self.tracklist_end_timer = Timer(1, self._tracklist_end, [tracklist_id]) self.tracklist_end_timer.start() @@ -676,28 +677,29 @@ def _tracklist_start(self): loaded_item = state["loaded_item"] if not loaded_item: self.logger.log.error("Tried to call _tracklist_start() with no loaded item!") - return - if not self.isPlaying: + elif not self.isPlaying: self.logger.log.info("Not tracklisting since not playing.") - return - tracklist_id = state["tracklist_id"] - if (not tracklist_id): - if (state["tracklist_mode"] == "fader-live" and not state["live"]): - self.logger.log.info("Not tracklisting since fader is not live.") - else: - self.logger.log.info("Tracklisting item: {}".format(loaded_item.name)) - tracklist_id = self.api.post_tracklist_start(loaded_item) - if not tracklist_id: - self.logger.log.warning("Failed to tracklist {}".format(loaded_item.name)) - else: - self.logger.log.info("Tracklist id: {}".format(tracklist_id)) - self.state.update("tracklist_id", tracklist_id) else: - self.logger.log.info("Not tracklisting item {}, already got tracklistid: {}".format( - loaded_item.name, tracklist_id)) + tracklist_id = state["tracklist_id"] + if (not tracklist_id): + if (state["tracklist_mode"] == "fader-live" and not state["live"]): + self.logger.log.info("Not tracklisting since fader is not live.") + else: + self.logger.log.info("Tracklisting item: {}".format(loaded_item.name)) + tracklist_id = self.api.post_tracklist_start(loaded_item) + if not tracklist_id: + self.logger.log.warning("Failed to tracklist {}".format(loaded_item.name)) + else: + self.logger.log.info("Tracklist id: {}".format(tracklist_id)) + self.state.update("tracklist_id", tracklist_id) + else: + self.logger.log.info("Not tracklisting item {}, already got tracklistid: {}".format( + loaded_item.name, tracklist_id)) + + # No matter what we end up doing, we need to kill this timer so future ones can run. self.tracklist_start_timer = None def _tracklist_end(self, tracklist_id): From d3df33ecf01ec83dcb61ae00513d1836ec681713 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 1 Sep 2021 23:40:28 +0100 Subject: [PATCH 024/156] More logging improvements. --- helpers/myradio_api.py | 2 +- helpers/state_manager.py | 3 +-- player.py | 14 +++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 7f3703c..d73cacf 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -346,7 +346,7 @@ def post_tracklist_start(self, item: PlanItem): self._log("Not tracklisting, {} is not a track.".format(item.name)) return False - self._log("Tracklisting item: {}".format(item.name)) + self._log("Tracklisting item: '{}'".format(item.name)) source: str = self.config.get()["myradio_api_tracklist_source"] data = { diff --git a/helpers/state_manager.py b/helpers/state_manager.py index 6e9dee9..fb45cb2 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -166,8 +166,7 @@ def update(self, key: str, value: Any, index: int = -1): if not allow: # Just some debug logging. - if update_file and (key not in ["playing", "loaded", "initialised"]): - self._log("Key: {},\nnew:{}\nold:{}, ".format(key, getattr(value, "__dict__", None), getattr(state_to_update[key], "__dict__", None)), DEBUG) + if update_file and (key not in ["playing", "loaded", "initialised", "remaining", "pos_true"]): self._log("Not updating state for key '{}' with value '{}' of type '{}'.".format(key, value, type(value)), DEBUG) # We're trying to update the state with the same value. diff --git a/player.py b/player.py index a349beb..c7eefc3 100644 --- a/player.py +++ b/player.py @@ -659,9 +659,9 @@ def _potentially_end_tracklist(self): self.logger.log.info("No tracklist to end.") return - self.logger.log.info("Setting timer for ending tracklist_id {}".format(tracklist_id)) + self.logger.log.info("Setting timer for ending tracklist_id '{}'".format(tracklist_id)) if tracklist_id: - self.logger.log.info("Attempting to end tracklist_id {}".format(tracklist_id)) + self.logger.log.info("Attempting to end tracklist_id '{}'".format(tracklist_id)) if self.tracklist_end_timer: self.logger.log.error("Failed to potentially end tracklist, timer already busy.") return @@ -688,15 +688,15 @@ def _tracklist_start(self): if (state["tracklist_mode"] == "fader-live" and not state["live"]): self.logger.log.info("Not tracklisting since fader is not live.") else: - self.logger.log.info("Tracklisting item: {}".format(loaded_item.name)) + self.logger.log.info("Tracklisting item: '{}'".format(loaded_item.name)) tracklist_id = self.api.post_tracklist_start(loaded_item) if not tracklist_id: - self.logger.log.warning("Failed to tracklist {}".format(loaded_item.name)) + self.logger.log.warning("Failed to tracklist '{}'".format(loaded_item.name)) else: - self.logger.log.info("Tracklist id: {}".format(tracklist_id)) + self.logger.log.info("Tracklist id: '{}'".format(tracklist_id)) self.state.update("tracklist_id", tracklist_id) else: - self.logger.log.info("Not tracklisting item {}, already got tracklistid: {}".format( + self.logger.log.info("Not tracklisting item '{}', already got tracklistid: '{}'".format( loaded_item.name, tracklist_id)) # No matter what we end up doing, we need to kill this timer so future ones can run. @@ -705,7 +705,7 @@ def _tracklist_start(self): def _tracklist_end(self, tracklist_id): if tracklist_id: - self.logger.log.info("Attempting to end tracklist_id {}".format(tracklist_id)) + self.logger.log.info("Attempting to end tracklist_id '{}'".format(tracklist_id)) self.api.post_tracklist_end(tracklist_id) else: self.logger.log.error("Tracklist_id to _tracklist_end() missing. Failed to end tracklist.") From 127d99539950ee9a29522a36417f2d14bb9b39ac Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 1 Sep 2021 23:50:12 +0100 Subject: [PATCH 025/156] Fix duplicate get request breaking await --- helpers/myradio_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index d73cacf..28ada7a 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -44,7 +44,6 @@ async def async_call(self, url, method="GET", data=None, timeout=10): func = session.get(url) status_code = -1 if method == "GET": - func = session.get(url) status_code = 200 elif method == "POST": func = session.post(url, data=data) From be1855236a88d9a2ec24e7d1862b00213d8cbe45 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 2 Sep 2021 00:08:39 +0100 Subject: [PATCH 026/156] Fix api call logic warnings. --- helpers/myradio_api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 28ada7a..7cf1efd 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -41,9 +41,8 @@ def __init__(self, logger: LoggingManager, config: StateManager): async def async_call(self, url, method="GET", data=None, timeout=10): async with aiohttp.ClientSession(read_timeout=timeout) as session: - func = session.get(url) - status_code = -1 if method == "GET": + func = session.get(url) status_code = 200 elif method == "POST": func = session.post(url, data=data) @@ -51,6 +50,8 @@ async def async_call(self, url, method="GET", data=None, timeout=10): elif method == "PUT": func = session.put(url) status_code = 201 + else: + return async with func as response: if response.status != status_code: @@ -61,8 +62,6 @@ async def async_call(self, url, method="GET", data=None, timeout=10): return await response.read() def call(self, url, method="GET", data=None, timeout=10, json_payload=True): - r = None - status_code = -1 if method == "GET": r = requests.get(url, timeout=timeout) status_code = 200 @@ -72,6 +71,8 @@ def call(self, url, method="GET", data=None, timeout=10, json_payload=True): elif method == "PUT": r = requests.put(url, data, timeout=timeout) status_code = 200 + else: + return if r.status_code != status_code: self._logException( From e3854b524853f38005b625d34885d5956529de83 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 2 Sep 2021 00:10:32 +0100 Subject: [PATCH 027/156] Change order of shutdown to shorten audio gap --- server.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/server.py b/server.py index f92f07d..89f8d05 100644 --- a/server.py +++ b/server.py @@ -242,15 +242,19 @@ def stopServer(self): self.websockets_server.join(timeout=PROCESS_KILL_TIMEOUT_S) del self.websockets_server - print("Stopping Players") - for q in self.player_to_q: - q.put("ALL:QUIT") - - for player in self.player: - player.join(timeout=PROCESS_KILL_TIMEOUT_S) + print("Stopping File Manager") + if self.file_manager: + self.file_manager.terminate() + self.file_manager.join(timeout=PROCESS_KILL_TIMEOUT_S) + del self.file_manager - del self.player + print("Stopping Controllers") + if self.controller_handler: + self.controller_handler.terminate() + self.controller_handler.join(timeout=PROCESS_KILL_TIMEOUT_S) + del self.controller_handler + # Stop the Webserver late as we can to allow Presenter to pull images for the disconnection page if it needs to. print("Stopping Web Server") if self.webserver: self.webserver.terminate() @@ -263,17 +267,16 @@ def stopServer(self): self.player_handler.join(timeout=PROCESS_KILL_TIMEOUT_S) del self.player_handler - print("Stopping File Manager") - if self.file_manager: - self.file_manager.terminate() - self.file_manager.join(timeout=PROCESS_KILL_TIMEOUT_S) - del self.file_manager + # Now we've stopped everything else, now is the time to stop the players. This is to keep playing for as long as possible during a restart. + print("Stopping Players") + for q in self.player_to_q: + q.put("ALL:QUIT") + + for player in self.player: + player.join(timeout=PROCESS_KILL_TIMEOUT_S) + + del self.player - print("Stopping Controllers") - if self.controller_handler: - self.controller_handler.terminate() - self.controller_handler.join(timeout=PROCESS_KILL_TIMEOUT_S) - del self.controller_handler print("Stopped all processes.") From 0076ea9c244d917e85786509a2263f2211bb03e1 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 2 Sep 2021 00:34:36 +0100 Subject: [PATCH 028/156] Add default audio output option and improve text. --- ui-templates/config_player.html | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ui-templates/config_player.html b/ui-templates/config_player.html index 982f6e6..2b329e4 100644 --- a/ui-templates/config_player.html +++ b/ui-templates/config_player.html @@ -4,13 +4,21 @@

Audio Outputs

Please note: Currently BAPSicle does not support choosing which Host API is used. Only supported options can be selected.
- On MacOS: The available inputs will not update automatically, the server must be restarted. + On MacOS: The available output devices will not update automatically, the BAPSicle server must be restarted.

Currently Selected

{% for channel in data.channels %} - Channel {{channel.channel}}: {% if channel.output %}{{channel.output}}{% else %}Default Audio Device{% endif %}
+ Player {{channel.channel}}: {% if channel.output %}{{channel.output}}{% else %}Default Audio Device{% endif %}
{% endfor %} +
+ +Set for: +{% for channel in data.channels %} + Player {{channel.channel}} / +{% endfor %} +Default Audio Output +

{% for host_api in data.outputs %} {{host_api.name}} @@ -21,12 +29,12 @@

Currently Selected

Set for: {% for channel in data.channels %} {% if channel.output == output.name %} - Channel {{channel.channel}} + Player {{channel.channel}} {% else %} - Channel {{channel.channel}} + Player {{channel.channel}} {% endif %} + / {% endfor %} - - {% endif %}{% if output.name %}{{output.name}}{% else %}System Default Output{% endif %}
{% endfor %} From d6bf061c6512f2dfc50e275d491fbff192845b1d Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 2 Sep 2021 00:35:03 +0100 Subject: [PATCH 029/156] Reinitialise audio output on every load, to fix silent channels. --- player.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/player.py b/player.py index c7eefc3..8ef5b06 100644 --- a/player.py +++ b/player.py @@ -396,9 +396,13 @@ def clear_channel_plan(self) -> bool: def load(self, weight: int): if not self.isPlaying: + loaded_state = self.state.get() self.unload() - showplan = self.state.get()["show_plan"] + self.logger.log.info("Resetting output (in case of sound output gone silent somehow) to " + str(loaded_state["output"])) + self.output(loaded_state["output"]) + + showplan = loaded_state["show_plan"] loaded_item: Optional[PlanItem] = None From 9ce43d542c877ca4d76d6c1a3c825f2aa675c919 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 2 Sep 2021 21:10:37 +0100 Subject: [PATCH 030/156] Fix error handling for non existent serial ports --- controllers/mattchbox_usb.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index 00f20aa..1df27ca 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -73,10 +73,9 @@ def connect(self, port: Optional[str]): try: self.ser.open() self.logger.log.info("Connected to serial port {}".format(port)) - except serial.SerialException as e: + except (FileNotFoundError, serial.SerialException) as e: self.logger.log.error( - "Could not open serial port" + str(port), - e + "Could not open serial port {}:\n{}".format(port, e) ) self._disconnected() self.ser = None From 3ac694e21cfc56900dc5d019ed5f67dae428aa21 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 2 Sep 2021 21:28:50 +0100 Subject: [PATCH 031/156] Make uninitialised sound-card output obvious --- ui-templates/config_player.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ui-templates/config_player.html b/ui-templates/config_player.html index 2b329e4..1c09a90 100644 --- a/ui-templates/config_player.html +++ b/ui-templates/config_player.html @@ -9,7 +9,16 @@

Audio Outputs

Currently Selected

{% for channel in data.channels %} - Player {{channel.channel}}: {% if channel.output %}{{channel.output}}{% else %}Default Audio Device{% endif %}
+ Player {{channel.channel}}: + {% if channel.output %} + {{channel.output}} + {% else %} + Default Audio Device + {% endif %} + {% if not channel.initialised %} + - ERROR: Player did not start, potentially missing sound output? + {% endif %} +
{% endfor %}
From 95c08438656e57f1a6ff520bf42aa204e6061f60 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 3 Sep 2021 19:56:23 +0100 Subject: [PATCH 032/156] Add pydub requirement --- build/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/build/requirements.txt b/build/requirements.txt index 1d8b09f..eba9d34 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -13,3 +13,4 @@ typing_extensions==3.10.0.0 pyserial==3.5 requests==2.26.0 Jinja2==3.0.1 +pydub==0.25.1 From ff605bf57f2032f91a8432c16e347a46495f2386 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 3 Sep 2021 21:49:16 +0100 Subject: [PATCH 033/156] Update GET_PLAN to GETPLAN and add SETPLAYED --- player.py | 11 ++++++----- websocket_server.py | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/player.py b/player.py index 8ef5b06..48a2c11 100644 --- a/player.py +++ b/player.py @@ -598,14 +598,14 @@ def set_marker(self, timeslotitemid: str, marker_str: str): return success - def reset_played(self, weight: int): + def set_played(self, weight: int, played: bool): plan: List[PlanItem] = self.state.get()["show_plan"] if weight == -1: for item in plan: - item.play_count_reset() + item.play_count_increment() if played else item.play_count_reset() self.state.update("show_plan", plan) elif len(plan) > weight: - plan[weight].play_count_reset() + plan[weight].play_count_increment() if played else plan[weight].play_count_reset() self.state.update("show_plan", plan[weight], weight) else: return False @@ -1001,7 +1001,7 @@ def __init__( ) ), # Show Plan Items - "GET_PLAN": lambda: self._retMsg( + "GETPLAN": lambda: self._retMsg( self.get_plan(int(self.last_msg.split(":")[1])) ), "LOAD": lambda: self._retMsg( @@ -1021,7 +1021,8 @@ def __init__( ), "CLEAR": lambda: self._retMsg(self.clear_channel_plan()), "SETMARKER": lambda: self._retMsg(self.set_marker(self.last_msg.split(":")[1], self.last_msg.split(":", 2)[2])), - "RESETPLAYED": lambda: self._retMsg(self.reset_played(int(self.last_msg.split(":")[1]))), + "RESETPLAYED": lambda: self._retMsg(self.set_played(weight=int(self.last_msg.split(":")[1]), played = False)), + "SETPLAYED": lambda: self._retMsg(self.set_played(weight=int(self.last_msg.split(":")[1]), played = True)), "SETLIVE": lambda: self._retMsg(self.set_live(self.last_msg.split(":")[1] == "True")), } diff --git a/websocket_server.py b/websocket_server.py index 594b552..5be356d 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -146,7 +146,9 @@ def sendCommand(self, channel, data): extra += str(data["weight"]) elif command == "RESETPLAYED": extra += str(data["weight"]) - elif command == "GET_PLAN": + elif command == "SETPLAYED": + extra += str(data["weight"]) + elif command == "GETPLAN": extra += str(data["timeslotId"]) elif command == "SETMARKER": extra += "{}:{}".format( From ca96d7e1ab93c6e7da10b01f77ad0e49cb948728 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 3 Sep 2021 22:25:13 +0100 Subject: [PATCH 034/156] Fix other GETPLAN references --- file_manager.py | 2 +- web_server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/file_manager.py b/file_manager.py index ed6d482..96ad701 100644 --- a/file_manager.py +++ b/file_manager.py @@ -54,7 +54,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): command = message.split(":",2)[1] # If we have requested a new show plan, empty the music-tmp directory for the previous show. - if command == "GET_PLAN": + if command == "GETPLAN": if self.channel_received != [False]*self.channel_count and self.channel_received[channel] != True: # We've already received a delete trigger on a channel, let's not delete the folder more than once. diff --git a/web_server.py b/web_server.py index 32f9446..e8f6c79 100644 --- a/web_server.py +++ b/web_server.py @@ -297,7 +297,7 @@ def player_all_stop(request): def plan_load(request, timeslotid: int): for channel in player_to_q: - channel.put("UI:GET_PLAN:" + str(timeslotid)) + channel.put("UI:GETPLAN:" + str(timeslotid)) return redirect("/status") From cade8ecce819ddcaca64d34dbbccbe7dc1d57f9b Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 3 Sep 2021 23:23:21 +0100 Subject: [PATCH 035/156] Support recording played at times for licensing warnings in WS --- baps_types/plan.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/baps_types/plan.py b/baps_types/plan.py index 940fe58..f770e3a 100644 --- a/baps_types/plan.py +++ b/baps_types/plan.py @@ -16,9 +16,12 @@ import json from typing import Any, Dict, List, Optional, Union import os +from time import time from baps_types.marker import Marker +def _time_ms(): + return round(time() * 1000) class PlanItem: _timeslotitemid: str = "0" _weight: int = 0 @@ -29,6 +32,7 @@ class PlanItem: _managedid: Optional[int] _markers: List[Marker] = [] _play_count: int + _played_at: int _clean: bool @property @@ -59,14 +63,22 @@ def filename(self, value: Optional[str]): def play_count(self) -> int: return self._play_count + @property + def played_at(self) -> int: + return self._played_at + def play_count_increment(self): self._play_count += 1 + self._played_at = _time_ms() def play_count_decrement(self): self._play_count = max(0,self._play_count - 1) + if self._play_count == 0: + self._played_at = 0 def play_count_reset(self): self._play_count = 0 + self._played_at = 0 @property def name(self) -> str: @@ -150,6 +162,7 @@ def __dict__(self): "outro": self.outro, "markers": self.markers, "played": self.play_count > 0, + "played_at": self.played_at, "play_count": self.play_count, "clean": self.clean } @@ -173,6 +186,7 @@ def __init__(self, new_item: Dict[str, Any]): [Marker(marker) for marker in new_item["markers"]] if "markers" in new_item else [] ) self._play_count = new_item["play_count"] if "play_count" in new_item else 0 + self._played_at = new_item["played_at"] if "played_at" in new_item else 0 self._clean = new_item["clean"] if "clean" in new_item else True # TODO: Edit this to handle markers when MyRadio supports them From 8a66ad86e6d13c90d347d969614d7e54194770df Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 4 Sep 2021 15:16:44 +0100 Subject: [PATCH 036/156] Improve handling of dead processes --- build/requirements.txt | 1 + server.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/build/requirements.txt b/build/requirements.txt index eba9d34..664cc26 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -14,3 +14,4 @@ pyserial==3.5 requests==2.26.0 Jinja2==3.0.1 pydub==0.25.1 +psutil diff --git a/server.py b/server.py index 89f8d05..62d6cbb 100644 --- a/server.py +++ b/server.py @@ -20,6 +20,8 @@ from typing import Any, Optional import json from setproctitle import setproctitle +import psutil + from helpers.os_environment import isBundelled, isMacOS if not isMacOS(): @@ -111,7 +113,9 @@ def check_processes(self): while not terminator.terminate and self.state.get()["running_state"] == "running": for channel in range(self.state.get()["num_channels"]): - if not self.player[channel] or not self.player[channel].is_alive(): + # Use pid_exists to confirm process is actually still running. Python may not report is_alive() correctly (especially over system sleeps etc.) + # https://medium.com/pipedrive-engineering/encountering-some-python-trickery-683bd5f66750 + if not self.player[channel] or not self.player[channel].is_alive() or not psutil.pid_exists(self.player[channel].pid): log_function("Player {} not running, (re)starting.".format(channel)) self.player[channel] = multiprocessing.Process( target=player.Player, @@ -119,7 +123,7 @@ def check_processes(self): ) self.player[channel].start() - if not self.player_handler or not self.player_handler.is_alive(): + if not self.player_handler or not self.player_handler.is_alive() or not psutil.pid_exists(self.player_handler.pid): log_function("Player Handler not running, (re)starting.") self.player_handler = multiprocessing.Process( target=PlayerHandler, @@ -127,7 +131,7 @@ def check_processes(self): ) self.player_handler.start() - if not self.file_manager or not self.file_manager.is_alive(): + if not self.file_manager or not self.file_manager.is_alive() or not psutil.pid_exists(self.file_manager.pid): log_function("File Manager not running, (re)starting.") self.file_manager = multiprocessing.Process( target=FileManager, @@ -135,21 +139,21 @@ def check_processes(self): ) self.file_manager.start() - if not self.websockets_server or not self.websockets_server.is_alive(): + if not self.websockets_server or not self.websockets_server.is_alive() or not psutil.pid_exists(self.websockets_server.pid): log_function("Websocket Server not running, (re)starting.") self.websockets_server = multiprocessing.Process( target=WebsocketServer, args=(self.player_to_q, self.websocket_to_q, self.state) ) self.websockets_server.start() - if not self.webserver or not self.webserver.is_alive(): + if not self.webserver or not self.webserver.is_alive() or not psutil.pid_exists(self.webserver.pid): log_function("Webserver not running, (re)starting.") self.webserver = multiprocessing.Process( target=WebServer, args=(self.player_to_q, self.ui_to_q, self.state) ) self.webserver.start() - if not self.controller_handler or not self.controller_handler.is_alive(): + if not self.controller_handler or not self.controller_handler.is_alive() or not psutil.pid_exists(self.controller_handler.pid): log_function("Controller Handler not running, (re)starting.") self.controller_handler = multiprocessing.Process( target=MattchBox, args=(self.player_to_q, self.controller_to_q, self.state) From 10c33af1331b8a99fc4995f5640d5093b2f19ffe Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 8 Sep 2021 23:36:59 +0100 Subject: [PATCH 037/156] Logging improvements --- helpers/myradio_api.py | 3 +-- helpers/state_manager.py | 4 ++-- ui-templates/status.html | 2 +- websocket_server.py | 5 ++--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 7cf1efd..02fde77 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -375,8 +375,7 @@ def post_tracklist_end(self, tracklistitemid: int): self._log("Ending tracklistitemid {}".format(tracklistitemid)) - result = self.api_call("/tracklistItem/{}/endtime".format(tracklistitemid), method="PUT") - print(result) + self.api_call("/tracklistItem/{}/endtime".format(tracklistitemid), method="PUT") def _log(self, text: str, level: int = INFO): self.logger.log.log(level, "MyRadio API: " + text) diff --git a/helpers/state_manager.py b/helpers/state_manager.py index fb45cb2..7c32a28 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -36,7 +36,7 @@ def __init__( # Try creating the directory. os.mkdir(path_dir) except Exception: - print("Failed to create state directory.") + self._logException("Failed to create state directory.") return self.filepath = resolve_external_file_path("/state/" + name + ".json") @@ -48,7 +48,7 @@ def __init__( # Try creating the file. open(self.filepath, "x") except Exception: - self._log("Failed to create state file.", CRITICAL) + self._logException("Failed to create state file.") return file_raw: str diff --git a/ui-templates/status.html b/ui-templates/status.html index a67f86e..17482b5 100644 --- a/ui-templates/status.html +++ b/ui-templates/status.html @@ -43,7 +43,7 @@

Plan Items

{% endfor %}
{% else %} -

RIP. Failed to get this.

+

Failed to get status from Player Channel. Channel may be busy or has failed.

{% endif %}
{% endfor %} diff --git a/websocket_server.py b/websocket_server.py index 5be356d..6fe3741 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -59,7 +59,7 @@ def quit(self): _exit(0) def __del__(self): - print("Deleting websocket server") + self.logger.log.info("Deleting websocket server") self.quit() async def websocket_handler(self, websocket, path): @@ -225,7 +225,6 @@ async def handle_to_webstudio(self): continue command = message.split(":")[1] - # print("Websocket Out:", command) if command == "STATUS": try: message = message.split("OKAY:")[1] @@ -263,4 +262,4 @@ async def handle_to_webstudio(self): if __name__ == "__main__": - print("Don't do this") + raise Exception("Don't run this file standalone.") From 217a7dfdf3169d2d30d02e7ab008c464feac7050 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 8 Sep 2021 23:49:08 +0100 Subject: [PATCH 038/156] Update to merged WebStudio / BAPS3 presenter --- .gitmodules | 2 +- package.json | 2 +- presenter | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 6241b00..afda702 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "presenter"] path = presenter - url = https://github.com/michael-grace/WebStudio.git + url = https://github.com/UniversityRadioYork/WebStudio.git diff --git a/package.json b/package.json index 6cfd2ff..27ffe9f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "scripts": { "presenter-install": "cd presenter && git submodule update --init && yarn --network-timeout 100000", - "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build && cp -r build ../presenter-build && cd ../ && npm install", + "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", "presenter-start": "cd presenter && yarn start" }, diff --git a/presenter b/presenter index c10eaa1..89aa41b 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit c10eaa17861a305be765dc831c773ca7b2b46224 +Subproject commit 89aa41bd450098e673a4492d52e917f69f2453cf From ce17793c5e6226612d78741fd6446a4d0a0425a1 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 9 Sep 2021 00:34:54 +0100 Subject: [PATCH 039/156] Fix MacOS bundled launch with sanic logging dir. --- build/build-macos.sh | 1 + launch.py | 2 +- web_server.py | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build/build-macos.sh b/build/build-macos.sh index 6efb5dd..5421ca7 100755 --- a/build/build-macos.sh +++ b/build/build-macos.sh @@ -29,5 +29,6 @@ cd build brew install platypus platypus --load-profile ./BAPSicle.platypus --overwrite ./output/BAPSicle.app +chmod +x output/BAPSicle.app/Contents/Resources/BAPSicle rm ../build.py diff --git a/launch.py b/launch.py index b53a590..859b7c7 100755 --- a/launch.py +++ b/launch.py @@ -70,7 +70,7 @@ def printer(msg: Any): if sys.argv[1] == "Presenter": webbrowser.open("http://localhost:13500/presenter/") except Exception as e: - print("ALERT:BAPSicle failed with exception of type {}:\n{}".format(type(e).__name__, str(e))) + print("ALERT:BAPSicle failed with exception of type {}:{}".format(type(e).__name__, e)) sys.exit(1) sys.exit(0) diff --git a/web_server.py b/web_server.py index e8f6c79..7972170 100644 --- a/web_server.py +++ b/web_server.py @@ -26,7 +26,10 @@ env = Environment(loader=FileSystemLoader('%s/ui-templates/' % os.path.dirname(__file__)), autoescape=select_autoescape()) +LOG_FILEPATH = resolve_external_file_path("logs") +LOG_FILENAME = LOG_FILEPATH + "/WebServer.log" # From Sanic's default, but set to log to file. +os.makedirs(LOG_FILEPATH, exist_ok=True) LOGGING_CONFIG = dict( version=1, disable_existing_loggers=False, @@ -49,17 +52,17 @@ "file": { "class": "logging.FileHandler", "formatter": "generic", - "filename": "logs/WebServer.log" + "filename": LOG_FILENAME }, "error_file": { "class": "logging.FileHandler", "formatter": "generic", - "filename": "logs/WebServer.log" + "filename": LOG_FILENAME }, "access_file": { "class": "logging.FileHandler", "formatter": "access", - "filename": "logs/WebServer.log" + "filename": LOG_FILENAME }, }, formatters={ From 398c0ac0cee00c0ae526da5b5951c6af3394f8c3 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 9 Sep 2021 00:35:25 +0100 Subject: [PATCH 040/156] Fix await for response text. --- helpers/myradio_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 02fde77..070c66f 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -58,7 +58,7 @@ async def async_call(self, url, method="GET", data=None, timeout=10): self._logException( "Failed to get API request. Status code: " + str(response.status) ) - self._logException(str(response.text())) + self._logException(str(await response.text())) return await response.read() def call(self, url, method="GET", data=None, timeout=10, json_payload=True): From 2941d90f60d253783109253ef3fdde8e944f26f8 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 16:49:08 +0100 Subject: [PATCH 041/156] Lint with black for formatting. --- baps_types/plan.py | 54 ++++-- controllers/mattchbox_usb.py | 13 +- file_manager.py | 338 +++++++++++++++++++---------------- helpers/device_manager.py | 5 +- helpers/myradio_api.py | 29 +-- helpers/normalisation.py | 59 +++--- helpers/state_manager.py | 44 +++-- launch.py | 6 +- package.py | 3 +- player.py | 254 +++++++++++++++++--------- player_handler.py | 8 +- server.py | 77 ++++++-- setup.py | 3 +- tests/test_player.py | 21 ++- web_server.py | 88 +++++---- websocket_server.py | 33 ++-- 16 files changed, 642 insertions(+), 393 deletions(-) diff --git a/baps_types/plan.py b/baps_types/plan.py index f770e3a..e5afbbd 100644 --- a/baps_types/plan.py +++ b/baps_types/plan.py @@ -20,8 +20,11 @@ from baps_types.marker import Marker + def _time_ms(): return round(time() * 1000) + + class PlanItem: _timeslotitemid: str = "0" _weight: int = 0 @@ -72,7 +75,7 @@ def play_count_increment(self): self._played_at = _time_ms() def play_count_decrement(self): - self._play_count = max(0,self._play_count - 1) + self._play_count = max(0, self._play_count - 1) if self._play_count == 0: self._played_at = 0 @@ -118,7 +121,9 @@ def clean(self) -> bool: @property def intro(self) -> float: - markers = list(filter(lambda m: m.position == "start" and m.section is None, self._markers)) + markers = list( + filter(lambda m: m.position == "start" and m.section is None, self._markers) + ) # TODO: Handle multiple (shouldn't happen?) if len(markers) > 0: return markers[0].time @@ -126,7 +131,9 @@ def intro(self) -> float: @property def cue(self) -> float: - markers = list(filter(lambda m: m.position == "mid" and m.section is None, self._markers)) + markers = list( + filter(lambda m: m.position == "mid" and m.section is None, self._markers) + ) # TODO: Handle multiple (shouldn't happen?) if len(markers) > 0: return markers[0].time @@ -134,7 +141,9 @@ def cue(self) -> float: @property def outro(self) -> float: - markers = list(filter(lambda m: m.position == "end" and m.section is None, self._markers)) + markers = list( + filter(lambda m: m.position == "end" and m.section is None, self._markers) + ) # TODO: Handle multiple (shouldn't happen?) if len(markers) > 0: return markers[0].time @@ -164,7 +173,7 @@ def __dict__(self): "played": self.play_count > 0, "played_at": self.played_at, "play_count": self.play_count, - "clean": self.clean + "clean": self.clean, } def __init__(self, new_item: Dict[str, Any]): @@ -183,40 +192,59 @@ def __init__(self, new_item: Dict[str, Any]): self._artist = new_item["artist"] if "artist" in new_item else None self._length = new_item["length"] self._markers = ( - [Marker(marker) for marker in new_item["markers"]] if "markers" in new_item else [] + [Marker(marker) for marker in new_item["markers"]] + if "markers" in new_item + else [] ) self._play_count = new_item["play_count"] if "play_count" in new_item else 0 self._played_at = new_item["played_at"] if "played_at" in new_item else 0 self._clean = new_item["clean"] if "clean" in new_item else True # TODO: Edit this to handle markers when MyRadio supports them - if "intro" in new_item and (isinstance(new_item["intro"], int) or isinstance(new_item["intro"], float)) and new_item["intro"] > 0: + if ( + "intro" in new_item + and ( + isinstance(new_item["intro"], int) + or isinstance(new_item["intro"], float) + ) + and new_item["intro"] > 0 + ): marker = { "name": "Intro", "time": new_item["intro"], "position": "start", - "section": None + "section": None, } self.set_marker(Marker(json.dumps(marker))) - if "cue" in new_item and (isinstance(new_item["cue"], int) or isinstance(new_item["cue"], float)) and new_item["cue"] > 0: + if ( + "cue" in new_item + and (isinstance(new_item["cue"], int) or isinstance(new_item["cue"], float)) + and new_item["cue"] > 0 + ): marker = { "name": "Cue", "time": new_item["cue"], "position": "mid", - "section": None + "section": None, } self.set_marker(Marker(json.dumps(marker))) # TODO: Convert / handle outro being from end of item. - if "outro" in new_item and (isinstance(new_item["outro"], int) or isinstance(new_item["outro"], float)) and new_item["outro"] > 0: + if ( + "outro" in new_item + and ( + isinstance(new_item["outro"], int) + or isinstance(new_item["outro"], float) + ) + and new_item["outro"] > 0 + ): marker = { "name": "Outro", "time": new_item["outro"], "position": "end", - "section": None + "section": None, } self.set_marker(Marker(json.dumps(marker))) - # Fix any OS specific / or \'s if self.filename: if os.path.sep == "/": diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index 1df27ca..c7dcc11 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -1,4 +1,3 @@ - from helpers.the_terminator import Terminator from typing import List, Optional from multiprocessing import Queue, current_process @@ -95,12 +94,12 @@ def handler(self): self.logger.log.info("Received from controller: " + str(line)) if line == 255: self.ser.write(b"\xff") # Send 255 back, this is a keepalive. - elif line in [51,52,53]: + elif line in [51, 52, 53]: # We've received a status update about fader live status, fader is down. - self.sendToPlayer(line-51, "SETLIVE:False") - elif line in [61,62,63]: + self.sendToPlayer(line - 51, "SETLIVE:False") + elif line in [61, 62, 63]: # We've received a status update about fader live status, fader is up. - self.sendToPlayer(line-61, "SETLIVE:True") + self.sendToPlayer(line - 61, "SETLIVE:True") elif line in [1, 3, 5]: self.sendToPlayer(int(line / 2), "PLAYPAUSE") elif line in [2, 4, 6]: @@ -136,5 +135,7 @@ def handler(self): self.connect(None) def sendToPlayer(self, channel: int, msg: str): - self.logger.log.info("Sending message to player channel {}: {}".format(channel, msg)) + self.logger.log.info( + "Sending message to player channel {}: {}".format(channel, msg) + ) self.server_to_q[channel].put("CONTROLLER:" + msg) diff --git a/file_manager.py b/file_manager.py index 96ad701..a6cca0f 100644 --- a/file_manager.py +++ b/file_manager.py @@ -31,193 +31,223 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): terminator = Terminator() self.channel_count = len(channel_from_q) self.channel_received = None - self.last_known_show_plan = [[]]*self.channel_count + self.last_known_show_plan = [[]] * self.channel_count self.next_channel_preload = 0 - self.known_channels_preloaded = [False]*self.channel_count - self.known_channels_normalised = [False]*self.channel_count - self.last_known_item_ids = [[]]*self.channel_count + self.known_channels_preloaded = [False] * self.channel_count + self.known_channels_normalised = [False] * self.channel_count + self.last_known_item_ids = [[]] * self.channel_count try: while not terminator.terminate: # If all channels have received the delete command, reset for the next one. - if (self.channel_received == None or self.channel_received == [True]*self.channel_count): - self.channel_received = [False]*self.channel_count + if ( + self.channel_received == None + or self.channel_received == [True] * self.channel_count + ): + self.channel_received = [False] * self.channel_count for channel in range(self.channel_count): try: message = channel_from_q[channel].get_nowait() except Exception: - continue + continue try: - #source = message.split(":")[0] - command = message.split(":",2)[1] + # source = message.split(":")[0] + command = message.split(":", 2)[1] # If we have requested a new show plan, empty the music-tmp directory for the previous show. if command == "GETPLAN": - if self.channel_received != [False]*self.channel_count and self.channel_received[channel] != True: - # We've already received a delete trigger on a channel, let's not delete the folder more than once. - # If the channel was already in the process of being deleted, the user has requested it again, so allow it. - + if ( + self.channel_received != [False] * self.channel_count + and self.channel_received[channel] != True + ): + # We've already received a delete trigger on a channel, let's not delete the folder more than once. + # If the channel was already in the process of being deleted, the user has requested it again, so allow it. + + self.channel_received[channel] = True + continue + + # Delete the previous show files! + # Note: The players load into RAM. If something is playing over the load, the source file can still be deleted. + path: str = resolve_external_file_path("/music-tmp/") + + if not os.path.isdir(path): + self.logger.log.warning( + "Music-tmp folder is missing, not handling." + ) + continue + + files = [ + f + for f in os.listdir(path) + if os.path.isfile(os.path.join(path, f)) + ] + for file in files: + if isWindows(): + filepath = path + "\\" + file + else: + filepath = path + "/" + file + self.logger.log.info( + "Removing file {} on new show load.".format( + filepath + ) + ) + try: + os.remove(filepath) + except Exception: + self.logger.log.warning( + "Failed to remove, skipping. Likely file is still in use." + ) + continue self.channel_received[channel] = True - continue - - # Delete the previous show files! - # Note: The players load into RAM. If something is playing over the load, the source file can still be deleted. - path: str = resolve_external_file_path("/music-tmp/") - - if not os.path.isdir(path): - self.logger.log.warning("Music-tmp folder is missing, not handling.") - continue - - files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] - for file in files: - if isWindows(): - filepath = path+"\\"+file - else: - filepath = path+"/"+file - self.logger.log.info("Removing file {} on new show load.".format(filepath)) - try: - os.remove(filepath) - except Exception: - self.logger.log.warning("Failed to remove, skipping. Likely file is still in use.") - continue - self.channel_received[channel] = True - self.known_channels_preloaded = [False]*self.channel_count - self.known_channels_normalised = [False]*self.channel_count + self.known_channels_preloaded = [False] * self.channel_count + self.known_channels_normalised = [ + False + ] * self.channel_count # If we receive a new status message, let's check for files which have not been pre-loaded. if command == "STATUS": - extra = message.split(":",3) - if extra[2] != "OKAY": - continue - - status = json.loads(extra[3]) - show_plan = status["show_plan"] - item_ids = [] - for item in show_plan: - item_ids += item["timeslotitemid"] - - # If the new status update has a different order / list of items, let's update the show plan we know about - # This will trigger the chunk below to do the rounds again and preload any new files. - if item_ids != self.last_known_item_ids[channel]: - self.last_known_item_ids[channel] = item_ids - self.last_known_show_plan[channel] = show_plan - self.known_channels_preloaded[channel] = False + extra = message.split(":", 3) + if extra[2] != "OKAY": + continue + + status = json.loads(extra[3]) + show_plan = status["show_plan"] + item_ids = [] + for item in show_plan: + item_ids += item["timeslotitemid"] + + # If the new status update has a different order / list of items, let's update the show plan we know about + # This will trigger the chunk below to do the rounds again and preload any new files. + if item_ids != self.last_known_item_ids[channel]: + self.last_known_item_ids[channel] = item_ids + self.last_known_show_plan[channel] = show_plan + self.known_channels_preloaded[channel] = False except Exception: - self.logger.log.exception("Failed to handle message {} on channel {}.".format(message, channel)) + self.logger.log.exception( + "Failed to handle message {} on channel {}.".format( + message, channel + ) + ) # Let's try preload / normalise some files now we're free of messages. preloaded = self.do_preload() normalised = self.do_normalise() - if (not preloaded and not normalised): - # We didn't do any hard work, let's sleep. - sleep(0.2) + if not preloaded and not normalised: + # We didn't do any hard work, let's sleep. + sleep(0.2) except Exception as e: - self.logger.log.exception( - "Received unexpected exception: {}".format(e)) + self.logger.log.exception("Received unexpected exception: {}".format(e)) del self.logger - # Attempt to preload a file onto disk. def do_preload(self): - channel = self.next_channel_preload - - # All channels have preloaded all files, do nothing. - if (self.known_channels_preloaded == [True]*self.channel_count): - return False # Didn't preload anything - - # Right, let's have a quick check in the status for shows without filenames, to preload them. - # Keep an eye on if we downloaded anything. - # If we didn't, we know that all items in this channel have been downloaded. - downloaded_something = False - for i in range(len(self.last_known_show_plan[channel])): - - item_obj = PlanItem(self.last_known_show_plan[channel][i]) - - # We've not downloaded this file yet, let's do that. - if not item_obj.filename: - self.logger.log.info("Checking pre-load on channel {}, weight {}: {}".format(channel, item_obj.weight, item_obj.name)) - - # Getting the file name will only pull the new file if the file doesn't already exist, so this is not too inefficient. - item_obj.filename,did_download = sync(self.api.get_filename(item_obj, True)) - # Alright, we've done one, now let's give back control to process new statuses etc. - - # Save back the resulting item back in regular dict form - self.last_known_show_plan[channel][i] = item_obj.__dict__ - - if did_download: - downloaded_something = True - self.logger.log.info("File successfully preloaded: {}".format(item_obj.filename)) - break - else: - # We didn't download anything this time, file was already loaded. - # Let's try the next one. - continue - - # Tell the file manager that this channel is fully downloaded, this is so it can consider normalising once all channels have files. - self.known_channels_preloaded[channel] = not downloaded_something - - self.next_channel_preload += 1 - if self.next_channel_preload >= self.channel_count: - self.next_channel_preload = 0 - - return downloaded_something - + channel = self.next_channel_preload + + # All channels have preloaded all files, do nothing. + if self.known_channels_preloaded == [True] * self.channel_count: + return False # Didn't preload anything + + # Right, let's have a quick check in the status for shows without filenames, to preload them. + # Keep an eye on if we downloaded anything. + # If we didn't, we know that all items in this channel have been downloaded. + downloaded_something = False + for i in range(len(self.last_known_show_plan[channel])): + + item_obj = PlanItem(self.last_known_show_plan[channel][i]) + + # We've not downloaded this file yet, let's do that. + if not item_obj.filename: + self.logger.log.info( + "Checking pre-load on channel {}, weight {}: {}".format( + channel, item_obj.weight, item_obj.name + ) + ) + + # Getting the file name will only pull the new file if the file doesn't already exist, so this is not too inefficient. + item_obj.filename, did_download = sync( + self.api.get_filename(item_obj, True) + ) + # Alright, we've done one, now let's give back control to process new statuses etc. + + # Save back the resulting item back in regular dict form + self.last_known_show_plan[channel][i] = item_obj.__dict__ + + if did_download: + downloaded_something = True + self.logger.log.info( + "File successfully preloaded: {}".format(item_obj.filename) + ) + break + else: + # We didn't download anything this time, file was already loaded. + # Let's try the next one. + continue + + # Tell the file manager that this channel is fully downloaded, this is so it can consider normalising once all channels have files. + self.known_channels_preloaded[channel] = not downloaded_something + + self.next_channel_preload += 1 + if self.next_channel_preload >= self.channel_count: + self.next_channel_preload = 0 + + return downloaded_something # If we've preloaded everything, get to work normalising tracks before playback. def do_normalise(self): - # Some channels still have files to preload, do nothing. - if (self.known_channels_preloaded != [True]*self.channel_count): - return False # Didn't normalise - - # Quit early if all channels are normalised already. - if (self.known_channels_normalised == [True]*self.channel_count): - return False - - channel = self.next_channel_preload - - normalised_something = False - # Look through all the show plan files - for i in range(len(self.last_known_show_plan[channel])): - - item_obj = PlanItem(self.last_known_show_plan[channel][i]) - - filename = item_obj.filename - if not filename: - self.logger.log.exception("Somehow got empty filename when all channels are preloaded.") - continue # Try next song. - elif (not os.path.isfile(filename)): - self.logger.log.exception("Filename for normalisation does not exist. This is bad.") - continue - elif "normalised" in filename: - continue - # Sweet, we now need to try generating a normalised version. - try: - self.logger.log.info("Normalising on channel {}: {}".format(channel,filename)) - # This will return immediately if we already have a normalised file. - item_obj.filename = generate_normalised_file(filename) - # TODO Hacky - self.last_known_show_plan[channel][i] = item_obj.__dict__ - normalised_something = True - break # Now go let another channel have a go. - except Exception as e: - self.logger.log.exception("Failed to generate normalised file.", str(e)) - continue - - self.known_channels_normalised[channel] = not normalised_something - - self.next_channel_preload += 1 - if self.next_channel_preload >= self.channel_count: - self.next_channel_preload = 0 - - return normalised_something - - - - - + # Some channels still have files to preload, do nothing. + if self.known_channels_preloaded != [True] * self.channel_count: + return False # Didn't normalise + + # Quit early if all channels are normalised already. + if self.known_channels_normalised == [True] * self.channel_count: + return False + + channel = self.next_channel_preload + + normalised_something = False + # Look through all the show plan files + for i in range(len(self.last_known_show_plan[channel])): + + item_obj = PlanItem(self.last_known_show_plan[channel][i]) + + filename = item_obj.filename + if not filename: + self.logger.log.exception( + "Somehow got empty filename when all channels are preloaded." + ) + continue # Try next song. + elif not os.path.isfile(filename): + self.logger.log.exception( + "Filename for normalisation does not exist. This is bad." + ) + continue + elif "normalised" in filename: + continue + # Sweet, we now need to try generating a normalised version. + try: + self.logger.log.info( + "Normalising on channel {}: {}".format(channel, filename) + ) + # This will return immediately if we already have a normalised file. + item_obj.filename = generate_normalised_file(filename) + # TODO Hacky + self.last_known_show_plan[channel][i] = item_obj.__dict__ + normalised_something = True + break # Now go let another channel have a go. + except Exception as e: + self.logger.log.exception("Failed to generate normalised file.", str(e)) + continue + + self.known_channels_normalised[channel] = not normalised_something + + self.next_channel_preload += 1 + if self.next_channel_preload >= self.channel_count: + self.next_channel_preload = 0 + + return normalised_something diff --git a/helpers/device_manager.py b/helpers/device_manager.py index 889d190..ae5c930 100644 --- a/helpers/device_manager.py +++ b/helpers/device_manager.py @@ -2,6 +2,7 @@ import sounddevice as sd from helpers.os_environment import isLinux, isMacOS, isWindows import glob + if isWindows(): from serial.tools.list_ports_windows import comports @@ -39,7 +40,9 @@ def getAudioOutputs(cls) -> Tuple[List[Dict]]: else: host_apis[host_api_id]["usable"] = True - host_api_devices = (device for device in devices if device["hostapi"] == host_api_id) + host_api_devices = ( + device for device in devices if device["hostapi"] == host_api_id + ) outputs: List[Dict] = list(filter(cls._isOutput, host_api_devices)) outputs = sorted(outputs, key=lambda k: k["name"]) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 070c66f..77e8f65 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -56,7 +56,8 @@ async def async_call(self, url, method="GET", data=None, timeout=10): async with func as response: if response.status != status_code: self._logException( - "Failed to get API request. Status code: " + str(response.status) + "Failed to get API request. Status code: " + + str(response.status) ) self._logException(str(await response.text())) return await response.read() @@ -81,7 +82,9 @@ def call(self, url, method="GET", data=None, timeout=10, json_payload=True): self._logException(str(r.text)) return json.loads(r.text) if json_payload else r.text - async def async_api_call(self, url, api_version="v2", method="GET", data=None, timeout=10): + async def async_api_call( + self, url, api_version="v2", method="GET", data=None, timeout=10 + ): if api_version == "v2": url = "{}/v2{}".format(self.config.get()["myradio_api_url"], url) elif api_version == "non": @@ -198,7 +201,6 @@ async def get_showplan(self, timeslotid: int): self.logger.log.error("Show plan in unknown format.") return None - # Audio Library async def get_filename(self, item: PlanItem, did_download: bool = False): @@ -240,15 +242,18 @@ async def get_filename(self, item: PlanItem, did_download: bool = False): # If something else (another channel, the preloader etc) is downloading the track, wait for it. if os.path.isfile(filename + dl_suffix): time_waiting_s = 0 - self._log("Waiting for download to complete from another worker. " + filename, DEBUG) + self._log( + "Waiting for download to complete from another worker. " + filename, + DEBUG, + ) while time_waiting_s < 20: # TODO: Make something better here. # If the connectivity is super poor or we're loading reeaaaalllly long files, this may be annoying, but this is just in case somehow the other api download gives up. if os.path.isfile(filename): # Now the file is downloaded successfully return (filename, False) if did_download else filename - time_waiting_s +=1 - self._log("Still waiting",DEBUG) + time_waiting_s += 1 + self._log("Still waiting", DEBUG) time.sleep(1) # File doesn't exist, download it. @@ -300,7 +305,7 @@ async def get_playlist_aux(self): async def get_playlist_aux_items(self, library_id: str): # Sometimes they have "aux-", we only need the index. if library_id.index("-") > -1: - library_id = library_id[library_id.index("-") + 1:] + library_id = library_id[library_id.index("-") + 1 :] url = "/nipswebPlaylist/{}/items".format(library_id) request = await self.async_api_call(url) @@ -351,12 +356,14 @@ def post_tracklist_start(self, item: PlanItem): source: str = self.config.get()["myradio_api_tracklist_source"] data = { "trackid": item.trackid, - "sourceid": int(source) if source.isnumeric() else source + "sourceid": int(source) if source.isnumeric() else source, } # Starttime and timeslotid are default in the API to current time/show. tracklist_id = None try: - tracklist_id = self.api_call("/tracklistItem/", method="POST", data=data)["payload"]["audiologid"] + tracklist_id = self.api_call("/tracklistItem/", method="POST", data=data)[ + "payload" + ]["audiologid"] except Exception as e: self._logException("Failed to get tracklistid. {}".format(e)) @@ -370,7 +377,9 @@ def post_tracklist_end(self, tracklistitemid: int): self._log("Tracklistitemid is None, can't end tracklist.", WARNING) return False if not isinstance(tracklistitemid, int): - self._logException("Tracklistitemid '{}' is not an integer!".format(tracklistitemid)) + self._logException( + "Tracklistitemid '{}' is not an integer!".format(tracklistitemid) + ) return False self._log("Ending tracklistitemid {}".format(tracklistitemid)) diff --git a/helpers/normalisation.py b/helpers/normalisation.py index 900ffee..35f1d3b 100644 --- a/helpers/normalisation.py +++ b/helpers/normalisation.py @@ -1,49 +1,44 @@ import os -from helpers.os_environment import resolve_external_file_path -from pydub import AudioSegment, effects # Audio leveling! +from pydub import AudioSegment, effects # Audio leveling! # Stuff to help make BAPSicle play out leveled audio. -def match_target_amplitude(sound, target_dBFS): - change_in_dBFS = target_dBFS - sound.dBFS - return sound.apply_gain(change_in_dBFS) # Takes filename in, normalialises it and returns a normalised file path. def generate_normalised_file(filename: str): - if (not (isinstance(filename, str) and filename.endswith(".mp3"))): - raise ValueError("Invalid filename given.") + if not (isinstance(filename, str) and filename.endswith(".mp3")): + raise ValueError("Invalid filename given.") - # Already normalised. - if filename.endswith("-normalised.mp3"): - return filename + # Already normalised. + if filename.endswith("-normalised.mp3"): + return filename - normalised_filename = "{}-normalised.mp3".format(filename.rsplit(".",1)[0]) + normalised_filename = "{}-normalised.mp3".format(filename.rsplit(".", 1)[0]) - # The file already exists, short circuit. - if (os.path.exists(normalised_filename)): - return normalised_filename + # The file already exists, short circuit. + if os.path.exists(normalised_filename): + return normalised_filename - sound = AudioSegment.from_file(filename, "mp3") - normalised_sound = effects.normalize(sound) #match_target_amplitude(sound, -10) + sound = AudioSegment.from_file(filename, "mp3") + normalised_sound = effects.normalize(sound) - normalised_sound.export(normalised_filename, bitrate="320k", format="mp3") - return normalised_filename + normalised_sound.export(normalised_filename, bitrate="320k", format="mp3") + return normalised_filename -# Returns either a normalised file path (based on filename), or the original if not available. -def get_normalised_filename_if_available(filename:str): - if (not (isinstance(filename, str) and filename.endswith(".mp3"))): - raise ValueError("Invalid filename given.") - - # Already normalised. - if filename.endswith("-normalised.mp3"): - return filename +# Returns either a normalised file path (based on filename), or the original if not available. +def get_normalised_filename_if_available(filename: str): + if not (isinstance(filename, str) and filename.endswith(".mp3")): + raise ValueError("Invalid filename given.") - normalised_filename = "{}-normalised.mp3".format(filename.rstrip(".mp3")) + # Already normalised. + if filename.endswith("-normalised.mp3"): + return filename - # normalised version exists - if (os.path.exists(normalised_filename)): - return normalised_filename + normalised_filename = "{}-normalised.mp3".format(filename.rstrip(".mp3")) - # Else we've not got a normalised verison, just take original. - return filename + # normalised version exists + if os.path.exists(normalised_filename): + return normalised_filename + # Else we've not got a normalised verison, just take original. + return filename diff --git a/helpers/state_manager.py b/helpers/state_manager.py index 7c32a28..52e1321 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -1,6 +1,6 @@ import json import os -from logging import CRITICAL, DEBUG, INFO +from logging import DEBUG, INFO import time from datetime import datetime from copy import copy @@ -79,7 +79,7 @@ def __init__( # If there are any new config options in the default state, save them. # Uses update() to save them to file too. for key in default_state.keys(): - if not key in file_state.keys(): + if key not in file_state.keys(): self.update(key, default_state[key]) except Exception: @@ -114,7 +114,6 @@ def write_to_file(self, state): now = datetime.now() - current_time = now.strftime("%H:%M:%S") state_to_json["last_updated"] = current_time @@ -154,20 +153,30 @@ def update(self, key: str, value: Any, index: int = -1): allow = False # It's hard to compare lists, especially of complex objects like show plans, just write it. - if (isinstance(value, list)): + if isinstance(value, list): allow = True # If the two objects have dict representations, and they don't match, allow writing. # TODO: This should be easier. - if (getattr(value, "__dict__", None) and getattr(state_to_update[key], "__dict__", None)): + if getattr(value, "__dict__", None) and getattr( + state_to_update[key], "__dict__", None + ): if value.__dict__ != state_to_update[key].__dict__: allow = True if not allow: # Just some debug logging. - if update_file and (key not in ["playing", "loaded", "initialised", "remaining", "pos_true"]): - self._log("Not updating state for key '{}' with value '{}' of type '{}'.".format(key, value, type(value)), DEBUG) + if update_file and ( + key + not in ["playing", "loaded", "initialised", "remaining", "pos_true"] + ): + self._log( + "Not updating state for key '{}' with value '{}' of type '{}'.".format( + key, value, type(value) + ), + DEBUG, + ) # We're trying to update the state with the same value. # In this case, ignore the update @@ -176,11 +185,21 @@ def update(self, key: str, value: Any, index: int = -1): if index > -1 and key in state_to_update: if not isinstance(state_to_update[key], list): - self._log("Not updating state for key '{}' with value '{}' of type '{}' since index is set and key is not a list.".format(key, value, type(value)), DEBUG) + self._log( + "Not updating state for key '{}' with value '{}' of type '{}' since index is set and key is not a list.".format( + key, value, type(value) + ), + DEBUG, + ) return list_items = state_to_update[key] if index >= len(list_items): - self._log("Not updating state for key '{}' with value '{}' of type '{}' because index '{}' is too large..".format(key, value, type(value), index), DEBUG) + self._log( + "Not updating state for key '{}' with value '{}' of type '{}' because index '{}' is too large..".format( + key, value, type(value), index + ), + DEBUG, + ) return list_items[index] = value state_to_update[key] = list_items @@ -190,7 +209,12 @@ def update(self, key: str, value: Any, index: int = -1): self.state = state_to_update if update_file: - self._log("Writing change to key '{}' with value '{}' of type '{}' to disk.".format(key, value, type(value)), DEBUG) + self._log( + "Writing change to key '{}' with value '{}' of type '{}' to disk.".format( + key, value, type(value) + ), + DEBUG, + ) # Either a routine write, or state has changed. # Update the file self.write_to_file(state_to_update) diff --git a/launch.py b/launch.py index 859b7c7..dcf8088 100755 --- a/launch.py +++ b/launch.py @@ -70,7 +70,11 @@ def printer(msg: Any): if sys.argv[1] == "Presenter": webbrowser.open("http://localhost:13500/presenter/") except Exception as e: - print("ALERT:BAPSicle failed with exception of type {}:{}".format(type(e).__name__, e)) + print( + "ALERT:BAPSicle failed with exception of type {}:{}".format( + type(e).__name__, e + ) + ) sys.exit(1) sys.exit(0) diff --git a/package.py b/package.py index 18a9dfc..1689140 100644 --- a/package.py +++ b/package.py @@ -15,9 +15,10 @@ build_beta = True try: import build + build_commit = build.BUILD build_branch = build.BRANCH - build_beta = (build_branch != "release") + build_beta = build_branch != "release" except (ModuleNotFoundError, AttributeError): pass BUILD: str = build_commit diff --git a/player.py b/player.py index 48a2c11..cd2f29d 100644 --- a/player.py +++ b/player.py @@ -21,6 +21,7 @@ # Stop the Pygame Hello message. import os + os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" from queue import Empty @@ -148,7 +149,10 @@ def isCued(self): # Don't mess with playback, we only care about if it's supposed to be loaded. if not self._isLoaded(short_test=True): return False - return (self.state.get()["pos_true"] == self.state.get()["loaded_item"].cue and not self.isPlaying) + return ( + self.state.get()["pos_true"] == self.state.get()["loaded_item"].cue + and not self.isPlaying + ) @property def status(self): @@ -251,7 +255,9 @@ def seek(self, pos: float) -> bool: return False return True else: - self.logger.log.debug("Not playing during seek, setting pos state for next play.") + self.logger.log.debug( + "Not playing during seek, setting pos state for next play." + ) self.stopped_manually = True # Don't trigger _ended() on seeking. if pos > 0: self.state.update("paused", True) @@ -298,7 +304,9 @@ def _check_ghosts(self, item: PlanItem): # Kinda a bodge for the moment, each "Ghost" (item which is not saved in the database showplan yet) needs to have a unique temporary item. # To do this, we'll start with the channel number the item was originally added to (to stop items somehow simultaneously added to different channels from having the same id) # And chuck in the unix epoch in ns for good measure. - item.timeslotitemid = "GHOST-{}-{}".format(self.state.get()["channel"], time.time_ns()) + item.timeslotitemid = "GHOST-{}-{}".format( + self.state.get()["channel"], time.time_ns() + ) return item # TODO Allow just moving an item inside the channel instead of removing and adding. @@ -315,7 +323,6 @@ def add_to_plan(self, new_item: Dict[str, Any]) -> bool: self._fix_and_update_weights(plan_copy) - loaded_item = self.state.get()["loaded_item"] if loaded_item: @@ -346,13 +353,15 @@ def add_to_plan(self, new_item: Dict[str, Any]) -> bool: def remove_from_plan(self, weight: int) -> bool: plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) - found: Optional[PlanItem ] = None + found: Optional[PlanItem] = None before = [] for item in plan_copy: before += (item.weight, item.name) - self.logger.log.debug("Weights before removing weight {}:\n{}".format(weight, before)) + self.logger.log.debug( + "Weights before removing weight {}:\n{}".format(weight, before) + ) for i in plan_copy: if i.weight == weight: @@ -372,21 +381,19 @@ def remove_from_plan(self, weight: int) -> bool: # So we'll want to update the weight. # We're removing the loaded item from the channel. - #if loaded_item.weight == weight: - loaded_item.weight = -1 - - + # if loaded_item.weight == weight: + loaded_item.weight = -1 # If loaded_item wasn't the same instance, we'd want to do the below. # We removed an item above it. Shift it up. - #elif loaded_item.weight > weight: + # elif loaded_item.weight > weight: # loaded_item.weight -= 1 # Else, new weight stays the same. - #else: + # else: # return True - self.state.update("loaded_item", loaded_item) + self.state.update("loaded_item", loaded_item) return True return False @@ -399,7 +406,10 @@ def load(self, weight: int): loaded_state = self.state.get() self.unload() - self.logger.log.info("Resetting output (in case of sound output gone silent somehow) to " + str(loaded_state["output"])) + self.logger.log.info( + "Resetting output (in case of sound output gone silent somehow) to " + + str(loaded_state["output"]) + ) self.output(loaded_state["output"]) showplan = loaded_state["show_plan"] @@ -412,14 +422,12 @@ def load(self, weight: int): break if loaded_item is None: - self.logger.log.error( - "Failed to find weight: {}".format(weight)) + self.logger.log.error("Failed to find weight: {}".format(weight)) return False reload = False if loaded_item.filename == "" or loaded_item.filename is None: - self.logger.log.info( - "Filename is not specified, loading from API.") + self.logger.log.info("Filename is not specified, loading from API.") reload = True elif not os.path.exists(loaded_item.filename): self.logger.log.warn( @@ -434,7 +442,9 @@ def load(self, weight: int): return False # Swap with a normalised version if it's ready, else returns original. - loaded_item.filename = get_normalised_filename_if_available(loaded_item.filename) + loaded_item.filename = get_normalised_filename_if_available( + loaded_item.filename + ) self.state.update("loaded_item", loaded_item) @@ -452,8 +462,7 @@ def load(self, weight: int): while load_attempt < 5: load_attempt += 1 try: - self.logger.log.info("Loading file: " + - str(loaded_item.filename)) + self.logger.log.info("Loading file: " + str(loaded_item.filename)) mixer.music.load(loaded_item.filename) except Exception: # We couldn't load that file. @@ -461,12 +470,14 @@ def load(self, weight: int): "Couldn't load file: " + str(loaded_item.filename) ) time.sleep(1) - continue # Try loading again. + continue # Try loading again. if not self.isLoaded: - self.logger.log.error("Pygame loaded file without error, but never actually loaded.") + self.logger.log.error( + "Pygame loaded file without error, but never actually loaded." + ) time.sleep(1) - continue # Try loading again. + continue # Try loading again. try: if loaded_item.filename.endswith(".mp3"): @@ -475,14 +486,13 @@ def load(self, weight: int): else: # WARNING! Pygame / SDL can't seek .wav files :/ self.state.update( - "length", mixer.Sound( - loaded_item.filename).get_length() / 1000 + "length", + mixer.Sound(loaded_item.filename).get_length() / 1000, ) except Exception: - self.logger.log.exception( - "Failed to update the length of item.") + self.logger.log.exception("Failed to update the length of item.") time.sleep(1) - continue # Try loading again. + continue # Try loading again. # Everything worked, we made it! if loaded_item.cue > 0: @@ -561,7 +571,11 @@ def set_marker(self, timeslotitemid: str, marker_str: str): try: marker = Marker(marker_str) except Exception as e: - self.logger.log.error("Failed to create Marker instance with {} {}: {}".format(timeslotitemid, marker_str, e)) + self.logger.log.error( + "Failed to create Marker instance with {} {}: {}".format( + timeslotitemid, marker_str, e + ) + ) return False if timeslotitemid == "-1": @@ -569,10 +583,12 @@ def set_marker(self, timeslotitemid: str, marker_str: str): if not self.isLoaded: return False timeslotitemid = self.state.get()["loaded_item"].timeslotitemid - elif self.isLoaded and self.state.get()["loaded_item"].timeslotitemid == timeslotitemid: + elif ( + self.isLoaded + and self.state.get()["loaded_item"].timeslotitemid == timeslotitemid + ): set_loaded = True - plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) for i in range(len(self.state.get()["show_plan"])): @@ -585,15 +601,23 @@ def set_marker(self, timeslotitemid: str, marker_str: str): except Exception as e: self.logger.log.error( - "Failed to set marker on item {}: {} with marker \n{}".format(timeslotitemid, e, marker)) + "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.get()["loaded_item"].set_marker(marker)) + self.state.update( + "loaded_item", self.state.get()["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)) + "Failed to set marker on loaded_item {}: {} with marker \n{}".format( + timeslotitemid, e, marker + ) + ) success = False return success @@ -605,7 +629,9 @@ def set_played(self, weight: int, played: bool): item.play_count_increment() if played else item.play_count_reset() self.state.update("show_plan", plan) elif len(plan) > weight: - plan[weight].play_count_increment() if played else plan[weight].play_count_reset() + plan[weight].play_count_increment() if played else plan[ + weight + ].play_count_reset() self.state.update("show_plan", plan[weight], weight) else: return False @@ -617,11 +643,10 @@ def set_live(self, live: bool): self.state.update("live", live) # If we're going to live (potentially from not live/PFL), potentially tracklist if it's playing. - if (live): + if live: self._potentially_tracklist() return True - # Helper functions # This essentially allows the tracklist end API call to happen in a separate thread, to avoid hanging playout/loading. @@ -629,18 +654,24 @@ def _potentially_tracklist(self): mode = self.state.get()["tracklist_mode"] time: int = -1 - if mode in ["on","fader-live"]: + if mode in ["on", "fader-live"]: time = 1 # Let's do it pretty quickly. elif mode == "delayed": # Let's do it in a bit, once we're sure it's been playing. (Useful if we've got no idea if it's live or cueing.) time = TRACKLISTING_DELAYED_S if time >= 0 and not self.tracklist_start_timer: - self.logger.log.info("Setting timer for tracklisting in {} secs due to Mode: {}".format(time, mode)) + self.logger.log.info( + "Setting timer for tracklisting in {} secs due to Mode: {}".format( + time, mode + ) + ) self.tracklist_start_timer = Timer(time, self._tracklist_start) self.tracklist_start_timer.start() elif self.tracklist_start_timer: - self.logger.log.error("Failed to potentially tracklist, timer already busy.") + self.logger.log.error( + "Failed to potentially tracklist, timer already busy." + ) # This essentially allows the tracklist end API call to happen in a separate thread, to avoid hanging playout/loading. def _potentially_end_tracklist(self): @@ -663,24 +694,34 @@ def _potentially_end_tracklist(self): self.logger.log.info("No tracklist to end.") return - self.logger.log.info("Setting timer for ending tracklist_id '{}'".format(tracklist_id)) + self.logger.log.info( + "Setting timer for ending tracklist_id '{}'".format(tracklist_id) + ) if tracklist_id: - self.logger.log.info("Attempting to end tracklist_id '{}'".format(tracklist_id)) + self.logger.log.info( + "Attempting to end tracklist_id '{}'".format(tracklist_id) + ) if self.tracklist_end_timer: - self.logger.log.error("Failed to potentially end tracklist, timer already busy.") + self.logger.log.error( + "Failed to potentially end tracklist, timer already busy." + ) return self.state.update("tracklist_id", None) # This threads it, so it won't hang track loading if it fails. self.tracklist_end_timer = Timer(1, self._tracklist_end, [tracklist_id]) self.tracklist_end_timer.start() else: - self.logger.log.warning("Failed to potentially end tracklist, no tracklist started.") + self.logger.log.warning( + "Failed to potentially end tracklist, no tracklist started." + ) def _tracklist_start(self): state = self.state.get() loaded_item = state["loaded_item"] if not loaded_item: - self.logger.log.error("Tried to call _tracklist_start() with no loaded item!") + self.logger.log.error( + "Tried to call _tracklist_start() with no loaded item!" + ) elif not self.isPlaying: self.logger.log.info("Not tracklisting since not playing.") @@ -688,20 +729,27 @@ def _tracklist_start(self): else: tracklist_id = state["tracklist_id"] - if (not tracklist_id): - if (state["tracklist_mode"] == "fader-live" and not state["live"]): + if not tracklist_id: + if state["tracklist_mode"] == "fader-live" and not state["live"]: self.logger.log.info("Not tracklisting since fader is not live.") else: - self.logger.log.info("Tracklisting item: '{}'".format(loaded_item.name)) + self.logger.log.info( + "Tracklisting item: '{}'".format(loaded_item.name) + ) tracklist_id = self.api.post_tracklist_start(loaded_item) if not tracklist_id: - self.logger.log.warning("Failed to tracklist '{}'".format(loaded_item.name)) + self.logger.log.warning( + "Failed to tracklist '{}'".format(loaded_item.name) + ) else: self.logger.log.info("Tracklist id: '{}'".format(tracklist_id)) self.state.update("tracklist_id", tracklist_id) else: - self.logger.log.info("Not tracklisting item '{}', already got tracklistid: '{}'".format( - loaded_item.name, tracklist_id)) + self.logger.log.info( + "Not tracklisting item '{}', already got tracklistid: '{}'".format( + loaded_item.name, tracklist_id + ) + ) # No matter what we end up doing, we need to kill this timer so future ones can run. self.tracklist_start_timer = None @@ -709,10 +757,14 @@ def _tracklist_start(self): def _tracklist_end(self, tracklist_id): if tracklist_id: - self.logger.log.info("Attempting to end tracklist_id '{}'".format(tracklist_id)) + self.logger.log.info( + "Attempting to end tracklist_id '{}'".format(tracklist_id) + ) self.api.post_tracklist_end(tracklist_id) else: - self.logger.log.error("Tracklist_id to _tracklist_end() missing. Failed to end tracklist.") + self.logger.log.error( + "Tracklist_id to _tracklist_end() missing. Failed to end tracklist." + ) self.tracklist_end_timer = None @@ -727,7 +779,11 @@ def _ended(self): return # Track has ended - self.logger.log.info("Playback ended of {}, weight {}:".format(loaded_item.name, loaded_item.weight)) + self.logger.log.info( + "Playback ended of {}, weight {}:".format( + loaded_item.name, loaded_item.weight + ) + ) # Repeat 1 # TODO ENUM @@ -742,19 +798,25 @@ def _ended(self): # If it's been removed, weight will be -1. # Just stop in this case. if loaded_item.weight < 0: - self.logger.log.debug("Loaded item is no longer in channel (weight {}), not auto advancing.".format(loaded_item.weight)) + self.logger.log.debug( + "Loaded item is no longer in channel (weight {}), not auto advancing.".format( + loaded_item.weight + ) + ) else: - self.logger.log.debug("Found current loaded item in this channel show plan. Auto Advancing.") + self.logger.log.debug( + "Found current loaded item in this channel show plan. Auto Advancing." + ) # If there's another item after this one, load that. - if len(state["show_plan"]) > loaded_item.weight+1: - self.load(loaded_item.weight+1) + if len(state["show_plan"]) > loaded_item.weight + 1: + self.load(loaded_item.weight + 1) return # Repeat All (Jump to top again) # TODO ENUM elif state["repeat"] == "all": - self.load(0) # Jump to the top. + self.load(0) # Jump to the top. return # No automations, just stop playing. @@ -795,8 +857,7 @@ def _updateState(self, pos: Optional[float] = None): self.state.update( "remaining", - max(0, (self.state.get()["length"] - - self.state.get()["pos_true"])), + max(0, (self.state.get()["length"] - self.state.get()["pos_true"])), ) def _ping_times(self): @@ -832,17 +893,18 @@ def _retMsg( response += "FAIL" if self.out_q: - if ("STATUS:" not in response): + if "STATUS:" not in response: # Don't fill logs with status pushes, it's a mess. self.logger.log.debug(("Sending: {}".format(response))) self.out_q.put(response) else: - self.logger.log.exception("Message return Queue is missing!!!! Can't send message.") + self.logger.log.exception( + "Message return Queue is missing!!!! Can't send message." + ) def _send_status(self): # TODO This is hacky - self._retMsg(str(self.status), okay_str=True, - custom_prefix="ALL:STATUS:") + self._retMsg(str(self.status), okay_str=True, custom_prefix="ALL:STATUS:") def _fix_and_update_weights(self, plan): def _sort_weight(e: PlanItem): @@ -854,7 +916,6 @@ def _sort_weight(e: PlanItem): self.logger.log.debug("Weights before fixing:\n{}".format(before)) - plan.sort(key=_sort_weight) # Sort into weighted order. sorted = [] @@ -874,7 +935,11 @@ def _sort_weight(e: PlanItem): self.state.update("show_plan", plan) def __init__( - self, channel: int, in_q: multiprocessing.Queue, out_q: multiprocessing.Queue, server_state: StateManager + self, + channel: int, + in_q: multiprocessing.Queue, + out_q: multiprocessing.Queue, + server_state: StateManager, ): process_title = "Player: Channel " + str(channel) @@ -899,7 +964,9 @@ def __init__( self.state.update("channel", channel) self.state.update("tracklist_mode", server_state.get()["tracklist_mode"]) - self.state.update("live", True) # Channel is live until controller says it isn't. + self.state.update( + "live", True + ) # Channel is live until controller says it isn't. # Just in case there's any weights somehow messed up, let's fix them. plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) @@ -908,8 +975,7 @@ def __init__( loaded_state = copy.copy(self.state.state) if loaded_state["output"]: - self.logger.log.info("Setting output to: " + - str(loaded_state["output"])) + self.logger.log.info("Setting output to: " + str(loaded_state["output"])) self.output(loaded_state["output"]) else: self.logger.log.info("Using default output device.") @@ -918,7 +984,7 @@ def __init__( loaded_item = loaded_state["loaded_item"] if loaded_item: # No need to load on init, the output switch does this, as it would for regular output switching. - #self.load(loaded_item.weight) + # self.load(loaded_item.weight) # Load may jump to the cue point, as it would do on a regular load. # If we were at a different state before, we have to override it now. @@ -946,8 +1012,7 @@ def __init__( self.last_msg_source = "" self.last_msg = "" self.logger.log.warn( - "Message from unknown sender source: {}".format( - source) + "Message from unknown sender source: {}".format(source) ) continue @@ -981,9 +1046,13 @@ def __init__( # Unpause, so we don't jump to 0, we play from the current pos. "PLAY": lambda: self._retMsg(self.unpause()), "PAUSE": lambda: self._retMsg(self.pause()), - "PLAYPAUSE": lambda: self._retMsg(self.unpause() if not self.isPlaying else self.pause()), # For the hardware controller. + "PLAYPAUSE": lambda: self._retMsg( + self.unpause() if not self.isPlaying else self.pause() + ), # For the hardware controller. "UNPAUSE": lambda: self._retMsg(self.unpause()), - "STOP": lambda: self._retMsg(self.stop(user_initiated=True)), + "STOP": lambda: self._retMsg( + self.stop(user_initiated=True) + ), "SEEK": lambda: self._retMsg( self.seek(float(self.last_msg.split(":")[1])) ), @@ -1011,19 +1080,33 @@ def __init__( "UNLOAD": lambda: self._retMsg(self.unload()), "ADD": lambda: self._retMsg( self.add_to_plan( - json.loads( - ":".join(self.last_msg.split(":")[1:])) + json.loads(":".join(self.last_msg.split(":")[1:])) ) ), "REMOVE": lambda: self._retMsg( - self.remove_from_plan( - int(self.last_msg.split(":")[1])) + self.remove_from_plan(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], self.last_msg.split(":", 2)[2])), - "RESETPLAYED": lambda: self._retMsg(self.set_played(weight=int(self.last_msg.split(":")[1]), played = False)), - "SETPLAYED": lambda: self._retMsg(self.set_played(weight=int(self.last_msg.split(":")[1]), played = True)), - "SETLIVE": lambda: self._retMsg(self.set_live(self.last_msg.split(":")[1] == "True")), + "SETMARKER": lambda: self._retMsg( + self.set_marker( + self.last_msg.split(":")[1], + self.last_msg.split(":", 2)[2], + ) + ), + "RESETPLAYED": lambda: self._retMsg( + self.set_played( + weight=int(self.last_msg.split(":")[1]), + played=False, + ) + ), + "SETPLAYED": lambda: self._retMsg( + self.set_played( + weight=int(self.last_msg.split(":")[1]), played=True + ) + ), + "SETLIVE": lambda: self._retMsg( + self.set_live(self.last_msg.split(":")[1] == "True") + ), } message_type: str = self.last_msg.split(":")[0] @@ -1051,8 +1134,7 @@ def __init__( except SystemExit: self.logger.log.info("Received SystemExit") except Exception as e: - self.logger.log.exception( - "Received unexpected Exception: {}".format(e)) + self.logger.log.exception("Received unexpected Exception: {}".format(e)) self.logger.log.info("Quiting player " + str(channel)) self.quit() diff --git a/player_handler.py b/player_handler.py index 5f1446f..9802654 100644 --- a/player_handler.py +++ b/player_handler.py @@ -10,7 +10,9 @@ class PlayerHandler: logger: LoggingManager - def __init__(self, channel_from_q, websocket_to_q, ui_to_q, controller_to_q, file_to_q): + def __init__( + self, channel_from_q, websocket_to_q, ui_to_q, controller_to_q, file_to_q + ): self.logger = LoggingManager("PlayerHandler") process_title = "Player Handler" @@ -31,7 +33,6 @@ def __init__(self, channel_from_q, websocket_to_q, ui_to_q, controller_to_q, fil if command == "GET_PLAN" or command == "STATUS": file_to_q[channel].put(message) - # TODO ENUM if source in ["ALL", "WEBSOCKET"]: websocket_to_q[channel].put(message) @@ -46,7 +47,6 @@ def __init__(self, channel_from_q, websocket_to_q, ui_to_q, controller_to_q, fil sleep(0.02) except Exception as e: - self.logger.log.exception( - "Received unexpected exception: {}".format(e)) + self.logger.log.exception("Received unexpected exception: {}".format(e)) del self.logger _exit(0) diff --git a/server.py b/server.py index 62d6cbb..f49c0b8 100644 --- a/server.py +++ b/server.py @@ -110,28 +110,53 @@ def check_processes(self): terminator = Terminator() log_function = self.logger.log.info - while not terminator.terminate and self.state.get()["running_state"] == "running": + while ( + not terminator.terminate and self.state.get()["running_state"] == "running" + ): for channel in range(self.state.get()["num_channels"]): # Use pid_exists to confirm process is actually still running. Python may not report is_alive() correctly (especially over system sleeps etc.) # https://medium.com/pipedrive-engineering/encountering-some-python-trickery-683bd5f66750 - if not self.player[channel] or not self.player[channel].is_alive() or not psutil.pid_exists(self.player[channel].pid): + if ( + not self.player[channel] + or not self.player[channel].is_alive() + or not psutil.pid_exists(self.player[channel].pid) + ): log_function("Player {} not running, (re)starting.".format(channel)) self.player[channel] = multiprocessing.Process( target=player.Player, - args=(channel, self.player_to_q[channel], self.player_from_q[channel], self.state) + args=( + channel, + self.player_to_q[channel], + self.player_from_q[channel], + self.state, + ), ) self.player[channel].start() - if not self.player_handler or not self.player_handler.is_alive() or not psutil.pid_exists(self.player_handler.pid): + if ( + not self.player_handler + or not self.player_handler.is_alive() + or not psutil.pid_exists(self.player_handler.pid) + ): log_function("Player Handler not running, (re)starting.") self.player_handler = multiprocessing.Process( target=PlayerHandler, - args=(self.player_from_q, self.websocket_to_q, self.ui_to_q, self.controller_to_q, self.file_to_q), + args=( + self.player_from_q, + self.websocket_to_q, + self.ui_to_q, + self.controller_to_q, + self.file_to_q, + ), ) self.player_handler.start() - if not self.file_manager or not self.file_manager.is_alive() or not psutil.pid_exists(self.file_manager.pid): + if ( + not self.file_manager + or not self.file_manager.is_alive() + or not psutil.pid_exists(self.file_manager.pid) + ): log_function("File Manager not running, (re)starting.") self.file_manager = multiprocessing.Process( target=FileManager, @@ -139,24 +164,38 @@ def check_processes(self): ) self.file_manager.start() - if not self.websockets_server or not self.websockets_server.is_alive() or not psutil.pid_exists(self.websockets_server.pid): + if ( + not self.websockets_server + or not self.websockets_server.is_alive() + or not psutil.pid_exists(self.websockets_server.pid) + ): log_function("Websocket Server not running, (re)starting.") self.websockets_server = multiprocessing.Process( - target=WebsocketServer, args=(self.player_to_q, self.websocket_to_q, self.state) + target=WebsocketServer, + args=(self.player_to_q, self.websocket_to_q, self.state), ) self.websockets_server.start() - if not self.webserver or not self.webserver.is_alive() or not psutil.pid_exists(self.webserver.pid): + if ( + not self.webserver + or not self.webserver.is_alive() + or not psutil.pid_exists(self.webserver.pid) + ): log_function("Webserver not running, (re)starting.") self.webserver = multiprocessing.Process( target=WebServer, args=(self.player_to_q, self.ui_to_q, self.state) ) self.webserver.start() - if not self.controller_handler or not self.controller_handler.is_alive() or not psutil.pid_exists(self.controller_handler.pid): + if ( + not self.controller_handler + or not self.controller_handler.is_alive() + or not psutil.pid_exists(self.controller_handler.pid) + ): log_function("Controller Handler not running, (re)starting.") self.controller_handler = multiprocessing.Process( - target=MattchBox, args=(self.player_to_q, self.controller_to_q, self.state) + target=MattchBox, + args=(self.player_to_q, self.controller_to_q, self.state), ) self.controller_handler.start() @@ -179,7 +218,9 @@ def startServer(self): ProxyManager.register("StateManager", StateManager) manager = ProxyManager() manager.start() - self.state: StateManager = manager.StateManager("BAPSicleServer", self.logger, self.default_state) + self.state: StateManager = manager.StateManager( + "BAPSicleServer", self.logger, self.default_state + ) self.state.update("running_state", "running") @@ -203,8 +244,16 @@ def startServer(self): self.controller_to_q.append(multiprocessing.Queue()) self.file_to_q.append(multiprocessing.Queue()) - print("Welcome to BAPSicle Server version: {}, build: {}.".format(package.VERSION, package.BUILD)) - print("The Server UI is available at http://{}:{}".format(self.state.get()["host"], self.state.get()["port"])) + print( + "Welcome to BAPSicle Server version: {}, build: {}.".format( + package.VERSION, package.BUILD + ) + ) + print( + "The Server UI is available at http://{}:{}".format( + self.state.get()["host"], self.state.get()["port"] + ) + ) # TODO Move this to player or installer. if False: diff --git a/setup.py b/setup.py index b38ade3..9af4e1d 100644 --- a/setup.py +++ b/setup.py @@ -7,4 +7,5 @@ description=package.DESCRIPTION, author=package.AUTHOR, license=package.LICENSE, - packages=find_packages()) + packages=find_packages(), +) diff --git a/tests/test_player.py b/tests/test_player.py index ccd6d9e..1767966 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -68,7 +68,9 @@ class TestPlayer(unittest.TestCase): @classmethod def setUpClass(cls): cls.logger = LoggingManager("Test_Player") - cls.server_state = StateManager("BAPSicleServer", cls.logger, default_state={"tracklist_mode": "off"}) # Mostly dummy here. + cls.server_state = StateManager( + "BAPSicleServer", cls.logger, default_state={"tracklist_mode": "off"} + ) # Mostly dummy here. # clean up logic for the test suite declared in the test module # code that is executed after all tests in one test run @@ -82,7 +84,8 @@ def setUp(self): self.player_from_q = multiprocessing.Queue() self.player_to_q = multiprocessing.Queue() self.player = multiprocessing.Process( - target=Player, args=(-1, self.player_to_q, self.player_from_q, self.server_state) + target=Player, + args=(-1, self.player_to_q, self.player_from_q, self.server_state), ) self.player.start() self._send_msg_wait_OKAY("CLEAR") # Empty any previous track items. @@ -125,7 +128,7 @@ def _wait_for_msg( source = response[: response.index(":")] if source in sources_filter: return response[ - len(source + ":" + msg) + 1: + len(source + ":" + msg) + 1 : ] # +1 to remove trailing : on source. except Empty: pass @@ -339,9 +342,13 @@ def test_markers(self): # Now test that all the markers we setup are present. item = json_obj["show_plan"][0] self.assertEqual(item["weight"], 0) - self.assertEqual(item["intro"], 2.0) # Backwards compat with basic Webstudio intro/cue/outro + self.assertEqual( + item["intro"], 2.0 + ) # Backwards compat with basic Webstudio intro/cue/outro self.assertEqual(item["cue"], 3.14) - self.assertEqual([json.dumps(item) for item in item["markers"]], markers[0:2]) # Check the full marker configs match + self.assertEqual( + [json.dumps(item) for item in item["markers"]], markers[0:2] + ) # Check the full marker configs match item = json_obj["show_plan"][1] self.assertEqual(item["weight"], 1) @@ -355,7 +362,9 @@ def test_markers(self): self.assertEqual(item["intro"], 0.0) self.assertEqual(item["outro"], 0.0) self.assertEqual(item["cue"], 0.0) - self.assertEqual([json.dumps(item) for item in item["markers"]], markers[3:]) + self.assertEqual( + [json.dumps(item) for item in item["markers"]], markers[3:] + ) # TODO: Now test editing/deleting them diff --git a/web_server.py b/web_server.py index 7972170..363d7bc 100644 --- a/web_server.py +++ b/web_server.py @@ -16,7 +16,11 @@ import json import os -from helpers.os_environment import isBundelled, resolve_external_file_path, resolve_local_file_path +from helpers.os_environment import ( + isBundelled, + resolve_external_file_path, + resolve_local_file_path, +) from helpers.logging_manager import LoggingManager from helpers.device_manager import DeviceManager from helpers.state_manager import StateManager @@ -24,7 +28,10 @@ from helpers.normalisation import get_normalised_filename_if_available from helpers.myradio_api import MyRadioAPI -env = Environment(loader=FileSystemLoader('%s/ui-templates/' % os.path.dirname(__file__)), autoescape=select_autoescape()) +env = Environment( + loader=FileSystemLoader("%s/ui-templates/" % os.path.dirname(__file__)), + autoescape=select_autoescape(), +) LOG_FILEPATH = resolve_external_file_path("logs") LOG_FILENAME = LOG_FILEPATH + "/WebServer.log" @@ -52,17 +59,17 @@ "file": { "class": "logging.FileHandler", "formatter": "generic", - "filename": LOG_FILENAME + "filename": LOG_FILENAME, }, "error_file": { "class": "logging.FileHandler", "formatter": "generic", - "filename": LOG_FILENAME + "filename": LOG_FILENAME, }, "access_file": { "class": "logging.FileHandler", "formatter": "access", - "filename": LOG_FILENAME + "filename": LOG_FILENAME, }, }, formatters={ @@ -113,7 +120,7 @@ def ui_index(request): "server_build": config["server_build"], "server_name": config["server_name"], "server_beta": config["server_beta"], - "server_branch": config["server_branch"] + "server_branch": config["server_branch"], } return render_template("index.html", data=data) @@ -124,8 +131,7 @@ def ui_status(request): for i in range(server_state.get()["num_channels"]): channel_states.append(status(i)) - data = {"channels": channel_states, - "ui_page": "status", "ui_title": "Status"} + data = {"channels": channel_states, "ui_page": "status", "ui_title": "Status"} return render_template("status.html", data=data) @@ -153,7 +159,7 @@ def ui_config_server(request): "ui_title": "Server Config", "state": server_state.get(), "ser_ports": DeviceManager.getSerialPorts(), - "tracklist_modes": ["off", "on", "delayed", "fader-live"] + "tracklist_modes": ["off", "on", "delayed", "fader-live"], } return render_template("config_server.html", data=data) @@ -177,7 +183,9 @@ def ui_config_server_update(request): server_state.update("myradio_base_url", request.form.get("myradio_base_url")) server_state.update("myradio_api_url", request.form.get("myradio_api_url")) - server_state.update("myradio_api_tracklist_source", request.form.get("myradio_api_tracklist_source")) + server_state.update( + "myradio_api_tracklist_source", request.form.get("myradio_api_tracklist_source") + ) server_state.update("tracklist_mode", request.form.get("tracklist_mode")) return redirect("/restart") @@ -192,11 +200,7 @@ def ui_logs_list(request): log_files.append(file.rstrip(".log")) log_files.sort() - data = { - "ui_page": "logs", - "ui_title": "Logs", - "logs": log_files - } + data = {"ui_page": "logs", "ui_title": "Logs", "logs": log_files} return render_template("loglist.html", data=data) @@ -210,10 +214,12 @@ def ui_logs_render(request, path): log_file = open(resolve_external_file_path("/logs/{}.log").format(path)) data = { - "logs": log_file.read().splitlines()[-300*page:(-300*(page-1) if page > 1 else None)][::-1], + "logs": log_file.read().splitlines()[ + -300 * page : (-300 * (page - 1) if page > 1 else None) + ][::-1], "ui_page": "logs", "ui_title": "Logs - {}".format(path), - "page": page + "page": page, } log_file.close() return render_template("log.html", data=data) @@ -296,6 +302,7 @@ def player_all_stop(request): # Show Plan Functions + @app.route("/plan/load/") def plan_load(request, timeslotid: int): @@ -314,6 +321,7 @@ def plan_clear(request): # API Proxy Endpoints + @app.route("/plan/list") async def api_list_showplans(request): @@ -323,7 +331,11 @@ async def api_list_showplans(request): @app.route("/library/search/track") async def api_search_library(request): - return resp_json(await api.get_track_search(request.args.get("title"), request.args.get("artist"))) + return resp_json( + await api.get_track_search( + request.args.get("title"), request.args.get("artist") + ) + ) @app.route("/library/playlists/") @@ -368,7 +380,7 @@ def json_status(request): async def audio_file(request, type: str, id: int): if type not in ["managed", "track"]: abort(404) - filename = resolve_external_file_path("music-tmp/{}-{}.mp3".format(type,id)) + filename = resolve_external_file_path("music-tmp/{}-{}.mp3".format(type, id)) # Swap with a normalised version if it's ready, else returns original. filename = get_normalised_filename_if_available(filename) @@ -378,18 +390,25 @@ async def audio_file(request, type: str, id: int): # Static Files -app.static("/favicon.ico", resolve_local_file_path("ui-static/favicon.ico"), name="ui-favicon") +app.static( + "/favicon.ico", resolve_local_file_path("ui-static/favicon.ico"), name="ui-favicon" +) app.static("/static", resolve_local_file_path("ui-static"), name="ui-static") dist_directory = resolve_local_file_path("presenter-build") -app.static('/presenter', dist_directory) -app.static("/presenter/", resolve_local_file_path("presenter-build/index.html"), - strict_slashes=True, name="presenter-index") +app.static("/presenter", dist_directory) +app.static( + "/presenter/", + resolve_local_file_path("presenter-build/index.html"), + strict_slashes=True, + name="presenter-index", +) # Helper Functions + def status(channel: int): while not player_from_q[channel].empty(): player_from_q[channel].get() # Just waste any previous status responses. @@ -402,7 +421,7 @@ def status(channel: int): if response.startswith("UI:STATUS:"): response = response.split(":", 2)[2] # TODO: Handle OKAY / FAIL - response = response[response.index(":") + 1:] + response = response[response.index(":") + 1 :] try: response = json.loads(response) except Exception as e: @@ -416,6 +435,7 @@ def status(channel: int): sleep(0.02) + # WebServer Start / Stop Functions @@ -428,7 +448,7 @@ def quit(request): "ui_title": "Quitting BAPSicle", "title": "See you later!", "ui_menu": False, - "message": "BAPSicle is going back into winter hibernation, see you again soon!" + "message": "BAPSicle is going back into winter hibernation, see you again soon!", } return render_template("message.html", data) @@ -444,7 +464,7 @@ def restart(request): "ui_menu": False, "message": "Just putting BAPSicle back in the freezer for a moment!", "redirect_to": "/", - "redirect_wait_ms": 10000 + "redirect_wait_ms": 10000, } return render_template("message.html", data) @@ -467,13 +487,15 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateMana terminate = Terminator() while not terminate.terminate: try: - sync(app.run( - host=server_state.get()["host"], - port=server_state.get()["port"], - debug=(not isBundelled()), - auto_reload=False, - access_log=(not isBundelled()) - )) + sync( + app.run( + host=server_state.get()["host"], + port=server_state.get()["port"], + debug=(not isBundelled()), + auto_reload=False, + access_log=(not isBundelled()), + ) + ) except Exception: break loop = asyncio.get_event_loop() diff --git a/websocket_server.py b/websocket_server.py index 6fe3741..ca136f7 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -74,9 +74,7 @@ async def websocket_handler(self, websocket, path): self.from_webstudio = asyncio.create_task(self.handle_from_webstudio(websocket)) try: - self.threads = await shield( - asyncio.gather(self.from_webstudio) - ) + self.threads = await shield(asyncio.gather(self.from_webstudio)) finally: self.from_webstudio.cancel() @@ -92,13 +90,10 @@ async def handle_from_webstudio(self, websocket): channel = int(data["channel"]) self.sendCommand(channel, data) - await asyncio.wait( - [conn.send(message) for conn in self.baps_clients] - ) + await asyncio.wait([conn.send(message) for conn in self.baps_clients]) except websockets.exceptions.ConnectionClosedError as e: - self.logger.log.error( - "Client Disconncted {}, {}".format(websocket, e)) + self.logger.log.error("Client Disconncted {}, {}".format(websocket, e)) except Exception as e: self.logger.log.exception( @@ -152,8 +147,7 @@ def sendCommand(self, channel, data): extra += str(data["timeslotId"]) elif command == "SETMARKER": extra += "{}:{}".format( - data["timeslotitemid"], - json.dumps(data["marker"]) + data["timeslotitemid"], json.dumps(data["marker"]) ) # TODO: Move this to player handler. @@ -174,21 +168,22 @@ def sendCommand(self, channel, data): # Now send the special case. self.channel_to_q[new_channel].put( - "WEBSOCKET:ADD:" + json.dumps(item)) + "WEBSOCKET:ADD:" + json.dumps(item) + ) # Don't bother, we should be done. return except ValueError as e: self.logger.log.exception( - "Error decoding extra data {} for command {} ".format( - e, command - ) + "Error decoding extra data {} for command {} ".format(e, command) ) pass # Stick the message together and send! - message += command # Put the command in at the end, in case MOVE etc changed it. + message += ( + command # Put the command in at the end, in case MOVE etc changed it. + ) if extra != "": message += ":" + extra @@ -202,9 +197,7 @@ def sendCommand(self, channel, data): ) else: - self.logger.log.error( - "Command missing from message. Data: {}".format(data) - ) + self.logger.log.error("Command missing from message. Data: {}".format(data)) async def handle_to_webstudio(self): @@ -244,9 +237,7 @@ async def handle_to_webstudio(self): data = json.dumps( {"command": command, "data": message, "channel": channel} ) - await asyncio.wait( - [conn.send(data) for conn in self.baps_clients] - ) + await asyncio.wait([conn.send(data) for conn in self.baps_clients]) except queue.Empty: continue except ValueError: From e5e3267e75f036965fac620b4133b7fd2355f7de Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 17:18:35 +0100 Subject: [PATCH 042/156] Add linting for autopep8 --- build/requirements-dev.txt | 3 + build/requirements.txt | 2 +- file_manager.py | 32 ++++++---- launch.py | 3 +- package.json | 3 +- player.py | 125 ++++++++++++++++++++++--------------- player_handler.py | 3 +- server.py | 17 ++--- web_server.py | 33 ++++++---- websocket_server.py | 15 +++-- 10 files changed, 145 insertions(+), 91 deletions(-) create mode 100644 build/requirements-dev.txt diff --git a/build/requirements-dev.txt b/build/requirements-dev.txt new file mode 100644 index 0000000..45c47db --- /dev/null +++ b/build/requirements-dev.txt @@ -0,0 +1,3 @@ +flake8 +black +autopep8 diff --git a/build/requirements.txt b/build/requirements.txt index 664cc26..84c18be 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,4 +1,4 @@ -autopep8 +wheel pygame==2.0.1 sanic==21.3.4 sanic-Cors==1.0.0 diff --git a/file_manager.py b/file_manager.py index a6cca0f..cb99c9b 100644 --- a/file_manager.py +++ b/file_manager.py @@ -41,7 +41,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): while not terminator.terminate: # If all channels have received the delete command, reset for the next one. if ( - self.channel_received == None + self.channel_received is None or self.channel_received == [True] * self.channel_count ): self.channel_received = [False] * self.channel_count @@ -60,18 +60,22 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): if command == "GETPLAN": if ( - self.channel_received != [False] * self.channel_count + self.channel_received != [ + False] * self.channel_count and self.channel_received[channel] != True ): # We've already received a delete trigger on a channel, let's not delete the folder more than once. - # If the channel was already in the process of being deleted, the user has requested it again, so allow it. + # If the channel was already in the process of being deleted, the user has + # requested it again, so allow it. self.channel_received[channel] = True continue # Delete the previous show files! - # Note: The players load into RAM. If something is playing over the load, the source file can still be deleted. - path: str = resolve_external_file_path("/music-tmp/") + # Note: The players load into RAM. If something is playing over the load, + # the source file can still be deleted. + path: str = resolve_external_file_path( + "/music-tmp/") if not os.path.isdir(path): self.logger.log.warning( @@ -102,7 +106,8 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): ) continue self.channel_received[channel] = True - self.known_channels_preloaded = [False] * self.channel_count + self.known_channels_preloaded = [ + False] * self.channel_count self.known_channels_normalised = [ False ] * self.channel_count @@ -142,7 +147,8 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): sleep(0.2) except Exception as e: - self.logger.log.exception("Received unexpected exception: {}".format(e)) + self.logger.log.exception( + "Received unexpected exception: {}".format(e)) del self.logger # Attempt to preload a file onto disk. @@ -169,7 +175,8 @@ def do_preload(self): ) ) - # Getting the file name will only pull the new file if the file doesn't already exist, so this is not too inefficient. + # Getting the file name will only pull the new file if the file doesn't + # already exist, so this is not too inefficient. item_obj.filename, did_download = sync( self.api.get_filename(item_obj, True) ) @@ -181,7 +188,8 @@ def do_preload(self): if did_download: downloaded_something = True self.logger.log.info( - "File successfully preloaded: {}".format(item_obj.filename) + "File successfully preloaded: {}".format( + item_obj.filename) ) break else: @@ -189,7 +197,8 @@ def do_preload(self): # Let's try the next one. continue - # Tell the file manager that this channel is fully downloaded, this is so it can consider normalising once all channels have files. + # Tell the file manager that this channel is fully downloaded, this is so + # it can consider normalising once all channels have files. self.known_channels_preloaded[channel] = not downloaded_something self.next_channel_preload += 1 @@ -241,7 +250,8 @@ def do_normalise(self): normalised_something = True break # Now go let another channel have a go. except Exception as e: - self.logger.log.exception("Failed to generate normalised file.", str(e)) + self.logger.log.exception( + "Failed to generate normalised file.", str(e)) continue self.known_channels_normalised[channel] = not normalised_something diff --git a/launch.py b/launch.py index dcf8088..5e2ac64 100755 --- a/launch.py +++ b/launch.py @@ -39,7 +39,8 @@ def startServer(notifications=False): # Catch the handler being killed externally. except Exception as e: - printer("Received Exception {} with args: {}".format(type(e).__name__, e.args)) + printer("Received Exception {} with args: {}".format( + type(e).__name__, e.args)) if server and server.is_alive(): server.terminate() server.join() diff --git a/package.json b/package.json index 27ffe9f..9b38633 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "presenter-install": "cd presenter && git submodule update --init && yarn --network-timeout 100000", "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", - "presenter-start": "cd presenter && yarn start" + "presenter-start": "cd presenter && yarn start", + "lint": "autopep8 -r ./*.py -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place" }, "repository": { "type": "git", diff --git a/player.py b/player.py index cd2f29d..bcacec9 100644 --- a/player.py +++ b/player.py @@ -20,29 +20,28 @@ # that we respond with something, FAIL or OKAY. The server doesn't like to be kept waiting. # Stop the Pygame Hello message. +import package +from baps_types.marker import Marker +from baps_types.plan import PlanItem +from helpers.logging_manager import LoggingManager +from helpers.state_manager import StateManager +from helpers.myradio_api import MyRadioAPI +from helpers.normalisation import get_normalised_filename_if_available +from threading import Timer +from syncer import sync +from mutagen.mp3 import MP3 +from pygame import mixer +from typing import Any, Callable, Dict, List, Optional +import time +import json +import copy +import setproctitle +import multiprocessing +from queue import Empty import os os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" -from queue import Empty -import multiprocessing -import setproctitle -import copy -import json -import time -from typing import Any, Callable, Dict, List, Optional -from pygame import mixer -from mutagen.mp3 import MP3 -from syncer import sync -from threading import Timer - -from helpers.normalisation import get_normalised_filename_if_available -from helpers.myradio_api import MyRadioAPI -from helpers.state_manager import StateManager -from helpers.logging_manager import LoggingManager -from baps_types.plan import PlanItem -from baps_types.marker import Marker -import package # TODO ENUM VALID_MESSAGE_SOURCES = ["WEBSOCKET", "UI", "CONTROLLER", "TEST", "ALL"] @@ -329,25 +328,28 @@ def add_to_plan(self, new_item: Dict[str, Any]) -> bool: # Right. So this may be confusing. # So... If the user has just moved the loaded item in the channel (by removing above and readding) # Then we want to re-associate the loaded_item object reference with the new one. - # The loaded item object before this change is now an ophan, which was kept around while the loaded item was potentially moved to another channel. + # The loaded item object before this change is now an ophan, which was + # kept around while the loaded item was potentially moved to another + # channel. if loaded_item.timeslotitemid == new_item_obj.timeslotitemid: self.state.update("loaded_item", new_item_obj) # NOPE NOPE NOPE # THIS IS AN EXAMPLE OF WHAT NOT TO DO! - # ONCE AGAIN, THE LOADED ITEM IS THE SAME OBJECT INSTANCE AS THE ONE IN THE SHOW PLAN (AS LONG AS IT HASN'T BEEN RE/MOVED) + # ONCE AGAIN, THE LOADED ITEM IS THE SAME OBJECT INSTANCE AS THE ONE IN + # THE SHOW PLAN (AS LONG AS IT HASN'T BEEN RE/MOVED) - ## loaded_item.weight = new_item_obj.weight + # loaded_item.weight = new_item_obj.weight # Bump the loaded_item's weight if we just added a new item above it. - ##elif loaded_item.weight >= new_item_obj.weight: - ## loaded_item.weight += 1 + # elif loaded_item.weight >= new_item_obj.weight: + # loaded_item.weight += 1 # Else, new weight stays the same. - ##else: - ## return True + # else: + # return True - ##self.state.update("loaded_item", loaded_item) + # self.state.update("loaded_item", loaded_item) return True @@ -422,12 +424,14 @@ def load(self, weight: int): break if loaded_item is None: - self.logger.log.error("Failed to find weight: {}".format(weight)) + self.logger.log.error( + "Failed to find weight: {}".format(weight)) return False reload = False if loaded_item.filename == "" or loaded_item.filename is None: - self.logger.log.info("Filename is not specified, loading from API.") + self.logger.log.info( + "Filename is not specified, loading from API.") reload = True elif not os.path.exists(loaded_item.filename): self.logger.log.warn( @@ -436,7 +440,8 @@ def load(self, weight: int): reload = True if reload: - loaded_item.filename = sync(self.api.get_filename(item=loaded_item)) + loaded_item.filename = sync( + self.api.get_filename(item=loaded_item)) if not loaded_item.filename: return False @@ -462,7 +467,8 @@ def load(self, weight: int): while load_attempt < 5: load_attempt += 1 try: - self.logger.log.info("Loading file: " + str(loaded_item.filename)) + self.logger.log.info( + "Loading file: " + str(loaded_item.filename)) mixer.music.load(loaded_item.filename) except Exception: # We couldn't load that file. @@ -487,10 +493,12 @@ def load(self, weight: int): # WARNING! Pygame / SDL can't seek .wav files :/ self.state.update( "length", - mixer.Sound(loaded_item.filename).get_length() / 1000, + mixer.Sound( + loaded_item.filename).get_length() / 1000, ) except Exception: - self.logger.log.exception("Failed to update the length of item.") + self.logger.log.exception( + "Failed to update the length of item.") time.sleep(1) continue # Try loading again. @@ -505,7 +513,8 @@ def load(self, weight: int): return True - self.logger.log.error("Failed to load track after numerous retries.") + self.logger.log.error( + "Failed to load track after numerous retries.") return False return False @@ -610,7 +619,8 @@ def set_marker(self, timeslotitemid: str, marker_str: str): if set_loaded: try: self.state.update( - "loaded_item", self.state.get()["loaded_item"].set_marker(marker) + "loaded_item", self.state.get( + )["loaded_item"].set_marker(marker) ) except Exception as e: self.logger.log.error( @@ -677,7 +687,8 @@ def _potentially_tracklist(self): def _potentially_end_tracklist(self): if self.tracklist_start_timer: - self.logger.log.info("A tracklist start timer was running, cancelling.") + self.logger.log.info( + "A tracklist start timer was running, cancelling.") self.tracklist_start_timer.cancel() self.tracklist_start_timer = None @@ -708,7 +719,8 @@ def _potentially_end_tracklist(self): return self.state.update("tracklist_id", None) # This threads it, so it won't hang track loading if it fails. - self.tracklist_end_timer = Timer(1, self._tracklist_end, [tracklist_id]) + self.tracklist_end_timer = Timer( + 1, self._tracklist_end, [tracklist_id]) self.tracklist_end_timer.start() else: self.logger.log.warning( @@ -731,7 +743,8 @@ def _tracklist_start(self): tracklist_id = state["tracklist_id"] if not tracklist_id: if state["tracklist_mode"] == "fader-live" and not state["live"]: - self.logger.log.info("Not tracklisting since fader is not live.") + self.logger.log.info( + "Not tracklisting since fader is not live.") else: self.logger.log.info( "Tracklisting item: '{}'".format(loaded_item.name) @@ -742,7 +755,8 @@ def _tracklist_start(self): "Failed to tracklist '{}'".format(loaded_item.name) ) else: - self.logger.log.info("Tracklist id: '{}'".format(tracklist_id)) + self.logger.log.info( + "Tracklist id: '{}'".format(tracklist_id)) self.state.update("tracklist_id", tracklist_id) else: self.logger.log.info( @@ -857,7 +871,8 @@ def _updateState(self, pos: Optional[float] = None): self.state.update( "remaining", - max(0, (self.state.get()["length"] - self.state.get()["pos_true"])), + max(0, (self.state.get()["length"] - + self.state.get()["pos_true"])), ) def _ping_times(self): @@ -904,7 +919,8 @@ def _retMsg( def _send_status(self): # TODO This is hacky - self._retMsg(str(self.status), okay_str=True, custom_prefix="ALL:STATUS:") + self._retMsg(str(self.status), okay_str=True, + custom_prefix="ALL:STATUS:") def _fix_and_update_weights(self, plan): def _sort_weight(e: PlanItem): @@ -949,7 +965,8 @@ def __init__( self.running = True self.out_q = out_q - self.logger = LoggingManager("Player" + str(channel), debug=package.build_beta) + self.logger = LoggingManager( + "Player" + str(channel), debug=package.build_beta) self.api = MyRadioAPI(self.logger, server_state) @@ -963,7 +980,8 @@ def __init__( self.state.add_callback(self._send_status) self.state.update("channel", channel) - self.state.update("tracklist_mode", server_state.get()["tracklist_mode"]) + self.state.update("tracklist_mode", server_state.get()[ + "tracklist_mode"]) self.state.update( "live", True ) # Channel is live until controller says it isn't. @@ -975,7 +993,8 @@ def __init__( loaded_state = copy.copy(self.state.state) if loaded_state["output"]: - self.logger.log.info("Setting output to: " + str(loaded_state["output"])) + self.logger.log.info("Setting output to: " + + str(loaded_state["output"])) self.output(loaded_state["output"]) else: self.logger.log.info("Using default output device.") @@ -996,7 +1015,8 @@ def __init__( if loaded_state["playing"] is True: self.logger.log.info("Resuming playback on init.") - self.unpause() # Use un-pause as we don't want to jump to a new position. + # Use un-pause as we don't want to jump to a new position. + self.unpause() else: self.logger.log.info("No file was previously loaded to resume.") @@ -1012,7 +1032,8 @@ def __init__( self.last_msg_source = "" self.last_msg = "" self.logger.log.warn( - "Message from unknown sender source: {}".format(source) + "Message from unknown sender source: {}".format( + source) ) continue @@ -1080,11 +1101,13 @@ def __init__( "UNLOAD": lambda: self._retMsg(self.unload()), "ADD": lambda: self._retMsg( self.add_to_plan( - json.loads(":".join(self.last_msg.split(":")[1:])) + json.loads( + ":".join(self.last_msg.split(":")[1:])) ) ), "REMOVE": lambda: self._retMsg( - self.remove_from_plan(int(self.last_msg.split(":")[1])) + self.remove_from_plan( + int(self.last_msg.split(":")[1])) ), "CLEAR": lambda: self._retMsg(self.clear_channel_plan()), "SETMARKER": lambda: self._retMsg( @@ -1105,7 +1128,8 @@ def __init__( ) ), "SETLIVE": lambda: self._retMsg( - self.set_live(self.last_msg.split(":")[1] == "True") + self.set_live( + self.last_msg.split(":")[1] == "True") ), } @@ -1134,7 +1158,8 @@ def __init__( except SystemExit: self.logger.log.info("Received SystemExit") except Exception as e: - self.logger.log.exception("Received unexpected Exception: {}".format(e)) + self.logger.log.exception( + "Received unexpected Exception: {}".format(e)) self.logger.log.info("Quiting player " + str(channel)) self.quit() diff --git a/player_handler.py b/player_handler.py index 9802654..da17b64 100644 --- a/player_handler.py +++ b/player_handler.py @@ -47,6 +47,7 @@ def __init__( sleep(0.02) except Exception as e: - self.logger.log.exception("Received unexpected exception: {}".format(e)) + self.logger.log.exception( + "Received unexpected exception: {}".format(e)) del self.logger _exit(0) diff --git a/server.py b/server.py index f49c0b8..eae1d52 100644 --- a/server.py +++ b/server.py @@ -22,15 +22,12 @@ from setproctitle import setproctitle import psutil -from helpers.os_environment import isBundelled, isMacOS +from helpers.os_environment import isMacOS if not isMacOS(): # Rip, this doesn't like threading on MacOS. import pyttsx3 -if isBundelled(): - import build - import package from typing import Dict, List from helpers.state_manager import StateManager @@ -111,7 +108,8 @@ def check_processes(self): log_function = self.logger.log.info while ( - not terminator.terminate and self.state.get()["running_state"] == "running" + not terminator.terminate and self.state.get()[ + "running_state"] == "running" ): for channel in range(self.state.get()["num_channels"]): @@ -122,7 +120,8 @@ def check_processes(self): or not self.player[channel].is_alive() or not psutil.pid_exists(self.player[channel].pid) ): - log_function("Player {} not running, (re)starting.".format(channel)) + log_function( + "Player {} not running, (re)starting.".format(channel)) self.player[channel] = multiprocessing.Process( target=player.Player, args=( @@ -183,7 +182,8 @@ def check_processes(self): ): log_function("Webserver not running, (re)starting.") self.webserver = multiprocessing.Process( - target=WebServer, args=(self.player_to_q, self.ui_to_q, self.state) + target=WebServer, args=( + self.player_to_q, self.ui_to_q, self.state) ) self.webserver.start() @@ -320,7 +320,8 @@ def stopServer(self): self.player_handler.join(timeout=PROCESS_KILL_TIMEOUT_S) del self.player_handler - # Now we've stopped everything else, now is the time to stop the players. This is to keep playing for as long as possible during a restart. + # Now we've stopped everything else, now is the time to stop the players. + # This is to keep playing for as long as possible during a restart. print("Stopping Players") for q in self.player_to_q: q.put("ALL:QUIT") diff --git a/web_server.py b/web_server.py index 363d7bc..90f6527 100644 --- a/web_server.py +++ b/web_server.py @@ -1,4 +1,4 @@ -from sanic import Sanic, log +from sanic import Sanic from sanic.exceptions import NotFound, abort from sanic.response import html, file, redirect from sanic.response import json as resp_json @@ -131,7 +131,8 @@ def ui_status(request): for i in range(server_state.get()["num_channels"]): channel_states.append(status(i)) - data = {"channels": channel_states, "ui_page": "status", "ui_title": "Status"} + data = {"channels": channel_states, + "ui_page": "status", "ui_title": "Status"} return render_template("status.html", data=data) @@ -175,16 +176,20 @@ def ui_config_server_update(request): server_state.update("ws_port", int(request.form.get("ws_port"))) serial_port = request.form.get("serial_port") - server_state.update("serial_port", None if serial_port == "None" else serial_port) + server_state.update("serial_port", None if serial_port == + "None" else serial_port) # Because we're not showing the api key once it's set. if "myradio_api_key" in request.form and request.form.get("myradio_api_key") != "": - server_state.update("myradio_api_key", request.form.get("myradio_api_key")) + server_state.update("myradio_api_key", + request.form.get("myradio_api_key")) - server_state.update("myradio_base_url", request.form.get("myradio_base_url")) + server_state.update("myradio_base_url", + request.form.get("myradio_base_url")) server_state.update("myradio_api_url", request.form.get("myradio_api_url")) server_state.update( - "myradio_api_tracklist_source", request.form.get("myradio_api_tracklist_source") + "myradio_api_tracklist_source", request.form.get( + "myradio_api_tracklist_source") ) server_state.update("tracklist_mode", request.form.get("tracklist_mode")) @@ -195,9 +200,9 @@ def ui_config_server_update(request): def ui_logs_list(request): files = os.listdir(resolve_external_file_path("/logs")) log_files = [] - for file in files: - if file.endswith(".log"): - log_files.append(file.rstrip(".log")) + for file_name in files: + if file_name.endswith(".log"): + log_files.append(file_name.rstrip(".log")) log_files.sort() data = {"ui_page": "logs", "ui_title": "Logs", "logs": log_files} @@ -215,7 +220,7 @@ def ui_logs_render(request, path): log_file = open(resolve_external_file_path("/logs/{}.log").format(path)) data = { "logs": log_file.read().splitlines()[ - -300 * page : (-300 * (page - 1) if page > 1 else None) + -300 * page:(-300 * (page - 1) if page > 1 else None) ][::-1], "ui_page": "logs", "ui_title": "Logs - {}".format(path), @@ -380,7 +385,8 @@ def json_status(request): async def audio_file(request, type: str, id: int): if type not in ["managed", "track"]: abort(404) - filename = resolve_external_file_path("music-tmp/{}-{}.mp3".format(type, id)) + filename = resolve_external_file_path( + "music-tmp/{}-{}.mp3".format(type, id)) # Swap with a normalised version if it's ready, else returns original. filename = get_normalised_filename_if_available(filename) @@ -411,7 +417,8 @@ async def audio_file(request, type: str, id: int): def status(channel: int): while not player_from_q[channel].empty(): - player_from_q[channel].get() # Just waste any previous status responses. + # Just waste any previous status responses. + player_from_q[channel].get() player_to_q[channel].put("UI:STATUS") retries = 0 @@ -421,7 +428,7 @@ def status(channel: int): if response.startswith("UI:STATUS:"): response = response.split(":", 2)[2] # TODO: Handle OKAY / FAIL - response = response[response.index(":") + 1 :] + response = response[response.index(":") + 1:] try: response = json.loads(response) except Exception as e: diff --git a/websocket_server.py b/websocket_server.py index ca136f7..e43ecb3 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -71,7 +71,8 @@ async def websocket_handler(self, websocket, path): for channel in self.channel_to_q: channel.put("WEBSOCKET:STATUS") - self.from_webstudio = asyncio.create_task(self.handle_from_webstudio(websocket)) + self.from_webstudio = asyncio.create_task( + self.handle_from_webstudio(websocket)) try: self.threads = await shield(asyncio.gather(self.from_webstudio)) @@ -93,7 +94,8 @@ async def handle_from_webstudio(self, websocket): await asyncio.wait([conn.send(message) for conn in self.baps_clients]) except websockets.exceptions.ConnectionClosedError as e: - self.logger.log.error("Client Disconncted {}, {}".format(websocket, e)) + self.logger.log.error( + "Client Disconncted {}, {}".format(websocket, e)) except Exception as e: self.logger.log.exception( @@ -176,13 +178,15 @@ def sendCommand(self, channel, data): except ValueError as e: self.logger.log.exception( - "Error decoding extra data {} for command {} ".format(e, command) + "Error decoding extra data {} for command {} ".format( + e, command) ) pass # Stick the message together and send! message += ( - command # Put the command in at the end, in case MOVE etc changed it. + # Put the command in at the end, in case MOVE etc changed it. + command ) if extra != "": message += ":" + extra @@ -197,7 +201,8 @@ def sendCommand(self, channel, data): ) else: - self.logger.log.error("Command missing from message. Data: {}".format(data)) + self.logger.log.error( + "Command missing from message. Data: {}".format(data)) async def handle_to_webstudio(self): From 0848771e3f9c70cf97ab289d35091d46d51c6585 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 17:48:57 +0100 Subject: [PATCH 043/156] Tweaked autopep8 lint --- baps_types/marker.py | 2 +- baps_types/plan.py | 2 +- build/generate-build-exe-config.py | 3 ++- dev/pre-commit | 9 ++++--- file_manager.py | 8 +++--- helpers/myradio_api.py | 7 ++--- helpers/normalisation.py | 2 ++ helpers/state_manager.py | 10 ++++--- package.json | 2 +- player.py | 43 ++++++++++++++++-------------- server.py | 3 ++- tests/test_player.py | 8 +++--- 12 files changed, 57 insertions(+), 42 deletions(-) diff --git a/baps_types/marker.py b/baps_types/marker.py index d4f6bff..4d61726 100644 --- a/baps_types/marker.py +++ b/baps_types/marker.py @@ -1,5 +1,5 @@ import json -from typing import Dict, Literal, Optional, Union +from typing import Dict, Optional, Union POSITIONS = ["start", "mid", "end"] PARAMS = ["name", "time", "position", "section"] diff --git a/baps_types/plan.py b/baps_types/plan.py index e5afbbd..28406ea 100644 --- a/baps_types/plan.py +++ b/baps_types/plan.py @@ -14,7 +14,7 @@ import json -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional import os from time import time diff --git a/build/generate-build-exe-config.py b/build/generate-build-exe-config.py index fa729b9..8d5a60d 100644 --- a/build/generate-build-exe-config.py +++ b/build/generate-build-exe-config.py @@ -21,7 +21,8 @@ if not isWindows(): option["value"] = option["value"].replace(";", ":") elif relative_fix: - option["value"] += "./" # Add the windows relative path. + # Add the windows relative path. + option["value"] += "./" out_file = open('build-exe-config.json', 'w') out_file.write(json.dumps(config, indent=2)) diff --git a/dev/pre-commit b/dev/pre-commit index 22aa5b4..7e330a9 100755 --- a/dev/pre-commit +++ b/dev/pre-commit @@ -7,10 +7,11 @@ import re import subprocess import sys -#See for codes https://pypi.org/project/autopep8/ #features +# See for codes https://pypi.org/project/autopep8/ #features # don't fill in both of these select_codes = [] -ignore_codes = ["E402","E226","E24","W50","W690"] #"E121", "E122", "E123", "E124", "E125", "E126", "E127", "E128", "E129", "E131", "E501"] +# "E121", "E122", "E123", "E124", "E125", "E126", "E127", "E128", "E129", "E131", "E501"] +ignore_codes = ["E402", "E226", "E24", "W50", "W690"] # Add things like "--max-line-length=120" below overrides = ["--max-line-length=127"] @@ -18,7 +19,7 @@ overrides = ["--max-line-length=127"] def system(*args, **kwargs): kwargs.setdefault('stdout', subprocess.PIPE) proc = subprocess.Popen(args, **kwargs) - out,_ = proc.communicate() + out, _ = proc.communicate() return out @@ -44,7 +45,7 @@ def main(): print("'autopep8' is required. Please install with `pip install autopep8`.", file=sys.stderr) exit(1) - modified = re.compile('^[AM]+\s+(?P.*\.py)', re.MULTILINE) + modified = re.compile('^[AM]+\\s+(?P.*\\.py)', re.MULTILINE) basedir = system('git', 'rev-parse', '--show-toplevel').decode("utf-8").strip() files = system('git', 'status', '--porcelain').decode("utf-8") files = modified.findall(files) diff --git a/file_manager.py b/file_manager.py index cb99c9b..06af766 100644 --- a/file_manager.py +++ b/file_manager.py @@ -62,9 +62,10 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): if ( self.channel_received != [ False] * self.channel_count - and self.channel_received[channel] != True + and self.channel_received[channel] is False ): - # We've already received a delete trigger on a channel, let's not delete the folder more than once. + # We've already received a delete trigger on a channel, + # let's not delete the folder more than once. # If the channel was already in the process of being deleted, the user has # requested it again, so allow it. @@ -124,7 +125,8 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): for item in show_plan: item_ids += item["timeslotitemid"] - # If the new status update has a different order / list of items, let's update the show plan we know about + # If the new status update has a different order / list of items, + # let's update the show plan we know about # This will trigger the chunk below to do the rounds again and preload any new files. if item_ids != self.last_known_item_ids[channel]: self.last_known_item_ids[channel] = item_ids diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 77e8f65..f1cb32a 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -172,7 +172,7 @@ async def get_showplans(self): # Remove jukebox etc for show in shows: - if not "timeslot_id" in show: + if "timeslot_id" not in show: shows.remove(show) return shows @@ -248,7 +248,8 @@ async def get_filename(self, item: PlanItem, did_download: bool = False): ) while time_waiting_s < 20: # TODO: Make something better here. - # If the connectivity is super poor or we're loading reeaaaalllly long files, this may be annoying, but this is just in case somehow the other api download gives up. + # If the connectivity is super poor or we're loading reeaaaalllly long files, + # this may be annoying, but this is just in case somehow the other api download gives up. if os.path.isfile(filename): # Now the file is downloaded successfully return (filename, False) if did_download else filename @@ -305,7 +306,7 @@ async def get_playlist_aux(self): async def get_playlist_aux_items(self, library_id: str): # Sometimes they have "aux-", we only need the index. if library_id.index("-") > -1: - library_id = library_id[library_id.index("-") + 1 :] + library_id = library_id[library_id.index("-") + 1:] url = "/nipswebPlaylist/{}/items".format(library_id) request = await self.async_api_call(url) diff --git a/helpers/normalisation.py b/helpers/normalisation.py index 35f1d3b..c7a8e1e 100644 --- a/helpers/normalisation.py +++ b/helpers/normalisation.py @@ -4,6 +4,8 @@ # Stuff to help make BAPSicle play out leveled audio. # Takes filename in, normalialises it and returns a normalised file path. + + def generate_normalised_file(filename: str): if not (isinstance(filename, str) and filename.endswith(".mp3")): raise ValueError("Invalid filename given.") diff --git a/helpers/state_manager.py b/helpers/state_manager.py index 52e1321..c6133b5 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -78,9 +78,10 @@ def __init__( # If there are any new config options in the default state, save them. # Uses update() to save them to file too. - for key in default_state.keys(): - if key not in file_state.keys(): - self.update(key, default_state[key]) + if default_state: + for key in default_state.keys(): + if key not in file_state.keys(): + self.update(key, default_state[key]) except Exception: self._logException( @@ -186,7 +187,8 @@ def update(self, key: str, value: Any, index: int = -1): if index > -1 and key in state_to_update: if not isinstance(state_to_update[key], list): self._log( - "Not updating state for key '{}' with value '{}' of type '{}' since index is set and key is not a list.".format( + "Not updating state for key '{}' with value '{}' of type '{}' since index is set and key is not a list." + .format( key, value, type(value) ), DEBUG, diff --git a/package.json b/package.json index 9b38633..ea10526 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", "presenter-start": "cd presenter && yarn start", - "lint": "autopep8 -r ./*.py -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place" + "lint": "autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . " }, "repository": { "type": "git", diff --git a/player.py b/player.py index bcacec9..2f70f31 100644 --- a/player.py +++ b/player.py @@ -20,28 +20,29 @@ # that we respond with something, FAIL or OKAY. The server doesn't like to be kept waiting. # Stop the Pygame Hello message. -import package -from baps_types.marker import Marker -from baps_types.plan import PlanItem -from helpers.logging_manager import LoggingManager -from helpers.state_manager import StateManager -from helpers.myradio_api import MyRadioAPI -from helpers.normalisation import get_normalised_filename_if_available -from threading import Timer -from syncer import sync -from mutagen.mp3 import MP3 -from pygame import mixer -from typing import Any, Callable, Dict, List, Optional -import time -import json -import copy -import setproctitle -import multiprocessing -from queue import Empty import os os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" +from queue import Empty +import multiprocessing +import setproctitle +import copy +import json +import time +from typing import Any, Callable, Dict, List, Optional +from pygame import mixer +from mutagen.mp3 import MP3 +from syncer import sync +from threading import Timer + +from helpers.normalisation import get_normalised_filename_if_available +from helpers.myradio_api import MyRadioAPI +from helpers.state_manager import StateManager +from helpers.logging_manager import LoggingManager +from baps_types.plan import PlanItem +from baps_types.marker import Marker +import package # TODO ENUM VALID_MESSAGE_SOURCES = ["WEBSOCKET", "UI", "CONTROLLER", "TEST", "ALL"] @@ -300,8 +301,10 @@ def get_plan(self, message: int): def _check_ghosts(self, item: PlanItem): if isinstance(item.timeslotitemid, str) and item.timeslotitemid.startswith("I"): - # Kinda a bodge for the moment, each "Ghost" (item which is not saved in the database showplan yet) needs to have a unique temporary item. - # To do this, we'll start with the channel number the item was originally added to (to stop items somehow simultaneously added to different channels from having the same id) + # Kinda a bodge for the moment, each "Ghost" (item which is not saved in the database showplan yet) + # needs to have a unique temporary item. + # To do this, we'll start with the channel number the item was originally added to + # (to stop items somehow simultaneously added to different channels from having the same id) # And chuck in the unix epoch in ns for good measure. item.timeslotitemid = "GHOST-{}-{}".format( self.state.get()["channel"], time.time_ns() diff --git a/server.py b/server.py index eae1d52..83a80d0 100644 --- a/server.py +++ b/server.py @@ -113,7 +113,8 @@ def check_processes(self): ): for channel in range(self.state.get()["num_channels"]): - # Use pid_exists to confirm process is actually still running. Python may not report is_alive() correctly (especially over system sleeps etc.) + # Use pid_exists to confirm process is actually still running. + # Python may not report is_alive() correctly (especially over system sleeps etc.) # https://medium.com/pipedrive-engineering/encountering-some-python-trickery-683bd5f66750 if ( not self.player[channel] diff --git a/tests/test_player.py b/tests/test_player.py index 1767966..0667336 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -128,7 +128,7 @@ def _wait_for_msg( source = response[: response.index(":")] if source in sources_filter: return response[ - len(source + ":" + msg) + 1 : + len(source + ":" + msg) + 1: ] # +1 to remove trailing : on source. except Empty: pass @@ -316,7 +316,8 @@ def test_markers(self): self._send_msg_wait_OKAY("LOAD:2") # To test currently loaded marker sets. markers = [ - # Markers are stored as float, to compare against later, these must all be floats, despite int being supported. + # Markers are stored as float, to compare against later, + # these must all be floats, despite int being supported. getMarkerJSON("Intro Name", 2.0, "start", None), getMarkerJSON("Cue Name", 3.14, "mid", None), getMarkerJSON("Outro Name", 4.0, "end", None), @@ -358,7 +359,8 @@ def test_markers(self): # In this case, we want to make sure both the current and loaded items are updated for item in [json_obj["show_plan"][2], json_obj["loaded_item"]]: self.assertEqual(item["weight"], 2) - # This is a loop marker. It should not appear as a standard intro, outro or cue. Default of 0.0 should apply to all. + # This is a loop marker. It should not appear as a standard intro, outro or cue. + # Default of 0.0 should apply to all. self.assertEqual(item["intro"], 0.0) self.assertEqual(item["outro"], 0.0) self.assertEqual(item["cue"], 0.0) From ec2ddd055e2d2c19a8b04ae4c491f82bf32e90c2 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 18:01:59 +0100 Subject: [PATCH 044/156] update tests for python 3.10 and complexity --- .github/workflows/test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3fa1d4b..79f3a4d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9] + python-version: [3.8, 3.9, 3.10] steps: - uses: actions/checkout@v2 @@ -21,7 +21,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install flake8 + pip install -r build/requirements-dev.txt pip install -r build/requirements.txt pip install -r build/requirements-macos.txt - name: Install bapsicle as module @@ -32,7 +32,7 @@ jobs: # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --ignore=E402,E226,E24,W50,W690 --max-complexity=20 --max-line-length=127 --statistics + flake8 . --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics - name: Test with unittest if: ${{ always() }} timeout-minutes: 10 From 30a5309e38613dd092fb71ec71d66e544620495e Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 18:06:03 +0100 Subject: [PATCH 045/156] try 3.10 rc. --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 79f3a4d..1f94c71 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, 3.10] + python-version: [3.8, 3.9, "3.10-rc - 3.10"] steps: - uses: actions/checkout@v2 From 55309c509b17a38f38fd4027951695df5aabbb5b Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 18:07:55 +0100 Subject: [PATCH 046/156] 3.10 rc attempt 3 --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1f94c71..bab85c7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10-rc - 3.10"] + python-version: [3.8, 3.9, "3.10.0-rc.2"] steps: - uses: actions/checkout@v2 From 794433f0342fdfc0f07bd7caa1cc29d9fb5460db Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 18:12:43 +0100 Subject: [PATCH 047/156] Pull in newer pygame for 3.10 support --- build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/requirements.txt b/build/requirements.txt index 84c18be..5da5f34 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,5 +1,5 @@ wheel -pygame==2.0.1 +pygame==2.0.2.dev2 sanic==21.3.4 sanic-Cors==1.0.0 syncer==1.3.0 From 315d3ae392eed3f04f5995c02a2aa72d69b61ec0 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 18:15:14 +0100 Subject: [PATCH 048/156] Remove python 3.10 for now, pygame doesn't build. --- .github/workflows/test.yaml | 2 +- build/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bab85c7..95dee60 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10.0-rc.2"] + python-version: [3.8, 3.9] steps: - uses: actions/checkout@v2 diff --git a/build/requirements.txt b/build/requirements.txt index 5da5f34..84c18be 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,5 +1,5 @@ wheel -pygame==2.0.2.dev2 +pygame==2.0.1 sanic==21.3.4 sanic-Cors==1.0.0 syncer==1.3.0 From 8b3f527403bc077079d5359053726ccc8a35e4a8 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 18:56:44 +0100 Subject: [PATCH 049/156] try news branch --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index afda702..d7562aa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "presenter"] path = presenter url = https://github.com/UniversityRadioYork/WebStudio.git + branch = mstratford/bapsicle-news From 3ccd00e56d47cedf3ed16467a5f18fbd40f0b5ef Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 19:11:16 +0100 Subject: [PATCH 050/156] Try build with no news wavs to break CI. --- presenter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presenter b/presenter index 89aa41b..0a0ed87 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit 89aa41bd450098e673a4492d52e917f69f2453cf +Subproject commit 0a0ed8702a470ac52318c928eb0782358aa1fdbf From f75702acbce6cbff5f681625edbc7ba66ebb093f Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 19:18:13 +0100 Subject: [PATCH 051/156] Fix wav error in presenter --- presenter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presenter b/presenter index 0a0ed87..3b5677e 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit 0a0ed8702a470ac52318c928eb0782358aa1fdbf +Subproject commit 3b5677e4824c8365492a0132037fe60d7e43932e From 540704af04d24b63a3c9afc06f8e7e6b900fe5a2 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 19:22:28 +0100 Subject: [PATCH 052/156] Another import fix --- presenter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presenter b/presenter index 3b5677e..a909a9e 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit 3b5677e4824c8365492a0132037fe60d7e43932e +Subproject commit a909a9eb89969003bcfcf89cbb8e4b4666fa126e From 2cc4a57a1593fda1c5311b669881050370280e36 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 19:56:41 +0100 Subject: [PATCH 053/156] make presenter-start baps version too. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea10526..6ac33ba 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "presenter-install": "cd presenter && git submodule update --init && yarn --network-timeout 100000", "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", - "presenter-start": "cd presenter && yarn start", + "presenter-start": "cd presenter && yarn start-baps", "lint": "autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . " }, "repository": { From b405b7b1ff824210b2fa2b54f850329f29213154 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 21:19:45 +0100 Subject: [PATCH 054/156] Install wheel in CI. --- build/build-linux.sh | 1 + build/build-macos.sh | 1 + build/build-windows.bat | 1 + build/requirements-dev.txt | 1 + 4 files changed, 4 insertions(+) diff --git a/build/build-linux.sh b/build/build-linux.sh index 8eb2254..c67ee8d 100755 --- a/build/build-linux.sh +++ b/build/build-linux.sh @@ -12,6 +12,7 @@ apt install libportaudio2 python3 -m venv ../venv source ../venv/bin/activate +pip3 install wheel pip3 install -r requirements.txt pip3 install -r requirements-linux.txt pip3 install -e ../ diff --git a/build/build-macos.sh b/build/build-macos.sh index 5421ca7..1648b57 100755 --- a/build/build-macos.sh +++ b/build/build-macos.sh @@ -10,6 +10,7 @@ echo "BRANCH: str = \"$build_branch\"" >> ../build.py python3 -m venv ../venv source ../venv/bin/activate +pip3 install wheel pip3 install -r requirements.txt pip3 install -r requirements-macos.txt pip3 install -e ..\ diff --git a/build/build-windows.bat b/build/build-windows.bat index 36fc696..639a625 100644 --- a/build/build-windows.bat +++ b/build/build-windows.bat @@ -17,6 +17,7 @@ if "%1" == "no-venv" goto skip-venv :skip-venv +pip install wheel pip install -r requirements.txt pip install -r requirements-windows.txt pip install -e ..\ diff --git a/build/requirements-dev.txt b/build/requirements-dev.txt index 45c47db..db4ee3f 100644 --- a/build/requirements-dev.txt +++ b/build/requirements-dev.txt @@ -1,3 +1,4 @@ +wheel flake8 black autopep8 From 55f6fccbc0a2d15bdf762d96857baa74f8a17035 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 21:20:03 +0100 Subject: [PATCH 055/156] Take master branch webstudio with news fixes. --- .gitmodules | 2 +- presenter | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index d7562aa..a53e55e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "presenter"] path = presenter url = https://github.com/UniversityRadioYork/WebStudio.git - branch = mstratford/bapsicle-news + branch = master diff --git a/presenter b/presenter index a909a9e..bddd49f 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit a909a9eb89969003bcfcf89cbb8e4b4666fa126e +Subproject commit bddd49f4f7bb8ed5f89a967b5ac8c6d3cb04f059 From ac5409587d4ef9056cb290eef3ced675ab4fc82f Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 11 Sep 2021 23:44:15 +0100 Subject: [PATCH 056/156] Add WIP UI and manager for alerts. --- baps_types/alert.py | 65 ++++++++++++++++++++++++++++++++++++++++ helpers/alert_manager.py | 29 ++++++++++++++++++ ui-templates/alerts.html | 41 +++++++++++++++++++++++++ ui-templates/index.html | 20 +++++++++++++ web_server.py | 20 ++++++++++++- 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 baps_types/alert.py create mode 100644 helpers/alert_manager.py create mode 100644 ui-templates/alerts.html diff --git a/baps_types/alert.py b/baps_types/alert.py new file mode 100644 index 0000000..493a544 --- /dev/null +++ b/baps_types/alert.py @@ -0,0 +1,65 @@ +from typing import Any, Dict +from datetime import datetime + +CRITICAL = "Critical" +WARNING = "Warning" + +class Alert: + start_time: int = 0 + last_time: int = 0 + end_time: int = -1 + id: str + title: str + description: str + module: str + severity: str + + + @property + def ui_class(self) -> str: + if self.severity == CRITICAL: + return "danger" + if self.severity == WARNING: + return "warning" + return "info" + + # return self._weight + + # weight.setter + # def weight(self, value: int): + # self._weight = value + + + @property + def __dict__(self): + attrs = ["start_time", "last_time", "end_time", "id", "title", "description", "module", "severity"] + out = {} + for attr in attrs: + out[attr] = self.__getattribute__(attr) + + return out + + def __init__(self, new_data: Dict[str,Any]): + required_vars = [ + "start_time", # Just in case an alert wants to show starting earlier than it is reported. + "id", + "title", + "description", + "module", + "severity" + ] + + for key in required_vars: + if key not in new_data.keys(): + raise KeyError("Key {} is missing from data to create Alert.".format(key)) + + #if type(new_data[key]) != type(getattr(self,key)): + # raise TypeError("Key {} has type {}, was expecting {}.".format(key, type(new_data[key]), type(getattr(self,key)))) + + # Account for if the creator didn't want to set a custom time. + if key == "start_time" and new_data[key] == -1: + new_data[key] = datetime.now() + + setattr(self,key,new_data[key]) + + self.last_time = self.start_time diff --git a/helpers/alert_manager.py b/helpers/alert_manager.py new file mode 100644 index 0000000..820f45d --- /dev/null +++ b/helpers/alert_manager.py @@ -0,0 +1,29 @@ +from typing import List +from baps_types.alert import CRITICAL, Alert + +class AlertManager(): + _alerts: List[Alert] + + def __init__(self): + self._alerts = [Alert( + { + "start_time": -1, + "id": "test", + "title": "Test Alert", + "description": "This is a test alert.", + "module": "Test", + "severity": CRITICAL + } + )] + + @property + def alerts_current(self): + return self._alerts + + @property + def alert_count_current(self): + return len(self._alerts) + + @property + def alert_count_previous(self): + return len(self._alerts) diff --git a/ui-templates/alerts.html b/ui-templates/alerts.html new file mode 100644 index 0000000..edf1b7e --- /dev/null +++ b/ui-templates/alerts.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% block head %} + +{% endblock %} +{% block content_inner %} + {% if data %} +

Current Alerts: {{ data.alert_count_current }}

+ +
+ {% for alert in data.alerts_current %} +
+
+

+ + {{ alert.severity }} +

+ Since {{ alert.start_time }} + Last Seen {{ alert.last_time }} + {% if alert.end_time > -1 %} + Ended {{ alert.end_time }} + {% endif %} +
+ +
+
+ Module: {{ alert.module }}
+ {{ alert.description }} +
+
+
+ {% endfor %} +
+
+

Previous Alerts: {{ data.alert_count_previous }}

+
+ +
+ {% endif %} +{% endblock %} diff --git a/ui-templates/index.html b/ui-templates/index.html index 53c5aa4..bb8b21b 100644 --- a/ui-templates/index.html +++ b/ui-templates/index.html @@ -1,4 +1,7 @@ {% extends 'base.html' %} +{% block head %} + +{% endblock %} {% block content %}
@@ -16,6 +19,23 @@

Welcome to
BAPSicle!

Open BAPS Presenter + +
+ {% if data.alert_count > 0 %} + diff --git a/ui-templates/index.html b/ui-templates/index.html index bb8b21b..fe62487 100644 --- a/ui-templates/index.html +++ b/ui-templates/index.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} {% block head %} - + {% endblock %} {% block content %} From f1e04c3d8a6e2020c5aea3a576e61cde040391ad Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 16 Sep 2021 23:42:20 +0100 Subject: [PATCH 060/156] Switch sanic debug to follow package.BETA --- web_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_server.py b/web_server.py index 85ee75e..a46dc8e 100644 --- a/web_server.py +++ b/web_server.py @@ -17,7 +17,6 @@ import os from helpers.os_environment import ( - isBundelled, resolve_external_file_path, resolve_local_file_path, ) @@ -28,6 +27,7 @@ from helpers.normalisation import get_normalised_filename_if_available from helpers.myradio_api import MyRadioAPI from helpers.alert_manager import AlertManager +import package env = Environment( loader=FileSystemLoader("%s/ui-templates/" % os.path.dirname(__file__)), @@ -525,9 +525,9 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateMana app.run( host=server_state.get()["host"], port=server_state.get()["port"], - debug=(not isBundelled()), auto_reload=False, - access_log=(not isBundelled()), + debug=not package.BETA, + access_log=not package.BETA, ) ) except Exception: From 7dc0facf73295702e723795cf99ae75f0b0ea389 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 21 Sep 2021 22:49:05 +0100 Subject: [PATCH 061/156] Add basic player restart alerts --- alerts/player.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ player.py | 5 ++- 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 alerts/player.py diff --git a/alerts/player.py b/alerts/player.py new file mode 100644 index 0000000..45184c1 --- /dev/null +++ b/alerts/player.py @@ -0,0 +1,93 @@ +# Any alerts produced by the player.py instances. +import json +from typing import Any, Dict, List, Optional +from datetime import datetime, timedelta +from helpers.os_environment import resolve_external_file_path +from helpers.alert_manager import AlertProvider +from baps_types.alert import CRITICAL, WARNING, Alert + +MODULE = "Player" # This should match the log file, so the UI will link to the logs page. + +class PlayerAlertProvider(AlertProvider): + + _server_state: Dict[str, Any] + _states: List[Optional[Dict[str,Any]]] = [] + _player_count: int + + def __init__(self): + # Player count only changes after server restart, may as well just load this once. + with open(resolve_external_file_path("state/BAPSicleServer.json")) as file: + self._server_state = json.loads(file.read()) + + self._player_count = int(self._server_state["num_channels"]) + self._states = [None] * self._player_count + + # To simplify monitoring (and allow detection of things going super weird), we are going to read from the state file to work out the alerts. + def get_alerts(self): + for channel in range(self._player_count): + with open(resolve_external_file_path("state/Player{}.json".format(channel))) as file: + self._states[channel] = json.loads(file.read()) + + funcs = [self._channel_count, self._initialised, self._start_time] + + alerts: List[Alert] = [] + + for func in funcs: + func_alerts = func() + if func_alerts: + alerts.extend(func_alerts) + + return alerts + + def _channel_count(self): + if self._player_count <= 0: + return [Alert({ + "start_time": -1, # Now + "id": "no_channels", + "title": "There are no players configured.", + "description": "The number of channels configured is {}. Please set to at least 1 on the 'Server Config' page.".format(self._player_count), + "module": MODULE+"Handler", + "severity": CRITICAL + })] + + def _initialised(self): + alerts: List[Alert] = [] + for channel in range(self._player_count): + if self._states[channel] and not self._states[channel]["initialised"]: + alerts.append(Alert({ + "start_time": -1, # Now + "id": "player_{}_not_initialised".format(channel), + "title": "Player {} is not initialised.".format(channel), + "description": "This typically means the player channel was not able find the configured sound output on the system. Please check the 'Player Config' and Player logs to determine the cause.", + "module": MODULE+str(channel), + "severity": CRITICAL + })) + return alerts + + def _start_time(self): + server_start_time = self._server_state["start_time"] + server_start_time = datetime.fromtimestamp(server_start_time) + delta = timedelta( + seconds=30, + ) + + alerts: List[Alert] = [] + for channel in range(self._player_count): + start_time = self._states[channel]["start_time"] + start_time = datetime.fromtimestamp(start_time) + if (start_time > server_start_time + delta): + alerts.append(Alert({ + "start_time": -1, + "id": "player_{}_restarted".format(channel), + "title": "Player {} restarted after the server started.".format(channel), + "description": +"""Player {} last restarted at {}, after the server first started at {}, suggesting a failure. + +This likely means there was an unhandled exception in the player code, causing the server to restart the player. + +Please check player logs to investigate the cause. Please restart the server to clear this warning.""" + .format(channel, str(start_time).rsplit(".",1)[0], str(server_start_time).rsplit(".",1)[0]), + "module": MODULE+str(channel), + "severity": WARNING + })) + return alerts diff --git a/player.py b/player.py index 2f70f31..5230432 100644 --- a/player.py +++ b/player.py @@ -35,6 +35,7 @@ from mutagen.mp3 import MP3 from syncer import sync from threading import Timer +from datetime import datetime from helpers.normalisation import get_normalised_filename_if_available from helpers.myradio_api import MyRadioAPI @@ -969,7 +970,7 @@ def __init__( self.out_q = out_q self.logger = LoggingManager( - "Player" + str(channel), debug=package.build_beta) + "Player" + str(channel), debug=package.BETA) self.api = MyRadioAPI(self.logger, server_state) @@ -980,6 +981,8 @@ def __init__( self.__rate_limited_params, ) + self.state.update("start_time", datetime.now().timestamp()) + self.state.add_callback(self._send_status) self.state.update("channel", channel) From 642aaf04d599a63114c17ddd4896f1667338a985 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 21 Sep 2021 22:50:52 +0100 Subject: [PATCH 062/156] Remove channel states from alerts page info --- web_server.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web_server.py b/web_server.py index a46dc8e..89d17f8 100644 --- a/web_server.py +++ b/web_server.py @@ -144,10 +144,6 @@ def ui_status(request): @app.route("/alerts") def ui_alerts(request): - channel_states = [] - for i in range(server_state.get()["num_channels"]): - channel_states.append(status(i)) - data = { "alerts_current": alerts.alerts_current, "alerts_count_current": alerts.alert_count_current, From 4e9a1c4b6e29c2259e96ffd2f6d5eaf7efc60695 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 21 Sep 2021 22:52:27 +0100 Subject: [PATCH 063/156] Switch to quit. --- ui-templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-templates/index.html b/ui-templates/index.html index fe62487..52ebd66 100644 --- a/ui-templates/index.html +++ b/ui-templates/index.html @@ -33,7 +33,7 @@

We're all good!


Logs Restart - Shutdown + Quit

From 382de936a336896e88998f5f9948a2bcb940c4fd Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 21 Sep 2021 23:02:24 +0100 Subject: [PATCH 064/156] Allow player config to better handle borked players. --- ui-templates/config_player.html | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/ui-templates/config_player.html b/ui-templates/config_player.html index 1c09a90..78d2c79 100644 --- a/ui-templates/config_player.html +++ b/ui-templates/config_player.html @@ -9,21 +9,25 @@

Audio Outputs

Currently Selected

{% for channel in data.channels %} - Player {{channel.channel}}: - {% if channel.output %} - {{channel.output}} + Player {{loop.index0}}: + {% if channel %} + {% if channel.output %} + {{channel.output}} + {% else %} + Default Audio Device + {% endif %} + {% if not channel.initialised %} + - ERROR: Player did not start, potentially configured sound output is missing? Check Alerts. + {% endif %} {% else %} - Default Audio Device - {% endif %} - {% if not channel.initialised %} - - ERROR: Player did not start, potentially missing sound output? + - ERROR: Player did not respond, likely it is dead :/ Check Alerts. {% endif %}
{% endfor %}
Set for: -{% for channel in data.channels %} +{% for channel in data.channels if channel %} Player {{channel.channel}} / {% endfor %} Default Audio Output @@ -37,7 +41,9 @@

Currently Selected

{% if host_api.usable %} Set for: {% for channel in data.channels %} - {% if channel.output == output.name %} + {% if not channel %} + Player {{loop.index0}} + {% elif channel.output == output.name %} Player {{channel.channel}} {% else %} Player {{channel.channel}} From bc2e60cdc9c33820b4bba95de2b0fda856aa2304 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 22 Sep 2021 00:15:17 +0100 Subject: [PATCH 065/156] Update alerts to actually update/end, move list to macro. --- baps_types/alert.py | 20 +++++++++------ helpers/alert_manager.py | 41 ++++++++++++++++++++---------- ui-templates/alerts.html | 35 ++++--------------------- ui-templates/parts/alert_list.html | 31 ++++++++++++++++++++++ web_server.py | 4 +-- 5 files changed, 78 insertions(+), 53 deletions(-) create mode 100644 ui-templates/parts/alert_list.html diff --git a/baps_types/alert.py b/baps_types/alert.py index 493a544..f6958fc 100644 --- a/baps_types/alert.py +++ b/baps_types/alert.py @@ -1,13 +1,13 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional from datetime import datetime CRITICAL = "Critical" WARNING = "Warning" class Alert: - start_time: int = 0 - last_time: int = 0 - end_time: int = -1 + start_time: datetime + last_time: datetime + end_time: Optional[datetime] id: str title: str description: str @@ -23,11 +23,14 @@ def ui_class(self) -> str: return "warning" return "info" - # return self._weight + # This alert has happened again. + def reoccured(self): + self.last_time = datetime.now() + self.end_time = None - # weight.setter - # def weight(self, value: int): - # self._weight = value + # This alert has finished, just update end time and keep last_time. + def cleared(self): + self.end_time = datetime.now() @property @@ -63,3 +66,4 @@ def __init__(self, new_data: Dict[str,Any]): setattr(self,key,new_data[key]) self.last_time = self.start_time + self.end_time = None diff --git a/helpers/alert_manager.py b/helpers/alert_manager.py index fea93ed..ab9c476 100644 --- a/helpers/alert_manager.py +++ b/helpers/alert_manager.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any, List, Optional #Magic for importing alert providers from alerts directory. from pkgutil import iter_modules @@ -54,25 +54,40 @@ def __init__(self): def poll_alerts(self): # Poll modules for any alerts. - alerts: List[Alert] = [] + new_alerts: List[Optional[Alert]] = [] for provider in self._providers: provider_alerts = provider.get_alerts() if provider_alerts: - alerts.extend(provider_alerts) - - self._alerts = alerts + new_alerts.extend(provider_alerts) + + # Here we replace new firing alerts with older ones, to keep any context. + # (This doesn't do anything yet really, for future use.) + for existing in self._alerts: + found = False + for new in new_alerts: + # given we're removing alerts, got to skip any we removed. + if not new: + continue + + if existing.id == new.id: + # Alert is continuing. Replace it with the old one. + index = new_alerts.index(new) + existing.reoccured() + new_alerts[index] = None # We're going to merge the existing and new, so clear the new one out. + found = True + break + if found == False: + # The existing alert is gone, mark it as ended. + existing.cleared() + + self._alerts.extend([value for value in new_alerts if value]) # Remove any nulled out new alerts @property def alerts_current(self): self.poll_alerts() - return self._alerts - - @property - def alert_count_current(self): - self.poll_alerts() - return len(self._alerts) + return [alert for alert in self._alerts if not alert.end_time] @property - def alert_count_previous(self): + def alerts_previous(self): self.poll_alerts() - return len(self._alerts) + return [alert for alert in self._alerts if alert.end_time] diff --git a/ui-templates/alerts.html b/ui-templates/alerts.html index ff100c0..36e5fdd 100644 --- a/ui-templates/alerts.html +++ b/ui-templates/alerts.html @@ -1,43 +1,18 @@ {% extends 'base.html' %} +{% from 'parts/alert_list.html' import alert_list %} {% block head %} {% endblock %} {% block content_inner %} {% if data %}

Current Alerts: {{ data.alert_count_current }}

-
- {% for alert in data.alerts_current %} -
-
-

- - {{ alert.severity }} -

- Since {{ alert.start_time }} - Last Seen {{ alert.last_time }} - {% if alert.end_time > -1 %} - Ended {{ alert.end_time }} - {% endif %} -
- -
-
- Module: {{ alert.module }} - {% autoescape false %} -

{{ alert.description | replace("\n\n", "

") | replace("\n", "
")}}

- {% endautoescape %} -
-
-
- {% endfor %} + {{ alert_list(data.alerts_current) }}

Previous Alerts: {{ data.alert_count_previous }}

-
- -
+
+ {{ alert_list(data.alerts_previous) }} +
{% endif %} {% endblock %} diff --git a/ui-templates/parts/alert_list.html b/ui-templates/parts/alert_list.html new file mode 100644 index 0000000..6c8ae48 --- /dev/null +++ b/ui-templates/parts/alert_list.html @@ -0,0 +1,31 @@ +{% macro alert_list(alerts) %} + {% for alert in alerts %} +
+
+

+ + {{ alert.severity }} +

+ Since {{ alert.start_time }} + Last Seen {{ alert.last_time }} + {% if alert.end_time %} + Ended {{ alert.end_time }} + {% endif %} +
+ +
+
+ Module: {{ alert.module }} + {% autoescape false %} +

{{ alert.description | replace("\n\n", "

") | replace("\n", "
")}}

+ {% endautoescape %} +
+
+
+ {% endfor %} + {% if not alerts %} + No alerts here. + {% endif %} +{% endmacro %} diff --git a/web_server.py b/web_server.py index 89d17f8..08252a7 100644 --- a/web_server.py +++ b/web_server.py @@ -122,7 +122,7 @@ def ui_index(request): data = { "ui_page": "index", "ui_title": "", - "alert_count": alerts.alert_count_current, + "alert_count": len(alerts.alerts_current), "server_version": config["server_version"], "server_build": config["server_build"], "server_name": config["server_name"], @@ -146,7 +146,7 @@ def ui_status(request): def ui_alerts(request): data = { "alerts_current": alerts.alerts_current, - "alerts_count_current": alerts.alert_count_current, + "alerts_previous": alerts.alerts_previous, "ui_page": "alerts", "ui_title": "Alerts" } From b8c6f087c6381add5c5e645c0df58a20dca60fc6 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 22 Sep 2021 00:57:51 +0100 Subject: [PATCH 066/156] Tidy up alert rendering. --- alerts/player.py | 3 ++- alerts/server.py | 3 ++- baps_types/happytime.py | 3 +++ ui-templates/alerts.html | 12 ++++-------- ui-templates/parts/alert_list.html | 16 ++++++---------- web_server.py | 6 ++++++ 6 files changed, 23 insertions(+), 20 deletions(-) create mode 100644 baps_types/happytime.py diff --git a/alerts/player.py b/alerts/player.py index 45184c1..ff1b437 100644 --- a/alerts/player.py +++ b/alerts/player.py @@ -5,6 +5,7 @@ from helpers.os_environment import resolve_external_file_path from helpers.alert_manager import AlertProvider from baps_types.alert import CRITICAL, WARNING, Alert +from baps_types.happytime import happytime MODULE = "Player" # This should match the log file, so the UI will link to the logs page. @@ -86,7 +87,7 @@ def _start_time(self): This likely means there was an unhandled exception in the player code, causing the server to restart the player. Please check player logs to investigate the cause. Please restart the server to clear this warning.""" - .format(channel, str(start_time).rsplit(".",1)[0], str(server_start_time).rsplit(".",1)[0]), + .format(channel, happytime(start_time), happytime(server_start_time)), "module": MODULE+str(channel), "severity": WARNING })) diff --git a/alerts/server.py b/alerts/server.py index 7b56151..375f87d 100644 --- a/alerts/server.py +++ b/alerts/server.py @@ -5,6 +5,7 @@ from helpers.os_environment import resolve_external_file_path from helpers.alert_manager import AlertProvider from baps_types.alert import CRITICAL, WARNING, Alert +from baps_types.happytime import happytime MODULE = "BAPSicleServer" # This should match the log file, so the UI will link to the logs page. @@ -66,7 +67,7 @@ def _start_time(self): It may have been automatically restarted by the OS. If this is not expected, please check logs to investigate why BAPSicle restarted/crashed.""" - .format(str(start_time).rsplit(".",1)[0]), + .format(happytime(start_time)), "module": MODULE, "severity": WARNING })] diff --git a/baps_types/happytime.py b/baps_types/happytime.py new file mode 100644 index 0000000..898730e --- /dev/null +++ b/baps_types/happytime.py @@ -0,0 +1,3 @@ +from datetime import datetime +def happytime(date: datetime): + return date.strftime("%Y-%m-%d %H:%M:%S") diff --git a/ui-templates/alerts.html b/ui-templates/alerts.html index 36e5fdd..c90fd60 100644 --- a/ui-templates/alerts.html +++ b/ui-templates/alerts.html @@ -5,14 +5,10 @@ {% endblock %} {% block content_inner %} {% if data %} -

Current Alerts: {{ data.alert_count_current }}

-
- {{ alert_list(data.alerts_current) }} -
+

Current Alerts: {{ data.alerts_current | length }}

+ {{ alert_list(data.alerts_current) }}
-

Previous Alerts: {{ data.alert_count_previous }}

-
- {{ alert_list(data.alerts_previous) }} -
+

Previous Alerts: {{ data.alerts_previous | length }}

+ {{ alert_list(data.alerts_previous) }} {% endif %} {% endblock %} diff --git a/ui-templates/parts/alert_list.html b/ui-templates/parts/alert_list.html index 6c8ae48..c559d18 100644 --- a/ui-templates/parts/alert_list.html +++ b/ui-templates/parts/alert_list.html @@ -2,21 +2,17 @@ {% for alert in alerts %}
-

- - {{ alert.severity }} -

- Since {{ alert.start_time }} - Last Seen {{ alert.last_time }} + {{ alert.severity }} +

{{ alert.title }}

+ Since {{ alert.start_time | happytime }} + Last Seen {{ alert.last_time | happytime }} {% if alert.end_time %} - Ended {{ alert.end_time }} + Ended {{ alert.end_time | happytime }} {% endif %}
-
+
Module: {{ alert.module }} {% autoescape false %}

{{ alert.description | replace("\n\n", "

") | replace("\n", "
")}}

diff --git a/web_server.py b/web_server.py index 08252a7..b0187a2 100644 --- a/web_server.py +++ b/web_server.py @@ -28,6 +28,7 @@ from helpers.myradio_api import MyRadioAPI from helpers.alert_manager import AlertManager import package +from baps_types.happytime import happytime env = Environment( loader=FileSystemLoader("%s/ui-templates/" % os.path.dirname(__file__)), @@ -95,6 +96,11 @@ def render_template(file, data, status=200): return html(html_content, status=status) +def _filter_happytime(date): + return happytime(date) + +env.filters["happytime"] = _filter_happytime + logger: LoggingManager server_state: StateManager api: MyRadioAPI From a172d03f0efe3254a28d36afb989da9e47f49d98 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 22 Sep 2021 19:49:24 +0100 Subject: [PATCH 067/156] pep8 --- alerts/dummy.py | 26 ++++--- alerts/player.py | 158 ++++++++++++++++++++------------------- alerts/server.py | 100 +++++++++++++------------ baps_types/alert.py | 61 ++++++++------- baps_types/happytime.py | 2 + helpers/alert_manager.py | 146 ++++++++++++++++++------------------ web_server.py | 13 +++- 7 files changed, 261 insertions(+), 245 deletions(-) diff --git a/alerts/dummy.py b/alerts/dummy.py index 92cd898..7dacca3 100644 --- a/alerts/dummy.py +++ b/alerts/dummy.py @@ -2,17 +2,19 @@ from package import BETA from baps_types.alert import WARNING, Alert # Dummy alert provider for testing basics like UI without needing to actually cause errors. + + class DummyAlertProvider(AlertProvider): - def get_alerts(self): - if BETA: - return [Alert( - { - "start_time": -1, - "id": "test", - "title": "BAPSicle is in Debug Mode", - "description": "This is a test alert. It will not appear on production builds.", - "module": "Test", - "severity": WARNING - } - )] + def get_alerts(self): + if BETA: + return [Alert( + { + "start_time": -1, + "id": "test", + "title": "BAPSicle is in Debug Mode", + "description": "This is a test alert. It will not appear on production builds.", + "module": "Test", + "severity": WARNING + } + )] diff --git a/alerts/player.py b/alerts/player.py index ff1b437..1c9b70b 100644 --- a/alerts/player.py +++ b/alerts/player.py @@ -7,88 +7,90 @@ from baps_types.alert import CRITICAL, WARNING, Alert from baps_types.happytime import happytime -MODULE = "Player" # This should match the log file, so the UI will link to the logs page. +MODULE = "Player" # This should match the log file, so the UI will link to the logs page. + class PlayerAlertProvider(AlertProvider): - _server_state: Dict[str, Any] - _states: List[Optional[Dict[str,Any]]] = [] - _player_count: int - - def __init__(self): - # Player count only changes after server restart, may as well just load this once. - with open(resolve_external_file_path("state/BAPSicleServer.json")) as file: - self._server_state = json.loads(file.read()) - - self._player_count = int(self._server_state["num_channels"]) - self._states = [None] * self._player_count - - # To simplify monitoring (and allow detection of things going super weird), we are going to read from the state file to work out the alerts. - def get_alerts(self): - for channel in range(self._player_count): - with open(resolve_external_file_path("state/Player{}.json".format(channel))) as file: - self._states[channel] = json.loads(file.read()) - - funcs = [self._channel_count, self._initialised, self._start_time] - - alerts: List[Alert] = [] - - for func in funcs: - func_alerts = func() - if func_alerts: - alerts.extend(func_alerts) - - return alerts - - def _channel_count(self): - if self._player_count <= 0: - return [Alert({ - "start_time": -1, # Now - "id": "no_channels", - "title": "There are no players configured.", - "description": "The number of channels configured is {}. Please set to at least 1 on the 'Server Config' page.".format(self._player_count), - "module": MODULE+"Handler", - "severity": CRITICAL - })] - - def _initialised(self): - alerts: List[Alert] = [] - for channel in range(self._player_count): - if self._states[channel] and not self._states[channel]["initialised"]: - alerts.append(Alert({ - "start_time": -1, # Now - "id": "player_{}_not_initialised".format(channel), - "title": "Player {} is not initialised.".format(channel), - "description": "This typically means the player channel was not able find the configured sound output on the system. Please check the 'Player Config' and Player logs to determine the cause.", - "module": MODULE+str(channel), - "severity": CRITICAL - })) - return alerts - - def _start_time(self): - server_start_time = self._server_state["start_time"] - server_start_time = datetime.fromtimestamp(server_start_time) - delta = timedelta( - seconds=30, - ) - - alerts: List[Alert] = [] - for channel in range(self._player_count): - start_time = self._states[channel]["start_time"] - start_time = datetime.fromtimestamp(start_time) - if (start_time > server_start_time + delta): - alerts.append(Alert({ - "start_time": -1, - "id": "player_{}_restarted".format(channel), - "title": "Player {} restarted after the server started.".format(channel), - "description": -"""Player {} last restarted at {}, after the server first started at {}, suggesting a failure. + _server_state: Dict[str, Any] + _states: List[Optional[Dict[str, Any]]] = [] + _player_count: int + + def __init__(self): + # Player count only changes after server restart, may as well just load this once. + with open(resolve_external_file_path("state/BAPSicleServer.json")) as file: + self._server_state = json.loads(file.read()) + + self._player_count = int(self._server_state["num_channels"]) + self._states = [None] * self._player_count + + # To simplify monitoring (and allow detection of things going super + # weird), we are going to read from the state file to work out the alerts. + def get_alerts(self): + for channel in range(self._player_count): + with open(resolve_external_file_path("state/Player{}.json".format(channel))) as file: + self._states[channel] = json.loads(file.read()) + + funcs = [self._channel_count, self._initialised, self._start_time] + + alerts: List[Alert] = [] + + for func in funcs: + func_alerts = func() + if func_alerts: + alerts.extend(func_alerts) + + return alerts + + def _channel_count(self): + if self._player_count <= 0: + return [Alert({ + "start_time": -1, # Now + "id": "no_channels", + "title": "There are no players configured.", + "description": "The number of channels configured is {}. Please set to at least 1 on the 'Server Config' page.".format(self._player_count), + "module": MODULE+"Handler", + "severity": CRITICAL + })] + + def _initialised(self): + alerts: List[Alert] = [] + for channel in range(self._player_count): + if self._states[channel] and not self._states[channel]["initialised"]: + alerts.append(Alert({ + "start_time": -1, # Now + "id": "player_{}_not_initialised".format(channel), + "title": "Player {} is not initialised.".format(channel), + "description": "This typically means the player channel was not able find the configured sound output on the system. Please check the 'Player Config' and Player logs to determine the cause.", + "module": MODULE+str(channel), + "severity": CRITICAL + })) + return alerts + + def _start_time(self): + server_start_time = self._server_state["start_time"] + server_start_time = datetime.fromtimestamp(server_start_time) + delta = timedelta( + seconds=30, + ) + + alerts: List[Alert] = [] + for channel in range(self._player_count): + start_time = self._states[channel]["start_time"] + start_time = datetime.fromtimestamp(start_time) + if (start_time > server_start_time + delta): + alerts.append(Alert({ + "start_time": -1, + "id": "player_{}_restarted".format(channel), + "title": "Player {} restarted after the server started.".format(channel), + "description": + """Player {} last restarted at {}, after the server first started at {}, suggesting a failure. This likely means there was an unhandled exception in the player code, causing the server to restart the player. Please check player logs to investigate the cause. Please restart the server to clear this warning.""" - .format(channel, happytime(start_time), happytime(server_start_time)), - "module": MODULE+str(channel), - "severity": WARNING - })) - return alerts + .format(channel, happytime(start_time), happytime(server_start_time)), + "module": MODULE+str(channel), + "severity": WARNING + })) + return alerts diff --git a/alerts/server.py b/alerts/server.py index 375f87d..6a07d06 100644 --- a/alerts/server.py +++ b/alerts/server.py @@ -7,67 +7,69 @@ from baps_types.alert import CRITICAL, WARNING, Alert from baps_types.happytime import happytime -MODULE = "BAPSicleServer" # This should match the log file, so the UI will link to the logs page. +MODULE = "BAPSicleServer" # This should match the log file, so the UI will link to the logs page. + class ServerAlertProvider(AlertProvider): - _state: Dict[str, Any] - # To simplify monitoring (and allow detection of things going super weird), we are going to read from the state file to work out the alerts. - def get_alerts(self): - with open(resolve_external_file_path("state/BAPSicleServer.json")) as file: - self._state = json.loads(file.read()) + _state: Dict[str, Any] + # To simplify monitoring (and allow detection of things going super + # weird), we are going to read from the state file to work out the alerts. + def get_alerts(self): + with open(resolve_external_file_path("state/BAPSicleServer.json")) as file: + self._state = json.loads(file.read()) - funcs = [self._api_key, self._start_time] + funcs = [self._api_key, self._start_time] - alerts: List[Alert] = [] + alerts: List[Alert] = [] - for func in funcs: - func_alerts = func() - if func_alerts: - alerts.extend(func_alerts) + for func in funcs: + func_alerts = func() + if func_alerts: + alerts.extend(func_alerts) - return alerts + return alerts - def _api_key(self): - if not self._state["myradio_api_key"]: - return [Alert({ - "start_time": -1, # Now - "id": "api_key_missing", - "title": "MyRadio API Key is not configured.", - "description": "This means you will be unable to load show plans, audio items, or tracklist. Please set one on the 'Server Config' page.", - "module": MODULE, - "severity": CRITICAL - })] + def _api_key(self): + if not self._state["myradio_api_key"]: + return [Alert({ + "start_time": -1, # Now + "id": "api_key_missing", + "title": "MyRadio API Key is not configured.", + "description": "This means you will be unable to load show plans, audio items, or tracklist. Please set one on the 'Server Config' page.", + "module": MODULE, + "severity": CRITICAL + })] - if len(self._state["myradio_api_key"]) < 10: - return [Alert({ - "start_time": -1, - "id": "api_key_missing", - "title": "MyRadio API Key seems incorrect.", - "description": "The API key is less than 10 characters, it's probably not a valid one. If it is valid, it shouldn't be.", - "module": MODULE, - "severity": WARNING - })] + if len(self._state["myradio_api_key"]) < 10: + return [Alert({ + "start_time": -1, + "id": "api_key_missing", + "title": "MyRadio API Key seems incorrect.", + "description": "The API key is less than 10 characters, it's probably not a valid one. If it is valid, it shouldn't be.", + "module": MODULE, + "severity": WARNING + })] - def _start_time(self): - start_time = self._state["start_time"] - start_time = datetime.fromtimestamp(start_time) - delta = timedelta( - days=1, - ) - if (start_time + delta > datetime.now()): - return [Alert({ - "start_time": -1, - "id": "server_restarted", - "title": "BAPSicle restarted recently.", - "description": -"""The BAPSicle server restarted at {}, less than a day ago. + def _start_time(self): + start_time = self._state["start_time"] + start_time = datetime.fromtimestamp(start_time) + delta = timedelta( + days=1, + ) + if (start_time + delta > datetime.now()): + return [Alert({ + "start_time": -1, + "id": "server_restarted", + "title": "BAPSicle restarted recently.", + "description": + """The BAPSicle server restarted at {}, less than a day ago. It may have been automatically restarted by the OS. If this is not expected, please check logs to investigate why BAPSicle restarted/crashed.""" - .format(happytime(start_time)), - "module": MODULE, - "severity": WARNING - })] + .format(happytime(start_time)), + "module": MODULE, + "severity": WARNING + })] diff --git a/baps_types/alert.py b/baps_types/alert.py index f6958fc..05e792a 100644 --- a/baps_types/alert.py +++ b/baps_types/alert.py @@ -4,6 +4,7 @@ CRITICAL = "Critical" WARNING = "Warning" + class Alert: start_time: datetime last_time: datetime @@ -14,56 +15,54 @@ class Alert: module: str severity: str - @property def ui_class(self) -> str: - if self.severity == CRITICAL: - return "danger" - if self.severity == WARNING: - return "warning" - return "info" + if self.severity == CRITICAL: + return "danger" + if self.severity == WARNING: + return "warning" + return "info" # This alert has happened again. def reoccured(self): - self.last_time = datetime.now() - self.end_time = None + self.last_time = datetime.now() + self.end_time = None # This alert has finished, just update end time and keep last_time. def cleared(self): - self.end_time = datetime.now() - + self.end_time = datetime.now() @property def __dict__(self): attrs = ["start_time", "last_time", "end_time", "id", "title", "description", "module", "severity"] out = {} for attr in attrs: - out[attr] = self.__getattribute__(attr) + out[attr] = self.__getattribute__(attr) return out - def __init__(self, new_data: Dict[str,Any]): - required_vars = [ - "start_time", # Just in case an alert wants to show starting earlier than it is reported. - "id", - "title", - "description", - "module", - "severity" - ] + def __init__(self, new_data: Dict[str, Any]): + required_vars = [ + "start_time", # Just in case an alert wants to show starting earlier than it is reported. + "id", + "title", + "description", + "module", + "severity" + ] - for key in required_vars: - if key not in new_data.keys(): - raise KeyError("Key {} is missing from data to create Alert.".format(key)) + for key in required_vars: + if key not in new_data.keys(): + raise KeyError("Key {} is missing from data to create Alert.".format(key)) - #if type(new_data[key]) != type(getattr(self,key)): - # raise TypeError("Key {} has type {}, was expecting {}.".format(key, type(new_data[key]), type(getattr(self,key)))) + # if type(new_data[key]) != type(getattr(self,key)): + # raise TypeError("Key {} has type {}, was expecting {}.".format(key, type(new_data[key]), type(getattr(self,key)))) - # Account for if the creator didn't want to set a custom time. - if key == "start_time" and new_data[key] == -1: - new_data[key] = datetime.now() + # Account for if the creator didn't want to set a custom time. + if key == "start_time" and new_data[key] == -1: + new_data[key] = datetime.now() - setattr(self,key,new_data[key]) + setattr(self, key, new_data[key]) - self.last_time = self.start_time - self.end_time = None + self.last_time = self.start_time + self.end_time = None diff --git a/baps_types/happytime.py b/baps_types/happytime.py index 898730e..9fb6426 100644 --- a/baps_types/happytime.py +++ b/baps_types/happytime.py @@ -1,3 +1,5 @@ from datetime import datetime + + def happytime(date: datetime): return date.strftime("%Y-%m-%d %H:%M:%S") diff --git a/helpers/alert_manager.py b/helpers/alert_manager.py index ab9c476..f2ca4fe 100644 --- a/helpers/alert_manager.py +++ b/helpers/alert_manager.py @@ -1,14 +1,15 @@ from typing import Any, List, Optional -#Magic for importing alert providers from alerts directory. +# Magic for importing alert providers from alerts directory. from pkgutil import iter_modules from importlib import import_module -from inspect import getmembers,isclass +from inspect import getmembers, isclass from sys import modules from baps_types.alert import CRITICAL, Alert import alerts + def iter_namespace(ns_pkg): # Specifying the second argument (prefix) to iter_modules makes the # returned name an absolute name instead of a relative one. This allows @@ -19,75 +20,78 @@ def iter_namespace(ns_pkg): class AlertProvider(): - def __init__(self): - return None + def __init__(self): + return None + + def get_alerts(self): + return [] - def get_alerts(self): - return [] class AlertManager(): - _alerts: List[Alert] - _providers: List[AlertProvider] = [] - - def __init__(self): - self._alerts = [] - - # Find all the alert providers from the /alerts/ directory. - providers = { - name: import_module(name) - for _, name, _ - in iter_namespace(alerts) - } - - for provider in providers: - classes: List[Any] = [mem[1] for mem in getmembers(modules[provider], isclass) if mem[1].__module__ == modules[provider].__name__] - - if (len(classes) != 1): - print(classes) - raise Exception("Can't import plugin " + provider + " because it doesn't have 1 class.") - - self._providers.append(classes[0]()) - - - print("Discovered alert providers: ", self._providers) - - def poll_alerts(self): - - # Poll modules for any alerts. - new_alerts: List[Optional[Alert]] = [] - for provider in self._providers: - provider_alerts = provider.get_alerts() - if provider_alerts: - new_alerts.extend(provider_alerts) - - # Here we replace new firing alerts with older ones, to keep any context. - # (This doesn't do anything yet really, for future use.) - for existing in self._alerts: - found = False - for new in new_alerts: - # given we're removing alerts, got to skip any we removed. - if not new: - continue - - if existing.id == new.id: - # Alert is continuing. Replace it with the old one. - index = new_alerts.index(new) - existing.reoccured() - new_alerts[index] = None # We're going to merge the existing and new, so clear the new one out. - found = True - break - if found == False: - # The existing alert is gone, mark it as ended. - existing.cleared() - - self._alerts.extend([value for value in new_alerts if value]) # Remove any nulled out new alerts - - @property - def alerts_current(self): - self.poll_alerts() - return [alert for alert in self._alerts if not alert.end_time] - - @property - def alerts_previous(self): - self.poll_alerts() - return [alert for alert in self._alerts if alert.end_time] + _alerts: List[Alert] + _providers: List[AlertProvider] = [] + + def __init__(self): + self._alerts = [] + + # Find all the alert providers from the /alerts/ directory. + providers = { + name: import_module(name) + for _, name, _ + in iter_namespace(alerts) + } + + for provider in providers: + classes: List[Any] = [ + mem[1] for mem in getmembers( + modules[provider], + isclass) if mem[1].__module__ == modules[provider].__name__] + + if (len(classes) != 1): + print(classes) + raise Exception("Can't import plugin " + provider + " because it doesn't have 1 class.") + + self._providers.append(classes[0]()) + + print("Discovered alert providers: ", self._providers) + + def poll_alerts(self): + + # Poll modules for any alerts. + new_alerts: List[Optional[Alert]] = [] + for provider in self._providers: + provider_alerts = provider.get_alerts() + if provider_alerts: + new_alerts.extend(provider_alerts) + + # Here we replace new firing alerts with older ones, to keep any context. + # (This doesn't do anything yet really, for future use.) + for existing in self._alerts: + found = False + for new in new_alerts: + # given we're removing alerts, got to skip any we removed. + if not new: + continue + + if existing.id == new.id: + # Alert is continuing. Replace it with the old one. + index = new_alerts.index(new) + existing.reoccured() + new_alerts[index] = None # We're going to merge the existing and new, so clear the new one out. + found = True + break + if not found: + # The existing alert is gone, mark it as ended. + existing.cleared() + + self._alerts.extend([value for value in new_alerts if value]) # Remove any nulled out new alerts + + @property + def alerts_current(self): + self.poll_alerts() + return [alert for alert in self._alerts if not alert.end_time] + + @property + def alerts_previous(self): + self.poll_alerts() + return [alert for alert in self._alerts if alert.end_time] diff --git a/web_server.py b/web_server.py index b0187a2..1ba3940 100644 --- a/web_server.py +++ b/web_server.py @@ -99,6 +99,7 @@ def render_template(file, data, status=200): def _filter_happytime(date): return happytime(date) + env.filters["happytime"] = _filter_happytime logger: LoggingManager @@ -114,14 +115,17 @@ def _filter_happytime(date): @app.exception(NotFound) def page_not_found(request, e: Any): - data = {"ui_page": "404", "ui_title": "404", "code": 404, "title": "Page Not Found", "message": "Looks like you fell off the tip of the iceberg." } + data = {"ui_page": "404", "ui_title": "404", "code": 404, "title": "Page Not Found", + "message": "Looks like you fell off the tip of the iceberg."} return render_template("error.html", data=data, status=404) -@app.exception(Exception, ServerError) -def server_error(request, e: Exception): - data = {"ui_page": "500", "ui_title": "500", "code": 500, "title": "Something went very wrong!", "message": "Looks like the server fell over. Try viewing the WebServer logs for more details." } +# Future use. +def error_page(code=500, ui_title="500", title="Something went very wrong!", + message="Looks like the server fell over. Try viewing the WebServer logs for more details."): + data = {"ui_page": ui_title, "ui_title": ui_title, "code": code, "title": title, "message": message} return render_template("error.html", data=data, status=500) + @app.route("/") def ui_index(request): config = server_state.get() @@ -148,6 +152,7 @@ def ui_status(request): "ui_page": "status", "ui_title": "Status"} return render_template("status.html", data=data) + @app.route("/alerts") def ui_alerts(request): data = { From 0cdfd7b2fe2e75ce0b6e2021cb47254f3f8f83e5 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 22 Sep 2021 20:14:58 +0100 Subject: [PATCH 068/156] Fix linting --- alerts/player.py | 7 +++++-- alerts/server.py | 6 ++++-- baps_types/alert.py | 3 ++- helpers/alert_manager.py | 2 +- package.json | 2 +- web_server.py | 3 ++- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/alerts/player.py b/alerts/player.py index 1c9b70b..9ed9da8 100644 --- a/alerts/player.py +++ b/alerts/player.py @@ -48,7 +48,9 @@ def _channel_count(self): "start_time": -1, # Now "id": "no_channels", "title": "There are no players configured.", - "description": "The number of channels configured is {}. Please set to at least 1 on the 'Server Config' page.".format(self._player_count), + "description": "The number of channels configured is {}. \ + Please set to at least 1 on the 'Server Config' page." + .format(self._player_count), "module": MODULE+"Handler", "severity": CRITICAL })] @@ -61,7 +63,8 @@ def _initialised(self): "start_time": -1, # Now "id": "player_{}_not_initialised".format(channel), "title": "Player {} is not initialised.".format(channel), - "description": "This typically means the player channel was not able find the configured sound output on the system. Please check the 'Player Config' and Player logs to determine the cause.", + "description": "This typically means the player channel was not able find the configured sound output \ + on the system. Please check the 'Player Config' and Player logs to determine the cause.", "module": MODULE+str(channel), "severity": CRITICAL })) diff --git a/alerts/server.py b/alerts/server.py index 6a07d06..d89e186 100644 --- a/alerts/server.py +++ b/alerts/server.py @@ -37,7 +37,8 @@ def _api_key(self): "start_time": -1, # Now "id": "api_key_missing", "title": "MyRadio API Key is not configured.", - "description": "This means you will be unable to load show plans, audio items, or tracklist. Please set one on the 'Server Config' page.", + "description": "This means you will be unable to load show plans, audio items, or tracklist. \ + Please set one on the 'Server Config' page.", "module": MODULE, "severity": CRITICAL })] @@ -47,7 +48,8 @@ def _api_key(self): "start_time": -1, "id": "api_key_missing", "title": "MyRadio API Key seems incorrect.", - "description": "The API key is less than 10 characters, it's probably not a valid one. If it is valid, it shouldn't be.", + "description": "The API key is less than 10 characters, it's probably not a valid one. \ + If it is valid, it shouldn't be.", "module": MODULE, "severity": WARNING })] diff --git a/baps_types/alert.py b/baps_types/alert.py index 05e792a..33ddd76 100644 --- a/baps_types/alert.py +++ b/baps_types/alert.py @@ -56,7 +56,8 @@ def __init__(self, new_data: Dict[str, Any]): raise KeyError("Key {} is missing from data to create Alert.".format(key)) # if type(new_data[key]) != type(getattr(self,key)): - # raise TypeError("Key {} has type {}, was expecting {}.".format(key, type(new_data[key]), type(getattr(self,key)))) + # raise TypeError("Key {} has type {}, was expecting {}." + # .format(key, type(new_data[key]), type(getattr(self,key)))) # Account for if the creator didn't want to set a custom time. if key == "start_time" and new_data[key] == -1: diff --git a/helpers/alert_manager.py b/helpers/alert_manager.py index f2ca4fe..53cdaea 100644 --- a/helpers/alert_manager.py +++ b/helpers/alert_manager.py @@ -6,7 +6,7 @@ from inspect import getmembers, isclass from sys import modules -from baps_types.alert import CRITICAL, Alert +from baps_types.alert import Alert import alerts diff --git a/package.json b/package.json index f4fb81f..cae6ea8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", "presenter-start": "cd presenter && yarn start-baps", - "lint": "autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . " + "lint": "./venv/bin/autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . && ./venv/bin/flake8 . --exclude=\"*node_modules*,*venv/*,presenter/*\" --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics" }, "repository": { "type": "git", diff --git a/web_server.py b/web_server.py index 1ba3940..142db8a 100644 --- a/web_server.py +++ b/web_server.py @@ -1,5 +1,5 @@ from sanic import Sanic -from sanic.exceptions import NotFound, ServerError, abort +from sanic.exceptions import NotFound, abort from sanic.response import html, file, redirect from sanic.response import json as resp_json from sanic_cors import CORS @@ -119,6 +119,7 @@ def page_not_found(request, e: Any): "message": "Looks like you fell off the tip of the iceberg."} return render_template("error.html", data=data, status=404) + # Future use. def error_page(code=500, ui_title="500", title="Something went very wrong!", message="Looks like the server fell over. Try viewing the WebServer logs for more details."): From fe562ac60f50e51b612dce32cf9a83e0ef63357a Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 22 Sep 2021 20:31:28 +0100 Subject: [PATCH 069/156] Correct comment --- helpers/alert_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/alert_manager.py b/helpers/alert_manager.py index 53cdaea..b2e2775 100644 --- a/helpers/alert_manager.py +++ b/helpers/alert_manager.py @@ -65,7 +65,7 @@ def poll_alerts(self): new_alerts.extend(provider_alerts) # Here we replace new firing alerts with older ones, to keep any context. - # (This doesn't do anything yet really, for future use.) + # (This doesn't do much yet really, just remembering the start_time) for existing in self._alerts: found = False for new in new_alerts: From 432c645759361d30c5ad448d5003881a1a114307 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 22 Sep 2021 23:53:30 +0100 Subject: [PATCH 070/156] Fix returning OKAY to play when unloaded. Closes #16 --- player.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/player.py b/player.py index 2f70f31..da5477d 100644 --- a/player.py +++ b/player.py @@ -199,9 +199,7 @@ def unpause(self): if not self.isPlaying: state = self.state.get() position: float = state["pos_true"] - try: - self.play(position) - except Exception: + if not self.play(position): self.logger.log.exception( "Failed to unpause from pos: " + str(position) ) From 5103023e85f8880e151ececf44cc81aee28fb040 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 22 Sep 2021 23:57:50 +0100 Subject: [PATCH 071/156] Try non-normalised / redownload file on failure to load. --- helpers/myradio_api.py | 84 +++++++++++++++++++++------------------- helpers/normalisation.py | 21 ++++++++++ player.py | 79 ++++++++++++++++++++++++++----------- 3 files changed, 122 insertions(+), 62 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index f1cb32a..2744038 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -60,6 +60,7 @@ async def async_call(self, url, method="GET", data=None, timeout=10): + str(response.status) ) self._logException(str(await response.text())) + return None # Given the output was bad, don't forward it. return await response.read() def call(self, url, method="GET", data=None, timeout=10, json_payload=True): @@ -102,12 +103,12 @@ async def async_api_call( request = None if method == "GET": - request = self.async_call(url, method="GET", timeout=timeout) + request = await self.async_call(url, method="GET", timeout=timeout) elif method == "POST": self._log("POST data: {}".format(data)) - request = self.async_call(url, data=data, method="POST", timeout=timeout) + request = await self.async_call(url, data=data, method="POST", timeout=timeout) elif method == "PUT": - request = self.async_call(url, method="PUT", timeout=timeout) + request = await self.async_call(url, method="PUT", timeout=timeout) else: self._logException("Invalid API method. Request not sent.") return None @@ -157,7 +158,7 @@ async def get_showplans(self): self._logException("Failed to get list of show plans.") return None - payload = json.loads(await request)["payload"] + payload = json.loads(request)["payload"] shows = [] if not payload["current"]: @@ -186,7 +187,7 @@ async def get_showplan(self, timeslotid: int): self._logException("Failed to get show plan.") return None - payload = json.loads(await request)["payload"] + payload = json.loads(request)["payload"] plan = {} @@ -203,7 +204,7 @@ async def get_showplan(self, timeslotid: int): # Audio Library - async def get_filename(self, item: PlanItem, did_download: bool = False): + async def get_filename(self, item: PlanItem, did_download: bool = False, redownload=False): format = "mp3" # TODO: Maybe we want this customisable? if item.trackid: itemType = "track" @@ -234,28 +235,30 @@ async def get_filename(self, item: PlanItem, did_download: bool = False): filename: str = resolve_external_file_path( "/music-tmp/{}-{}.{}".format(itemType, id, format) ) - # Check if we already downloaded the file. If we did, give that. - if os.path.isfile(filename): - self._log("Already got file: " + filename, DEBUG) - return (filename, False) if did_download else filename - - # If something else (another channel, the preloader etc) is downloading the track, wait for it. - if os.path.isfile(filename + dl_suffix): - time_waiting_s = 0 - self._log( - "Waiting for download to complete from another worker. " + filename, - DEBUG, - ) - while time_waiting_s < 20: - # TODO: Make something better here. - # If the connectivity is super poor or we're loading reeaaaalllly long files, - # this may be annoying, but this is just in case somehow the other api download gives up. - if os.path.isfile(filename): - # Now the file is downloaded successfully - return (filename, False) if did_download else filename - time_waiting_s += 1 - self._log("Still waiting", DEBUG) - time.sleep(1) + + if not redownload: + # Check if we already downloaded the file. If we did, give that, unless we're forcing a redownload. + if os.path.isfile(filename): + self._log("Already got file: " + filename, DEBUG) + return (filename, False) if did_download else filename + + # If something else (another channel, the preloader etc) is downloading the track, wait for it. + if os.path.isfile(filename + dl_suffix): + time_waiting_s = 0 + self._log( + "Waiting for download to complete from another worker. " + filename, + DEBUG, + ) + while time_waiting_s < 20: + # TODO: Make something better here. + # If the connectivity is super poor or we're loading reeaaaalllly long files, + # this may be annoying, but this is just in case somehow the other api download gives up. + if os.path.isfile(filename): + # Now the file is downloaded successfully + return (filename, False) if did_download else filename + time_waiting_s += 1 + self._log("Still waiting", DEBUG) + time.sleep(1) # File doesn't exist, download it. try: @@ -267,17 +270,18 @@ async def get_filename(self, item: PlanItem, did_download: bool = False): request = await self.async_api_call(url, api_version="non") - if not request: + if not request or not isinstance(request, (bytes, bytearray)): return (None, False) if did_download else None try: with open(filename + dl_suffix, "wb") as file: - file.write(await request) + file.write(request) os.rename(filename + dl_suffix, filename) except Exception as e: self._logException("Failed to write music file: {}".format(e)) return (None, False) if did_download else None + self._log("Successfully re/downloaded file.", DEBUG) return (filename, True) if did_download else filename # Gets the list of managed music playlists. @@ -285,22 +289,22 @@ async def get_playlist_music(self): url = "/playlist/allitonesplaylists" request = await self.async_api_call(url) - if not request: + if not request or not isinstance(request, bytes): self._logException("Failed to retrieve music playlists.") return None - return json.loads(await request)["payload"] + return json.loads(request)["payload"] # Gets the list of managed aux playlists (sfx, beds etc.) async def get_playlist_aux(self): url = "/nipswebPlaylist/allmanagedplaylists" request = await self.async_api_call(url) - if not request: + if not request or not isinstance(request, bytes): self._logException("Failed to retrieve music playlists.") return None - return json.loads(await request)["payload"] + return json.loads(request)["payload"] # Loads the playlist items for a certain managed aux playlist async def get_playlist_aux_items(self, library_id: str): @@ -311,13 +315,13 @@ async def get_playlist_aux_items(self, library_id: str): url = "/nipswebPlaylist/{}/items".format(library_id) request = await self.async_api_call(url) - if not request: + if not request or not isinstance(request, bytes): self._logException( "Failed to retrieve items for aux playlist {}.".format(library_id) ) return None - return json.loads(await request)["payload"] + return json.loads(request)["payload"] # Loads the playlist items for a certain managed playlist @@ -325,13 +329,13 @@ async def get_playlist_music_items(self, library_id: str): url = "/playlist/{}/tracks".format(library_id) request = await self.async_api_call(url) - if not request: + if not request or not isinstance(request, bytes): self._logException( "Failed to retrieve items for music playlist {}.".format(library_id) ) return None - return json.loads(await request)["payload"] + return json.loads(request)["payload"] async def get_track_search( self, title: Optional[str], artist: Optional[str], limit: int = 100 @@ -341,11 +345,11 @@ async def get_track_search( ) request = await self.async_api_call(url) - if not request: + if not request or not isinstance(request, bytes): self._logException("Failed to search for track.") return None - return json.loads(await request)["payload"] + return json.loads(request)["payload"] def post_tracklist_start(self, item: PlanItem): if item.type != "central": diff --git a/helpers/normalisation.py b/helpers/normalisation.py index c7a8e1e..2168361 100644 --- a/helpers/normalisation.py +++ b/helpers/normalisation.py @@ -44,3 +44,24 @@ def get_normalised_filename_if_available(filename: str): # Else we've not got a normalised verison, just take original. return filename + + +# Returns the original file from the normalised one, useful if the normalised one is borked. +def get_original_filename_from_normalised(filename: str): + if not (isinstance(filename, str) and filename.endswith(".mp3")): + raise ValueError("Invalid filename given.") + + # Already not normalised. + if not filename.endswith("-normalised.mp3"): + if os.path.exists(filename): + return filename + return None + + # Take the filename, remove "-normalised" from it. + original_filename = "{}.mp3".format(filename.rsplit("-", 1)[0]) + + # original version exists + if os.path.exists(original_filename): + return original_filename + + return None diff --git a/player.py b/player.py index da5477d..d622303 100644 --- a/player.py +++ b/player.py @@ -36,7 +36,7 @@ from syncer import sync from threading import Timer -from helpers.normalisation import get_normalised_filename_if_available +from helpers.normalisation import get_normalised_filename_if_available, get_original_filename_from_normalised from helpers.myradio_api import MyRadioAPI from helpers.state_manager import StateManager from helpers.logging_manager import LoggingManager @@ -170,9 +170,10 @@ def status(self): # Audio Playout Related Methods def play(self, pos: float = 0): - if not self.isLoaded: - return self.logger.log.info("Playing from pos: " + str(pos)) + if not self.isLoaded: + self.logger.log.warning("Player is not loaded.") + return False try: mixer.music.play(0, pos) self.state.update("pos_offset", pos) @@ -329,7 +330,7 @@ def add_to_plan(self, new_item: Dict[str, Any]) -> bool: # Right. So this may be confusing. # So... If the user has just moved the loaded item in the channel (by removing above and readding) # Then we want to re-associate the loaded_item object reference with the new one. - # The loaded item object before this change is now an ophan, which was + # The loaded item object before this change is now an orphan, which was # kept around while the loaded item was potentially moved to another # channel. if loaded_item.timeslotitemid == new_item_obj.timeslotitemid: @@ -406,9 +407,13 @@ def clear_channel_plan(self) -> bool: def load(self, weight: int): if not self.isPlaying: - loaded_state = self.state.get() + # If we have something loaded already, unload it first. self.unload() + loaded_state = self.state.get() + + # Sometimes (at least on windows), the pygame player will lose output to the sound output after a while. + # It's odd, but essentially, to stop / recover from this, we de-init the pygame mixer and init it again. self.logger.log.info( "Resetting output (in case of sound output gone silent somehow) to " + str(loaded_state["output"]) @@ -419,16 +424,19 @@ def load(self, weight: int): loaded_item: Optional[PlanItem] = None + # Go find the show plan item of the weight we've been asked to load. for i in range(len(showplan)): if showplan[i].weight == weight: loaded_item = showplan[i] break + # If we didn't find it, exit. if loaded_item is None: self.logger.log.error( "Failed to find weight: {}".format(weight)) return False + # The file_manager helper may have pre-downloaded the file already, or we've played it before. reload = False if loaded_item.filename == "" or loaded_item.filename is None: self.logger.log.info( @@ -440,10 +448,12 @@ def load(self, weight: int): ) reload = True + # Ask the API for the file if we need it. if reload: - loaded_item.filename = sync( - self.api.get_filename(item=loaded_item)) + file = sync(self.api.get_filename(item=loaded_item)) + loaded_item.filename = str(file) if file else None + # If the API still couldn't get the file, RIP. if not loaded_item.filename: return False @@ -452,38 +462,57 @@ def load(self, weight: int): loaded_item.filename ) + # We're comitting to load this item. self.state.update("loaded_item", loaded_item) + # Given we've just messed around with filenames etc, update the item in the show plan too. for i in range(len(showplan)): if showplan[i].weight == weight: self.state.update("show_plan", index=i, value=loaded_item) break - # TODO: Update the show plan filenames??? load_attempt = 0 - if not isinstance(loaded_item.filename, str): - return False - + # Let's have 5 attempts at loading the item audio while load_attempt < 5: load_attempt += 1 + + original_file = None + if load_attempt == 3: + # Ok, we tried twice already to load the file. + # Let's see if we can recover from this. + # Try swapping the normalised version out for the original. + original_file = get_original_filename_from_normalised( + loaded_item.filename + ) + self.logger.log.warning("3rd attempt. Trying the non-normalised file: {}".format(original_file)) + + if load_attempt == 4: + # well, we've got so far that the normalised and original files didn't load. + # Take a last ditch effort to download the original file again. + file = sync(self.api.get_filename(item=loaded_item, redownload=True)) + if file: + original_file = str(file) + self.logger.log.warning("4rd attempt. Trying to redownload the file, got: {}".format(original_file)) + + if original_file: + loaded_item.filename = original_file + try: self.logger.log.info( - "Loading file: " + str(loaded_item.filename)) + "Attempt {} Loading file: {}".format(load_attempt, loaded_item.filename)) mixer.music.load(loaded_item.filename) except Exception: # We couldn't load that file. self.logger.log.exception( "Couldn't load file: " + str(loaded_item.filename) ) - time.sleep(1) continue # Try loading again. if not self.isLoaded: self.logger.log.error( "Pygame loaded file without error, but never actually loaded." ) - time.sleep(1) continue # Try loading again. try: @@ -500,23 +529,25 @@ def load(self, weight: int): except Exception: self.logger.log.exception( "Failed to update the length of item.") - time.sleep(1) continue # Try loading again. # Everything worked, we made it! + # Write the loaded item again once more, to confirm the filename if we've reattempted. + self.state.update("loaded_item", loaded_item) + if loaded_item.cue > 0: self.seek(loaded_item.cue) else: self.seek(0) - if self.state.get()["play_on_load"]: + if loaded_state["play_on_load"]: self.unpause() return True - self.logger.log.error( - "Failed to load track after numerous retries.") - return False + # Even though we failed, make sure state is up to date with latest failure. + # We're comitting to load this item. + self.state.update("loaded_item", loaded_item) return False @@ -546,8 +577,10 @@ def quit(self): self.logger.log.exception("Failed to quit mixer.") def output(self, name: Optional[str] = None): - wasPlaying = self.state.get()["playing"] - oldPos = self.state.get()["pos_true"] + wasPlaying = self.isPlaying + + state = self.state.get() + oldPos = state["pos_true"] name = None if (not name or name.lower() == "none") else name @@ -564,7 +597,7 @@ def output(self, name: Optional[str] = None): ) return False - loadedItem = self.state.get()["loaded_item"] + loadedItem = state["loaded_item"] if loadedItem: self.logger.log.info("Reloading after output change.") self.load(loadedItem.weight) @@ -799,6 +832,8 @@ def _ended(self): loaded_item.name, loaded_item.weight ) ) + # Just make sure that if we stop and do nothing, we end up at 0. + self.state.update("pos",0) # Repeat 1 # TODO ENUM From 84f0bae69b41448bd9756f3887f9c4474aaf3fab Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Sep 2021 00:02:12 +0100 Subject: [PATCH 072/156] Add better linting and 3.1.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6ac33ba..cae6ea8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bapsicle", "nice_name": "BAPSicle", - "version": "3.0.0", + "version": "3.1.0", "description": "BAPS3, the third generation of University Radio York's Broadcast and Presenting Suite. This package includes the Server (BAPSicle) and Presenter (WebStudio)", "main": "index.js", "directories": { @@ -13,7 +13,7 @@ "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", "presenter-start": "cd presenter && yarn start-baps", - "lint": "autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . " + "lint": "./venv/bin/autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . && ./venv/bin/flake8 . --exclude=\"*node_modules*,*venv/*,presenter/*\" --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics" }, "repository": { "type": "git", From 827b7c685f329ad62144eb9b877e19403b332003 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Sep 2021 00:03:36 +0100 Subject: [PATCH 073/156] Little lint --- player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.py b/player.py index d622303..2120241 100644 --- a/player.py +++ b/player.py @@ -833,7 +833,7 @@ def _ended(self): ) ) # Just make sure that if we stop and do nothing, we end up at 0. - self.state.update("pos",0) + self.state.update("pos", 0) # Repeat 1 # TODO ENUM From b79a4a6806e9a19064ae0e04f0f2ec589e94355b Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Sep 2021 00:30:09 +0100 Subject: [PATCH 074/156] Try unique package names per build. --- .github/workflows/build.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2cf7c09..87ea24c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,6 +1,7 @@ name: Package on: [push] - +env: + version: ${{github.base_ref}}-${{github.sha}} jobs: build-macos: @@ -25,7 +26,7 @@ jobs: - name: Archive Build uses: actions/upload-artifact@v2 with: - name: Package - MacOS + name: BAPSicle-${{ env.version }}-MacOS path: | build/output/BAPSicle.zip @@ -51,7 +52,7 @@ jobs: - name: Archive Build uses: actions/upload-artifact@v2 with: - name: Package - Ubuntu + name: BAPSicle-${{ env.version }}-Ubuntu path: | build/output/BAPSicle @@ -77,7 +78,7 @@ jobs: - name: Archive Build uses: actions/upload-artifact@v2 with: - name: Package - Windows + name: BAPSicle-${{ env.version }}-Windows path: | build/output/BAPSicle.exe install/ From dd0c3f340cea94c0c452eeb4776e80528f2489dd Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Sep 2021 00:46:16 +0100 Subject: [PATCH 075/156] Try other refs. --- .github/workflows/build.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 87ea24c..514c6c6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,7 +1,8 @@ name: Package -on: [push] +on: [push,pull_request] + env: - version: ${{github.base_ref}}-${{github.sha}} + version: ${{github.head_ref}}${{github.ref}} jobs: build-macos: From 84282987b5303ab553c2ab404a12ef31a8e34670 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Sep 2021 00:51:04 +0100 Subject: [PATCH 076/156] Just pull request with sha. --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 514c6c6..169b869 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,8 +1,8 @@ name: Package -on: [push,pull_request] +on: [pull_request] env: - version: ${{github.head_ref}}${{github.ref}} + version: ${{github.head_ref}-${{github.sha}} jobs: build-macos: From 485a79934b1f96469791e204c88ab22bed87f5fc Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Sep 2021 01:09:06 +0100 Subject: [PATCH 077/156] Try getting branch via bash. --- .github/workflows/build.yaml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 169b869..e163552 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,8 +1,6 @@ name: Package -on: [pull_request] +on: [push] -env: - version: ${{github.head_ref}-${{github.sha}} jobs: build-macos: @@ -24,10 +22,14 @@ jobs: npm run presenter-make build/build-macos.sh zip -r build/output/BAPSicle.zip build/output/BAPSicle.app + - name: Extract branch name + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + id: extract_branch - name: Archive Build uses: actions/upload-artifact@v2 with: - name: BAPSicle-${{ env.version }}-MacOS + name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-MacOS path: | build/output/BAPSicle.zip @@ -50,10 +52,14 @@ jobs: run: | npm run presenter-make build/build-linux.sh + - name: Extract branch name + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + id: extract_branch - name: Archive Build uses: actions/upload-artifact@v2 with: - name: BAPSicle-${{ env.version }}-Ubuntu + name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-Ubuntu path: | build/output/BAPSicle @@ -76,10 +82,14 @@ jobs: run: | npm run presenter-make build/build-windows.bat no-venv + - name: Extract branch name + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + id: extract_branch - name: Archive Build uses: actions/upload-artifact@v2 with: - name: BAPSicle-${{ env.version }}-Windows + name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-Windows path: | build/output/BAPSicle.exe install/ From 076440d7c81501ae5e443f854246ec59ecf7a963 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Sep 2021 01:20:12 +0100 Subject: [PATCH 078/156] Get branch without user prefix (can't have / in filename) --- .github/workflows/build.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e163552..fcd751b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -24,7 +24,7 @@ jobs: zip -r build/output/BAPSicle.zip build/output/BAPSicle.app - name: Extract branch name shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" id: extract_branch - name: Archive Build uses: actions/upload-artifact@v2 @@ -54,7 +54,7 @@ jobs: build/build-linux.sh - name: Extract branch name shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" id: extract_branch - name: Archive Build uses: actions/upload-artifact@v2 @@ -84,7 +84,7 @@ jobs: build/build-windows.bat no-venv - name: Extract branch name shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" id: extract_branch - name: Archive Build uses: actions/upload-artifact@v2 From c82b19309a1f0ebf9c1102f2e1cb4f3883c69847 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 24 Sep 2021 20:26:48 +0100 Subject: [PATCH 079/156] Make sure server definitely quits with timeout. --- launch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/launch.py b/launch.py index 5e2ac64..aaeae03 100755 --- a/launch.py +++ b/launch.py @@ -35,7 +35,7 @@ def startServer(notifications=False): if server and server.is_alive(): server.terminate() - server.join() + server.join(timeout=20) # If we somehow get stuck stopping BAPSicle let it die. # Catch the handler being killed externally. except Exception as e: @@ -43,7 +43,7 @@ def startServer(notifications=False): type(e).__name__, e.args)) if server and server.is_alive(): server.terminate() - server.join() + server.join(timeout=20) def printer(msg: Any): @@ -81,3 +81,4 @@ def printer(msg: Any): sys.exit(0) else: startServer() + sys.exit(0) From bc1bd45cd467ec8c1184c836dab00e0f71ad1310 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 24 Sep 2021 21:11:39 +0100 Subject: [PATCH 080/156] Switch to a single queue wherever possible --- controllers/controller.py | 4 +- controllers/mattchbox_usb.py | 10 ++-- file_manager.py | 42 +++++++------- player.py | 7 ++- player_handler.py | 47 ++++++++-------- server.py | 33 +++++------ web_server.py | 17 ++++-- websocket_server.py | 105 ++++++++++++++++++----------------- 8 files changed, 138 insertions(+), 127 deletions(-) diff --git a/controllers/controller.py b/controllers/controller.py index 7582441..25aae87 100644 --- a/controllers/controller.py +++ b/controllers/controller.py @@ -6,9 +6,9 @@ class Controller: # Main controller class. All implementations of controller support should inherit this. callbacks: List[Callable] = [] player_to_q: List[Queue] - player_from_q: List[Queue] + player_from_q: Queue - def __init__(self, player_to_q: List[Queue], player_from_q: List[Queue]): + def __init__(self, player_to_q: List[Queue], player_from_q: Queue): self.handler() return diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index c7dcc11..12552d1 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -18,7 +18,7 @@ class MattchBox(Controller): logger: LoggingManager def __init__( - self, server_to_q: List[Queue], server_from_q: List[Queue], state: StateManager + self, player_to_q: List[Queue], player_from_q: Queue, state: StateManager ): process_title = "ControllerHandler" @@ -39,8 +39,8 @@ def __init__( self.next_port = self.server_state.get()["serial_port"] self.logger.log.info("Server config gives port as: {}".format(self.next_port)) - self.server_from_q = server_from_q - self.server_to_q = server_to_q + self.player_from_q = player_from_q + self.player_to_q = player_to_q self.handler() @@ -59,7 +59,7 @@ def _state_handler(self): def _disconnected(self): # If we lose the controller, make sure to set channels live, so we tracklist. - for i in range(len(self.server_from_q)): + for i in range(len(self.player_to_q)): self.sendToPlayer(i, "SETLIVE:True") self.server_state.update("ser_connected", False) @@ -138,4 +138,4 @@ def sendToPlayer(self, channel: int, msg: str): self.logger.log.info( "Sending message to player channel {}: {}".format(channel, msg) ) - self.server_to_q[channel].put("CONTROLLER:" + msg) + self.player_to_q[channel].put("CONTROLLER:" + msg) diff --git a/file_manager.py b/file_manager.py index 06af766..fa011d1 100644 --- a/file_manager.py +++ b/file_manager.py @@ -19,7 +19,7 @@ class FileManager: logger: LoggingManager api: MyRadioAPI - def __init__(self, channel_from_q: List[Queue], server_config: StateManager): + def __init__(self, channel_from_q: Queue, server_config: StateManager): self.logger = LoggingManager("FileManager") self.api = MyRadioAPI(self.logger, server_config) @@ -29,7 +29,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): current_process().name = process_title terminator = Terminator() - self.channel_count = len(channel_from_q) + self.channel_count = server_config.get()["num_channels"] self.channel_received = None self.last_known_show_plan = [[]] * self.channel_count self.next_channel_preload = 0 @@ -46,15 +46,25 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): ): self.channel_received = [False] * self.channel_count - for channel in range(self.channel_count): + try: + message = channel_from_q.get_nowait() + except Exception: + # No new messages + # Let's try preload / normalise some files now we're free of messages. + preloaded = self.do_preload() + normalised = self.do_normalise() + + if not preloaded and not normalised: + # We didn't do any hard work, let's sleep. + sleep(0.2) + else: try: - message = channel_from_q[channel].get_nowait() - except Exception: - continue - try: - # source = message.split(":")[0] - command = message.split(":", 2)[1] + split = message.split(":", 1) + + channel = int(split[0]) + # source = split[1] + command = split[2] # If we have requested a new show plan, empty the music-tmp directory for the previous show. if command == "GETPLAN": @@ -114,12 +124,12 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): ] * self.channel_count # If we receive a new status message, let's check for files which have not been pre-loaded. - if command == "STATUS": - extra = message.split(":", 3) - if extra[2] != "OKAY": + elif command == "STATUS": + extra = message.split(":", 4) + if extra[3] != "OKAY": continue - status = json.loads(extra[3]) + status = json.loads(extra[4]) show_plan = status["show_plan"] item_ids = [] for item in show_plan: @@ -140,13 +150,7 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): ) ) - # Let's try preload / normalise some files now we're free of messages. - preloaded = self.do_preload() - normalised = self.do_normalise() - if not preloaded and not normalised: - # We didn't do any hard work, let's sleep. - sleep(0.2) except Exception as e: self.logger.log.exception( diff --git a/player.py b/player.py index 2120241..5e1ca31 100644 --- a/player.py +++ b/player.py @@ -923,16 +923,17 @@ def _ping_times(self): def _retAll(self, msg): if self.out_q: - self.out_q.put("ALL:" + msg) + self.out_q.put("{}:ALL:{}".format(self.state.get()["channel"],msg)) def _retMsg( self, msg: Any, okay_str: bool = False, custom_prefix: Optional[str] = None ): + response = "{}:".format(self.state.get()["channel"]) # Make sure to add the message source back, so that it can be sent to the correct destination in the main server. if custom_prefix: - response = custom_prefix + response += custom_prefix else: - response = "{}:{}:".format(self.last_msg_source, self.last_msg) + response += "{}:{}:".format(self.last_msg_source, self.last_msg) if msg is True: response += "OKAY" elif isinstance(msg, str): diff --git a/player_handler.py b/player_handler.py index da17b64..ea7026e 100644 --- a/player_handler.py +++ b/player_handler.py @@ -22,28 +22,31 @@ def __init__( terminator = Terminator() try: while not terminator.terminate: - - for channel in range(len(channel_from_q)): - try: - message = channel_from_q[channel].get_nowait() - source = message.split(":")[0] - command = message.split(":")[1] - - # Let the file manager manage the files based on status and loading new show plan triggers. - if command == "GET_PLAN" or command == "STATUS": - file_to_q[channel].put(message) - - # TODO ENUM - if source in ["ALL", "WEBSOCKET"]: - websocket_to_q[channel].put(message) - if source in ["ALL", "UI"]: - if not message.split(":")[1] == "POS": - # We don't care about position update spam - ui_to_q[channel].put(message) - if source in ["ALL", "CONTROLLER"]: - controller_to_q[channel].put(message) - except Exception: - pass + try: + # Format ::: + q_msg = channel_from_q.get_nowait() + if not isinstance(q_msg, str): + continue + split = q_msg.split(":",1) + message = split[1] + source = message.split(":")[0] + command = message.split(":")[1] + + # Let the file manager manage the files based on status and loading new show plan triggers. + if command == "GETPLAN" or command == "STATUS": + file_to_q.put(q_msg) + + # TODO ENUM + if source in ["ALL", "WEBSOCKET"]: + websocket_to_q.put(q_msg) + if source in ["ALL", "UI"]: + if not message.split(":")[1] == "POS": + # We don't care about position update spam + ui_to_q.put(q_msg) + if source in ["ALL", "CONTROLLER"]: + controller_to_q.put(q_msg) + except Exception: + pass sleep(0.02) except Exception as e: diff --git a/server.py b/server.py index 83a80d0..e3376c7 100644 --- a/server.py +++ b/server.py @@ -73,13 +73,11 @@ class BAPSicleServer: } player_to_q: List[Queue] = [] - player_from_q: List[Queue] = [] - ui_to_q: List[Queue] = [] - websocket_to_q: List[Queue] = [] - controller_to_q: List[Queue] = [] - file_to_q: List[Queue] = [] - api_from_q: Queue - api_to_q: Queue + player_from_q: Queue + ui_to_q: Queue + websocket_to_q: Queue + controller_to_q: Queue + file_to_q: Queue player: List[multiprocessing.Process] = [] websockets_server: Optional[multiprocessing.Process] = None @@ -97,10 +95,8 @@ def __init__(self): self.stopServer() - if self.state.get()["running_state"] == "restarting": - continue - - break + if self.state.get()["running_state"] != "restarting": + break def check_processes(self): @@ -128,7 +124,7 @@ def check_processes(self): args=( channel, self.player_to_q[channel], - self.player_from_q[channel], + self.player_from_q, self.state, ), ) @@ -239,11 +235,12 @@ def startServer(self): for channel in range(self.state.get()["num_channels"]): self.player_to_q.append(multiprocessing.Queue()) - self.player_from_q.append(multiprocessing.Queue()) - self.ui_to_q.append(multiprocessing.Queue()) - self.websocket_to_q.append(multiprocessing.Queue()) - self.controller_to_q.append(multiprocessing.Queue()) - self.file_to_q.append(multiprocessing.Queue()) + + self.player_from_q = multiprocessing.Queue() + self.ui_to_q = multiprocessing.Queue() + self.websocket_to_q = multiprocessing.Queue() + self.controller_to_q = multiprocessing.Queue() + self.file_to_q = multiprocessing.Queue() print( "Welcome to BAPSicle Server version: {}, build: {}.".format( @@ -291,7 +288,7 @@ def stopServer(self): print("Stopping BASPicle Server.") print("Stopping Websocket Server") - self.websocket_to_q[0].put("WEBSOCKET:QUIT") + self.websocket_to_q.put("0:WEBSOCKET:QUIT") if self.websockets_server: self.websockets_server.join(timeout=PROCESS_KILL_TIMEOUT_S) del self.websockets_server diff --git a/web_server.py b/web_server.py index 90f6527..c044976 100644 --- a/web_server.py +++ b/web_server.py @@ -8,9 +8,10 @@ from jinja2 import Environment, FileSystemLoader from jinja2.utils import select_autoescape from urllib.parse import unquote -from setproctitle import setproctitle from typing import Any, Optional, List +from setproctitle import setproctitle from multiprocessing.queues import Queue +from multiprocessing.process import current_process from queue import Empty from time import sleep import json @@ -99,7 +100,7 @@ def render_template(file, data, status=200): api: MyRadioAPI player_to_q: List[Queue] = [] -player_from_q: List[Queue] = [] +player_from_q: Queue # General UI Endpoints @@ -416,15 +417,18 @@ async def audio_file(request, type: str, id: int): def status(channel: int): - while not player_from_q[channel].empty(): + while not player_from_q.empty(): # Just waste any previous status responses. - player_from_q[channel].get() + player_from_q.get() player_to_q[channel].put("UI:STATUS") retries = 0 while retries < 40: try: - response = player_from_q[channel].get_nowait() + message = player_from_q.get_nowait() + split = message.split(":",1) + channel = int(split[0]) + response = split[1] if response.startswith("UI:STATUS:"): response = response.split(":", 2)[2] # TODO: Handle OKAY / FAIL @@ -477,7 +481,7 @@ def restart(request): # Don't use reloader, it causes Nested Processes! -def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateManager): +def WebServer(player_to: List[Queue], player_from: Queue, state: StateManager): global player_to_q, player_from_q, server_state, api, app player_to_q = player_to @@ -489,6 +493,7 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateMana process_title = "Web Server" setproctitle(process_title) + current_process().name = process_title CORS(app, supports_credentials=True) # Allow ALL CORS!!! terminate = Terminator() diff --git a/websocket_server.py b/websocket_server.py index e43ecb3..59f64c8 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -19,8 +19,8 @@ class WebsocketServer: threads = Future baps_clients = set() - channel_to_q: List[multiprocessing.Queue] - webstudio_to_q: List[multiprocessing.Queue] + player_to_q: List[multiprocessing.Queue] + player_from_q: multiprocessing.Queue server_name: str logger: LoggingManager to_webstudio: Task @@ -29,10 +29,10 @@ class WebsocketServer: def __init__(self, in_q, out_q, state): - self.channel_to_q = in_q - self.webstudio_to_q = out_q + self.player_to_q = in_q + self.player_from_q = out_q - process_title = "Websockets Servr" + process_title = "Websockets Server" setproctitle(process_title) current_process().name = process_title @@ -68,7 +68,7 @@ async def websocket_handler(self, websocket, path): json.dumps({"message": "Hello", "serverName": self.server_name}) ) self.logger.log.info("New Client: {}".format(websocket)) - for channel in self.channel_to_q: + for channel in self.player_to_q: channel.put("WEBSOCKET:STATUS") self.from_webstudio = asyncio.create_task( @@ -85,7 +85,7 @@ async def handle_from_webstudio(self, websocket): data = json.loads(message) if "channel" not in data: # Didn't specify a channel, send to all. - for channel in range(len(self.channel_to_q)): + for channel in range(len(self.player_to_q)): self.sendCommand(channel, data) else: channel = int(data["channel"]) @@ -107,7 +107,7 @@ async def handle_from_webstudio(self, websocket): self.baps_clients.remove(websocket) def sendCommand(self, channel, data): - if channel not in range(len(self.channel_to_q)): + if channel not in range(len(self.player_to_q)): self.logger.log.exception( "Received channel number larger than server supported channels." ) @@ -157,7 +157,7 @@ def sendCommand(self, channel, data): elif command == "MOVE": # remove the exiting item first - self.channel_to_q[channel].put( + self.player_to_q[channel].put( "{}REMOVE:{}".format(message, data["weight"]) ) @@ -169,7 +169,7 @@ def sendCommand(self, channel, data): item["weight"] = int(data["new_weight"]) # Now send the special case. - self.channel_to_q[new_channel].put( + self.player_to_q[new_channel].put( "WEBSOCKET:ADD:" + json.dumps(item) ) @@ -192,7 +192,7 @@ def sendCommand(self, channel, data): message += ":" + extra try: - self.channel_to_q[channel].put(message) + self.player_to_q[channel].put(message) except Exception as e: self.logger.log.exception( "Failed to send message {} to channel {}: {}".format( @@ -208,51 +208,52 @@ async def handle_to_webstudio(self): terminator = Terminator() while not terminator.terminate: + await asyncio.sleep(0.02) + try: + message = self.player_from_q.get_nowait() + split = message.split(":") + channel = int(split[0]) + source = split[1] + # TODO ENUM + if source not in ["WEBSOCKET", "ALL"]: + self.logger.log.error( + "ERROR: Message received from invalid source to websocket_handler. Ignored.", + source, + message, + ) + continue - for channel in range(len(self.webstudio_to_q)): - try: - message = self.webstudio_to_q[channel].get_nowait() - source = message.split(":")[0] - # TODO ENUM - if source not in ["WEBSOCKET", "ALL"]: - self.logger.log.error( - "ERROR: Message received from invalid source to websocket_handler. Ignored.", - source, - message, - ) + command = split[2] + if command == "STATUS": + try: + message = message.split("OKAY:")[1] + message = json.loads(message) + except Exception: + continue # TODO more logging + elif command == "POS": + try: + message = split[3] + except Exception: continue + elif command == "QUIT": + self.quit() + else: + continue - command = message.split(":")[1] - if command == "STATUS": - try: - message = message.split("OKAY:")[1] - message = json.loads(message) - except Exception: - continue # TODO more logging - elif command == "POS": - try: - message = message.split(":", 2)[2] - except Exception: - continue - elif command == "QUIT": - self.quit() - else: - continue + data = json.dumps( + {"command": command, "data": message, "channel": channel} + ) + await asyncio.wait([conn.send(data) for conn in self.baps_clients]) + except queue.Empty: + continue + except ValueError: + # Typically a "Set of coroutines/Futures is empty." when sending to a dead client. + continue + except Exception as e: + self.logger.log.exception( + "Exception trying to send to websocket:", e + ) - data = json.dumps( - {"command": command, "data": message, "channel": channel} - ) - await asyncio.wait([conn.send(data) for conn in self.baps_clients]) - except queue.Empty: - continue - except ValueError: - # Typically a "Set of coroutines/Futures is empty." when sending to a dead client. - continue - except Exception as e: - self.logger.log.exception( - "Exception trying to send to websocket:", e - ) - await asyncio.sleep(0.02) self.quit() From d6751dc41f7d5200b3a5801cedd342b49155e707 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 24 Sep 2021 21:12:38 +0100 Subject: [PATCH 081/156] clear all the queues on exit. --- server.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/server.py b/server.py index e3376c7..bcddc9a 100644 --- a/server.py +++ b/server.py @@ -329,6 +329,28 @@ def stopServer(self): del self.player + print("Deleting all queues.") + # Should speed up GC on exit a bit. + queues = [ + self.player_to_q, + self.player_from_q, + self.ui_to_q, + self.websocket_to_q, + self.controller_to_q, + self.file_to_q, + ] + for queue in queues: + if isinstance(queue, List): + for inner_queue in queue: + while not inner_queue.empty(): + inner_queue.get() + del inner_queue + elif isinstance(queue, Queue): + while not queue.empty(): + queue.get() + for queue in queues: + del queue + print("Stopped all processes.") From 5d0d1117ea5c348c54a97ac812ae423f0e089d3c Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 24 Sep 2021 21:13:22 +0100 Subject: [PATCH 082/156] Show proxy manager threads in VScode debugger --- helpers/state_manager.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/helpers/state_manager.py b/helpers/state_manager.py index c6133b5..6cf6140 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -5,6 +5,8 @@ from datetime import datetime from copy import copy from typing import Any, Dict, List +from setproctitle import setproctitle +from multiprocessing import current_process from baps_types.plan import PlanItem from helpers.logging_manager import LoggingManager @@ -28,6 +30,11 @@ def __init__( rate_limit_params=[], rate_limit_period_s=5, ): + # When a StateManager is shared via proxy to other processes, it has a thread itself. + process_title = "StateManager Proxy" + setproctitle(process_title) + current_process().name = process_title + self.logger = logger path_dir: str = resolve_external_file_path("/state") From 212f449e13ec81382fb11fdca2961ac576d0d252 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 25 Sep 2021 18:51:30 +0100 Subject: [PATCH 083/156] Show loaded status --- ui-templates/status.html | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-templates/status.html b/ui-templates/status.html index 17482b5..5b9fc68 100644 --- a/ui-templates/status.html +++ b/ui-templates/status.html @@ -12,6 +12,7 @@

Player {{player.channel}}

Initialised: {{player.initialised}}
+ Successful Load: {{player.loaded}}
Fader Live: {{player.live}}
Current Tracklist: {{player.tracklist_id}}

From 2b2e77fb2798143d3742e2645793d3cf580fcb5e Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 25 Sep 2021 19:25:23 +0100 Subject: [PATCH 084/156] Fix borked API, makes webstudio not white screen. --- helpers/myradio_api.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 2744038..0573ba1 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -102,16 +102,21 @@ async def async_api_call( self._log("Requesting API V2 URL with method {}: {}".format(method, url)) request = None - if method == "GET": - request = await self.async_call(url, method="GET", timeout=timeout) - elif method == "POST": - self._log("POST data: {}".format(data)) - request = await self.async_call(url, data=data, method="POST", timeout=timeout) - elif method == "PUT": - request = await self.async_call(url, method="PUT", timeout=timeout) - else: - self._logException("Invalid API method. Request not sent.") + try: + if method == "GET": + request = await self.async_call(url, method="GET", timeout=timeout) + elif method == "POST": + self._log("POST data: {}".format(data)) + request = await self.async_call(url, data=data, method="POST", timeout=timeout) + elif method == "PUT": + request = await self.async_call(url, method="PUT", timeout=timeout) + else: + self._logException("Invalid API method. Request not sent.") + return None + except aiohttp.ClientError: + self._logException("Failed async API request.") return None + self._log("Finished request.") return request @@ -291,7 +296,7 @@ async def get_playlist_music(self): if not request or not isinstance(request, bytes): self._logException("Failed to retrieve music playlists.") - return None + return [] return json.loads(request)["payload"] @@ -302,7 +307,7 @@ async def get_playlist_aux(self): if not request or not isinstance(request, bytes): self._logException("Failed to retrieve music playlists.") - return None + return [] return json.loads(request)["payload"] @@ -319,7 +324,7 @@ async def get_playlist_aux_items(self, library_id: str): self._logException( "Failed to retrieve items for aux playlist {}.".format(library_id) ) - return None + return [] return json.loads(request)["payload"] @@ -333,7 +338,7 @@ async def get_playlist_music_items(self, library_id: str): self._logException( "Failed to retrieve items for music playlist {}.".format(library_id) ) - return None + return [] return json.loads(request)["payload"] @@ -347,7 +352,7 @@ async def get_track_search( if not request or not isinstance(request, bytes): self._logException("Failed to search for track.") - return None + return [] return json.loads(request)["payload"] From 1b6b3aa9c88228b7a25f9562154ba1fbecc55ec3 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 25 Sep 2021 19:26:04 +0100 Subject: [PATCH 085/156] 404 correctly on missing audio files. --- web_server.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web_server.py b/web_server.py index 142db8a..a66e147 100644 --- a/web_server.py +++ b/web_server.py @@ -426,7 +426,12 @@ async def audio_file(request, type: str, id: int): filename = get_normalised_filename_if_available(filename) # Send file or 404 - return await file(filename) + try: + response = await file(filename) + except FileNotFoundError: + abort(404) + return + return response # Static Files From 49590dcc6c396c9e648001b78d99cc2abfe62c35 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 25 Sep 2021 19:26:37 +0100 Subject: [PATCH 086/156] Fix a failed download leaving temp file. --- helpers/myradio_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index 0573ba1..f7bc5d0 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -276,6 +276,8 @@ async def get_filename(self, item: PlanItem, did_download: bool = False, redownl request = await self.async_api_call(url, api_version="non") if not request or not isinstance(request, (bytes, bytearray)): + # Remove the .downloading temp file given we gave up trying to download. + os.remove(filename + dl_suffix) return (None, False) if did_download else None try: From 6cfded26cce195bc539dfd7441fdebc0ace78602 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 25 Sep 2021 19:28:13 +0100 Subject: [PATCH 087/156] Semi-fix load failed error in webstudio not showing If a load failed immediately, webstudio didn't see anything loaded, so doesn't report it failed. --- player.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/player.py b/player.py index 7e3411e..c88035c 100644 --- a/player.py +++ b/player.py @@ -437,6 +437,9 @@ def load(self, weight: int): "Failed to find weight: {}".format(weight)) return False + # This item exists, so we're comitting to load this item. + self.state.update("loaded_item", loaded_item) + # The file_manager helper may have pre-downloaded the file already, or we've played it before. reload = False if loaded_item.filename == "" or loaded_item.filename is None: @@ -463,10 +466,8 @@ def load(self, weight: int): loaded_item.filename ) - # We're comitting to load this item. + # Given we've just messed around with filenames etc, update the item again. self.state.update("loaded_item", loaded_item) - - # Given we've just messed around with filenames etc, update the item in the show plan too. for i in range(len(showplan)): if showplan[i].weight == weight: self.state.update("show_plan", index=i, value=loaded_item) From 3bf05b2783ed7f850441f2882915edfe932413a4 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 25 Sep 2021 20:06:09 +0100 Subject: [PATCH 088/156] Fix preloader not preloading due to GET_PLAN change to GETPLAN --- player_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player_handler.py b/player_handler.py index da17b64..4cc7ea7 100644 --- a/player_handler.py +++ b/player_handler.py @@ -30,7 +30,7 @@ def __init__( command = message.split(":")[1] # Let the file manager manage the files based on status and loading new show plan triggers. - if command == "GET_PLAN" or command == "STATUS": + if command == "GETPLAN" or command == "STATUS": file_to_q[channel].put(message) # TODO ENUM From 8cc342b032a3727e88c470d76a1cfa4676482b55 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 25 Sep 2021 20:06:51 +0100 Subject: [PATCH 089/156] 3.1.0 again? --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 939f4ff..addf7be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { "name": "bapsicle", - "version": "3.0.0", + "version": "3.1.0", "lockfileVersion": 1 } From 9dc09905142ea4fd548920695e2fb3eef0960167 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 30 Sep 2021 19:12:26 +0100 Subject: [PATCH 090/156] Add normalisation switch option. --- file_manager.py | 12 ++++++++++++ server.py | 1 + ui-templates/config_server.html | 11 +++++++++++ web_server.py | 2 ++ 4 files changed, 26 insertions(+) diff --git a/file_manager.py b/file_manager.py index 06af766..df8ea47 100644 --- a/file_manager.py +++ b/file_manager.py @@ -29,6 +29,14 @@ def __init__(self, channel_from_q: List[Queue], server_config: StateManager): current_process().name = process_title terminator = Terminator() + + self.normalisation_mode = server_config.get()["normalisation_mode"] + + if self.normalisation_mode != "on": + self.logger.log.info("Normalisation is disabled.") + else: + self.logger.log.info("Normalisation is enabled.") + self.channel_count = len(channel_from_q) self.channel_received = None self.last_known_show_plan = [[]] * self.channel_count @@ -211,6 +219,10 @@ def do_preload(self): # If we've preloaded everything, get to work normalising tracks before playback. def do_normalise(self): + + if self.normalisation_mode != "on": + return False + # Some channels still have files to preload, do nothing. if self.known_channels_preloaded != [True] * self.channel_count: return False # Didn't normalise diff --git a/server.py b/server.py index 82dde6e..aa08889 100644 --- a/server.py +++ b/server.py @@ -71,6 +71,7 @@ class BAPSicleServer: "myradio_api_tracklist_source": "", "running_state": "running", "tracklist_mode": "off", + "normalisation_mode": "on", } player_to_q: List[Queue] = [] diff --git a/ui-templates/config_server.html b/ui-templates/config_server.html index 6c9ee7f..20fd45d 100644 --- a/ui-templates/config_server.html +++ b/ui-templates/config_server.html @@ -48,6 +48,17 @@ Delayed tracklisting is 20s, to account for cueing with fader down.
Fader Live means if a BAPS Controller is present with support, tracklists will trigger only if fader is up.

+
+ + +

+ Normalisation requests significant CPU requirements, if you're finding the CPU usuage is too high / causing audio glitches, disable this feature. +


diff --git a/web_server.py b/web_server.py index 142db8a..ee80e33 100644 --- a/web_server.py +++ b/web_server.py @@ -190,6 +190,7 @@ def ui_config_server(request): "state": server_state.get(), "ser_ports": DeviceManager.getSerialPorts(), "tracklist_modes": ["off", "on", "delayed", "fader-live"], + "normalisation_modes": ["off", "on"], } return render_template("config_server.html", data=data) @@ -221,6 +222,7 @@ def ui_config_server_update(request): "myradio_api_tracklist_source") ) server_state.update("tracklist_mode", request.form.get("tracklist_mode")) + server_state.update("normalisation_mode", request.form.get("normalisation_mode")) return redirect("/restart") From 1ee542ea3ec06739a027dc27f7ef8f88373fd906 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 12 Oct 2021 20:45:29 +0100 Subject: [PATCH 091/156] Fix linux to use pulseaudio. --- .gitignore | 2 ++ dev/scripts/get_linux_outputs.py | 24 ++++++++++++++++++++++++ helpers/device_manager.py | 14 ++++++++------ package-lock.json | 12 ++++++++++-- package.json | 5 ++++- player.py | 5 ++++- server.py | 6 ++++-- web_server.py | 15 +++++++++------ 8 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 dev/scripts/get_linux_outputs.py diff --git a/.gitignore b/.gitignore index 66518ac..772a7f1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ music-tmp/ presenter-build + +node_modules/ \ No newline at end of file diff --git a/dev/scripts/get_linux_outputs.py b/dev/scripts/get_linux_outputs.py new file mode 100644 index 0000000..323b6f2 --- /dev/null +++ b/dev/scripts/get_linux_outputs.py @@ -0,0 +1,24 @@ +import os + +os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" +os.putenv('SDL_AUDIODRIVER', 'pulseaudio') +import pygame._sdl2 as sdl2 +import pygame +from pygame import mixer +#pygame.init() +import time +mixer.init(44100, -16, 2, 1024) +is_capture = 0 # zero to request playback devices, non-zero to request recording devices +num = sdl2.get_num_audio_devices(is_capture) +names = [str(sdl2.get_audio_device_name(i, is_capture), encoding="utf-8") for i in range(num)] +mixer.quit() +for i in names: + print(i) + mixer.init(44100, -16, 2, 1024, devicename=i) + print(mixer.get_init()) + mixer.music.load("/home/mstratford/Downloads/managed_play.mp3") + mixer.music.play() + #my_song = mixer.Sound("/home/mstratford/Downloads/managed_play.mp3") + #my_song.play() + time.sleep(5) + pygame.quit() \ No newline at end of file diff --git a/helpers/device_manager.py b/helpers/device_manager.py index ae5c930..a7871f9 100644 --- a/helpers/device_manager.py +++ b/helpers/device_manager.py @@ -20,10 +20,10 @@ def _isHostAPI(cls, host_api) -> bool: return host_api @classmethod - def _getAudioDevices(cls) -> sd.DeviceList: + def _getSDAudioDevices(cls): # To update the list of devices - # Sadly this doesn't work on MacOS. - if not isMacOS(): + # Sadly this only works on Windows. Linux hangs, MacOS crashes. + if isWindows(): sd._terminate() sd._initialize() devices: sd.DeviceList = sd.query_devices() @@ -31,11 +31,13 @@ def _getAudioDevices(cls) -> sd.DeviceList: @classmethod def getAudioOutputs(cls) -> Tuple[List[Dict]]: - host_apis = sd.query_hostapis() - devices: sd.DeviceList = cls._getAudioDevices() + + host_apis = list(sd.query_hostapis()) + devices: sd.DeviceList = cls._getSDAudioDevices() for host_api_id in range(len(host_apis)): - if isWindows() and host_apis[host_api_id]["name"] not in WINDOWS_APIS: + # Linux SDL uses PortAudio, which SoundDevice doesn't find. So mark all as unsable. + if (isWindows() and host_apis[host_api_id]["name"] not in WINDOWS_APIS) or (isLinux()): host_apis[host_api_id]["usable"] = False else: host_apis[host_api_id]["usable"] = True diff --git a/package-lock.json b/package-lock.json index 939f4ff..ac7f15d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,13 @@ { "name": "bapsicle", - "version": "3.0.0", - "lockfileVersion": 1 + "version": "3.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "yarn": { + "version": "1.22.15", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.15.tgz", + "integrity": "sha512-AzoEDxj256BOS/jqDXA3pjyhmi4FRBBUMgYoTHI4EIt2EhREkvH0soPVEtnD+DQIJfU5R9bKhcZ1H9l8zPWeoA==" + } + } } diff --git a/package.json b/package.json index cae6ea8..2d3161e 100644 --- a/package.json +++ b/package.json @@ -24,5 +24,8 @@ "bugs": { "url": "https://github.com/universityradioyork/bapsicle/issues" }, - "homepage": "https://github.com/universityradioyork/bapsicle#readme" + "homepage": "https://github.com/universityradioyork/bapsicle#readme", + "dependencies": { + "yarn": "^1.22.15" + } } diff --git a/player.py b/player.py index 5230432..0574168 100644 --- a/player.py +++ b/player.py @@ -21,8 +21,11 @@ # Stop the Pygame Hello message. import os - os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" +from helpers.os_environment import isLinux +# It's the only one we could get to work. +if isLinux(): + os.putenv('SDL_AUDIODRIVER', 'pulseaudio') from queue import Empty import multiprocessing diff --git a/server.py b/server.py index 82dde6e..4734dd4 100644 --- a/server.py +++ b/server.py @@ -23,7 +23,7 @@ from setproctitle import setproctitle import psutil -from helpers.os_environment import isMacOS +from helpers.os_environment import isLinux, isMacOS if not isMacOS(): # Rip, this doesn't like threading on MacOS. @@ -206,7 +206,9 @@ def check_processes(self): time.sleep(1) def startServer(self): - if isMacOS(): + # On MacOS, the default causes something to keep creating new processes. + # On Linux, this is needed to make pulseaudio initiate properly. + if isMacOS() or isLinux(): multiprocessing.set_start_method("spawn", True) process_title = "startServer" diff --git a/web_server.py b/web_server.py index 142db8a..1ae5068 100644 --- a/web_server.py +++ b/web_server.py @@ -540,9 +540,12 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateMana ) except Exception: break - loop = asyncio.get_event_loop() - if loop: - loop.close() - if app: - app.stop() - del app + try: + loop = asyncio.get_event_loop() + if loop: + loop.close() + if app: + app.stop() + del app + except: + pass From e4cc6f7b61d8df6bcc79ecf41a25419323baa3b2 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Wed, 20 Oct 2021 20:49:19 +0100 Subject: [PATCH 092/156] Prompt before restarting the server if anything is playing --- ui-templates/restart-confirm.html | 9 +++++++++ web_server.py | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 ui-templates/restart-confirm.html diff --git a/ui-templates/restart-confirm.html b/ui-templates/restart-confirm.html new file mode 100644 index 0000000..3f7b3c6 --- /dev/null +++ b/ui-templates/restart-confirm.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} +{% block content_inner %} +
+

Hang on a second!

+

Something is currently playing. Restarting will interrupt it! Are you sure?

+ Cancel + Confirm +
+{% endblock %} diff --git a/web_server.py b/web_server.py index 142db8a..04ed686 100644 --- a/web_server.py +++ b/web_server.py @@ -496,6 +496,11 @@ def quit(request): @app.route("/restart") def restart(request): + if request.args.get("confirm", '') != "true": + for i in range(server_state.get()["num_channels"]): + state = status(i) + if state["playing"]: + return render_template("restart-confirm.html", data=None) server_state.update("running_state", "restarting") data = { From ecbfaa4a62955bef69b5818f9c352a36960bfa5e Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 2 Nov 2021 22:46:41 +0000 Subject: [PATCH 093/156] use sudo to install audio pkg --- build/build-linux.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/build-linux.sh b/build/build-linux.sh index c67ee8d..9b27b8a 100755 --- a/build/build-linux.sh +++ b/build/build-linux.sh @@ -7,7 +7,7 @@ build_branch="$(git branch --show-current)" echo "BUILD: str = \"$build_commit\"" > ../build.py echo "BRANCH: str = \"$build_branch\"" >> ../build.py -apt install libportaudio2 +sudo apt install libportaudio2 python3 -m venv ../venv source ../venv/bin/activate @@ -19,6 +19,8 @@ pip3 install -e ../ python3 ./generate-build-exe-config.py +chmod +x output/BAPSicle + python3 ./build-exe.py bash ./build-exe-pyinstaller-command.sh From 592cf11a79141bedf015491214bbd6655c430263 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 2 Nov 2021 22:47:24 +0000 Subject: [PATCH 094/156] Fix mp3 support on linux with pygame 2.0.1 --- build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/requirements.txt b/build/requirements.txt index 84c18be..015bd70 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,5 +1,5 @@ wheel -pygame==2.0.1 +pygame==2.0.2 sanic==21.3.4 sanic-Cors==1.0.0 syncer==1.3.0 From c475dbb5d5d72e87db75885a76aea4ff8e24de65 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 2 Nov 2021 22:48:52 +0000 Subject: [PATCH 095/156] Switch to SDL outputs with pulseaudio for linux --- dev/scripts/get_linux_outputs.py | 2 +- helpers/device_manager.py | 15 +++++++++++++++ ui-templates/config_player.html | 21 +++++++++++++++++++++ web_server.py | 8 +++++++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/dev/scripts/get_linux_outputs.py b/dev/scripts/get_linux_outputs.py index 323b6f2..6449302 100644 --- a/dev/scripts/get_linux_outputs.py +++ b/dev/scripts/get_linux_outputs.py @@ -5,7 +5,7 @@ import pygame._sdl2 as sdl2 import pygame from pygame import mixer -#pygame.init() +pygame.init() import time mixer.init(44100, -16, 2, 1024) is_capture = 0 # zero to request playback devices, non-zero to request recording devices diff --git a/helpers/device_manager.py b/helpers/device_manager.py index a7871f9..aa5a7b0 100644 --- a/helpers/device_manager.py +++ b/helpers/device_manager.py @@ -1,6 +1,12 @@ from typing import Any, Dict, List, Optional, Tuple import sounddevice as sd from helpers.os_environment import isLinux, isMacOS, isWindows +import os + +os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" +os.putenv('SDL_AUDIODRIVER', 'pulseaudio') +import pygame._sdl2 as sdl2 +from pygame import mixer import glob if isWindows(): @@ -53,6 +59,15 @@ def getAudioOutputs(cls) -> Tuple[List[Dict]]: return host_apis + @classmethod + def getAudioDevices(cls) -> List[str]: + mixer.init(44100, -16, 2, 1024) + is_capture = 0 # zero to request playback devices, non-zero to request recording devices + num = sdl2.get_num_audio_devices(is_capture) + names = [str(sdl2.get_audio_device_name(i, is_capture), encoding="utf-8") for i in range(num)] + mixer.quit() + return names + @classmethod def getSerialPorts(cls) -> List[Optional[str]]: """Lists serial port names diff --git a/ui-templates/config_player.html b/ui-templates/config_player.html index 78d2c79..6ddbfd3 100644 --- a/ui-templates/config_player.html +++ b/ui-templates/config_player.html @@ -33,6 +33,26 @@

Currently Selected

Default Audio Output

+{% if data.sdl_direct %} +Linux (Pulse Audio) +
+ +{% for output in data.outputs %} +Set for: + {% for channel in data.channels %} + {% if not channel %} + Player {{loop.index0}} + {% elif channel.output == output %} + Player {{channel.channel}} + {% else %} + Player {{channel.channel}} + {% endif %} + / + {% endfor %} +{% if output %}{{output}}{% else %}System Default Output{% endif %}
+{% endfor %} +
+{% else %} {% for host_api in data.outputs %} {{host_api.name}}
@@ -54,4 +74,5 @@

Currently Selected

{% endfor %}
{% endfor %} +{% endif %} {% endblock %} diff --git a/web_server.py b/web_server.py index 1ae5068..5b809ef 100644 --- a/web_server.py +++ b/web_server.py @@ -17,6 +17,7 @@ import os from helpers.os_environment import ( + isLinux, resolve_external_file_path, resolve_local_file_path, ) @@ -171,11 +172,16 @@ def ui_config_player(request): for i in range(server_state.get()["num_channels"]): channel_states.append(status(i)) - outputs = DeviceManager.getAudioOutputs() + outputs = None + if isLinux(): + outputs = DeviceManager.getAudioDevices() + else: + outputs = DeviceManager.getAudioOutputs() data = { "channels": channel_states, "outputs": outputs, + "sdl_direct": isLinux(), "ui_page": "config", "ui_title": "Player Config", } From 65944e59b396bd0dd5a2cd50cd4494dfdaeb49da Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 2 Nov 2021 23:02:09 +0000 Subject: [PATCH 096/156] Fix lints. --- dev/scripts/get_linux_outputs.py | 6 +++--- web_server.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/scripts/get_linux_outputs.py b/dev/scripts/get_linux_outputs.py index 6449302..1c53024 100644 --- a/dev/scripts/get_linux_outputs.py +++ b/dev/scripts/get_linux_outputs.py @@ -18,7 +18,7 @@ print(mixer.get_init()) mixer.music.load("/home/mstratford/Downloads/managed_play.mp3") mixer.music.play() - #my_song = mixer.Sound("/home/mstratford/Downloads/managed_play.mp3") - #my_song.play() + # my_song = mixer.Sound("/home/mstratford/Downloads/managed_play.mp3") + # my_song.play() time.sleep(5) - pygame.quit() \ No newline at end of file + pygame.quit() diff --git a/web_server.py b/web_server.py index 5b809ef..83acc14 100644 --- a/web_server.py +++ b/web_server.py @@ -181,7 +181,7 @@ def ui_config_player(request): data = { "channels": channel_states, "outputs": outputs, - "sdl_direct": isLinux(), + "sdl_direct": isLinux(), "ui_page": "config", "ui_title": "Player Config", } @@ -553,5 +553,5 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateMana if app: app.stop() del app - except: + except Exception: pass From 0f25345a6a10bdd925263ed008c514f8c2b509bb Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 2 Nov 2021 23:26:26 +0000 Subject: [PATCH 097/156] Fix pulseaudio error on !linux --- helpers/device_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helpers/device_manager.py b/helpers/device_manager.py index aa5a7b0..df68409 100644 --- a/helpers/device_manager.py +++ b/helpers/device_manager.py @@ -4,7 +4,8 @@ import os os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" -os.putenv('SDL_AUDIODRIVER', 'pulseaudio') +if isLinux(): + os.putenv('SDL_AUDIODRIVER', 'pulseaudio') import pygame._sdl2 as sdl2 from pygame import mixer import glob From e287e65cf37a6424488269dccc2635cb4beee6b3 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 3 Nov 2021 00:01:40 +0000 Subject: [PATCH 098/156] Default normalisation off due to ffmpeg requirement. --- server.py | 2 +- ui-templates/config_server.html | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index aa08889..19e3bf1 100644 --- a/server.py +++ b/server.py @@ -71,7 +71,7 @@ class BAPSicleServer: "myradio_api_tracklist_source": "", "running_state": "running", "tracklist_mode": "off", - "normalisation_mode": "on", + "normalisation_mode": "off", } player_to_q: List[Queue] = [] diff --git a/ui-templates/config_server.html b/ui-templates/config_server.html index 20fd45d..18a2852 100644 --- a/ui-templates/config_server.html +++ b/ui-templates/config_server.html @@ -48,7 +48,6 @@ Delayed tracklisting is 20s, to account for cueing with fader down.
Fader Live means if a BAPS Controller is present with support, tracklists will trigger only if fader is up.

-

- Normalisation requests significant CPU requirements, if you're finding the CPU usuage is too high / causing audio glitches, disable this feature. + Normalisation requests significant CPU requirements, if you're finding the CPU usuage is too high / causing audio glitches, disable this feature. ffmpeg or avconf required.


From 71689ce84c2e03d7f15a7a680f1105ebdbd5c65d Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 3 Nov 2021 00:17:47 +0000 Subject: [PATCH 099/156] Update presenter webstudio to fix node production builds. --- presenter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presenter b/presenter index bddd49f..7cfd48b 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit bddd49f4f7bb8ed5f89a967b5ac8c6d3cb04f059 +Subproject commit 7cfd48b003cfac45737b78c084ae45431a207765 From b7f75dfe321f90871d017c20d97aa57d443a04b4 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Mon, 6 Dec 2021 22:19:07 +0000 Subject: [PATCH 100/156] Fix marking played / unplayed to all channels from 0. --- presenter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presenter b/presenter index 7cfd48b..238da52 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit 7cfd48b003cfac45737b78c084ae45431a207765 +Subproject commit 238da52d94f9a2e1f476e5c8e155e50dd8519cb7 From ac7d7d022246edc73fa72f3c3bbea05a90325ce7 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Dec 2021 17:14:54 +0000 Subject: [PATCH 101/156] merge of "dependabot/pip/build/websockets-9.1" and "dev" --- build/requirements.txt | 4 ++-- websocket_server.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/requirements.txt b/build/requirements.txt index 5fd6c4e..c139e14 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,7 +1,7 @@ wheel pygame==2.0.2 -sanic==21.3.4 -sanic-Cors==1.0.0 +sanic==21.9.3 +sanic-Cors==1.0.1 syncer==1.3.0 aiohttp==3.7.4.post0 mutagen==1.45.1 diff --git a/websocket_server.py b/websocket_server.py index e43ecb3..bff97af 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -7,7 +7,8 @@ import websockets import json from os import _exit -from websockets.server import Serve +from websockets.legacy.server import Serve +from websockets.server import serve from setproctitle import setproctitle from multiprocessing import current_process @@ -39,7 +40,7 @@ def __init__(self, in_q, out_q, state): self.logger = LoggingManager("Websockets") self.server_name = state.get()["server_name"] - self.websocket_server = websockets.serve( + self.websocket_server = serve( self.websocket_handler, state.get()["host"], state.get()["ws_port"] ) From fd87bbd50604779b79fa0e5b74b920a145588b60 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Dec 2021 17:25:12 +0000 Subject: [PATCH 102/156] Bump to websockets 10.1 --- build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/requirements.txt b/build/requirements.txt index c139e14..ea525e9 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -8,7 +8,7 @@ mutagen==1.45.1 sounddevice==0.4.2 setproctitle==1.2.2 pyttsx3==2.90 -websockets==9.1 +websockets==10.1 typing_extensions==3.10.0.0 pyserial==3.5 requests==2.26.0 From 8b4265a33a62aef1fc143662280fea3d5cf544ee Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Dec 2021 17:43:54 +0000 Subject: [PATCH 103/156] Force node 14 --- .github/workflows/build.yaml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fcd751b..32999fe 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,7 +9,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9] + python-version: ['3.9'] + node-version: ['14'] steps: - uses: actions/checkout@v2 @@ -17,6 +18,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - uses: actions/checkout@v2 + - name: Set up Node ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} - name: Build .app run: | npm run presenter-make @@ -40,7 +46,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9] + python-version: ['3.9'] + node-version: ['14'] steps: - uses: actions/checkout@v2 @@ -48,6 +55,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - uses: actions/checkout@v2 + - name: Set up Node ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} - name: Build executable run: | npm run presenter-make @@ -70,7 +82,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9] + python-version: ['3.9'] + node-version: ['14'] steps: - uses: actions/checkout@v2 @@ -78,6 +91,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - uses: actions/checkout@v2 + - name: Set up Node ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} - name: Build .exe run: | npm run presenter-make From ba4e257fdd646b3d9125f9348aad00389af8a545 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Dec 2021 19:31:36 +0000 Subject: [PATCH 104/156] Fix player bootlooping if mixer doesn't init --- player.py | 15 +++++++++++---- websocket_server.py | 22 +++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/player.py b/player.py index b5455ff..9ce20d2 100644 --- a/player.py +++ b/player.py @@ -22,6 +22,7 @@ # Stop the Pygame Hello message. import os os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" + from helpers.os_environment import isLinux # It's the only one we could get to work. if isLinux(): @@ -34,7 +35,7 @@ import json import time from typing import Any, Callable, Dict, List, Optional -from pygame import mixer +from pygame import mixer, error from mutagen.mp3 import MP3 from syncer import sync from threading import Timer @@ -160,7 +161,7 @@ def isCued(self): @property def status(self): - state = copy.copy(self.state.state) + state = self.state.state # Not the biggest fan of this, but maybe I'll get a better solution for this later state["loaded_item"] = ( @@ -1054,12 +1055,18 @@ def __init__( self.logger.log.info( "Seeking to pos_true: " + str(loaded_state["pos_true"]) ) - self.seek(loaded_state["pos_true"]) + try: + self.seek(loaded_state["pos_true"]) + except error: + self.logger.log.error("Failed to seek on player start. Continuing anyway.") if loaded_state["playing"] is True: self.logger.log.info("Resuming playback on init.") # Use un-pause as we don't want to jump to a new position. - self.unpause() + try: + self.unpause() + except error: + self.logger.log.error("Failed to unpause on player start. Continuing anyway.") else: self.logger.log.info("No file was previously loaded to resume.") diff --git a/websocket_server.py b/websocket_server.py index bff97af..90b1af8 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -213,7 +213,21 @@ async def handle_to_webstudio(self): for channel in range(len(self.webstudio_to_q)): try: message = self.webstudio_to_q[channel].get_nowait() - source = message.split(":")[0] + msg_split = message.split(":",3) + parts = len(msg_split) + source = msg_split[0] + command = msg_split[1] + if parts == 4: + #status = msg_split[2] + data = msg_split[3] + elif parts == 3: + data = msg_split[2] + else: + self.logger.log.exception( + "Invalid message size:", msg_split + ) + continue + # TODO ENUM if source not in ["WEBSOCKET", "ALL"]: self.logger.log.error( @@ -223,16 +237,14 @@ async def handle_to_webstudio(self): ) continue - command = message.split(":")[1] if command == "STATUS": try: - message = message.split("OKAY:")[1] - message = json.loads(message) + message = json.loads(data) except Exception: continue # TODO more logging elif command == "POS": try: - message = message.split(":", 2)[2] + message = data except Exception: continue elif command == "QUIT": From 7a1f7be898e965b5a41c393cdd984fe9c457f3ac Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Dec 2021 20:26:13 +0000 Subject: [PATCH 105/156] Improve efficiency by reducing file test load spam --- player.py | 73 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/player.py b/player.py index 9ce20d2..0020937 100644 --- a/player.py +++ b/player.py @@ -22,7 +22,6 @@ # Stop the Pygame Hello message. import os os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" - from helpers.os_environment import isLinux # It's the only one we could get to work. if isLinux(): @@ -116,43 +115,45 @@ def isPaused(self) -> bool: @property def isLoaded(self): - return self._isLoaded() + return self.state.get()["loaded"] - def _isLoaded(self, short_test: bool = False): - if not self.state.get()["loaded_item"]: - return False - if self.isPlaying: - return True + def _checkIsLoaded(self, short_test: bool = False): - # If we don't want to do any testing if it's really loaded, fine. - if short_test: - return True + loaded = True + + if not self.state.get()["loaded_item"] or not self.isInit: + loaded = False + elif not self.isPlaying: + # If we don't want to do any testing if it's really loaded, fine. + if not short_test: + + # Because Pygame/SDL is annoying + # We're not playing now, so we can quickly test run + # If that works, we're loaded. + try: + mixer.music.set_volume(0) + mixer.music.play(0) + except Exception: + try: + mixer.music.set_volume(1) + except Exception: + self.logger.log.exception( + "Failed to reset volume after attempting loaded test." + ) + pass + loaded = False + finally: + mixer.music.stop() - # Because Pygame/SDL is annoying - # We're not playing now, so we can quickly test run - # If that works, we're loaded. - try: - mixer.music.set_volume(0) - mixer.music.play(0) - except Exception: - try: mixer.music.set_volume(1) - except Exception: - self.logger.log.exception( - "Failed to reset volume after attempting loaded test." - ) - pass - return False - finally: - mixer.music.stop() - mixer.music.set_volume(1) - return True + self.state.update("loaded", loaded) + return loaded @property def isCued(self): # Don't mess with playback, we only care about if it's supposed to be loaded. - if not self._isLoaded(short_test=True): + if not self.isLoaded: return False return ( self.state.get()["pos_true"] == self.state.get()["loaded_item"].cue @@ -540,6 +541,7 @@ def load(self, weight: int): # Everything worked, we made it! # Write the loaded item again once more, to confirm the filename if we've reattempted. self.state.update("loaded_item", loaded_item) + self._checkIsLoaded() if loaded_item.cue > 0: self.seek(loaded_item.cue) @@ -554,6 +556,7 @@ def load(self, weight: int): # Even though we failed, make sure state is up to date with latest failure. # We're comitting to load this item. self.state.update("loaded_item", loaded_item) + self._checkIsLoaded() return False @@ -901,7 +904,6 @@ def _updateState(self, pos: Optional[float] = None): self._ended() self.state.update("playing", self.isPlaying) - self.state.update("loaded", self.isLoaded) self.state.update( "pos_true", @@ -1072,7 +1074,6 @@ def __init__( try: while self.running: - time.sleep(0.02) self._updateState() self._ping_times() try: @@ -1098,11 +1099,19 @@ def __init__( except Empty: # The incomming message queue was empty, # skip message processing - pass + + # If we're getting no messages, sleep. + # But if we do have messages, once we've done with one, we'll check for the next one more quickly. + time.sleep(0.05) else: # We got a message. + ## Check if we're successfully loaded + # This is here so that we can check often, but not every single loop + # Only when user gives input. + self._checkIsLoaded() + # Output re-inits the mixer, so we can do this any time. if self.last_msg.startswith("OUTPUT"): split = self.last_msg.split(":") From c538ce3a46fb53d6cfc1e30be7013b2624a44202 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Dec 2021 20:26:37 +0000 Subject: [PATCH 106/156] Fix "STOPPED" message. --- websocket_server.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/websocket_server.py b/websocket_server.py index 90b1af8..413e20b 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -217,16 +217,12 @@ async def handle_to_webstudio(self): parts = len(msg_split) source = msg_split[0] command = msg_split[1] + data = None if parts == 4: #status = msg_split[2] data = msg_split[3] elif parts == 3: data = msg_split[2] - else: - self.logger.log.exception( - "Invalid message size:", msg_split - ) - continue # TODO ENUM if source not in ["WEBSOCKET", "ALL"]: From 325f62ed376f5ff3cfc1f735e528971d8157cfb8 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 23 Dec 2021 22:48:38 +0000 Subject: [PATCH 107/156] Comments and reordering. --- player.py | 653 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 375 insertions(+), 278 deletions(-) diff --git a/player.py b/player.py index 0020937..384ac75 100644 --- a/player.py +++ b/player.py @@ -13,11 +13,11 @@ October, November 2020 """ -# This is the player. Reliability is critical here, so we're catching -# literally every exception possible and handling it. +# This is the player. It does everything regarding playing sound. +# Reliability is critical here, so we're catching literally every exception possible and handling it. -# It is key that whenever the parent server tells us to do something -# that we respond with something, FAIL or OKAY. The server doesn't like to be kept waiting. +# It is key that whenever the clients tells us to do something +# that we respond with something, FAIL or OKAY. They don't like to be kept waiting/ignored. # Stop the Pygame Hello message. import os @@ -70,6 +70,7 @@ class Player: tracklist_start_timer: Optional[Timer] = None tracklist_end_timer: Optional[Timer] = None + # The default state that should be set if there is no previous state info. __default_state = { "initialised": False, "loaded_item": None, @@ -92,8 +93,12 @@ class Player: "tracklist_id": None, } + # These tell the StateManager which variables we don't care about really accurate history for. + # This means that the internal running state of the player will have quickly updating info (multiple times a sec) + # But we will rate limit (a few secs) saving updates to these variables to the state JSON file. __rate_limited_params = ["pos", "pos_offset", "pos_true", "remaining"] + # Checks if the mixer is init'd. It will throw an exception if not. @property def isInit(self): try: @@ -106,7 +111,7 @@ def isInit(self): @property def isPlaying(self) -> bool: if self.isInit: - return (not self.isPaused) and bool(mixer.music.get_busy()) + return not self.isPaused and mixer.music.get_busy() return False @property @@ -117,6 +122,9 @@ def isPaused(self) -> bool: def isLoaded(self): return self.state.get()["loaded"] + # Checks if a file has been loaded + # This should be run with a long test before client requests for status etc. + # Short tests are used in other logic in the player, without fussing over full playback tests. def _checkIsLoaded(self, short_test: bool = False): loaded = True @@ -124,12 +132,10 @@ def _checkIsLoaded(self, short_test: bool = False): if not self.state.get()["loaded_item"] or not self.isInit: loaded = False elif not self.isPlaying: - # If we don't want to do any testing if it's really loaded, fine. + # Because this function can be called very often, only some (less frequent) checks will initiate a full trial of loading success, for efficiency. if not short_test: - - # Because Pygame/SDL is annoying # We're not playing now, so we can quickly test run - # If that works, we're loaded. + # If that works, we're truely loaded. try: mixer.music.set_volume(0) mixer.music.play(0) @@ -150,9 +156,9 @@ def _checkIsLoaded(self, short_test: bool = False): self.state.update("loaded", loaded) return loaded + # Is the player at a cue marker point? @property def isCued(self): - # Don't mess with playback, we only care about if it's supposed to be loaded. if not self.isLoaded: return False return ( @@ -160,11 +166,14 @@ def isCued(self): and not self.isPlaying ) + # Returns the state of the player as a nice friendly JSON dump. @property def status(self): + # Get a copy of the server state. state = self.state.state # Not the biggest fan of this, but maybe I'll get a better solution for this later + # Convert objects to a nice JSON friendly dicts. state["loaded_item"] = ( state["loaded_item"].__dict__ if state["loaded_item"] else None ) @@ -175,242 +184,9 @@ def status(self): # Audio Playout Related Methods - def play(self, pos: float = 0): - self.logger.log.info("Playing from pos: " + str(pos)) - if not self.isLoaded: - self.logger.log.warning("Player is not loaded.") - return False - try: - mixer.music.play(0, pos) - self.state.update("pos_offset", pos) - except Exception: - self.logger.log.exception("Failed to play at pos: " + str(pos)) - return False - self.state.update("paused", False) - self._potentially_tracklist() - self.stopped_manually = False - return True - - def pause(self): - try: - mixer.music.stop() - except Exception: - self.logger.log.exception("Failed to pause.") - return False - - self.stopped_manually = True - self.state.update("paused", True) - return True - - def unpause(self): - if not self.isPlaying: - state = self.state.get() - position: float = state["pos_true"] - if not self.play(position): - self.logger.log.exception( - "Failed to unpause from pos: " + str(position) - ) - return False - - self.state.update("paused", False) - - # Increment Played count - loaded_item = state["loaded_item"] - if loaded_item: - loaded_item.play_count_increment() - self.state.update("loaded_item", loaded_item) - - return True - return False - - def stop(self, user_initiated: bool = False): - try: - mixer.music.stop() - except Exception: - self.logger.log.exception("Failed to stop playing.") - return False - self.state.update("paused", False) - - if user_initiated: - self._potentially_end_tracklist() - - self.stopped_manually = True - - if not self.state.get()["loaded_item"]: - self.logger.log.warning("Tried to stop without a loaded item.") - return True - - # This lets users toggle (using the stop button) between cue point and 0. - if user_initiated and not self.isCued: - # if there's a cue point ant we're not at it, go there. - self.seek(self.state.get()["loaded_item"].cue) - else: - # Otherwise, let's go to 0. - self.seek(0) - - return True - - def seek(self, pos: float) -> bool: - self.logger.log.info("Seeking to pos:" + str(pos)) - if self.isPlaying: - try: - self.play(pos) - except Exception: - self.logger.log.exception("Failed to seek to pos: " + str(pos)) - return False - return True - else: - self.logger.log.debug( - "Not playing during seek, setting pos state for next play." - ) - self.stopped_manually = True # Don't trigger _ended() on seeking. - if pos > 0: - self.state.update("paused", True) - self._updateState(pos=pos) - return True - - def set_auto_advance(self, message: bool) -> bool: - self.state.update("auto_advance", message) - return True - - def set_repeat(self, message: str) -> bool: - if message in ["all", "one", "none"]: - self.state.update("repeat", message) - return True - else: - return False - - def set_play_on_load(self, message: bool) -> bool: - self.state.update("play_on_load", message) - return True - - # Show Plan Related Methods - def get_plan(self, message: int): - plan = sync(self.api.get_showplan(message)) - self.clear_channel_plan() - channel = self.state.get()["channel"] - self.logger.log.debug(plan) - if not isinstance(plan, dict): - return False - if str(channel) in plan.keys(): - for plan_item in plan[str(channel)]: - try: - self.add_to_plan(plan_item) - except Exception as e: - self.logger.log.critical( - "Failed to add item to show plan: {}".format(e) - ) - continue - - return True - - def _check_ghosts(self, item: PlanItem): - if isinstance(item.timeslotitemid, str) and item.timeslotitemid.startswith("I"): - # Kinda a bodge for the moment, each "Ghost" (item which is not saved in the database showplan yet) - # needs to have a unique temporary item. - # To do this, we'll start with the channel number the item was originally added to - # (to stop items somehow simultaneously added to different channels from having the same id) - # And chuck in the unix epoch in ns for good measure. - item.timeslotitemid = "GHOST-{}-{}".format( - self.state.get()["channel"], time.time_ns() - ) - return item - - # TODO Allow just moving an item inside the channel instead of removing and adding. - def add_to_plan(self, new_item: Dict[str, Any]) -> bool: - new_item_obj = PlanItem(new_item) - new_item_obj = self._check_ghosts(new_item_obj) - plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) - # Shift any plan items after the new position down one to make space. - for item in plan_copy: - if item.weight >= new_item_obj.weight: - item.weight += 1 - - plan_copy += [new_item_obj] # Add the new item. - - self._fix_and_update_weights(plan_copy) - - loaded_item = self.state.get()["loaded_item"] - if loaded_item: - - # Right. So this may be confusing. - # So... If the user has just moved the loaded item in the channel (by removing above and readding) - # Then we want to re-associate the loaded_item object reference with the new one. - # The loaded item object before this change is now an orphan, which was - # kept around while the loaded item was potentially moved to another - # channel. - if loaded_item.timeslotitemid == new_item_obj.timeslotitemid: - self.state.update("loaded_item", new_item_obj) - - # NOPE NOPE NOPE - # THIS IS AN EXAMPLE OF WHAT NOT TO DO! - # ONCE AGAIN, THE LOADED ITEM IS THE SAME OBJECT INSTANCE AS THE ONE IN - # THE SHOW PLAN (AS LONG AS IT HASN'T BEEN RE/MOVED) - - # loaded_item.weight = new_item_obj.weight - - # Bump the loaded_item's weight if we just added a new item above it. - # elif loaded_item.weight >= new_item_obj.weight: - # loaded_item.weight += 1 - - # Else, new weight stays the same. - # else: - # return True - - # self.state.update("loaded_item", loaded_item) - - return True - - def remove_from_plan(self, weight: int) -> bool: - plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) - found: Optional[PlanItem] = None - - before = [] - for item in plan_copy: - before += (item.weight, item.name) - - self.logger.log.debug( - "Weights before removing weight {}:\n{}".format(weight, before) - ) - - for i in plan_copy: - if i.weight == weight: - found = i - plan_copy.remove(i) - - if found: - self._fix_and_update_weights(plan_copy) - - # If we removed the loaded item from this channel, update it's weight - # So we know how/not to autoadvance. - loaded_item = self.state.get()["loaded_item"] - if loaded_item == found: - # Loaded_item is actually the same PlanItem instance as in the show_plan. - # So if it's still in the show plan, we'll have corrected it's weight already. - # If it was removed above, fix_weights won't have done anything - # So we'll want to update the weight. - - # We're removing the loaded item from the channel. - # if loaded_item.weight == weight: - loaded_item.weight = -1 - - # If loaded_item wasn't the same instance, we'd want to do the below. - - # We removed an item above it. Shift it up. - # elif loaded_item.weight > weight: - # loaded_item.weight -= 1 - # Else, new weight stays the same. - # else: - # return True - - self.state.update("loaded_item", loaded_item) - return True - return False - - def clear_channel_plan(self) -> bool: - self.state.update("show_plan", []) - return True + # Loads a plan item into the player, ready for playing. + # This includes some retry logic to try and double-down on ensuring it plays successfully. def load(self, weight: int): if not self.isPlaying: # If we have something loaded already, unload it first. @@ -424,7 +200,7 @@ def load(self, weight: int): "Resetting output (in case of sound output gone silent somehow) to " + str(loaded_state["output"]) ) - self.output(loaded_state["output"]) + self.set_output(loaded_state["output"]) showplan = loaded_state["show_plan"] @@ -516,12 +292,6 @@ def load(self, weight: int): ) continue # Try loading again. - if not self.isLoaded: - self.logger.log.error( - "Pygame loaded file without error, but never actually loaded." - ) - continue # Try loading again. - try: if loaded_item.filename.endswith(".mp3"): song = MP3(loaded_item.filename) @@ -541,8 +311,16 @@ def load(self, weight: int): # Everything worked, we made it! # Write the loaded item again once more, to confirm the filename if we've reattempted. self.state.update("loaded_item", loaded_item) + + # Now just double check that pygame could actually play it (silently) self._checkIsLoaded() + if not self.isLoaded: + self.logger.log.error( + "Pygame loaded file without error, but never actually loaded." + ) + continue # Try loading again. + # If the track has a cue point, let's jump to that, ready. if loaded_item.cue > 0: self.seek(loaded_item.cue) else: @@ -560,6 +338,8 @@ def load(self, weight: int): return False + # Remove the currently loaded item from the player. + # Not much reason to do this, but if it makes you happy. def unload(self): if not self.isPlaying: try: @@ -570,22 +350,122 @@ def unload(self): self.logger.log.exception("Failed to unload channel.") return False - self._potentially_end_tracklist() + #self._potentially_end_tracklist() # If we unloaded successfully, reset the tracklist_id, ready for the next item. if not self.isLoaded: self.state.update("tracklist_id", None) + # If we successfully unloaded, this will return true, for success! return not self.isLoaded - def quit(self): + + # Starts playing the loaded item, from a given position (secs) + def play(self, pos: float = 0): + self.logger.log.info("Playing from pos: " + str(pos)) + if not self.isLoaded: + self.logger.log.warning("Player is not loaded.") + return False try: - mixer.quit() + mixer.music.play(0, pos) + self.state.update("pos_offset", pos) + except Exception: + self.logger.log.exception("Failed to play at pos: " + str(pos)) + return False + self.state.update("paused", False) + self._potentially_tracklist() + self.stopped_manually = False + return True + + # Pauses the player + def pause(self): + # Because the player's position is stored by a event from pygame while playing only, + # the current playback position state will remain, in case we unpause later. + try: + mixer.music.stop() + except Exception: + self.logger.log.exception("Failed to pause.") + return False + + self.stopped_manually = True + self.state.update("paused", True) + return True + + # Plays the player, from the playback position it was already at. + def unpause(self): + if not self.isPlaying: + state = self.state.get() + position: float = state["pos_true"] + if not self.play(position): + self.logger.log.exception( + "Failed to unpause from pos: " + str(position) + ) + return False + self.state.update("paused", False) - self.logger.log.info("Quit mixer.") + + # Increment Played count + loaded_item = state["loaded_item"] + if loaded_item: + loaded_item.play_count_increment() + self.state.update("loaded_item", loaded_item) + + return True + return False + + # Stop the player. + def stop(self, user_initiated: bool = False): + try: + mixer.music.stop() except Exception: - self.logger.log.exception("Failed to quit mixer.") + self.logger.log.exception("Failed to stop playing.") + return False + self.state.update("paused", False) + + # When it wasn't _ended() calling this, end the tracklist. + # _ended() already calls this, but user stops won't have. + if user_initiated: + self._potentially_end_tracklist() + self.stopped_manually = True + + if not self.state.get()["loaded_item"]: + self.logger.log.warning("Tried to stop without a loaded item.") + return True + + # This lets users toggle (using the stop button) between cue point and 0. + + if user_initiated and not self.isCued: + # if there's a cue point ant we're not at it, go there. + self.seek(self.state.get()["loaded_item"].cue) + else: + # Otherwise, let's go to 0. + self.seek(0) - def output(self, name: Optional[str] = None): + return True + + # Move the audio position (secs) of the player + def seek(self, pos: float) -> bool: + self.logger.log.info("Seeking to pos:" + str(pos)) + if self.isPlaying: + # If we're playing, just start playing directly from that position + try: + self.play(pos) + except Exception: + self.logger.log.exception("Failed to seek to pos: " + str(pos)) + return False + return True + else: + # If we're not actually playing at the moment, set the player to be paused at the new position + self.logger.log.debug( + "Not playing during seek, setting pos state for next play." + ) + self.stopped_manually = True # Don't trigger _ended() on seeking. + if pos > 0: + self.state.update("paused", True) + self._updateState(pos=pos) + return True + + # Set the output device name and initialise the pygame audio mixer. + def set_output(self, name: Optional[str] = None): wasPlaying = self.isPlaying state = self.state.get() @@ -593,12 +473,19 @@ def output(self, name: Optional[str] = None): name = None if (not name or name.lower() == "none") else name + # Stop the mixer if it's already init'd. self.quit() self.state.update("output", name) try: + # Setup the mixer. + # Sample rate of 44100Hz (44.1KHz) (matching the MP3's and typical CD/online source material) + # 16 bits per sample + # 2 channels (stereo) + # sample buffer of 1024 samples if name: mixer.init(44100, -16, 2, 1024, devicename=name) else: + # Use the default system output mixer.init(44100, -16, 2, 1024) except Exception: self.logger.log.exception( @@ -606,6 +493,7 @@ def output(self, name: Optional[str] = None): ) return False + # If we had something loaded before, load it back in and play it. loadedItem = state["loaded_item"] if loadedItem: self.logger.log.info("Reloading after output change.") @@ -616,11 +504,193 @@ def output(self, name: Optional[str] = None): return True - # Timeslotitemid can be a ghost (un-submitted item), so may be "IXXX" + # De-initialises the pygame mixer. + def quit(self): + try: + mixer.quit() + self.state.update("paused", False) + self.logger.log.info("Quit mixer.") + except Exception: + self.logger.log.exception("Failed to quit mixer.") + + + # Sets whether auto advance is on or off + # Auto advance is where the next item in the list is selected after the current item is finished playing. + def set_auto_advance(self, message: bool) -> bool: + self.state.update("auto_advance", message) + return True + + # As you'd expect, all rotates around all of the items in the channel plan, and loops to the first from the last. + # One plays the same item over and over again + def set_repeat(self, message: str) -> bool: + if message in ["all", "one", "none"]: + self.state.update("repeat", message) + return True + else: + return False + + # Set whether the player should play the item as soon as it's been selected. + def set_play_on_load(self, message: bool) -> bool: + self.state.update("play_on_load", message) + return True + + + # Show Plan Related Methods + + def _check_ghosts(self, item: PlanItem): + # Webstudio returns intermediate "I" objects when dragging in from the media sidebar. + if isinstance(item.timeslotitemid, str) and item.timeslotitemid.startswith("I"): + # Kinda a bodge for the moment, each "Ghost" (item which is not saved in the database showplan yet) + # needs to have a unique temporary item. + # To do this, we'll start with the channel number the item was originally added to + # (to stop items somehow simultaneously added to different channels from having the same id) + # And chuck in the unix epoch in ns for good measure. + item.timeslotitemid = "GHOST-{}-{}".format( + self.state.get()["channel"], time.time_ns() + ) + return item + + # Pull in from the API the show plan items for this player channel. + def get_plan(self, show_plan_id: int): + # Call the API + # sync turns the asyncronous API into syncronous. + plan = sync(self.api.get_showplan(show_plan_id)) + + # Empty the channel plan so we can put the updated items in. + self.clear_channel_plan() + channel = self.state.get()["channel"] + self.logger.log.debug(plan) + # If there isn't a show plan for the required show, return failure without filling in the plan. + if not isinstance(plan, dict): + return False + + # Add the items, if this channel has any. + if str(channel) in plan.keys(): + plan_items = plan[str(channel)] + try: + self.add_to_plan(plan_items) + except Exception as e: + self.logger.log.error( + "Failed to add items to show plan: {}".format(e) + ) + return False + + return True + + # Add a list of new show plan items to the channel. + # These will be in dict format, we'll validate them and turn them into proper plan objects. + # TODO Allow just moving an item inside the channel instead of removing and adding. + def add_to_plan(self, new_items: List[Dict[str, Any]]) -> bool: + plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) + + for new_item in new_items: + new_item_obj = PlanItem(new_item) + new_item_obj = self._check_ghosts(new_item_obj) + + # Shift any plan items after the new position down one to make space. + for item in plan_copy: + if item.weight >= new_item_obj.weight: + item.weight += 1 + + plan_copy += [new_item_obj] # Add the new item. + + loaded_item = self.state.get()["loaded_item"] + if loaded_item: + + # Right. So this may be confusing. + # So... If the user has just moved the loaded item in the channel (by removing above and readding) + # Then we want to re-associate the loaded_item object reference with the new one. + # The loaded item object before this change is now an orphan, which was + # kept around while the loaded item was potentially moved to another + # channel. + if loaded_item.timeslotitemid == new_item_obj.timeslotitemid: + self.state.update("loaded_item", new_item_obj) + + # NOPE NOPE NOPE + # THIS IS AN EXAMPLE OF WHAT NOT TO DO! + # ONCE AGAIN, THE LOADED ITEM IS THE SAME OBJECT INSTANCE AS THE ONE IN + # THE SHOW PLAN (AS LONG AS IT HASN'T BEEN RE/MOVED) + + # loaded_item.weight = new_item_obj.weight + + # Bump the loaded_item's weight if we just added a new item above it. + # elif loaded_item.weight >= new_item_obj.weight: + # loaded_item.weight += 1 + + # Else, new weight stays the same. + # else: + # return True + + # self.state.update("loaded_item", loaded_item) + + # Just in case somehow we've ended up with items with the same weights (or gaps) + # We'll correct them. + # This function also orders and saves the updated plan copy we've given it. + self._fix_and_update_weights(plan_copy) + + return True + + # Removes an item from the show plan with the given weight (index) + def remove_from_plan(self, weight: int) -> bool: + plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) + found: Optional[PlanItem] = None + + # Give some helpful debug + before = [] + for item in plan_copy: + before += (item.weight, item.name) + + self.logger.log.debug( + "Weights before removing weight {}:\n{}".format(weight, before) + ) + + # Look for the item with the correct weight + for i in plan_copy: + if i.weight == weight: + found = i + plan_copy.remove(i) + + if found: + self._fix_and_update_weights(plan_copy) + + # If we removed the loaded item from this channel, update it's weight + # So we know how/not to autoadvance. + loaded_item = self.state.get()["loaded_item"] + if loaded_item == found: + # Loaded_item is actually the same PlanItem instance as in the show_plan. + # So if it's still in the show plan, we'll have corrected it's weight already. + # If it was removed above, fix_weights won't have done anything + # So we'll want to update the weight. + + # We're removing the loaded item from the channel. + # if loaded_item.weight == weight: + loaded_item.weight = -1 + + # If loaded_item wasn't the same instance, we'd want to do the below. + + # We removed an item above it. Shift it up. + # elif loaded_item.weight > weight: + # loaded_item.weight -= 1 + # Else, new weight stays the same. + # else: + # return True + + self.state.update("loaded_item", loaded_item) + return True + return False + + # Empties the channel's plan. + def clear_channel_plan(self) -> bool: + self.state.update("show_plan", []) + return True + + # PlanItems can have markers. These are essentially bookmarked positions in the audio. + # Timeslotitemid can be a ghost (un-submitted item), so may be "IXXX", hence str. def set_marker(self, timeslotitemid: str, marker_str: str): set_loaded = False success = True try: + # Take a string representation of the marker (from clients) marker = Marker(marker_str) except Exception as e: self.logger.log.error( @@ -630,6 +700,7 @@ def set_marker(self, timeslotitemid: str, marker_str: str): ) return False + # Allow setting a marker for the currently loaded item. if timeslotitemid == "-1": set_loaded = True if not self.isLoaded: @@ -641,6 +712,9 @@ def set_marker(self, timeslotitemid: str, marker_str: str): ): set_loaded = True + # Loop over the show plan items. When you find the timeslotitemid the marker is for, update it. + # This is instead of weight, since the client asking doesn't know the weight of the item (or which channel it is) + # So all channels will look and update if necessary. plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) for i in range(len(self.state.get()["show_plan"])): @@ -659,6 +733,7 @@ def set_marker(self, timeslotitemid: str, marker_str: str): ) success = False + # If the item to update was the loaded item, update it. if set_loaded: try: self.state.update( @@ -675,6 +750,8 @@ def set_marker(self, timeslotitemid: str, marker_str: str): return success + # This marks an item as played, or not. + # A weight of -1 will affect all items in the channel def set_played(self, weight: int, played: bool): plan: List[PlanItem] = self.state.get()["show_plan"] if weight == -1: @@ -698,17 +775,20 @@ def set_live(self, live: bool): # If we're going to live (potentially from not live/PFL), potentially tracklist if it's playing. if live: self._potentially_tracklist() + # If the fader is now not live, don't bother stopping the tracklist, incase it's faded up again during the same playback. return True # Helper functions - # This essentially allows the tracklist end API call to happen in a separate thread, to avoid hanging playout/loading. + # This essentially allows the tracklist start API call to happen in a separate thread, to avoid hanging playout/loading. def _potentially_tracklist(self): mode = self.state.get()["tracklist_mode"] time: int = -1 - if mode in ["on", "fader-live"]: - time = 1 # Let's do it pretty quickly. + if mode == "on": + time = 0 # Let's do it pretty quickly. + if mode == "fader-live": + time = 4 # Give presenter a bit of a grace period in case they accidentally fade up the wrong one. elif mode == "delayed": # Let's do it in a bit, once we're sure it's been playing. (Useful if we've got no idea if it's live or cueing.) time = TRACKLISTING_DELAYED_S @@ -735,7 +815,7 @@ def _potentially_end_tracklist(self): self.tracklist_start_timer.cancel() self.tracklist_start_timer = None - # Decrement Played count on track we didn't play much of. + # Decrement Played count on track we didn't play enough of to tracklist. state = self.state.get() loaded_item = state["loaded_item"] if loaded_item and loaded_item.type == "central": @@ -748,9 +828,6 @@ def _potentially_end_tracklist(self): self.logger.log.info("No tracklist to end.") return - self.logger.log.info( - "Setting timer for ending tracklist_id '{}'".format(tracklist_id) - ) if tracklist_id: self.logger.log.info( "Attempting to end tracklist_id '{}'".format(tracklist_id) @@ -770,6 +847,7 @@ def _potentially_end_tracklist(self): "Failed to potentially end tracklist, no tracklist started." ) + # The actual function that will register with the API an item being played. def _tracklist_start(self): state = self.state.get() loaded_item = state["loaded_item"] @@ -811,6 +889,7 @@ def _tracklist_start(self): # No matter what we end up doing, we need to kill this timer so future ones can run. self.tracklist_start_timer = None + # The actual function that will register with the API an item being finished playing. def _tracklist_end(self, tracklist_id): if tracklist_id: @@ -823,8 +902,10 @@ def _tracklist_end(self, tracklist_id): "Tracklist_id to _tracklist_end() missing. Failed to end tracklist." ) + # No matter what we end up doing, we need to kill this timer so future ones can run. self.tracklist_end_timer = None + # When an item has ended (the pygame mixer has told us that it has stopped playing) def _ended(self): self._potentially_end_tracklist() @@ -844,7 +925,7 @@ def _ended(self): # Just make sure that if we stop and do nothing, we end up at 0. self.state.update("pos", 0) - # Repeat 1 + # Repeat 1? Spin that record again! # TODO ENUM if state["repeat"] == "one": self.play() @@ -882,10 +963,12 @@ def _ended(self): self.stop() self._retAll("STOPPED") # Tell clients that we've stopped playing. + # This runs every main loop, to update anything that changes often / automatically. def _updateState(self, pos: Optional[float] = None): - - self.state.update("initialised", self.isInit) - if self.isInit: + # Is pygame still happy? + isInit = self.isInit + self.state.update("initialised", isInit) + if isInit: if pos is not None: # Seeking sets the position like this when not playing. self.state.update("pos", pos) # Reset back to 0 if stopped. @@ -919,6 +1002,8 @@ def _updateState(self, pos: Optional[float] = None): self.state.get()["pos_true"])), ) + # Sends the current playback position to clients, so they can update their UI frequently. + # Run on every main loop, but rate limited. def _ping_times(self): UPDATES_FREQ_SECS = 0.2 @@ -929,10 +1014,12 @@ def _ping_times(self): self.last_time_update = time.time() self._retAll("POS:" + str(self.state.get()["pos_true"])) + # Broadcast a message to all other modules of the BAPSicle server. def _retAll(self, msg): if self.out_q: self.out_q.put("ALL:" + msg) + # Send a response back to an incoming command, with the original content and a success or failure. def _retMsg( self, msg: Any, okay_str: bool = False, custom_prefix: Optional[str] = None ): @@ -961,12 +1048,13 @@ def _retMsg( "Message return Queue is missing!!!! Can't send message." ) + # Send the current status to all other modules/clients. Used for updating all client UIs when one of them causes a change etc. def _send_status(self): - # TODO This is hacky self._retMsg(str(self.status), okay_str=True, custom_prefix="ALL:STATUS:") - def _fix_and_update_weights(self, plan): + # Takes an input show plan, checks and corrects duplicate / gaps in weights, and stores it. + def _fix_and_update_weights(self, plan: List[PlanItem]): def _sort_weight(e: PlanItem): return e.weight @@ -994,6 +1082,7 @@ def _sort_weight(e: PlanItem): self.logger.log.debug("Weights after sorting:\n{}".format(fixed)) self.state.update("show_plan", plan) + # Player start up. This is called from the BAPSicle server.py. def __init__( self, channel: int, @@ -1023,28 +1112,30 @@ def __init__( self.state.update("start_time", datetime.now().timestamp()) + # When the state changes, use _send_status() to tell all clients. self.state.add_callback(self._send_status) self.state.update("channel", channel) + # tracklist mode is shared between all players, so grab that from the server config. self.state.update("tracklist_mode", server_state.get()[ "tracklist_mode"]) self.state.update( "live", True - ) # Channel is live until controller says it isn't. + ) # Channel Fader is live until controller says it isn't. # Just in case there's any weights somehow messed up, let's fix them. plan_copy: List[PlanItem] = copy.copy(self.state.get()["show_plan"]) self._fix_and_update_weights(plan_copy) - loaded_state = copy.copy(self.state.state) + loaded_state = self.state.state if loaded_state["output"]: self.logger.log.info("Setting output to: " + str(loaded_state["output"])) - self.output(loaded_state["output"]) + self.set_output(loaded_state["output"]) else: self.logger.log.info("Using default output device.") - self.output() + self.set_output() loaded_item = loaded_state["loaded_item"] if loaded_item: @@ -1072,11 +1163,15 @@ def __init__( else: self.logger.log.info("No file was previously loaded to resume.") + # The main loop. This keeps running till something tells it to stop. try: while self.running: + # Update the state for playback position changes etc self._updateState() + # If we need to, tell clients of the position updates self._ping_times() try: + # Try and get a new command message from clients message = in_q.get_nowait() source = message.split(":")[0] if source not in VALID_MESSAGE_SOURCES: @@ -1115,8 +1210,9 @@ def __init__( # Output re-inits the mixer, so we can do this any time. if self.last_msg.startswith("OUTPUT"): split = self.last_msg.split(":") - self._retMsg(self.output(split[1])) + self._retMsg(self.set_output(split[1])) + # Only process these commands if we're properly initialised. elif self.isInit: message_types: Dict[ str, Callable[..., Any] @@ -1194,6 +1290,7 @@ def __init__( message_type: str = self.last_msg.split(":")[0] + # From the list above, work out which command type we have, and run it's handling function. if message_type in message_types.keys(): message_types[message_type]() @@ -1205,7 +1302,7 @@ def __init__( else: self._retMsg("Unknown Command") else: - + # We're not initialised, return a failed status if they asked for one, or just say the command failed if self.last_msg == "STATUS": self._retMsg(self.status) else: From 0b6e245703778c49313741bba03c626c008f7a00 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 11 Mar 2022 21:58:12 +0000 Subject: [PATCH 108/156] Add ffmpeg for ubuntu --- build/build-linux.sh | 1 + build/install-ubuntu.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build/build-linux.sh b/build/build-linux.sh index 9b27b8a..c5a9ea9 100755 --- a/build/build-linux.sh +++ b/build/build-linux.sh @@ -8,6 +8,7 @@ echo "BUILD: str = \"$build_commit\"" > ../build.py echo "BRANCH: str = \"$build_branch\"" >> ../build.py sudo apt install libportaudio2 +sudo apt install python3-pip python3-venv ffmpeg python3 -m venv ../venv source ../venv/bin/activate diff --git a/build/install-ubuntu.sh b/build/install-ubuntu.sh index fc5ec29..fe92ef4 100755 --- a/build/install-ubuntu.sh +++ b/build/install-ubuntu.sh @@ -1,2 +1,2 @@ #!/bin/bash -sudo apt-get -y install libasound-dev libportaudio2 +sudo apt-get -y install libasound-dev libportaudio2 ffmpeg From 2515ddf4905e2ff170fdc617be745b3ccbe6ae93 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 11 Mar 2022 22:06:13 +0000 Subject: [PATCH 109/156] Ignore node_modules. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 66518ac..a8b327a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ music-tmp/ presenter-build + +node_modules From 853efa6968d9856a1e6015320179d50824db96de Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 11 Mar 2022 23:28:59 +0000 Subject: [PATCH 110/156] Fix file manager using single msg queue. --- file_manager.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/file_manager.py b/file_manager.py index c19799c..f635418 100644 --- a/file_manager.py +++ b/file_manager.py @@ -65,11 +65,14 @@ def __init__(self, channel_from_q: Queue, server_config: StateManager): else: try: - split = message.split(":", 1) + split = message.split(":", 3) channel = int(split[0]) # source = split[1] command = split[2] + # rest of message = split[3] + + self.logger.log.debug("Got command {} for channel {}".format(command,channel)) # If we have requested a new show plan, empty the music-tmp directory for the previous show. if command == "GETPLAN": @@ -150,8 +153,8 @@ def __init__(self, channel_from_q: Queue, server_config: StateManager): except Exception: self.logger.log.exception( - "Failed to handle message {} on channel {}.".format( - message, channel + "Failed to handle message {}.".format( + message ) ) From 7400272778b59ecf511a7570eb0eb0f3cbf74983 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 11 Mar 2022 23:29:46 +0000 Subject: [PATCH 111/156] Enable debugging by default on dev builds. --- helpers/logging_manager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/helpers/logging_manager.py b/helpers/logging_manager.py index eea4243..16184d5 100644 --- a/helpers/logging_manager.py +++ b/helpers/logging_manager.py @@ -1,7 +1,9 @@ import logging from logging.handlers import RotatingFileHandler +from typing import Optional from helpers.os_environment import resolve_external_file_path import os +import package LOG_MAX_SIZE_MB = 20 LOG_BACKUP_COUNT = 4 @@ -11,7 +13,7 @@ class LoggingManager: logger: logging.Logger - def __init__(self, name: str, debug: bool = False): + def __init__(self, name: str, debug: Optional[bool] = None): self.logger = logging.getLogger(name) logpath: str = resolve_external_file_path("/logs") @@ -34,6 +36,10 @@ def __init__(self, name: str, debug: bool = False): print("Failed to create log file.") return + # Enable debug by default + if (debug == None and package.BETA): + debug = True + self.logger.setLevel(logging.DEBUG if debug else logging.INFO) fh = RotatingFileHandler( filename, From 52248bea1fa6a5834a3926b2d84208661859c01e Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 11 Mar 2022 23:55:53 +0000 Subject: [PATCH 112/156] Flake 8 lint --- file_manager.py | 5 +---- helpers/logging_manager.py | 2 +- launch.py | 2 +- player.py | 4 ++-- player_handler.py | 2 +- web_server.py | 2 +- websocket_server.py | 1 - 7 files changed, 7 insertions(+), 11 deletions(-) diff --git a/file_manager.py b/file_manager.py index f635418..d8368a4 100644 --- a/file_manager.py +++ b/file_manager.py @@ -1,6 +1,5 @@ from helpers.state_manager import StateManager from helpers.os_environment import isWindows, resolve_external_file_path -from typing import List from setproctitle import setproctitle from multiprocessing import current_process, Queue from time import sleep @@ -72,7 +71,7 @@ def __init__(self, channel_from_q: Queue, server_config: StateManager): command = split[2] # rest of message = split[3] - self.logger.log.debug("Got command {} for channel {}".format(command,channel)) + self.logger.log.debug("Got command {} for channel {}".format(command, channel)) # If we have requested a new show plan, empty the music-tmp directory for the previous show. if command == "GETPLAN": @@ -158,8 +157,6 @@ def __init__(self, channel_from_q: Queue, server_config: StateManager): ) ) - - except Exception as e: self.logger.log.exception( "Received unexpected exception: {}".format(e)) diff --git a/helpers/logging_manager.py b/helpers/logging_manager.py index 16184d5..91de833 100644 --- a/helpers/logging_manager.py +++ b/helpers/logging_manager.py @@ -37,7 +37,7 @@ def __init__(self, name: str, debug: Optional[bool] = None): return # Enable debug by default - if (debug == None and package.BETA): + if (debug is None and package.BETA): debug = True self.logger.setLevel(logging.DEBUG if debug else logging.INFO) diff --git a/launch.py b/launch.py index aaeae03..61ce58e 100755 --- a/launch.py +++ b/launch.py @@ -35,7 +35,7 @@ def startServer(notifications=False): if server and server.is_alive(): server.terminate() - server.join(timeout=20) # If we somehow get stuck stopping BAPSicle let it die. + server.join(timeout=20) # If we somehow get stuck stopping BAPSicle let it die. # Catch the handler being killed externally. except Exception as e: diff --git a/player.py b/player.py index a708c04..e85682d 100644 --- a/player.py +++ b/player.py @@ -931,7 +931,7 @@ def _ping_times(self): def _retAll(self, msg): if self.out_q: - self.out_q.put("{}:ALL:{}".format(self.state.get()["channel"],msg)) + self.out_q.put("{}:ALL:{}".format(self.state.get()["channel"], msg)) def _retMsg( self, msg: Any, okay_str: bool = False, custom_prefix: Optional[str] = None @@ -1108,7 +1108,7 @@ def __init__( # We got a message. - ## Check if we're successfully loaded + # Check if we're successfully loaded # This is here so that we can check often, but not every single loop # Only when user gives input. self._checkIsLoaded() diff --git a/player_handler.py b/player_handler.py index ea7026e..46658cb 100644 --- a/player_handler.py +++ b/player_handler.py @@ -27,7 +27,7 @@ def __init__( q_msg = channel_from_q.get_nowait() if not isinstance(q_msg, str): continue - split = q_msg.split(":",1) + split = q_msg.split(":", 1) message = split[1] source = message.split(":")[0] command = message.split(":")[1] diff --git a/web_server.py b/web_server.py index c58d03f..ab90de1 100644 --- a/web_server.py +++ b/web_server.py @@ -473,7 +473,7 @@ def status(channel: int): while retries < 40: try: message = player_from_q.get_nowait() - split = message.split(":",1) + split = message.split(":", 1) channel = int(split[0]) response = split[1] if response.startswith("UI:STATUS:"): diff --git a/websocket_server.py b/websocket_server.py index f53ba18..d58f777 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -255,7 +255,6 @@ async def handle_to_webstudio(self): "Exception trying to send to websocket:", e ) - self.quit() From 358b5bb0a1bf5bb51528c254ee3bd0f2eaa6a8a0 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 00:09:22 +0000 Subject: [PATCH 113/156] Update aiohttp for dependabot --- build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/requirements.txt b/build/requirements.txt index ea525e9..2dcdff0 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -3,7 +3,7 @@ pygame==2.0.2 sanic==21.9.3 sanic-Cors==1.0.1 syncer==1.3.0 -aiohttp==3.7.4.post0 +aiohttp==3.7.4 mutagen==1.45.1 sounddevice==0.4.2 setproctitle==1.2.2 From 81d986069f5a38f03f677d8335b73f6f867c5c3c Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 14:55:33 +0000 Subject: [PATCH 114/156] Fix dragging items caused by plan item not being list. --- player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.py b/player.py index 0e470b2..c594a55 100644 --- a/player.py +++ b/player.py @@ -1256,10 +1256,10 @@ def __init__( "LOADED?": lambda: self._retMsg(self.isLoaded), "UNLOAD": lambda: self._retMsg(self.unload()), "ADD": lambda: self._retMsg( - self.add_to_plan( + self.add_to_plan([ json.loads( ":".join(self.last_msg.split(":")[1:])) - ) + ]) ), "REMOVE": lambda: self._retMsg( self.remove_from_plan( From aba31048cc5d3784f7cef8a15fc64c43502e7f9e Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 15:01:51 +0000 Subject: [PATCH 115/156] Put BAPSicle in process names for htop filtering. --- controllers/mattchbox_usb.py | 2 +- file_manager.py | 2 +- helpers/state_manager.py | 2 +- launch.py | 2 +- player.py | 2 +- player_handler.py | 2 +- server.py | 2 +- web_server.py | 2 +- websocket_server.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index 12552d1..6e90d82 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -21,7 +21,7 @@ def __init__( self, player_to_q: List[Queue], player_from_q: Queue, state: StateManager ): - process_title = "ControllerHandler" + process_title = "BAPSicle - ControllerHandler" setproctitle(process_title) current_process().name = process_title diff --git a/file_manager.py b/file_manager.py index d8368a4..f49102e 100644 --- a/file_manager.py +++ b/file_manager.py @@ -23,7 +23,7 @@ def __init__(self, channel_from_q: Queue, server_config: StateManager): self.logger = LoggingManager("FileManager") self.api = MyRadioAPI(self.logger, server_config) - process_title = "File Manager" + process_title = "BAPSicle - File Manager" setproctitle(process_title) current_process().name = process_title diff --git a/helpers/state_manager.py b/helpers/state_manager.py index 6cf6140..a7932c5 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -31,7 +31,7 @@ def __init__( rate_limit_period_s=5, ): # When a StateManager is shared via proxy to other processes, it has a thread itself. - process_title = "StateManager Proxy" + process_title = "BAPSicle - StateManager Proxy" setproctitle(process_title) current_process().name = process_title diff --git a/launch.py b/launch.py index 61ce58e..301d4b1 100755 --- a/launch.py +++ b/launch.py @@ -58,7 +58,7 @@ def printer(msg: Any): # If it's not here, multiprocessing just doesn't run in the package. # Freeze support refers to being packaged with Pyinstaller. multiprocessing.freeze_support() - setproctitle("BAPSicle Launcher") + setproctitle("BAPSicle - Launcher") if len(sys.argv) > 1: # We got an argument! It's probably Platypus's UI. try: diff --git a/player.py b/player.py index c594a55..396fd55 100644 --- a/player.py +++ b/player.py @@ -1092,7 +1092,7 @@ def __init__( server_state: StateManager, ): - process_title = "Player: Channel " + str(channel) + process_title = "BAPSicle - Player: Channel " + str(channel) setproctitle.setproctitle(process_title) multiprocessing.current_process().name = process_title diff --git a/player_handler.py b/player_handler.py index 46658cb..b66d0d7 100644 --- a/player_handler.py +++ b/player_handler.py @@ -15,7 +15,7 @@ def __init__( ): self.logger = LoggingManager("PlayerHandler") - process_title = "Player Handler" + process_title = "BAPSicle - Player Handler" setproctitle(process_title) current_process().name = process_title diff --git a/server.py b/server.py index 3f59096..7a02f6f 100644 --- a/server.py +++ b/server.py @@ -208,7 +208,7 @@ def startServer(self): if isMacOS() or isLinux(): multiprocessing.set_start_method("spawn", True) - process_title = "startServer" + process_title = "BAPSicle - startServer" setproctitle(process_title) multiprocessing.current_process().name = process_title diff --git a/web_server.py b/web_server.py index ab90de1..a14627e 100644 --- a/web_server.py +++ b/web_server.py @@ -544,7 +544,7 @@ def WebServer(player_to: List[Queue], player_from: Queue, state: StateManager): api = MyRadioAPI(logger, state) alerts = AlertManager() - process_title = "Web Server" + process_title = "BAPSicle - Web Server" setproctitle(process_title) current_process().name = process_title CORS(app, supports_credentials=True) # Allow ALL CORS!!! diff --git a/websocket_server.py b/websocket_server.py index d58f777..f21107e 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -33,7 +33,7 @@ def __init__(self, in_q, out_q, state): self.player_to_q = in_q self.player_from_q = out_q - process_title = "Websockets Server" + process_title = "BAPSicle - Websockets Server" setproctitle(process_title) current_process().name = process_title From 02137a3948760c3325d35dead832f1f9a1d3e5a0 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 15:59:36 +0000 Subject: [PATCH 116/156] Improve launcher printing. --- launch.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/launch.py b/launch.py index 301d4b1..7816d7b 100755 --- a/launch.py +++ b/launch.py @@ -24,13 +24,13 @@ def startServer(notifications=False): time.sleep(1) if server and server.is_alive(): if notifications and not sent_start_notif: - print("NOTIFICATION:Welcome to BAPSicle!") + notif("Welcome to BAPSicle!") sent_start_notif = True pass else: - print("Server dead. Exiting.") + printer("Server dead. Exiting.") if notifications: - print("NOTIFICATION:BAPSicle Server Stopped!") + notif("BAPSicle Server Stopped!") sys.exit(0) if server and server.is_alive(): @@ -49,6 +49,9 @@ def startServer(notifications=False): def printer(msg: Any): print("LAUNCHER:{}".format(msg)) +def notif(msg: str): + print("NOTIFICATION:{}".format(msg)) + if __name__ == "__main__": # On Windows, calling this function is necessary. @@ -63,7 +66,7 @@ def printer(msg: Any): # We got an argument! It's probably Platypus's UI. try: if (sys.argv[1]) == "Start Server": - print("NOTIFICATION:BAPSicle is starting, please wait...") + notif("BAPSicle is starting, please wait...") webbrowser.open("http://localhost:13500/") startServer(notifications=True) if sys.argv[1] == "Server": @@ -71,7 +74,7 @@ def printer(msg: Any): if sys.argv[1] == "Presenter": webbrowser.open("http://localhost:13500/presenter/") except Exception as e: - print( + printer( "ALERT:BAPSicle failed with exception of type {}:{}".format( type(e).__name__, e ) From 1e77f337b16e125fd9fd20eb79911471d74504cc Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 16:00:05 +0000 Subject: [PATCH 117/156] Improve shutdown time of Controller Handler. --- controllers/mattchbox_usb.py | 55 +++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index 6e90d82..ec236e7 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -68,16 +68,18 @@ def connect(self, port: Optional[str]): if port: # connect to serial port self.ser = serial.serial_for_url(port, do_not_open=True) - self.ser.baudrate = 2400 - try: - self.ser.open() - self.logger.log.info("Connected to serial port {}".format(port)) - except (FileNotFoundError, serial.SerialException) as e: - self.logger.log.error( - "Could not open serial port {}:\n{}".format(port, e) - ) - self._disconnected() - self.ser = None + if self.ser: + self.ser.baudrate = 2400 + self.ser.timeout = 0.1 # Speed up waiting for a byte. + try: + self.ser.open() + self.logger.log.info("Connected to serial port {}".format(port)) + except (FileNotFoundError, serial.SerialException) as e: + self.logger.log.error( + "Could not open serial port {}:\n{}".format(port, e) + ) + self._disconnected() + self.ser = None else: self.ser = None @@ -88,22 +90,23 @@ def handler(self): self.ser and self.ser.is_open and self.port ): # If self.port is changing (via state_handler), we should stop. try: - line = int.from_bytes( - self.ser.read(1), "big" - ) # Endianness doesn't matter for 1 byte. - self.logger.log.info("Received from controller: " + str(line)) - if line == 255: - self.ser.write(b"\xff") # Send 255 back, this is a keepalive. - elif line in [51, 52, 53]: - # We've received a status update about fader live status, fader is down. - self.sendToPlayer(line - 51, "SETLIVE:False") - elif line in [61, 62, 63]: - # We've received a status update about fader live status, fader is up. - self.sendToPlayer(line - 61, "SETLIVE:True") - elif line in [1, 3, 5]: - self.sendToPlayer(int(line / 2), "PLAYPAUSE") - elif line in [2, 4, 6]: - self.sendToPlayer(int(line / 2) - 1, "STOP") + if self.ser.in_waiting > 0: + line = int.from_bytes( + self.ser.read(1), "big" + ) # Endianness doesn't matter for 1 byte. + self.logger.log.info("Received from controller: " + str(line)) + if line == 255: + self.ser.write(b"\xff") # Send 255 back, this is a keepalive. + elif line in [51, 52, 53]: + # We've received a status update about fader live status, fader is down. + self.sendToPlayer(line - 51, "SETLIVE:False") + elif line in [61, 62, 63]: + # We've received a status update about fader live status, fader is up. + self.sendToPlayer(line - 61, "SETLIVE:True") + elif line in [1, 3, 5]: + self.sendToPlayer(int(line / 2), "PLAYPAUSE") + elif line in [2, 4, 6]: + self.sendToPlayer(int(line / 2) - 1, "STOP") except Exception: time.sleep(5) self.connect(self.port) From 873fd45e51c1ec74b53189c2439480d78f3a817d Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 16:40:05 +0000 Subject: [PATCH 118/156] Cleanup queues more reliably --- server.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index 7a02f6f..5d31403 100644 --- a/server.py +++ b/server.py @@ -332,7 +332,7 @@ def stopServer(self): for player in self.player: player.join(timeout=PROCESS_KILL_TIMEOUT_S) - del self.player + #del self.player print("Deleting all queues.") # Should speed up GC on exit a bit. @@ -345,16 +345,15 @@ def stopServer(self): self.file_to_q, ] for queue in queues: + print(str(queue)) if isinstance(queue, List): for inner_queue in queue: - while not inner_queue.empty(): - inner_queue.get() - del inner_queue + inner_queue.close() + #del inner_queue elif isinstance(queue, Queue): - while not queue.empty(): - queue.get() - for queue in queues: - del queue + queue.close() + print("del", str(queue)) + #del queue print("Stopped all processes.") From f73845b104710eda810369b4c10f30325617681b Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 16:45:05 +0000 Subject: [PATCH 119/156] Further cleanup. --- server.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/server.py b/server.py index 5d31403..81a3645 100644 --- a/server.py +++ b/server.py @@ -332,7 +332,7 @@ def stopServer(self): for player in self.player: player.join(timeout=PROCESS_KILL_TIMEOUT_S) - #del self.player + del self.player print("Deleting all queues.") # Should speed up GC on exit a bit. @@ -345,15 +345,13 @@ def stopServer(self): self.file_to_q, ] for queue in queues: - print(str(queue)) if isinstance(queue, List): for inner_queue in queue: inner_queue.close() - #del inner_queue + del inner_queue elif isinstance(queue, Queue): queue.close() - print("del", str(queue)) - #del queue + del queue print("Stopped all processes.") From 47fcf40ec69318e539d20405a54e91f97d5ab5a5 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 17:02:32 +0000 Subject: [PATCH 120/156] Don't bother cleaning queues, it fails on hot-restarts. --- server.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/server.py b/server.py index 81a3645..46d7045 100644 --- a/server.py +++ b/server.py @@ -334,25 +334,6 @@ def stopServer(self): del self.player - print("Deleting all queues.") - # Should speed up GC on exit a bit. - queues = [ - self.player_to_q, - self.player_from_q, - self.ui_to_q, - self.websocket_to_q, - self.controller_to_q, - self.file_to_q, - ] - for queue in queues: - if isinstance(queue, List): - for inner_queue in queue: - inner_queue.close() - del inner_queue - elif isinstance(queue, Queue): - queue.close() - del queue - print("Stopped all processes.") From 5d4c7224d9c5e1ec0978b45e3636f77c2979619d Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 12 Mar 2022 17:08:29 +0000 Subject: [PATCH 121/156] Pep8 lint --- controllers/mattchbox_usb.py | 2 +- launch.py | 1 + player.py | 23 +++++++++++++---------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/controllers/mattchbox_usb.py b/controllers/mattchbox_usb.py index ec236e7..0105bdc 100644 --- a/controllers/mattchbox_usb.py +++ b/controllers/mattchbox_usb.py @@ -70,7 +70,7 @@ def connect(self, port: Optional[str]): self.ser = serial.serial_for_url(port, do_not_open=True) if self.ser: self.ser.baudrate = 2400 - self.ser.timeout = 0.1 # Speed up waiting for a byte. + self.ser.timeout = 0.1 # Speed up waiting for a byte. try: self.ser.open() self.logger.log.info("Connected to serial port {}".format(port)) diff --git a/launch.py b/launch.py index 7816d7b..712bb6a 100755 --- a/launch.py +++ b/launch.py @@ -49,6 +49,7 @@ def startServer(notifications=False): def printer(msg: Any): print("LAUNCHER:{}".format(msg)) + def notif(msg: str): print("NOTIFICATION:{}".format(msg)) diff --git a/player.py b/player.py index 396fd55..4d0daa7 100644 --- a/player.py +++ b/player.py @@ -132,7 +132,9 @@ def _checkIsLoaded(self, short_test: bool = False): if not self.state.get()["loaded_item"] or not self.isInit: loaded = False elif not self.isPlaying: - # Because this function can be called very often, only some (less frequent) checks will initiate a full trial of loading success, for efficiency. + # Because this function can be called very often, only some (less + # frequent) checks will initiate a full trial of loading success, for + # efficiency. if not short_test: # We're not playing now, so we can quickly test run # If that works, we're truely loaded. @@ -184,9 +186,9 @@ def status(self): # Audio Playout Related Methods - # Loads a plan item into the player, ready for playing. # This includes some retry logic to try and double-down on ensuring it plays successfully. + def load(self, weight: int): if not self.isPlaying: # If we have something loaded already, unload it first. @@ -350,7 +352,7 @@ def unload(self): self.logger.log.exception("Failed to unload channel.") return False - #self._potentially_end_tracklist() + # self._potentially_end_tracklist() # If we unloaded successfully, reset the tracklist_id, ready for the next item. if not self.isLoaded: self.state.update("tracklist_id", None) @@ -358,8 +360,8 @@ def unload(self): # If we successfully unloaded, this will return true, for success! return not self.isLoaded - # Starts playing the loaded item, from a given position (secs) + def play(self, pos: float = 0): self.logger.log.info("Playing from pos: " + str(pos)) if not self.isLoaded: @@ -513,9 +515,9 @@ def quit(self): except Exception: self.logger.log.exception("Failed to quit mixer.") - # Sets whether auto advance is on or off # Auto advance is where the next item in the list is selected after the current item is finished playing. + def set_auto_advance(self, message: bool) -> bool: self.state.update("auto_advance", message) return True @@ -534,7 +536,6 @@ def set_play_on_load(self, message: bool) -> bool: self.state.update("play_on_load", message) return True - # Show Plan Related Methods def _check_ghosts(self, item: PlanItem): @@ -775,7 +776,8 @@ def set_live(self, live: bool): # If we're going to live (potentially from not live/PFL), potentially tracklist if it's playing. if live: self._potentially_tracklist() - # If the fader is now not live, don't bother stopping the tracklist, incase it's faded up again during the same playback. + # If the fader is now not live, don't bother stopping the tracklist, + # incase it's faded up again during the same playback. return True # Helper functions @@ -786,9 +788,9 @@ def _potentially_tracklist(self): time: int = -1 if mode == "on": - time = 0 # Let's do it pretty quickly. + time = 0 # Let's do it pretty quickly. if mode == "fader-live": - time = 4 # Give presenter a bit of a grace period in case they accidentally fade up the wrong one. + time = 4 # Give presenter a bit of a grace period in case they accidentally fade up the wrong one. elif mode == "delayed": # Let's do it in a bit, once we're sure it's been playing. (Useful if we've got no idea if it's live or cueing.) time = TRACKLISTING_DELAYED_S @@ -1049,7 +1051,8 @@ def _retMsg( "Message return Queue is missing!!!! Can't send message." ) - # Send the current status to all other modules/clients. Used for updating all client UIs when one of them causes a change etc. + # Send the current status to all other modules/clients. Used for updating + # all client UIs when one of them causes a change etc. def _send_status(self): self._retMsg(str(self.status), okay_str=True, custom_prefix="ALL:STATUS:") From 978c0424735e6ad710b45c41d02899d46b881f11 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 18 Mar 2022 17:33:28 +0000 Subject: [PATCH 122/156] Revert sanic version to fix OSError on linux. --- build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/requirements.txt b/build/requirements.txt index 2dcdff0..6a1a9f0 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,6 +1,6 @@ wheel pygame==2.0.2 -sanic==21.9.3 +sanic==21.3.4 sanic-Cors==1.0.1 syncer==1.3.0 aiohttp==3.7.4 From f7e71cc32e595cf6bb0320bb5d1e908bcc41f66c Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 15:59:59 +0000 Subject: [PATCH 123/156] Build for python3.8 and 3.9 --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 32999fe..4722e68 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -46,7 +46,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9'] + python-version: ['3.8','3.9'] node-version: ['14'] steps: From a838ef76217b2cfea8f7585607b28c65257ab709 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 16:01:37 +0000 Subject: [PATCH 124/156] Fix exit hang on linux --- launch.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/launch.py b/launch.py index 712bb6a..eed32ec 100755 --- a/launch.py +++ b/launch.py @@ -2,6 +2,7 @@ import multiprocessing import time import sys +import os from typing import Any import webbrowser from setproctitle import setproctitle @@ -31,11 +32,14 @@ def startServer(notifications=False): printer("Server dead. Exiting.") if notifications: notif("BAPSicle Server Stopped!") - sys.exit(0) + os._exit(0) if server and server.is_alive(): + printer("Terminating server.") server.terminate() + printer("Waiting to terminate.") server.join(timeout=20) # If we somehow get stuck stopping BAPSicle let it die. + printer("Terminated") # Catch the handler being killed externally. except Exception as e: @@ -80,9 +84,10 @@ def notif(msg: str): type(e).__name__, e ) ) - sys.exit(1) + os._exit(1) - sys.exit(0) + os._exit(0) else: startServer() - sys.exit(0) + printer("Exiting.") + os._exit(0) From 86c0ca14e742f05486497a849746b6806742791a Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 16:01:55 +0000 Subject: [PATCH 125/156] Fix exit hang on linux --- server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 46d7045..03b4462 100644 --- a/server.py +++ b/server.py @@ -13,6 +13,7 @@ October, November 2020 """ from datetime import datetime +import os from file_manager import FileManager import multiprocessing from multiprocessing.queues import Queue @@ -98,7 +99,7 @@ def __init__(self): self.stopServer() if self.state.get()["running_state"] != "restarting": - break + os._exit(0) def check_processes(self): From 01dfb3f3d6cc915d05b93db43f6973489bb87888 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 16:03:18 +0000 Subject: [PATCH 126/156] Update sanic deprications and remove asyncio stuff not needed. --- web_server.py | 60 ++++++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/web_server.py b/web_server.py index a14627e..48ba3c2 100644 --- a/web_server.py +++ b/web_server.py @@ -1,10 +1,8 @@ from sanic import Sanic -from sanic.exceptions import NotFound, abort +from sanic.exceptions import NotFound, SanicException from sanic.response import html, file, redirect from sanic.response import json as resp_json from sanic_cors import CORS -from syncer import sync -import asyncio from jinja2 import Environment, FileSystemLoader from jinja2.utils import select_autoescape from urllib.parse import unquote @@ -89,7 +87,7 @@ }, ) -app = Sanic("BAPSicle Web Server", log_config=LOGGING_CONFIG) +app = Sanic("BAPSicle-WebServer", log_config=LOGGING_CONFIG) def render_template(file, data, status=200): @@ -258,8 +256,7 @@ def ui_logs_render(request, path): try: log_file = open(resolve_external_file_path("/logs/{}.log").format(path)) except FileNotFoundError: - abort(404) - return + raise SanicException("Not Found",404) data = { "logs": log_file.read().splitlines()[ @@ -285,10 +282,10 @@ def player_simple(request, channel: int, command: str): player_to_q[channel].put("UI:" + command.upper()) return redirect("/status") - abort(404) + raise SanicException("Not Found",404) -@app.route("/player//seek/") +@app.route("/player//seek/") def player_seek(request, channel: int, pos: float): player_to_q[channel].put("UI:SEEK:" + str(pos)) @@ -310,7 +307,7 @@ def player_remove(request, channel: int, channel_weight: int): return redirect("/status") -@app.route("/player//output/") +@app.route("/player//output/") def player_output(request, channel: int, name: Optional[str]): player_to_q[channel].put("UI:OUTPUT:" + unquote(str(name))) return redirect("/config/player") @@ -322,7 +319,7 @@ def player_autoadvance(request, channel: int, state: int): return redirect("/status") -@app.route("/player//repeat/") +@app.route("/player//repeat/") def player_repeat(request, channel: int, state: str): player_to_q[channel].put("UI:REPEAT:" + state.upper()) return redirect("/status") @@ -386,11 +383,11 @@ async def api_search_library(request): ) -@app.route("/library/playlists/") +@app.route("/library/playlists/") async def api_get_playlists(request, type: str): if type not in ["music", "aux"]: - abort(401) + raise SanicException("Bad Request",400) if type == "music": return resp_json(await api.get_playlist_music()) @@ -398,11 +395,11 @@ async def api_get_playlists(request, type: str): return resp_json(await api.get_playlist_aux()) -@app.route("/library/playlist//") +@app.route("/library/playlist//") async def api_get_playlist(request, type: str, library_id: str): if type not in ["music", "aux"]: - abort(401) + raise SanicException("Bad Request",400) if type == "music": return resp_json(await api.get_playlist_music_items(library_id)) @@ -424,10 +421,10 @@ def json_status(request): # Get audio for UI to generate waveforms. -@app.route("/audiofile//") +@app.route("/audiofile//") async def audio_file(request, type: str, id: int): if type not in ["managed", "track"]: - abort(404) + raise SanicException("Bad Request",400) filename = resolve_external_file_path( "music-tmp/{}-{}.mp3".format(type, id)) @@ -438,8 +435,7 @@ async def audio_file(request, type: str, id: int): try: response = await file(filename) except FileNotFoundError: - abort(404) - return + raise SanicException("Not Found",404) return response @@ -552,23 +548,13 @@ def WebServer(player_to: List[Queue], player_from: Queue, state: StateManager): terminate = Terminator() while not terminate.terminate: try: - sync( - app.run( - host=server_state.get()["host"], - port=server_state.get()["port"], - auto_reload=False, - debug=not package.BETA, - access_log=not package.BETA, - ) + app.run( + host=server_state.get()["host"], + port=server_state.get()["port"], + auto_reload=False, + debug=not package.BETA, + access_log=not package.BETA, ) - except Exception: - break - try: - loop = asyncio.get_event_loop() - if loop: - loop.close() - if app: - app.stop() - del app - except Exception: - pass + except Exception as e: + logger.log.exception(e) + sleep(1) From d6304ccff8f9c35ee087d324ac4d7cb073aeff1c Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 16:04:13 +0000 Subject: [PATCH 127/156] Fix missing pyinstaller in path --- build/build-exe.py | 7 ++++++- build/build-linux.sh | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/build/build-exe.py b/build/build-exe.py index c3fde21..8a46cd8 100644 --- a/build/build-exe.py +++ b/build/build-exe.py @@ -1,10 +1,15 @@ import json +from helpers.os_environment import isLinux file = open('build-exe-config.json', 'r') config = json.loads(file.read()) file.close() -cmd_str = "pyinstaller " +if isLinux(): + cmd_str = "python3 -m PyInstaller " +else: + cmd_str = "pyinstaller " + json_dests = ["icon_file", "clean_build"] pyi_dests = ["icon", "clean"] diff --git a/build/build-linux.sh b/build/build-linux.sh index c5a9ea9..49d60f0 100755 --- a/build/build-linux.sh +++ b/build/build-linux.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x cd "$(dirname "$0")" # Get the git commit / branch and write it into build.py. From 64b1ef71ac34405a130d2ad89f3f65fd799ebe40 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 16:04:29 +0000 Subject: [PATCH 128/156] Update sanic requirements. --- build/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/requirements.txt b/build/requirements.txt index 6a1a9f0..3909025 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,7 +1,7 @@ wheel pygame==2.0.2 -sanic==21.3.4 -sanic-Cors==1.0.1 +sanic==21.9.3 +sanic-Cors==2.0.1 syncer==1.3.0 aiohttp==3.7.4 mutagen==1.45.1 From efd0273feaa7bb66494a9c8c82a225a6a6173418 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 17:22:04 +0000 Subject: [PATCH 129/156] Revert "Fix exit hang on linux" This reverts commit 86c0ca14e742f05486497a849746b6806742791a. --- server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server.py b/server.py index 03b4462..46d7045 100644 --- a/server.py +++ b/server.py @@ -13,7 +13,6 @@ October, November 2020 """ from datetime import datetime -import os from file_manager import FileManager import multiprocessing from multiprocessing.queues import Queue @@ -99,7 +98,7 @@ def __init__(self): self.stopServer() if self.state.get()["running_state"] != "restarting": - os._exit(0) + break def check_processes(self): From 330a1dea5c5ef495037ffd60cec02bd88c848b77 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 17:22:42 +0000 Subject: [PATCH 130/156] Revert "Fix exit hang on linux" This reverts commit a838ef76217b2cfea8f7585607b28c65257ab709. --- launch.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/launch.py b/launch.py index eed32ec..712bb6a 100755 --- a/launch.py +++ b/launch.py @@ -2,7 +2,6 @@ import multiprocessing import time import sys -import os from typing import Any import webbrowser from setproctitle import setproctitle @@ -32,14 +31,11 @@ def startServer(notifications=False): printer("Server dead. Exiting.") if notifications: notif("BAPSicle Server Stopped!") - os._exit(0) + sys.exit(0) if server and server.is_alive(): - printer("Terminating server.") server.terminate() - printer("Waiting to terminate.") server.join(timeout=20) # If we somehow get stuck stopping BAPSicle let it die. - printer("Terminated") # Catch the handler being killed externally. except Exception as e: @@ -84,10 +80,9 @@ def notif(msg: str): type(e).__name__, e ) ) - os._exit(1) + sys.exit(1) - os._exit(0) + sys.exit(0) else: startServer() - printer("Exiting.") - os._exit(0) + sys.exit(0) From 617376a54be80ebd597c2c192743646945a8ed43 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 17:46:01 +0000 Subject: [PATCH 131/156] Shutdown webserver cleanly and let server.py restart it. --- web_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_server.py b/web_server.py index 48ba3c2..7e8af5e 100644 --- a/web_server.py +++ b/web_server.py @@ -14,6 +14,7 @@ from time import sleep import json import os +import sys from helpers.os_environment import ( isLinux, @@ -557,4 +558,4 @@ def WebServer(player_to: List[Queue], player_from: Queue, state: StateManager): ) except Exception as e: logger.log.exception(e) - sleep(1) + sys.exit(1) From c70092f9d9e03435943ad1ac39ac0c4fdaae5b37 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 17:46:19 +0000 Subject: [PATCH 132/156] Make sure all sanic deps are included --- build/build-exe-config.template.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/build-exe-config.template.json b/build/build-exe-config.template.json index 4f7e3ee..e99b569 100644 --- a/build/build-exe-config.template.json +++ b/build/build-exe-config.template.json @@ -80,6 +80,10 @@ { "optionDest": "datas", "value": "/package.json;./" + }, + { + "optionDest": "collect-all", + "value": "sanic" } ], "nonPyinstallerOptions": { From 433d05336c12b0e56086141423cd70a7db0ca148 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 17:49:45 +0000 Subject: [PATCH 133/156] Let script fail if it errors. --- build/build-macos.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build/build-macos.sh b/build/build-macos.sh index 1648b57..2b3337e 100755 --- a/build/build-macos.sh +++ b/build/build-macos.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x cd "$(dirname "$0")" # Get the git commit / branch and write it into build.py. From 67c74b09ddfa8d5ee772227afa5756e3c5ea7cc4 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 17:54:39 +0000 Subject: [PATCH 134/156] Fix typo --- ui-templates/config_server.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-templates/config_server.html b/ui-templates/config_server.html index 18a2852..04e7d99 100644 --- a/ui-templates/config_server.html +++ b/ui-templates/config_server.html @@ -56,7 +56,7 @@ {% endfor %}

- Normalisation requests significant CPU requirements, if you're finding the CPU usuage is too high / causing audio glitches, disable this feature. ffmpeg or avconf required. + Normalisation requests significant CPU requirements, if you're finding the CPU usage is too high / causing audio glitches, disable this feature. ffmpeg or avconf required.


From 76ddea56f8225ebea55198837a7982bafd9c4061 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sat, 19 Mar 2022 18:45:51 +0000 Subject: [PATCH 135/156] Attempt fix of setproctitle missing --- build/build-exe-config.template.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/build-exe-config.template.json b/build/build-exe-config.template.json index e99b569..c40e3c2 100644 --- a/build/build-exe-config.template.json +++ b/build/build-exe-config.template.json @@ -84,6 +84,10 @@ { "optionDest": "collect-all", "value": "sanic" + }, + { + "optionDest": "collect-all", + "value": "setproctitle" } ], "nonPyinstallerOptions": { From 1bd5779b8a87aa1862943b389fb905c2e0f30336 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 16 Oct 2022 12:20:48 +0100 Subject: [PATCH 136/156] Don't log MyRadio API keys --- helpers/myradio_api.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index f7bc5d0..f9d518f 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -94,12 +94,20 @@ async def async_api_call( self._logException("Invalid API version. Request not sent.") return None + url_without_api_key = url + if "?" in url: url += "&api_key={}".format(self.config.get()["myradio_api_key"]) + url_without_api_key += "&api_key=REDACTED" else: url += "?api_key={}".format(self.config.get()["myradio_api_key"]) + url_without_api_key += "?api_key=REDACTED" - self._log("Requesting API V2 URL with method {}: {}".format(method, url)) + self._log( + "Requesting API V2 URL with method {}: {}".format( + method, url_without_api_key + ) + ) request = None try: @@ -107,7 +115,9 @@ async def async_api_call( request = await self.async_call(url, method="GET", timeout=timeout) elif method == "POST": self._log("POST data: {}".format(data)) - request = await self.async_call(url, data=data, method="POST", timeout=timeout) + request = await self.async_call( + url, data=data, method="POST", timeout=timeout + ) elif method == "PUT": request = await self.async_call(url, method="PUT", timeout=timeout) else: @@ -131,12 +141,20 @@ def api_call(self, url, api_version="v2", method="GET", data=None, timeout=10): self._logException("Invalid API version. Request not sent.") return None + url_without_api_key = url + if "?" in url: url += "&api_key={}".format(self.config.get()["myradio_api_key"]) + url_without_api_key += "&api_key=REDACTED" else: url += "?api_key={}".format(self.config.get()["myradio_api_key"]) + url_without_api_key += "?api_key=REDACTED" - self._log("Requesting API V2 URL with method {}: {}".format(method, url)) + self._log( + "Requesting API V2 URL with method {}: {}".format( + method, url_without_api_key + ) + ) request = None if method == "GET": @@ -209,7 +227,9 @@ async def get_showplan(self, timeslotid: int): # Audio Library - async def get_filename(self, item: PlanItem, did_download: bool = False, redownload=False): + async def get_filename( + self, item: PlanItem, did_download: bool = False, redownload=False + ): format = "mp3" # TODO: Maybe we want this customisable? if item.trackid: itemType = "track" @@ -317,7 +337,7 @@ async def get_playlist_aux(self): async def get_playlist_aux_items(self, library_id: str): # Sometimes they have "aux-", we only need the index. if library_id.index("-") > -1: - library_id = library_id[library_id.index("-") + 1:] + library_id = library_id[library_id.index("-") + 1 :] url = "/nipswebPlaylist/{}/items".format(library_id) request = await self.async_api_call(url) From 92dbd19bf7a6c4fff87573e115fb718a41e6cf10 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Mon, 17 Oct 2022 18:57:04 +0100 Subject: [PATCH 137/156] Remove space. --- helpers/myradio_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index f9d518f..ee997fa 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -337,7 +337,7 @@ async def get_playlist_aux(self): async def get_playlist_aux_items(self, library_id: str): # Sometimes they have "aux-", we only need the index. if library_id.index("-") > -1: - library_id = library_id[library_id.index("-") + 1 :] + library_id = library_id[library_id.index("-") + 1:] url = "/nipswebPlaylist/{}/items".format(library_id) request = await self.async_api_call(url) From 06812b975ba76912c345bc5b75f179c36a37e6f8 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 10 Mar 2023 19:23:33 +0000 Subject: [PATCH 138/156] List filename in 404 error --- web_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_server.py b/web_server.py index 7e8af5e..1189abe 100644 --- a/web_server.py +++ b/web_server.py @@ -436,7 +436,7 @@ async def audio_file(request, type: str, id: int): try: response = await file(filename) except FileNotFoundError: - raise SanicException("Not Found",404) + raise SanicException("Not Found: "+filename,404) return response From ab2aead17e4bc98648ae1ca41d78df57c6ae2c47 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sat, 23 Mar 2024 11:11:30 +0000 Subject: [PATCH 139/156] chore: updates to build on latest python/linux --- build/requirements.txt | 2 +- helpers/device_manager.py | 3 +-- package-lock.json | 24 ++++++++++++++++++++---- package.json | 2 +- presenter | 2 +- websocket_server.py | 4 ++-- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/build/requirements.txt b/build/requirements.txt index 3909025..cfd24d3 100644 --- a/build/requirements.txt +++ b/build/requirements.txt @@ -1,5 +1,5 @@ wheel -pygame==2.0.2 +pygame==2.5.2 sanic==21.9.3 sanic-Cors==2.0.1 syncer==1.3.0 diff --git a/helpers/device_manager.py b/helpers/device_manager.py index df68409..a868af7 100644 --- a/helpers/device_manager.py +++ b/helpers/device_manager.py @@ -64,8 +64,7 @@ def getAudioOutputs(cls) -> Tuple[List[Dict]]: def getAudioDevices(cls) -> List[str]: mixer.init(44100, -16, 2, 1024) is_capture = 0 # zero to request playback devices, non-zero to request recording devices - num = sdl2.get_num_audio_devices(is_capture) - names = [str(sdl2.get_audio_device_name(i, is_capture), encoding="utf-8") for i in range(num)] + names = sdl2.audio.get_audio_device_names(is_capture) mixer.quit() return names diff --git a/package-lock.json b/package-lock.json index ac7f15d..7ed808c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,29 @@ { "name": "bapsicle", "version": "3.1.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "yarn": { + "packages": { + "": { + "name": "bapsicle", + "version": "3.1.0", + "license": "ISC", + "dependencies": { + "yarn": "^1.22.15" + } + }, + "node_modules/yarn": { "version": "1.22.15", "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.15.tgz", - "integrity": "sha512-AzoEDxj256BOS/jqDXA3pjyhmi4FRBBUMgYoTHI4EIt2EhREkvH0soPVEtnD+DQIJfU5R9bKhcZ1H9l8zPWeoA==" + "integrity": "sha512-AzoEDxj256BOS/jqDXA3pjyhmi4FRBBUMgYoTHI4EIt2EhREkvH0soPVEtnD+DQIJfU5R9bKhcZ1H9l8zPWeoA==", + "hasInstallScript": true, + "bin": { + "yarn": "bin/yarn.js", + "yarnpkg": "bin/yarn.js" + }, + "engines": { + "node": ">=4.0.0" + } } } } diff --git a/package.json b/package.json index 2d3161e..228239f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "scripts": { "presenter-install": "cd presenter && git submodule update --init && yarn --network-timeout 100000", - "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", + "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && NODE_OPTIONS=--openssl-legacy-provider yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", "presenter-start": "cd presenter && yarn start-baps", "lint": "./venv/bin/autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . && ./venv/bin/flake8 . --exclude=\"*node_modules*,*venv/*,presenter/*\" --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics" diff --git a/presenter b/presenter index 238da52..537e617 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit 238da52d94f9a2e1f476e5c8e155e50dd8519cb7 +Subproject commit 537e61733233b9d4e43865e5ca5ca9bd348a07ef diff --git a/websocket_server.py b/websocket_server.py index f21107e..ec8a4b9 100644 --- a/websocket_server.py +++ b/websocket_server.py @@ -92,7 +92,7 @@ async def handle_from_webstudio(self, websocket): channel = int(data["channel"]) self.sendCommand(channel, data) - await asyncio.wait([conn.send(message) for conn in self.baps_clients]) + await asyncio.wait([asyncio.Task(conn.send(message)) for conn in self.baps_clients]) except websockets.exceptions.ConnectionClosedError as e: self.logger.log.error( @@ -244,7 +244,7 @@ async def handle_to_webstudio(self): data = json.dumps( {"command": command, "data": message, "channel": channel} ) - await asyncio.wait([conn.send(data) for conn in self.baps_clients]) + await asyncio.wait([asyncio.Task(conn.send(data)) for conn in self.baps_clients]) except queue.Empty: continue except ValueError: From 4fcd72a2b1520d56cb73582e19bbea2222362895 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sat, 23 Mar 2024 11:17:24 +0000 Subject: [PATCH 140/156] fix(ci): add new python/node versions to CI --- .github/workflows/build.yaml | 12 ++++++------ .github/workflows/test.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4722e68..d63b6c6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,8 +9,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9'] - node-version: ['14'] + python-version: ['3.9', '3.11'] + node-version: ['20'] steps: - uses: actions/checkout@v2 @@ -46,8 +46,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.8','3.9'] - node-version: ['14'] + python-version: ['3.8','3.9', '3.11'] + node-version: ['20'] steps: - uses: actions/checkout@v2 @@ -82,8 +82,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9'] - node-version: ['14'] + python-version: ['3.9', '3.11'] + node-version: ['20'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 95dee60..a4b120c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9] + python-version: [3.8, 3.9, 3.11] steps: - uses: actions/checkout@v2 From 3cd70699e039cea1c0267285de684fd3f5bf02e8 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 24 Mar 2024 13:18:49 +0000 Subject: [PATCH 141/156] fix: fix presenter build on windows --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 228239f..69cff67 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "scripts": { "presenter-install": "cd presenter && git submodule update --init && yarn --network-timeout 100000", - "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && NODE_OPTIONS=--openssl-legacy-provider yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", + "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && set NODE_OPTIONS=--openssl-legacy-provider&& yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", "presenter-start": "cd presenter && yarn start-baps", "lint": "./venv/bin/autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . && ./venv/bin/flake8 . --exclude=\"*node_modules*,*venv/*,presenter/*\" --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics" From 058e5299dac70d17e623574475298760d0701240 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Fri, 29 Mar 2024 15:09:47 +0000 Subject: [PATCH 142/156] fix: bump presenter to support building with latest node on all platforms --- package.json | 2 +- presenter | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 69cff67..2d3161e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "scripts": { "presenter-install": "cd presenter && git submodule update --init && yarn --network-timeout 100000", - "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && set NODE_OPTIONS=--openssl-legacy-provider&& yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", + "presenter-make": "npm run presenter-install && (rm -r presenter-build || true) && cd presenter && yarn build-baps && cp -r build ../presenter-build && cd ../ && npm install", "test": "echo \"Error: no test specified\" && exit 1", "presenter-start": "cd presenter && yarn start-baps", "lint": "./venv/bin/autopep8 -r -a -a --ignore E402,E226,E24,W50,W690 --max-line-length 127 --in-place --exclude=\"*node_modules*,*venv/*,presenter/*\" . && ./venv/bin/flake8 . --exclude=\"*node_modules*,*venv/*,presenter/*\" --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics" diff --git a/presenter b/presenter index 537e617..8b7f59c 160000 --- a/presenter +++ b/presenter @@ -1 +1 @@ -Subproject commit 537e61733233b9d4e43865e5ca5ca9bd348a07ef +Subproject commit 8b7f59cdc6ed80b525b2dff665308d808a526d97 From 237f21a5bc9a64b57cc52806555329512c70463b Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Fri, 29 Mar 2024 15:10:21 +0000 Subject: [PATCH 143/156] chore(ci): disable broken MacOS builds --- .github/workflows/build.yaml | 68 ++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d63b6c6..ab96de7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,42 +2,42 @@ name: Package on: [push] jobs: - build-macos: +# build-macos: - runs-on: macos-latest - timeout-minutes: 15 - strategy: - fail-fast: false - matrix: - python-version: ['3.9', '3.11'] - node-version: ['20'] +# runs-on: macos-latest +# timeout-minutes: 15 +# strategy: +# fail-fast: false +# matrix: +# python-version: ['3.9', '3.11'] +# node-version: ['20'] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - uses: actions/checkout@v2 - - name: Set up Node ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - name: Build .app - run: | - npm run presenter-make - build/build-macos.sh - zip -r build/output/BAPSicle.zip build/output/BAPSicle.app - - name: Extract branch name - shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" - id: extract_branch - - name: Archive Build - uses: actions/upload-artifact@v2 - with: - name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-MacOS - path: | - build/output/BAPSicle.zip +# steps: +# - uses: actions/checkout@v2 +# - name: Set up Python ${{ matrix.python-version }} +# uses: actions/setup-python@v2 +# with: +# python-version: ${{ matrix.python-version }} +# - uses: actions/checkout@v2 +# - name: Set up Node ${{ matrix.node-version }} +# uses: actions/setup-node@v2 +# with: +# node-version: ${{ matrix.node-version }} +# - name: Build .app +# run: | +# npm run presenter-make +# build/build-macos.sh +# zip -r build/output/BAPSicle.zip build/output/BAPSicle.app +# - name: Extract branch name +# shell: bash +# run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" +# id: extract_branch +# - name: Archive Build +# uses: actions/upload-artifact@v2 +# with: +# name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-MacOS +# path: | +# build/output/BAPSicle.zip build-ubuntu: From 0e13dc08da70b0996289a01e635546da42b68de0 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Fri, 29 Mar 2024 16:32:06 +0000 Subject: [PATCH 144/156] feat: migrate to poetry --- build/build-linux.sh | 5 +- build/build-macos.sh | 5 +- build/build-windows.bat | 5 +- dev/install-githook.sh | 2 +- poetry.lock | 4313 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 40 + 6 files changed, 4357 insertions(+), 13 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/build/build-linux.sh b/build/build-linux.sh index 49d60f0..6e3b4b1 100755 --- a/build/build-linux.sh +++ b/build/build-linux.sh @@ -14,10 +14,7 @@ sudo apt install python3-pip python3-venv ffmpeg python3 -m venv ../venv source ../venv/bin/activate -pip3 install wheel -pip3 install -r requirements.txt -pip3 install -r requirements-linux.txt -pip3 install -e ../ +poetry install python3 ./generate-build-exe-config.py diff --git a/build/build-macos.sh b/build/build-macos.sh index 2b3337e..9d3bb98 100755 --- a/build/build-macos.sh +++ b/build/build-macos.sh @@ -11,10 +11,7 @@ echo "BRANCH: str = \"$build_branch\"" >> ../build.py python3 -m venv ../venv source ../venv/bin/activate -pip3 install wheel -pip3 install -r requirements.txt -pip3 install -r requirements-macos.txt -pip3 install -e ..\ +poetry install python3 ./generate-build-exe-config.py diff --git a/build/build-windows.bat b/build/build-windows.bat index 639a625..ef1d25b 100644 --- a/build/build-windows.bat +++ b/build/build-windows.bat @@ -17,10 +17,7 @@ if "%1" == "no-venv" goto skip-venv :skip-venv -pip install wheel -pip install -r requirements.txt -pip install -r requirements-windows.txt -pip install -e ..\ +poetry install : Generate the json config in case you wanted to use the gui to regenerate the command below manually. python generate-build-exe-config.py diff --git a/dev/install-githook.sh b/dev/install-githook.sh index 9173274..a4e8cd5 100755 --- a/dev/install-githook.sh +++ b/dev/install-githook.sh @@ -1,5 +1,5 @@ #!/bin/bash -pip3 install autopep8 +poetry install cd "$(dirname "$0")" cp "./pre-commit" "../.git/hooks/" diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..b809c0a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,4313 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "aiofiles" +version = "23.2.1" +description = "File support for asyncio." +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, + {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, +] + +[[package]] +name = "aiohttp" +version = "3.7.4" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.7.4-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c"}, + {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b"}, + {file = "aiohttp-3.7.4-cp36-cp36m-win32.whl", hash = "sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329"}, + {file = "aiohttp-3.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6"}, + {file = "aiohttp-3.7.4-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb"}, + {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242"}, + {file = "aiohttp-3.7.4-cp37-cp37m-win32.whl", hash = "sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9"}, + {file = "aiohttp-3.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9"}, + {file = "aiohttp-3.7.4-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9"}, + {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0"}, + {file = "aiohttp-3.7.4-cp38-cp38-win32.whl", hash = "sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb"}, + {file = "aiohttp-3.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc"}, + {file = "aiohttp-3.7.4-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7"}, + {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81"}, + {file = "aiohttp-3.7.4-cp39-cp39-win32.whl", hash = "sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0"}, + {file = "aiohttp-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e"}, + {file = "aiohttp-3.7.4.tar.gz", hash = "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de"}, +] + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<4.0" +multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotlipy", "cchardet"] + +[[package]] +name = "altgraph" +version = "0.17.4" +description = "Python graph (network) package" +optional = false +python-versions = "*" +files = [ + {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, + {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, +] + +[[package]] +name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.5.3" +files = [ + {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, + {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, +] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "auto-py-to-exe" +version = "2.43.3" +description = "Converts .py to .exe using a simple graphical interface." +optional = false +python-versions = ">=3.6" +files = [ + {file = "auto-py-to-exe-2.43.3.tar.gz", hash = "sha256:e289d365a055491d0c8d737a0440614c849722bff1117ce21c6b510705836c0b"}, + {file = "auto_py_to_exe-2.43.3-py2.py3-none-any.whl", hash = "sha256:ded0489e7184ef10451d801cd01483f7733a1e03bad3e9221f38001cc3f2f0fe"}, +] + +[package.dependencies] +Eel = ">=0.11.0" +pyinstaller = ">=5.8.0" +requests = "*" + +[[package]] +name = "autopep8" +version = "2.1.0" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.8" +files = [ + {file = "autopep8-2.1.0-py2.py3-none-any.whl", hash = "sha256:2bb76888c5edbcafe6aabab3c47ba534f5a2c2d245c2eddced4a30c4b4946357"}, + {file = "autopep8-2.1.0.tar.gz", hash = "sha256:1fa8964e4618929488f4ec36795c7ff12924a68b8bf01366c094fc52f770b6e7"}, +] + +[package.dependencies] +pycodestyle = ">=2.11.0" + +[[package]] +name = "bottle" +version = "0.12.25" +description = "Fast and simple WSGI-framework for small web-applications." +optional = false +python-versions = "*" +files = [ + {file = "bottle-0.12.25-py3-none-any.whl", hash = "sha256:d6f15f9d422670b7c073d63bd8d287b135388da187a0f3e3c19293626ce034ea"}, + {file = "bottle-0.12.25.tar.gz", hash = "sha256:e1a9c94970ae6d710b3fb4526294dfeb86f2cb4a81eff3a4b98dc40fb0e5e021"}, +] + +[[package]] +name = "bottle-websocket" +version = "0.2.9" +description = "WebSockets for bottle" +optional = false +python-versions = "*" +files = [ + {file = "bottle-websocket-0.2.9.tar.gz", hash = "sha256:9887f70dc0c7592ed8d0d11a14aa95dede6cd08d50d83d5b81fd963e5fec738b"}, +] + +[package.dependencies] +bottle = "*" +gevent-websocket = "*" + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +optional = false +python-versions = "*" +files = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.5.0" +files = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "comtypes" +version = "1.3.1" +description = "Pure Python COM package" +optional = false +python-versions = "*" +files = [ + {file = "comtypes-1.3.1-py3-none-any.whl", hash = "sha256:f1867428372972e8a095036e2b04b9a5495e8672d856cff0b7e21cb7022f2efd"}, + {file = "comtypes-1.3.1.zip", hash = "sha256:aa99693dac9ab784d02db36ea57ad4e1bc0e16db3bb303b6f427db2657810f14"}, +] + +[[package]] +name = "eel" +version = "0.16.0" +description = "For little HTML GUI applications, with easy Python/JS interop" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Eel-0.16.0.tar.gz", hash = "sha256:ac1ce574a90a2c908408b5fc94d7a88a7cbde49cd36af84135d7cfcffcdddb03"}, +] + +[package.dependencies] +bottle = "*" +bottle-websocket = "*" +future = "*" +pyparsing = "*" +whichcraft = "*" + +[package.extras] +jinja2 = ["jinja2 (>=2.10)"] + +[[package]] +name = "future" +version = "1.0.0" +description = "Clean single-source support for Python 3 and 2" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216"}, + {file = "future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"}, +] + +[[package]] +name = "gevent" +version = "24.2.1" +description = "Coroutine-based network library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "gevent-24.2.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07"}, + {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3"}, + {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026"}, + {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5"}, + {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb"}, + {file = "gevent-24.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060"}, + {file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98"}, + {file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789"}, + {file = "gevent-24.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc"}, + {file = "gevent-24.2.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5"}, + {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836"}, + {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c"}, + {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7"}, + {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be"}, + {file = "gevent-24.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91"}, + {file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"}, + {file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d"}, + {file = "gevent-24.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc"}, + {file = "gevent-24.2.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40"}, + {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0"}, + {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7"}, + {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f"}, + {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661"}, + {file = "gevent-24.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9"}, + {file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f"}, + {file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388"}, + {file = "gevent-24.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5"}, + {file = "gevent-24.2.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5"}, + {file = "gevent-24.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8"}, + {file = "gevent-24.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19"}, + {file = "gevent-24.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800"}, + {file = "gevent-24.2.1-cp38-cp38-win32.whl", hash = "sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6"}, + {file = "gevent-24.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de"}, + {file = "gevent-24.2.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe"}, + {file = "gevent-24.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8"}, + {file = "gevent-24.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7"}, + {file = "gevent-24.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1"}, + {file = "gevent-24.2.1-cp39-cp39-win32.whl", hash = "sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e"}, + {file = "gevent-24.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533"}, + {file = "gevent-24.2.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8"}, + {file = "gevent-24.2.1.tar.gz", hash = "sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056"}, +] + +[package.dependencies] +cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""} +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"] +test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests"] + +[[package]] +name = "gevent-websocket" +version = "0.10.1" +description = "Websocket handler for the gevent pywsgi server, a Python network library" +optional = false +python-versions = "*" +files = [ + {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, + {file = "gevent_websocket-0.10.1-py3-none-any.whl", hash = "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242"}, +] + +[package.dependencies] +gevent = "*" + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "httptools" +version = "0.6.1" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, + {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, + {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, + {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, + {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, + {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, + {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "jinja2" +version = "3.0.1" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.6" +files = [ + {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, + {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "macholib" +version = "1.16.3" +description = "Mach-O header analysis and editing" +optional = false +python-versions = "*" +files = [ + {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, + {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, +] + +[package.dependencies] +altgraph = ">=0.17" + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "multidict" +version = "5.2.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.6" +files = [ + {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"}, + {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"}, + {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"}, + {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"}, + {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"}, + {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"}, + {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"}, + {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"}, + {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"}, + {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"}, + {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"}, + {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"}, + {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"}, + {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"}, +] + +[[package]] +name = "mutagen" +version = "1.45.1" +description = "read and write audio tags for many formats" +optional = false +python-versions = ">=3.5, <4" +files = [ + {file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"}, + {file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pefile" +version = "2023.2.7" +description = "Python PE parsing module" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, + {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, +] + +[[package]] +name = "psutil" +version = "5.9.8" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydub" +version = "0.25.1" +description = "Manipulate audio with an simple and easy high level interface" +optional = false +python-versions = "*" +files = [ + {file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"}, + {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"}, +] + +[[package]] +name = "pygame" +version = "2.5.2" +description = "Python Game Development" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pygame-2.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a0769eb628c818761755eb0a0ca8216b95270ea8cbcbc82227e39ac9644643da"}, + {file = "pygame-2.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed9a3d98adafa0805ccbaaff5d2996a2b5795381285d8437a4a5d248dbd12b4a"}, + {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30d1618672a55e8c6669281ba264464b3ab563158e40d89e8c8b3faa0febebd"}, + {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39690e9be9baf58b7359d1f3b2336e1fd6f92fedbbce42987be5df27f8d30718"}, + {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03879ec299c9f4ba23901b2649a96b2143f0a5d787f0b6c39469989e2320caf1"}, + {file = "pygame-2.5.2-cp310-cp310-win32.whl", hash = "sha256:74e1d6284100e294f445832e6f6343be4fe4748decc4f8a51131ae197dae8584"}, + {file = "pygame-2.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:485239c7d32265fd35b76ae8f64f34b0637ae11e69d76de15710c4b9edcc7c8d"}, + {file = "pygame-2.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34646ca20e163dc6f6cf8170f1e12a2e41726780112594ac061fa448cf7ccd75"}, + {file = "pygame-2.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b8a6e351665ed26ea791f0e1fd649d3f483e8681892caef9d471f488f9ea5ee"}, + {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc346965847aef00013fa2364f41a64f068cd096dcc7778fc306ca3735f0eedf"}, + {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35632035fd81261f2d797fa810ea8c46111bd78ceb6089d52b61ed7dc3c5d05f"}, + {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, + {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, + {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, + {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, + {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, + {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, + {file = "pygame-2.5.2-cp312-cp312-win32.whl", hash = "sha256:224c308856334bc792f696e9278e50d099a87c116f7fc314cd6aa3ff99d21592"}, + {file = "pygame-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd2d2650faf54f9a0f5bd0db8409f79609319725f8f08af6507a0609deadcad4"}, + {file = "pygame-2.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b30bc1220c457169571aac998e54b013aaeb732d2fd8744966cb1cfab1f61d1"}, + {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78fcd7643358b886a44127ff7dec9041c056c212b3a98977674f83f99e9b12d3"}, + {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cf093a51cb294ede56c29d4acf41538c00f297fcf78a9b186fb7d23c0577b6"}, + {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe323acbf53a0195c8c98b1b941eba7ac24e3e2b28ae48e8cda566f15fc4945"}, + {file = "pygame-2.5.2-cp36-cp36m-win32.whl", hash = "sha256:5697528266b4716d9cdd44a5a1d210f4d86ef801d0f64ca5da5d0816704009d9"}, + {file = "pygame-2.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edda1f7cff4806a4fa39e0e8ccd75f38d1d340fa5fc52d8582ade87aca247d92"}, + {file = "pygame-2.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9bd738fd4ecc224769d0b4a719f96900a86578e26e0105193658a32966df2aae"}, + {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30a8d7cf12363b4140bf2f93b5eec4028376ca1d0fe4b550588f836279485308"}, + {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc12e4dea3e88ea8a553de6d56a37b704dbe2aed95105889f6afeb4b96e62097"}, + {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b34c73cb328024f8db3cb6487a37e54000148988275d8d6e5adf99d9323c937"}, + {file = "pygame-2.5.2-cp37-cp37m-win32.whl", hash = "sha256:7d0a2794649defa57ef50b096a99f7113d3d0c2e32d1426cafa7d618eadce4c7"}, + {file = "pygame-2.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:41f8779f52e0f6e6e6ccb8f0b5536e432bf386ee29c721a1c22cada7767b0cef"}, + {file = "pygame-2.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:677e37bc0ea7afd89dde5a88ced4458aa8656159c70a576eea68b5622ee1997b"}, + {file = "pygame-2.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47a8415d2bd60e6909823b5643a1d4ef5cc29417d817f2a214b255f6fa3a1e4c"}, + {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ff21201df6278b8ca2e948fb148ffe88f5481fd03760f381dd61e45954c7dff"}, + {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29a84b2e02814b9ba925357fd2e1df78efe5e1aa64dc3051eaed95d2b96eafd"}, + {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d78485c4d21133d6b2fbb504cd544ca655e50b6eb551d2995b3aa6035928adda"}, + {file = "pygame-2.5.2-cp38-cp38-win32.whl", hash = "sha256:d851247239548aa357c4a6840fb67adc2d570ce7cb56988d036a723d26b48bff"}, + {file = "pygame-2.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:88d1cdacc2d3471eceab98bf0c93c14d3a8461f93e58e3d926f20d4de3a75554"}, + {file = "pygame-2.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4f1559e7efe4efb9dc19d2d811d702f325d9605f9f6f9ececa39ee6890c798f5"}, + {file = "pygame-2.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cf2191b756ceb0e8458a761d0c665b0c70b538570449e0d39b75a5ba94ac5cf0"}, + {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cf2257447ce7f2d6de37e5fb019d2bbe32ed05a5721ace8bc78c2d9beaf3aee"}, + {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cbbfaba2b81434d62631d0b08b85fab16cf4a36e40b80298d3868927e1299"}, + {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daca456d5b9f52e088e06a127dec182b3638a775684fb2260f25d664351cf1ae"}, + {file = "pygame-2.5.2-cp39-cp39-win32.whl", hash = "sha256:3b3e619e33d11c297d7a57a82db40681f9c2c3ae1d5bf06003520b4fe30c435d"}, + {file = "pygame-2.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:1822d534bb7fe756804647b6da2c9ea5d7a62d8796b2e15d172d3be085de28c6"}, + {file = "pygame-2.5.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:e708fc8f709a0fe1d1876489345f2e443d47f3976d33455e2e1e937f972f8677"}, + {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c13edebc43c240fb0532969e914f0ccefff5ae7e50b0b788d08ad2c15ef793e4"}, + {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:263b4a7cbfc9fe2055abc21b0251cc17dea6dff750f0e1c598919ff350cdbffe"}, + {file = "pygame-2.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e58e2b0c791041e4bccafa5bd7650623ba1592b8fe62ae0a276b7d0ecb314b6c"}, + {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0bd67426c02ffe6c9827fc4bcbda9442fbc451d29b17c83a3c088c56fef2c90"}, + {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dcff6cbba1584cf7732ce1dbdd044406cd4f6e296d13bcb7fba963fb4aeefc9"}, + {file = "pygame-2.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce4b6c0bfe44d00bb0998a6517bd0cf9455f642f30f91bc671ad41c05bf6f6ae"}, + {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68c4e8e60b725ffc7a6c6ecd9bb5fcc5ed2d6e0e2a2c4a29a8454856ef16ad63"}, + {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f3849f97372a3381c66955f99a0d58485ccd513c3d00c030b869094ce6997a6"}, + {file = "pygame-2.5.2.tar.gz", hash = "sha256:c1b89eb5d539e7ac5cf75513125fb5f2f0a2d918b1fd6e981f23bf0ac1b1c24a"}, +] + +[[package]] +name = "pyinstaller" +version = "6.5.0" +description = "PyInstaller bundles a Python application and all its dependencies into a single package." +optional = false +python-versions = "<3.13,>=3.8" +files = [ + {file = "pyinstaller-6.5.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:81ec15c0deb8c7a0f95bea85b49eecc2df1bdeaf5fe487a41d97de6b0ad29dff"}, + {file = "pyinstaller-6.5.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5f432f3fdef053989e0a44134e483131c533dab7637e6afd80c3f7c26e6dbcc9"}, + {file = "pyinstaller-6.5.0-py3-none-manylinux2014_i686.whl", hash = "sha256:6ffd76a0194dac4df5e66dcfccc7b597f3eaa40ef9a3f63548f260aa2c187512"}, + {file = "pyinstaller-6.5.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:a54968df2228f0128607b1dced41bbff94149d459987fb5cd1a41893e9bb85df"}, + {file = "pyinstaller-6.5.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:0dae0edbe6d667b6b0ccd8c97a148f86474a82da7ce582296f9025f4c7242ec6"}, + {file = "pyinstaller-6.5.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:7c76bfcb624803c311fa8fb137e4780d0ec86d11b7d90a8f43f185e2554afdcc"}, + {file = "pyinstaller-6.5.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:6cfee8a74ea2d3a1dc8e99e732a87b314739dc14363778143caac31f8aee9039"}, + {file = "pyinstaller-6.5.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:9d828213aea5401bb33a36ca396f8dc76a59a25bce1d76a13c9ad94ba29fbe42"}, + {file = "pyinstaller-6.5.0-py3-none-win32.whl", hash = "sha256:61865eee5e0d8f8252722f6d001baec497b7cee79ebe62c33a6ba86ba0c7010d"}, + {file = "pyinstaller-6.5.0-py3-none-win_amd64.whl", hash = "sha256:e1266498893ce1d6cc7337e8d2acbf7905a10ed2b7c8377270117d6b7b922fc4"}, + {file = "pyinstaller-6.5.0-py3-none-win_arm64.whl", hash = "sha256:1b3b7d6d3b18d76a833fd5a4d7f4544c5e2c2a4db4a728ea191e62f69d5cc33c"}, + {file = "pyinstaller-6.5.0.tar.gz", hash = "sha256:b1e55113c5a40cb7041c908a57f212f3ebd3e444dbb245ca2f91d86a76dabec5"}, +] + +[package.dependencies] +altgraph = "*" +macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} +packaging = ">=22.0" +pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} +pyinstaller-hooks-contrib = ">=2024.3" +pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} +setuptools = ">=42.0.0" + +[package.extras] +completion = ["argcomplete"] +hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2024.3" +description = "Community maintained hooks for PyInstaller" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyinstaller-hooks-contrib-2024.3.tar.gz", hash = "sha256:d18657c29267c63563a96b8fc78db6ba9ae40af6702acb2f8c871df12c75b60b"}, + {file = "pyinstaller_hooks_contrib-2024.3-py2.py3-none-any.whl", hash = "sha256:6701752d525e1f4eda1eaec2c2affc206171e15c7a4e188a152fcf3ed3308024"}, +] + +[package.dependencies] +packaging = ">=22.0" +setuptools = ">=42.0.0" + +[[package]] +name = "pyobjc" +version = "10.2" +description = "Python<->ObjC Interoperability Module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-10.2-py3-none-any.whl", hash = "sha256:976c8f8af49a91195307b3efbc2d63517be63aae2b4b3689dcff4f317669c23a"}, + {file = "pyobjc-10.2.tar.gz", hash = "sha256:bfea9891750ce3af6439ee102e8e417917f1a7ed7fc4f54b5da9d7457fbb7fc6"}, +] + +[package.dependencies] +pyobjc-core = "10.2" +pyobjc-framework-Accessibility = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Accounts = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AddressBook = "10.2" +pyobjc-framework-AdServices = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AdSupport = {version = "10.2", markers = "platform_release >= \"18.0\""} +pyobjc-framework-AppleScriptKit = "10.2" +pyobjc-framework-AppleScriptObjC = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-ApplicationServices = "10.2" +pyobjc-framework-AppTrackingTransparency = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AudioVideoBridging = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AuthenticationServices = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-AutomaticAssessmentConfiguration = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Automator = "10.2" +pyobjc-framework-AVFoundation = {version = "10.2", markers = "platform_release >= \"11.0\""} +pyobjc-framework-AVKit = {version = "10.2", markers = "platform_release >= \"13.0\""} +pyobjc-framework-AVRouting = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BackgroundAssets = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BrowserEngineKit = {version = "10.2", markers = "platform_release >= \"23.4\""} +pyobjc-framework-BusinessChat = {version = "10.2", markers = "platform_release >= \"18.0\""} +pyobjc-framework-CalendarStore = {version = "10.2", markers = "platform_release >= \"9.0\""} +pyobjc-framework-CallKit = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CFNetwork = "10.2" +pyobjc-framework-Cinematic = {version = "10.2", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ClassKit = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CloudKit = {version = "10.2", markers = "platform_release >= \"14.0\""} +pyobjc-framework-Cocoa = "10.2" +pyobjc-framework-Collaboration = {version = "10.2", markers = "platform_release >= \"9.0\""} +pyobjc-framework-ColorSync = {version = "10.2", markers = "platform_release >= \"17.0\""} +pyobjc-framework-Contacts = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-ContactsUI = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-CoreAudio = "10.2" +pyobjc-framework-CoreAudioKit = "10.2" +pyobjc-framework-CoreBluetooth = {version = "10.2", markers = "platform_release >= \"14.0\""} +pyobjc-framework-CoreData = "10.2" +pyobjc-framework-CoreHaptics = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreLocation = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CoreMedia = {version = "10.2", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMediaIO = {version = "10.2", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMIDI = "10.2" +pyobjc-framework-CoreML = {version = "10.2", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreMotion = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreServices = "10.2" +pyobjc-framework-CoreSpotlight = {version = "10.2", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreText = "10.2" +pyobjc-framework-CoreWLAN = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CryptoTokenKit = {version = "10.2", markers = "platform_release >= \"14.0\""} +pyobjc-framework-DataDetection = {version = "10.2", markers = "platform_release >= \"21.0\""} +pyobjc-framework-DeviceCheck = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-DictionaryServices = {version = "10.2", markers = "platform_release >= \"9.0\""} +pyobjc-framework-DiscRecording = "10.2" +pyobjc-framework-DiscRecordingUI = "10.2" +pyobjc-framework-DiskArbitration = "10.2" +pyobjc-framework-DVDPlayback = "10.2" +pyobjc-framework-EventKit = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-ExceptionHandling = "10.2" +pyobjc-framework-ExecutionPolicy = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ExtensionKit = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ExternalAccessory = {version = "10.2", markers = "platform_release >= \"17.0\""} +pyobjc-framework-FileProvider = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FileProviderUI = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FinderSync = {version = "10.2", markers = "platform_release >= \"14.0\""} +pyobjc-framework-FSEvents = {version = "10.2", markers = "platform_release >= \"9.0\""} +pyobjc-framework-GameCenter = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameController = {version = "10.2", markers = "platform_release >= \"13.0\""} +pyobjc-framework-GameKit = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameplayKit = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-HealthKit = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ImageCaptureCore = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-InputMethodKit = {version = "10.2", markers = "platform_release >= \"9.0\""} +pyobjc-framework-InstallerPlugins = "10.2" +pyobjc-framework-InstantMessage = {version = "10.2", markers = "platform_release >= \"9.0\""} +pyobjc-framework-Intents = {version = "10.2", markers = "platform_release >= \"16.0\""} +pyobjc-framework-IntentsUI = {version = "10.2", markers = "platform_release >= \"21.0\""} +pyobjc-framework-IOBluetooth = "10.2" +pyobjc-framework-IOBluetoothUI = "10.2" +pyobjc-framework-IOSurface = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-iTunesLibrary = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-KernelManagement = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-LatentSemanticMapping = "10.2" +pyobjc-framework-LaunchServices = "10.2" +pyobjc-framework-libdispatch = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-libxpc = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-LinkPresentation = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-LocalAuthentication = {version = "10.2", markers = "platform_release >= \"14.0\""} +pyobjc-framework-LocalAuthenticationEmbeddedUI = {version = "10.2", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MailKit = {version = "10.2", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MapKit = {version = "10.2", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaAccessibility = {version = "10.2", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaLibrary = {version = "10.2", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaPlayer = {version = "10.2", markers = "platform_release >= \"16.0\""} +pyobjc-framework-MediaToolbox = {version = "10.2", markers = "platform_release >= \"13.0\""} +pyobjc-framework-Metal = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalFX = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-MetalKit = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalPerformanceShaders = {version = "10.2", markers = "platform_release >= \"17.0\""} +pyobjc-framework-MetalPerformanceShadersGraph = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-MetricKit = {version = "10.2", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MLCompute = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ModelIO = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MultipeerConnectivity = {version = "10.2", markers = "platform_release >= \"14.0\""} +pyobjc-framework-NaturalLanguage = {version = "10.2", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetFS = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-Network = {version = "10.2", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetworkExtension = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-NotificationCenter = {version = "10.2", markers = "platform_release >= \"14.0\""} +pyobjc-framework-OpenDirectory = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-OSAKit = "10.2" +pyobjc-framework-OSLog = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PassKit = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-PencilKit = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PHASE = {version = "10.2", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Photos = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PhotosUI = {version = "10.2", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PreferencePanes = "10.2" +pyobjc-framework-PubSub = {version = "10.2", markers = "platform_release >= \"9.0\" and platform_release < \"18.0\""} +pyobjc-framework-PushKit = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Quartz = "10.2" +pyobjc-framework-QuickLookThumbnailing = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ReplayKit = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-SafariServices = {version = "10.2", markers = "platform_release >= \"16.0\""} +pyobjc-framework-SafetyKit = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SceneKit = {version = "10.2", markers = "platform_release >= \"11.0\""} +pyobjc-framework-ScreenCaptureKit = {version = "10.2", markers = "platform_release >= \"21.4\""} +pyobjc-framework-ScreenSaver = "10.2" +pyobjc-framework-ScreenTime = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ScriptingBridge = {version = "10.2", markers = "platform_release >= \"9.0\""} +pyobjc-framework-SearchKit = "10.2" +pyobjc-framework-Security = "10.2" +pyobjc-framework-SecurityFoundation = "10.2" +pyobjc-framework-SecurityInterface = "10.2" +pyobjc-framework-SensitiveContentAnalysis = {version = "10.2", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ServiceManagement = {version = "10.2", markers = "platform_release >= \"10.0\""} +pyobjc-framework-SharedWithYou = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SharedWithYouCore = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ShazamKit = {version = "10.2", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Social = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-SoundAnalysis = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Speech = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-SpriteKit = {version = "10.2", markers = "platform_release >= \"13.0\""} +pyobjc-framework-StoreKit = {version = "10.2", markers = "platform_release >= \"11.0\""} +pyobjc-framework-Symbols = {version = "10.2", markers = "platform_release >= \"23.0\""} +pyobjc-framework-SyncServices = "10.2" +pyobjc-framework-SystemConfiguration = "10.2" +pyobjc-framework-SystemExtensions = {version = "10.2", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ThreadNetwork = {version = "10.2", markers = "platform_release >= \"22.0\""} +pyobjc-framework-UniformTypeIdentifiers = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-UserNotifications = {version = "10.2", markers = "platform_release >= \"18.0\""} +pyobjc-framework-UserNotificationsUI = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-VideoSubscriberAccount = {version = "10.2", markers = "platform_release >= \"18.0\""} +pyobjc-framework-VideoToolbox = {version = "10.2", markers = "platform_release >= \"12.0\""} +pyobjc-framework-Virtualization = {version = "10.2", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Vision = {version = "10.2", markers = "platform_release >= \"17.0\""} +pyobjc-framework-WebKit = "10.2" + +[package.extras] +allbindings = ["pyobjc-core (==10.2)", "pyobjc-framework-AVFoundation (==10.2)", "pyobjc-framework-AVKit (==10.2)", "pyobjc-framework-AVRouting (==10.2)", "pyobjc-framework-Accessibility (==10.2)", "pyobjc-framework-Accounts (==10.2)", "pyobjc-framework-AdServices (==10.2)", "pyobjc-framework-AdSupport (==10.2)", "pyobjc-framework-AddressBook (==10.2)", "pyobjc-framework-AppTrackingTransparency (==10.2)", "pyobjc-framework-AppleScriptKit (==10.2)", "pyobjc-framework-AppleScriptObjC (==10.2)", "pyobjc-framework-ApplicationServices (==10.2)", "pyobjc-framework-AudioVideoBridging (==10.2)", "pyobjc-framework-AuthenticationServices (==10.2)", "pyobjc-framework-AutomaticAssessmentConfiguration (==10.2)", "pyobjc-framework-Automator (==10.2)", "pyobjc-framework-BackgroundAssets (==10.2)", "pyobjc-framework-BrowserEngineKit (==10.2)", "pyobjc-framework-BusinessChat (==10.2)", "pyobjc-framework-CFNetwork (==10.2)", "pyobjc-framework-CalendarStore (==10.2)", "pyobjc-framework-CallKit (==10.2)", "pyobjc-framework-Cinematic (==10.2)", "pyobjc-framework-ClassKit (==10.2)", "pyobjc-framework-CloudKit (==10.2)", "pyobjc-framework-Cocoa (==10.2)", "pyobjc-framework-Collaboration (==10.2)", "pyobjc-framework-ColorSync (==10.2)", "pyobjc-framework-Contacts (==10.2)", "pyobjc-framework-ContactsUI (==10.2)", "pyobjc-framework-CoreAudio (==10.2)", "pyobjc-framework-CoreAudioKit (==10.2)", "pyobjc-framework-CoreBluetooth (==10.2)", "pyobjc-framework-CoreData (==10.2)", "pyobjc-framework-CoreHaptics (==10.2)", "pyobjc-framework-CoreLocation (==10.2)", "pyobjc-framework-CoreMIDI (==10.2)", "pyobjc-framework-CoreML (==10.2)", "pyobjc-framework-CoreMedia (==10.2)", "pyobjc-framework-CoreMediaIO (==10.2)", "pyobjc-framework-CoreMotion (==10.2)", "pyobjc-framework-CoreServices (==10.2)", "pyobjc-framework-CoreSpotlight (==10.2)", "pyobjc-framework-CoreText (==10.2)", "pyobjc-framework-CoreWLAN (==10.2)", "pyobjc-framework-CryptoTokenKit (==10.2)", "pyobjc-framework-DVDPlayback (==10.2)", "pyobjc-framework-DataDetection (==10.2)", "pyobjc-framework-DeviceCheck (==10.2)", "pyobjc-framework-DictionaryServices (==10.2)", "pyobjc-framework-DiscRecording (==10.2)", "pyobjc-framework-DiscRecordingUI (==10.2)", "pyobjc-framework-DiskArbitration (==10.2)", "pyobjc-framework-EventKit (==10.2)", "pyobjc-framework-ExceptionHandling (==10.2)", "pyobjc-framework-ExecutionPolicy (==10.2)", "pyobjc-framework-ExtensionKit (==10.2)", "pyobjc-framework-ExternalAccessory (==10.2)", "pyobjc-framework-FSEvents (==10.2)", "pyobjc-framework-FileProvider (==10.2)", "pyobjc-framework-FileProviderUI (==10.2)", "pyobjc-framework-FinderSync (==10.2)", "pyobjc-framework-GameCenter (==10.2)", "pyobjc-framework-GameController (==10.2)", "pyobjc-framework-GameKit (==10.2)", "pyobjc-framework-GameplayKit (==10.2)", "pyobjc-framework-HealthKit (==10.2)", "pyobjc-framework-IOBluetooth (==10.2)", "pyobjc-framework-IOBluetoothUI (==10.2)", "pyobjc-framework-IOSurface (==10.2)", "pyobjc-framework-ImageCaptureCore (==10.2)", "pyobjc-framework-InputMethodKit (==10.2)", "pyobjc-framework-InstallerPlugins (==10.2)", "pyobjc-framework-InstantMessage (==10.2)", "pyobjc-framework-Intents (==10.2)", "pyobjc-framework-IntentsUI (==10.2)", "pyobjc-framework-KernelManagement (==10.2)", "pyobjc-framework-LatentSemanticMapping (==10.2)", "pyobjc-framework-LaunchServices (==10.2)", "pyobjc-framework-LinkPresentation (==10.2)", "pyobjc-framework-LocalAuthentication (==10.2)", "pyobjc-framework-LocalAuthenticationEmbeddedUI (==10.2)", "pyobjc-framework-MLCompute (==10.2)", "pyobjc-framework-MailKit (==10.2)", "pyobjc-framework-MapKit (==10.2)", "pyobjc-framework-MediaAccessibility (==10.2)", "pyobjc-framework-MediaLibrary (==10.2)", "pyobjc-framework-MediaPlayer (==10.2)", "pyobjc-framework-MediaToolbox (==10.2)", "pyobjc-framework-Metal (==10.2)", "pyobjc-framework-MetalFX (==10.2)", "pyobjc-framework-MetalKit (==10.2)", "pyobjc-framework-MetalPerformanceShaders (==10.2)", "pyobjc-framework-MetalPerformanceShadersGraph (==10.2)", "pyobjc-framework-MetricKit (==10.2)", "pyobjc-framework-ModelIO (==10.2)", "pyobjc-framework-MultipeerConnectivity (==10.2)", "pyobjc-framework-NaturalLanguage (==10.2)", "pyobjc-framework-NetFS (==10.2)", "pyobjc-framework-Network (==10.2)", "pyobjc-framework-NetworkExtension (==10.2)", "pyobjc-framework-NotificationCenter (==10.2)", "pyobjc-framework-OSAKit (==10.2)", "pyobjc-framework-OSLog (==10.2)", "pyobjc-framework-OpenDirectory (==10.2)", "pyobjc-framework-PHASE (==10.2)", "pyobjc-framework-PassKit (==10.2)", "pyobjc-framework-PencilKit (==10.2)", "pyobjc-framework-Photos (==10.2)", "pyobjc-framework-PhotosUI (==10.2)", "pyobjc-framework-PreferencePanes (==10.2)", "pyobjc-framework-PubSub (==10.2)", "pyobjc-framework-PushKit (==10.2)", "pyobjc-framework-Quartz (==10.2)", "pyobjc-framework-QuickLookThumbnailing (==10.2)", "pyobjc-framework-ReplayKit (==10.2)", "pyobjc-framework-SafariServices (==10.2)", "pyobjc-framework-SafetyKit (==10.2)", "pyobjc-framework-SceneKit (==10.2)", "pyobjc-framework-ScreenCaptureKit (==10.2)", "pyobjc-framework-ScreenSaver (==10.2)", "pyobjc-framework-ScreenTime (==10.2)", "pyobjc-framework-ScriptingBridge (==10.2)", "pyobjc-framework-SearchKit (==10.2)", "pyobjc-framework-Security (==10.2)", "pyobjc-framework-SecurityFoundation (==10.2)", "pyobjc-framework-SecurityInterface (==10.2)", "pyobjc-framework-SensitiveContentAnalysis (==10.2)", "pyobjc-framework-ServiceManagement (==10.2)", "pyobjc-framework-SharedWithYou (==10.2)", "pyobjc-framework-SharedWithYouCore (==10.2)", "pyobjc-framework-ShazamKit (==10.2)", "pyobjc-framework-Social (==10.2)", "pyobjc-framework-SoundAnalysis (==10.2)", "pyobjc-framework-Speech (==10.2)", "pyobjc-framework-SpriteKit (==10.2)", "pyobjc-framework-StoreKit (==10.2)", "pyobjc-framework-Symbols (==10.2)", "pyobjc-framework-SyncServices (==10.2)", "pyobjc-framework-SystemConfiguration (==10.2)", "pyobjc-framework-SystemExtensions (==10.2)", "pyobjc-framework-ThreadNetwork (==10.2)", "pyobjc-framework-UniformTypeIdentifiers (==10.2)", "pyobjc-framework-UserNotifications (==10.2)", "pyobjc-framework-UserNotificationsUI (==10.2)", "pyobjc-framework-VideoSubscriberAccount (==10.2)", "pyobjc-framework-VideoToolbox (==10.2)", "pyobjc-framework-Virtualization (==10.2)", "pyobjc-framework-Vision (==10.2)", "pyobjc-framework-WebKit (==10.2)", "pyobjc-framework-iTunesLibrary (==10.2)", "pyobjc-framework-libdispatch (==10.2)", "pyobjc-framework-libxpc (==10.2)"] + +[[package]] +name = "pyobjc-core" +version = "10.2" +description = "Python<->ObjC Interoperability Module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-core-10.2.tar.gz", hash = "sha256:0153206e15d0e0d7abd53ee8a7fbaf5606602a032e177a028fc8589516a8771c"}, + {file = "pyobjc_core-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8eab50ce7f17017a0f1d68c3b7e88bb1bb033415fdff62b8e0a9ee4ab72f242"}, + {file = "pyobjc_core-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f2115971463073426ab926416e17e5c16de5b90d1a1f2a2d8724637eb1c21308"}, + {file = "pyobjc_core-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a70546246177c23acb323c9324330e37638f1a0a3d13664abcba3bb75e43012c"}, + {file = "pyobjc_core-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a9b5a215080d13bd7526031d21d5eb27a410780878d863f486053a0eba7ca9a5"}, + {file = "pyobjc_core-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eb1ab700a44bcc4ceb125091dfaae0b998b767b49990df5fdc83eb58158d8e3f"}, + {file = "pyobjc_core-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9a7163aff9c47d654f835f80361c1b112886ec754800d34e75d1e02ff52c3d7"}, +] + +[[package]] +name = "pyobjc-framework-accessibility" +version = "10.2" +description = "Wrappers for the framework Accessibility on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Accessibility-10.2.tar.gz", hash = "sha256:275c9ac0df1350bf751dbddc81d98f7702cf03ad66e0271876cef9aa70ca5c24"}, + {file = "pyobjc_framework_Accessibility-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c801ad06fadc17f281102408d8a98a844739b3496b9799e2cef2b630a8bec312"}, + {file = "pyobjc_framework_Accessibility-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:861c6f7683c1bd769df85e677905a051f17f01a99a59cbba63b68c2f4bf46066"}, + {file = "pyobjc_framework_Accessibility-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7e0a716b0cc89fbfb68d4ac0eba4bbd9c50a092255efa2794c4bb93b27b05b98"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-accounts" +version = "10.2" +description = "Wrappers for the framework Accounts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Accounts-10.2.tar.gz", hash = "sha256:40c8d7299b58b2300db0a6189a7c7056d38385e58700cb40137a744bb03708b7"}, + {file = "pyobjc_framework_Accounts-10.2-py2.py3-none-any.whl", hash = "sha256:9616c8c27f08baadfeea7c27fb1efac043e0785fbbbfbe05f20021402ac5295f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-addressbook" +version = "10.2" +description = "Wrappers for the framework AddressBook on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AddressBook-10.2.tar.gz", hash = "sha256:d6969fcbde1d78ec9fa0ebcefc2f453090e35d7590c4b4baf62174e060de6bce"}, + {file = "pyobjc_framework_AddressBook-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6558b05e9d40a7f40650cddde9b71cb6fb536edf891985c977d4abc626f07a63"}, + {file = "pyobjc_framework_AddressBook-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c57347c04b11310f980e3d6cadc84ebc701d2216853108f9b708c03b0d295a59"}, + {file = "pyobjc_framework_AddressBook-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ba6eff580e4764dc9616b73cf4794cd44b4389b98f95696bac0bb5190f6a1211"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-adservices" +version = "10.2" +description = "Wrappers for the framework AdServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AdServices-10.2.tar.gz", hash = "sha256:76eafba018c819c1770f88daa68d25fc5f06dc93a9f5e369d329d0f341dec3af"}, + {file = "pyobjc_framework_AdServices-10.2-py2.py3-none-any.whl", hash = "sha256:b03fcd460b632fc1b3fd8275060255e518933d1d0da06d6eda9b128b4e2999ec"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-adsupport" +version = "10.2" +description = "Wrappers for the framework AdSupport on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AdSupport-10.2.tar.gz", hash = "sha256:1eb76dc039d081e6d25b4fd334a3987bd9f73527a17844c421bcc8289dd16968"}, + {file = "pyobjc_framework_AdSupport-10.2-py2.py3-none-any.whl", hash = "sha256:4883ac30f1d78d764b57aacb46af78018f2302b9f7e8f4e1fccb25c3cb44ab74"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-applescriptkit" +version = "10.2" +description = "Wrappers for the framework AppleScriptKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppleScriptKit-10.2.tar.gz", hash = "sha256:871452c17b15ce33337cd7ebd293fe31daef9f02f16ebb649efc36165cfb02da"}, + {file = "pyobjc_framework_AppleScriptKit-10.2-py2.py3-none-any.whl", hash = "sha256:15af7d97f017563ff3771127a2b7c515496aa6083497415cbe8c27dd5811c50f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-applescriptobjc" +version = "10.2" +description = "Wrappers for the framework AppleScriptObjC on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppleScriptObjC-10.2.tar.gz", hash = "sha256:71f90e41be6beb392a833d915d3af13d10526bfb29bf35cb9af1578b5ec52566"}, + {file = "pyobjc_framework_AppleScriptObjC-10.2-py2.py3-none-any.whl", hash = "sha256:41156fcc36acc3ca7bd0a62af47af4ab8089330c6072db6047b91b52f815f049"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-applicationservices" +version = "10.2" +description = "Wrappers for the framework ApplicationServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ApplicationServices-10.2.tar.gz", hash = "sha256:f83d6ed3320afb6648be6defafe0f05bac00d0281fc84ee4766ff977309b659f"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2aebfed888f9bcb4f11d93f9ef9a76d561e92848dcb6011da5d5e9d3593371be"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edfd3153e64ee9573bcff7ccaa1fbbbd6964658f187464c461ad34f24552bc85"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d2c89b246c19a041221ff36e9121c92e86a4422016f809a40f5ce3d647882d9"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee1e69947f31aad5fdec44921ce37f7f921faf50a0ceb27ed40b6d54f4b15d0e"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:101f5b09d71e55bd39e6e91f0787433805d422622336b72fde969a7c54528045"}, + {file = "pyobjc_framework_ApplicationServices-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a3ef00c9aea09c5ef5840b8749d0753249869bc30e124145b763cd0b4b81155"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreText = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-apptrackingtransparency" +version = "10.2" +description = "Wrappers for the framework AppTrackingTransparency on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppTrackingTransparency-10.2.tar.gz", hash = "sha256:2eb7276fc70c676562e33c3f7b2fe254175236f968516c63cc8507f325ac56db"}, + {file = "pyobjc_framework_AppTrackingTransparency-10.2-py2.py3-none-any.whl", hash = "sha256:de140b6b6ca1df928d13d986b093f19b8be0c9ab7c42f4121bdbf58f5c69df48"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-audiovideobridging" +version = "10.2" +description = "Wrappers for the framework AudioVideoBridging on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AudioVideoBridging-10.2.tar.gz", hash = "sha256:94d77284aae3a151124aa170074c2902537f540debb076376d49f5ee54fb9ce1"}, + {file = "pyobjc_framework_AudioVideoBridging-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:180e0d1862ba7748a8e4dff0bfeb5dc162bc4d7b0c0888a333f11dbf2569af74"}, + {file = "pyobjc_framework_AudioVideoBridging-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1d68f51143e8da14940ea54edd1236e5cd229dcbc83350551945df285b482704"}, + {file = "pyobjc_framework_AudioVideoBridging-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e0bd4ed2f8a16795b1b46ea9bed746995044f2cd6afb808018ac9c1549dc60b4"}, + {file = "pyobjc_framework_AudioVideoBridging-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2300d1c9ac22cfa8ef80a86dcdf3bc0be1cfa26a61c560f976fbcd0abfb4f13c"}, + {file = "pyobjc_framework_AudioVideoBridging-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a5d48b8192385ef2e0bea47c7b65ca1e0d06d1bdc4a7da59f3d1df3932c5f11c"}, + {file = "pyobjc_framework_AudioVideoBridging-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3883a0f76187a120ad1478f674be245d2949d1bae436d697786ad4086d878b18"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-authenticationservices" +version = "10.2" +description = "Wrappers for the framework AuthenticationServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AuthenticationServices-10.2.tar.gz", hash = "sha256:1be0f05458c4ebfc3e018cb59b4a8bd9022c42b18fea449b0fbf5def0b5f7ef7"}, + {file = "pyobjc_framework_AuthenticationServices-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c797abcd8fb1d9f0f48849093fcbf480814256fca9f1a835445f551a369541e2"}, + {file = "pyobjc_framework_AuthenticationServices-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cf172a06ffab3faa98a95041ce5205ec6c516b59513390d48cfafe17ff4add08"}, + {file = "pyobjc_framework_AuthenticationServices-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3d6b81342caa28dbdc44c3f32820958e0b3865bd3e5bac7ba5ce9efc293e0d75"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-automaticassessmentconfiguration" +version = "10.2" +description = "Wrappers for the framework AutomaticAssessmentConfiguration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AutomaticAssessmentConfiguration-10.2.tar.gz", hash = "sha256:ead3f75200ad74dd013b4a6372054b84b2adeacdac656ca31e763e42fb76cf7b"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f4a60e1f1dae0d8d9223cb09c945aeb5688c09b3dffa9c6e9457981b05902874"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1ca34d71b7def8bf4e820e0ee64cae0d732309c6ddc126699a5bbd0c5719dc48"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:cb3242e2943768e02208c13b4eef47068f0e1ff8ad5572fabfaef7964737daaa"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-automator" +version = "10.2" +description = "Wrappers for the framework Automator on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Automator-10.2.tar.gz", hash = "sha256:fb753e5bd40bfe720fa9e60e2ea5a1777b4c92082ffeba42b4055cdd56cb022d"}, + {file = "pyobjc_framework_Automator-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0034f9e64c3e77b8e1adc54170868954af67b50457b768982de705d8cd170792"}, + {file = "pyobjc_framework_Automator-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e3b66af8ecd2effca8d51b512518137173b026ab13c30dbdac3d0d7ee059fc48"}, + {file = "pyobjc_framework_Automator-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d7f26be322fee0476125bfb2658a08db81b705ce0e8880e91c627562419a9821"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-avfoundation" +version = "10.2" +description = "Wrappers for the framework AVFoundation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVFoundation-10.2.tar.gz", hash = "sha256:4d394014f2477c0c6a596dbb01ef5d92944058d0e0d954ce6121a676ae9395ce"}, + {file = "pyobjc_framework_AVFoundation-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5f20c11a8870d7d58f0e4f20f918e45e922520aa5c9dbee61dc59ca4bc4bd26d"}, + {file = "pyobjc_framework_AVFoundation-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:283355d1f96c184e5f5f479870eb3bf510747307697616737bbc5d224af3abcb"}, + {file = "pyobjc_framework_AVFoundation-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a63a4e26c088023b0b1cb29d7da2c2246aa8eca2b56767fe1cc36a18c6fb650b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreAudio = ">=10.2" +pyobjc-framework-CoreMedia = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-avkit" +version = "10.2" +description = "Wrappers for the framework AVKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVKit-10.2.tar.gz", hash = "sha256:6497a5109a29235a7fd8bddcb6d79bd495ccd9373b41e84ca3f012a642e5b880"}, + {file = "pyobjc_framework_AVKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:480766be9da6bb1a6272a4f13f6a327ec952fe1cd41eb0e7c3a07abb07a3491f"}, + {file = "pyobjc_framework_AVKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c9cfdc9ef8f7c9abe53841e8e548b2671f0a425ce9e0e4961314f5d080401e68"}, + {file = "pyobjc_framework_AVKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ff811fc88fb9d676a4892adea21a1a82384777af53153c74bb829501721f3374"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-avrouting" +version = "10.2" +description = "Wrappers for the framework AVRouting on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVRouting-10.2.tar.gz", hash = "sha256:133d646cf36cfa329c2b3a060c7b81368a95bfbb24f30e2bae2804be65b93ec9"}, + {file = "pyobjc_framework_AVRouting-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1f72c23e92981311e218a04f3cfcd0ef4c3a93058af6e0042c7cf835320300cc"}, + {file = "pyobjc_framework_AVRouting-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a91d65fda866e64bd249c0f11017d0d9f569b3e9d6d159c38e64e1b144a04d85"}, + {file = "pyobjc_framework_AVRouting-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3357ed6b9bf583ded7eee4531ac1854c6e1cdfe91a278f2dec18593d2381d488"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-backgroundassets" +version = "10.2" +description = "Wrappers for the framework BackgroundAssets on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-BackgroundAssets-10.2.tar.gz", hash = "sha256:97ad7b0c693e406950c0c4af2edc9320eac9aef7fdf33274903f526b4682fcb7"}, + {file = "pyobjc_framework_BackgroundAssets-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a71de388248ed05eda85abc440dbe3f04ff39567745785df25b1d5316b2aa9f1"}, + {file = "pyobjc_framework_BackgroundAssets-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:476a7fc80a4083405c82b0bb0d8551cccd10f998a2a9d35c8eab76c82915d25e"}, + {file = "pyobjc_framework_BackgroundAssets-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:9c8721ea4695f1cd5f1f7d6b2a70b3e2b9cefe484289b7427cfda5d23b48e7b6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-browserenginekit" +version = "10.2" +description = "Wrappers for the framework BrowserEngineKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-BrowserEngineKit-10.2.tar.gz", hash = "sha256:a47648e62d3482d39179ffe51543322817dd7a639cef9dcd555dfcc7d6a6497f"}, + {file = "pyobjc_framework_BrowserEngineKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:98563b461e2c96ad387abe1885e91f7bc0686868b39c273774e087bbf1b500ac"}, + {file = "pyobjc_framework_BrowserEngineKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5eb3d8f9e198aaeb01a4a85aa739624942c39b626400d232271d632f1ee30e09"}, + {file = "pyobjc_framework_BrowserEngineKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6824c528e10da1b560d83f55e258232b8916d49d12a578dd00d3ec4e702d3011"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreAudio = ">=10.2" +pyobjc-framework-CoreMedia = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-businesschat" +version = "10.2" +description = "Wrappers for the framework BusinessChat on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-BusinessChat-10.2.tar.gz", hash = "sha256:44ecf240da59ce36f2d75d1ed9f58e05f2df46b9b1989ee0cc184a46c779fb4e"}, + {file = "pyobjc_framework_BusinessChat-10.2-py2.py3-none-any.whl", hash = "sha256:aa51d4d0b3b3eb050242e0d0e48b29e020ccfeb82a39c0d3a2289512734f53e4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-calendarstore" +version = "10.2" +description = "Wrappers for the framework CalendarStore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CalendarStore-10.2.tar.gz", hash = "sha256:131c14faa227a251d7254afd9c00fef203361dd76224d9700ba5e99682e191d8"}, + {file = "pyobjc_framework_CalendarStore-10.2-py2.py3-none-any.whl", hash = "sha256:e289236df651953a41be8ee4ce548f477a6ab8e90aa8bbd73f46ad29032ff13f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-callkit" +version = "10.2" +description = "Wrappers for the framework CallKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CallKit-10.2.tar.gz", hash = "sha256:45cd81a5b6b0107ba56e26d8e54e852b8a15b3487b7291b5818e10e94beee6d0"}, + {file = "pyobjc_framework_CallKit-10.2-py2.py3-none-any.whl", hash = "sha256:f3f26c877743a340718e0647ccee4604f9d87aa8ad5c3268c794d94f6f9246ee"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-cfnetwork" +version = "10.2" +description = "Wrappers for the framework CFNetwork on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CFNetwork-10.2.tar.gz", hash = "sha256:18ebd22c645b5b77c1df6d973a91cc035ddd4666346912b2a0c847803c23f4d4"}, + {file = "pyobjc_framework_CFNetwork-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6050428c99505e09db1fe5d0eafaaca4ead407ffaaab8a5c1e5ec09e7ad31053"}, + {file = "pyobjc_framework_CFNetwork-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f7a305d7f94a11dd32d3ab9159cde1f9655f107282373841668624b124935af8"}, + {file = "pyobjc_framework_CFNetwork-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:41a211b934afebbc9dd9ce76cb5c2862244a699a41badb660ab46c198414c4cb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-cinematic" +version = "10.2" +description = "Wrappers for the framework Cinematic on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Cinematic-10.2.tar.gz", hash = "sha256:514effad241be5c8df4ef870683fa1387909970a7f7d8bbf343c06e840931854"}, + {file = "pyobjc_framework_Cinematic-10.2-py2.py3-none-any.whl", hash = "sha256:962af237b284605ecd30d584d2d7fb75fda40e429327578de5d651644d0316da"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-AVFoundation = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreMedia = ">=10.2" +pyobjc-framework-Metal = ">=10.2" + +[[package]] +name = "pyobjc-framework-classkit" +version = "10.2" +description = "Wrappers for the framework ClassKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ClassKit-10.2.tar.gz", hash = "sha256:252e47e3284491e48000d4d87948b31e396aaa78eaf2447ba03a71f4b97cb989"}, + {file = "pyobjc_framework_ClassKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9d5f69e7bba660ca989e699d4b38a93db7ee3f8cff45e67a23fb852ac3caab49"}, + {file = "pyobjc_framework_ClassKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e1acc7231ef030125eaf7302f324909c56ba1c58ff91f4e160b6632938db64df"}, + {file = "pyobjc_framework_ClassKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:87158ca3d7cd78c50af353c23f32e1e8eb0adec47dc15fa4e4d777017d308b80"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-cloudkit" +version = "10.2" +description = "Wrappers for the framework CloudKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CloudKit-10.2.tar.gz", hash = "sha256:497a0dda5f5a9aafc795e1941ef3e3662c2f3240096ce68893d0d5de6d54a474"}, + {file = "pyobjc_framework_CloudKit-10.2-py2.py3-none-any.whl", hash = "sha256:32bd77c2b9109113b2321feb6ed6d754af99df6569d953371f1547123be80467"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Accounts = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreData = ">=10.2" +pyobjc-framework-CoreLocation = ">=10.2" + +[[package]] +name = "pyobjc-framework-cocoa" +version = "10.2" +description = "Wrappers for the Cocoa frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Cocoa-10.2.tar.gz", hash = "sha256:6383141379636b13855dca1b39c032752862b829f93a49d7ddb35046abfdc035"}, + {file = "pyobjc_framework_Cocoa-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9227b4f271fda2250f5a88cbc686ff30ae02c0f923bb7854bb47972397496b2"}, + {file = "pyobjc_framework_Cocoa-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6042b7703bdc33b7491959c715c1e810a3f8c7a560c94b36e00ef321480797"}, + {file = "pyobjc_framework_Cocoa-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:18886d5013cd7dc7ecd6e0df5134c767569b5247fc10a5e293c72ee3937b217b"}, + {file = "pyobjc_framework_Cocoa-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ecf01400ee698d2e0ff4c907bcf9608d9d710e97203fbb97b37d208507a9362"}, + {file = "pyobjc_framework_Cocoa-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0def036a7b24e3ae37a244c77bec96b7c9c8384bf6bb4d33369f0a0c8807a70d"}, + {file = "pyobjc_framework_Cocoa-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f47ecc393bc1019c4b47e8653207188df784ac006ad54d8c2eb528906ff7013"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" + +[[package]] +name = "pyobjc-framework-collaboration" +version = "10.2" +description = "Wrappers for the framework Collaboration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Collaboration-10.2.tar.gz", hash = "sha256:32e3a7fe8447f38fd3be5ea1fe9c1e52efef3889f4bd5781dffa3c5fa044fe20"}, + {file = "pyobjc_framework_Collaboration-10.2-py2.py3-none-any.whl", hash = "sha256:239a0505d702d49b5c3f0a3524531f9be63d599ea2cd3cbb5953147b34dbdcc1"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-colorsync" +version = "10.2" +description = "Wrappers for the framework ColorSync on Mac OS X" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ColorSync-10.2.tar.gz", hash = "sha256:108105c281b375dff7d226fcc3f860621a4880dcbab711660b74dc458a506231"}, + {file = "pyobjc_framework_ColorSync-10.2-py2.py3-none-any.whl", hash = "sha256:2fcc68eb6fa6300d34b95b1da1cc8d244f6999aed4b83099a3323d32e0349f98"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-contacts" +version = "10.2" +description = "Wrappers for the framework Contacts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Contacts-10.2.tar.gz", hash = "sha256:5a9de975f41c7dac3c219b4c60cd08b8ba385685db7997c8622f19e0a43e6857"}, + {file = "pyobjc_framework_Contacts-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ff801009c9346927b7efc82434ac14a0c2798bd018daf1e7d8aad74484b490"}, + {file = "pyobjc_framework_Contacts-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:52dd5e4b4574b2438420a56867ca2069e29414087dc27ad03e7c46d536f1e641"}, + {file = "pyobjc_framework_Contacts-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8a387c47e90c74a3e6f4bc81187f1fde18a020bb1d08067497a0c35f462299f9"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-contactsui" +version = "10.2" +description = "Wrappers for the framework ContactsUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ContactsUI-10.2.tar.gz", hash = "sha256:2dd5f1993c36caf13527de0890c6c49c08a339e58bc3b3fa303d5a04b672b418"}, + {file = "pyobjc_framework_ContactsUI-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c8af8b52853ba2a09664dad40255613f01089c9bc77e5316b29d27c65603863c"}, + {file = "pyobjc_framework_ContactsUI-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:47a8fd0aa5cb680b0ba0f1fdd37f56525729e5ed998df2a312e9f81feea8fbb0"}, + {file = "pyobjc_framework_ContactsUI-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0575650e6e5985950bcd424da0b50e981ea5e6819d1c6fbccb075585e424e121"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Contacts = ">=10.2" + +[[package]] +name = "pyobjc-framework-coreaudio" +version = "10.2" +description = "Wrappers for the framework CoreAudio on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreAudio-10.2.tar.gz", hash = "sha256:5e97ae7a65be85aee83aef004b31146c5fbf28325d870362959f7312b303fb67"}, + {file = "pyobjc_framework_CoreAudio-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:65ce01a9963692d9cf94aef36d8e342eb2e75b855a2f362f9cbcef9f3782a690"}, + {file = "pyobjc_framework_CoreAudio-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:22b5017ed340f9d3137baacb5f0c2354266017a4ed21890a795a0667788fc0cd"}, + {file = "pyobjc_framework_CoreAudio-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32608ce881b5e6a7cb332c2732762fa93829ac495c5344c33e8e8b72a2431b23"}, + {file = "pyobjc_framework_CoreAudio-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5182342257be1cdaa64bc38045cd81aca5b60bb86a9444194adbff58706ce91"}, + {file = "pyobjc_framework_CoreAudio-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:94ec138f95801019ec7f3e7010ad6f2c575aea69d2428aa9f5b159bf0355034a"}, + {file = "pyobjc_framework_CoreAudio-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:04ad3dddff27cb65d31a432aa1aa6290ff5d82a54bc5825da44ed7d80bcdb925"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coreaudiokit" +version = "10.2" +description = "Wrappers for the framework CoreAudioKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreAudioKit-10.2.tar.gz", hash = "sha256:38dfafba8eddb655aac352a967c0e713a90e10a4dd40d4ea1abbb4db01c5d33f"}, + {file = "pyobjc_framework_CoreAudioKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:aede4cd58a67008b014757178a01b984ee585cc055133a9eb8f10b310d764de8"}, + {file = "pyobjc_framework_CoreAudioKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4fb992025df80b8799fbd1605b0dd4b4b3f6b467c375a16da1b286f6ac2e2854"}, + {file = "pyobjc_framework_CoreAudioKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:28f17803e5eaf35a73caa327cbd0c857efbfdea57307637a60ff8309834b7a95"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreAudio = ">=10.2" + +[[package]] +name = "pyobjc-framework-corebluetooth" +version = "10.2" +description = "Wrappers for the framework CoreBluetooth on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreBluetooth-10.2.tar.gz", hash = "sha256:fb69d2c61082935b2b12827c1ba4bb22146eb3d251695fa1d58bbd5835260729"}, + {file = "pyobjc_framework_CoreBluetooth-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6e118f08ae08289195841e0066389632206b68a8377ac384b30ac0c7e262b779"}, + {file = "pyobjc_framework_CoreBluetooth-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:411de4f937264b5e2935be25b78362c58118e2ab9f6a7af4d4d005813c458354"}, + {file = "pyobjc_framework_CoreBluetooth-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:81da4426a492089f9dd9ca50814766101f97574675782f7be7ce1a63197d497a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coredata" +version = "10.2" +description = "Wrappers for the framework CoreData on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreData-10.2.tar.gz", hash = "sha256:0260bbf8f4ce6071749686fdc079618b3bd2b07976db7db4c864ecc62316bb3b"}, + {file = "pyobjc_framework_CoreData-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:41a22fe04544ba35e82232d89ad751b452c2314f07df6c72129a5ad6c3e4cbec"}, + {file = "pyobjc_framework_CoreData-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c29c6dce8ce155e15e960b9c542618516923c3ef55a50bf98ec95e60afe0aa3d"}, + {file = "pyobjc_framework_CoreData-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:cd7c419d9067ce9a9f83f6abd3c072caeb3aa20091f779881375067f7c1c417b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-corehaptics" +version = "10.2" +description = "Wrappers for the framework CoreHaptics on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreHaptics-10.2.tar.gz", hash = "sha256:7b98bd70b63506aef63401a6e03f67391d7582f39fbe8aa7bb7258dd66ab0e55"}, + {file = "pyobjc_framework_CoreHaptics-10.2-py2.py3-none-any.whl", hash = "sha256:c67fae4b543fc070cece622cfe5803796016a36d1020812428e0f22e5f5674aa"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-corelocation" +version = "10.2" +description = "Wrappers for the framework CoreLocation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreLocation-10.2.tar.gz", hash = "sha256:59497cc210023479e03191495c880e61fb6f44ad6c435ed1c8dd8def39f3aada"}, + {file = "pyobjc_framework_CoreLocation-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c2e02352a4dfbc090cebc9c0d3716470031e584d4d33f22d97307f04c23ef01f"}, + {file = "pyobjc_framework_CoreLocation-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f7eda101abb366be52d2fd85e15c79fdf0b9f64de9aab87dc0577653375595de"}, + {file = "pyobjc_framework_CoreLocation-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8f67af17b8267aa2a066684b518e66bbe7fee9651b779e372d6286d65914df82"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coremedia" +version = "10.2" +description = "Wrappers for the framework CoreMedia on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMedia-10.2.tar.gz", hash = "sha256:d726d86636217eaa135e5626d05c7eb0f9b4529ce1ed504e08069fe1e0421483"}, + {file = "pyobjc_framework_CoreMedia-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c91037fd4f9995021be9e849f1d7ac74579291d0130ad6898e3cb1940f870e1"}, + {file = "pyobjc_framework_CoreMedia-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63ed4f6dbe33e5f3d5293a78674329cb516a256df34ef92e7c1fefacdb5c32db"}, + {file = "pyobjc_framework_CoreMedia-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7fa13166a14d384bb6442e5f635310dd075c2a4b3b3bd67ac63b1e2e1fd2d65e"}, + {file = "pyobjc_framework_CoreMedia-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd7da9da1fad7168a63466d5c267dea8bce706808557baa960b6a931010dca48"}, + {file = "pyobjc_framework_CoreMedia-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c48c7f0c3709a900cd11002018950a72626af39a096d1001bb9a871574db794f"}, + {file = "pyobjc_framework_CoreMedia-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d3e86d03c6d7909058b8f1b8e54d9b5d93679049c7980eb0a5d930a5a63410e0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coremediaio" +version = "10.2" +description = "Wrappers for the framework CoreMediaIO on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMediaIO-10.2.tar.gz", hash = "sha256:12f9fd93e610e61258f1acb023b868ed196e9444c69e38dfd314f8c256d07c9e"}, + {file = "pyobjc_framework_CoreMediaIO-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4ede4da59fa2a611b4a9d5a532e0c09731f448186af6cc957ab733b388f86d5b"}, + {file = "pyobjc_framework_CoreMediaIO-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:184854c2ebc3d12466ad39e640b5f3d2bdb3792d8675c83f499bb48b078d3d91"}, + {file = "pyobjc_framework_CoreMediaIO-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aa9418277d16a1d5c0b576ad8a35f8e239d3461da60bb296df310090147331f7"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coremidi" +version = "10.2" +description = "Wrappers for the framework CoreMIDI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMIDI-10.2.tar.gz", hash = "sha256:8168cb1e57e5dbc31648cd68d9afe3306cd2751de03275ef5f7f9b6483f17c07"}, + {file = "pyobjc_framework_CoreMIDI-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:809a79fbf384df94884dfddcab4dad3e68eba9e85591f7b55d24f4af2fb8db94"}, + {file = "pyobjc_framework_CoreMIDI-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a48176d5f49f9e893f5a7ac86f7cd7ee63b66dc7941ef74c04876f87a1ae3475"}, + {file = "pyobjc_framework_CoreMIDI-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:cce95647865c7f374d3c9cf853a3a8a44ae06fda6fa2e65fc7ad6450dc60e50f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coreml" +version = "10.2" +description = "Wrappers for the framework CoreML on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreML-10.2.tar.gz", hash = "sha256:a1d7743a91160d096ccd3f5f5d824dafdd6b99d0c4342e8c18852333c9b3318e"}, + {file = "pyobjc_framework_CoreML-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ab99248b8ace0bebb11d15eb4094d8017093ebf76dadf828e324cacc9f1866f1"}, + {file = "pyobjc_framework_CoreML-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:074b81c0e0e4177d33b2da8267d377fb7842b47eb7b977bb07d674b9b05c32b5"}, + {file = "pyobjc_framework_CoreML-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:baedffd5ab34dc0294c2c30ad1b5bcff175957f51f107b1f9f8b20f80e15cc9c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coremotion" +version = "10.2" +description = "Wrappers for the framework CoreMotion on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMotion-10.2.tar.gz", hash = "sha256:1e1827f2f811ada123dd42809bc86f04a4c1ae3cec619ccf0f05a9387412bec1"}, + {file = "pyobjc_framework_CoreMotion-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:804abc6b22db933e7fb7ba3e60b30f4c60e8921f8bb5790c3612375f7b4a6f03"}, + {file = "pyobjc_framework_CoreMotion-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:76d5a2ed1cba375e3c423887bd93bbaab849c7a961156c5cead8e1429c26c24d"}, + {file = "pyobjc_framework_CoreMotion-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c0a8022dca1404795e93cd7317bca9f8ad601f3ecec7bed71312d80adad296e4"}, + {file = "pyobjc_framework_CoreMotion-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b727d5301ec386b8aa94de69a9257a412a4edbd69ca394d76b83d9f2bec6bc96"}, + {file = "pyobjc_framework_CoreMotion-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7e4571a08475428a8171a237284036a990011f212497f141222d281fa7e2ca5c"}, + {file = "pyobjc_framework_CoreMotion-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bef81c52af0d1be75b3bd7514d5f9ef7c6e868f385f0dd8c28ad62e5d3faeeb6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coreservices" +version = "10.2" +description = "Wrappers for the framework CoreServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreServices-10.2.tar.gz", hash = "sha256:90fa09e68e840fdd229b33354f4b2e55e9f95a221fcc30612f4bd92cdc530518"}, + {file = "pyobjc_framework_CoreServices-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2b0c6142490c7099c5be0a2fa10b1816e4280bc04ac4e5a4a9af17a9c2006482"}, + {file = "pyobjc_framework_CoreServices-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c2c05674d9d142abc62fcc8e39c8484bdcdfd3ad8a17f009b8aa7c631e227571"}, + {file = "pyobjc_framework_CoreServices-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f87ad202d896e596b31c98a9d0378b2e6d2e6732a2dfc7b82ceae4c70863364d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-FSEvents = ">=10.2" + +[[package]] +name = "pyobjc-framework-corespotlight" +version = "10.2" +description = "Wrappers for the framework CoreSpotlight on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreSpotlight-10.2.tar.gz", hash = "sha256:bc4ac490953db29f6a58bc6fca6f819f8a810d0bb15d5f067451b3a8cad1cb50"}, + {file = "pyobjc_framework_CoreSpotlight-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f69dc88ddfa116262009b15ac302b880aef2dad878bf472cbf574f4473f4b059"}, + {file = "pyobjc_framework_CoreSpotlight-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:09912188648e658a0f579bbfd2cf6765afb8e0f466ee666e24019cc9931b6bc5"}, + {file = "pyobjc_framework_CoreSpotlight-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:75ba49ee4bfdbf4df733bc8c508b4417f47c442a56b83ffe5527e76e1c5bad67"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-coretext" +version = "10.2" +description = "Wrappers for the framework CoreText on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreText-10.2.tar.gz", hash = "sha256:59ef8ca8d88bb53ce9980dda0b8094daa3e2dabe355847365ba965ff0b49f961"}, + {file = "pyobjc_framework_CoreText-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:44052f752f42b62d342fa8aced5d1b8928831e70830eccddc594726d40500d5c"}, + {file = "pyobjc_framework_CoreText-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0bc278f509a3fd3eea89124d81e77de11af10167c0df0d0cc15a369f060465a0"}, + {file = "pyobjc_framework_CoreText-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7b819119dc859e49c0ce9040ae09d6a3bd66658003793f486ef5a21e46a2d34f"}, + {file = "pyobjc_framework_CoreText-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2719c57ff08af6e4fdcddd0fa5eda56113808a1690c3325f1c6926740817f9a1"}, + {file = "pyobjc_framework_CoreText-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8239ce92f9496587a60fc1bfd4994136832bad99405bb45572f92d960cbe746e"}, + {file = "pyobjc_framework_CoreText-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:80a1d207fcdb2999841daa430c83d760ac1a3f2f65c605949fc5ff789425b1f6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-corewlan" +version = "10.2" +description = "Wrappers for the framework CoreWLAN on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreWLAN-10.2.tar.gz", hash = "sha256:f47dcf735145eb2f817db5c2134321a7cfb9274a634161ff3069617fd2afff42"}, + {file = "pyobjc_framework_CoreWLAN-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8dc102d7d08437b5421856ae8aac32e3e9846e546c1742e4d57343abd694688f"}, + {file = "pyobjc_framework_CoreWLAN-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:85bcf84fd38a2e949760dda3201f13f8bef73b341a623f6736834b7420386f16"}, + {file = "pyobjc_framework_CoreWLAN-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ada346a6da1075e16bf5f022ccad488632fe6de972d2d925616add87e3eb9fad"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-cryptotokenkit" +version = "10.2" +description = "Wrappers for the framework CryptoTokenKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CryptoTokenKit-10.2.tar.gz", hash = "sha256:c0adfde2d53da7df1f8827bdf0cbf4419590151dd1041711ab2f66a32bd986f5"}, + {file = "pyobjc_framework_CryptoTokenKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:89264d38ca58e8b5a586a3c13260d490ee2cdc9c1498211a804cec67f7659cd7"}, + {file = "pyobjc_framework_CryptoTokenKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e13d92966273420a154cde6694b4bc7dd3dc7679e93d651534dcf2b0c5246546"}, + {file = "pyobjc_framework_CryptoTokenKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a56af323d597332090a0787c00d16c40152c62cb278d951a59723006cd3e10de"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-datadetection" +version = "10.2" +description = "Wrappers for the framework DataDetection on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DataDetection-10.2.tar.gz", hash = "sha256:9532bb697b96ec4ffc04310550bf21c45c8494fc07d8067fc41cbfd94c8ba27d"}, + {file = "pyobjc_framework_DataDetection-10.2-py2.py3-none-any.whl", hash = "sha256:4435ebaa3b3fa3de855690469fefd2d8a3568f702f51540707efaf4363ec94aa"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-devicecheck" +version = "10.2" +description = "Wrappers for the framework DeviceCheck on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DeviceCheck-10.2.tar.gz", hash = "sha256:f620ede18e12dd36d92f24d1a68278821bcf7aeaea6577993fbfb328c118569d"}, + {file = "pyobjc_framework_DeviceCheck-10.2-py2.py3-none-any.whl", hash = "sha256:c9c87ae40af41c4c296af40317018732bba85e589111f5286b2f136f022c8ecd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-dictionaryservices" +version = "10.2" +description = "Wrappers for the framework DictionaryServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DictionaryServices-10.2.tar.gz", hash = "sha256:858b4edce36dfbb0f906f17c6aac1aae06350d508cf0b295949113ebf383bfb4"}, + {file = "pyobjc_framework_DictionaryServices-10.2-py2.py3-none-any.whl", hash = "sha256:39b577b35c52a033cbac030df1fdcd16fb109144e8c59cb2044a13fcd803ab49"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-CoreServices = ">=10.2" + +[[package]] +name = "pyobjc-framework-discrecording" +version = "10.2" +description = "Wrappers for the framework DiscRecording on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiscRecording-10.2.tar.gz", hash = "sha256:9670018a0970553882feb10e066585ad791c502539712f4117bad4a6647c79b3"}, + {file = "pyobjc_framework_DiscRecording-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cbf0d9904a24bece47a71b56f87090a769e96338c0acb3f33385c3e584ed1c96"}, + {file = "pyobjc_framework_DiscRecording-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0a7d9980ab9f59903d60d09172de4085028bbb97a63112f78b9cca0051a73639"}, + {file = "pyobjc_framework_DiscRecording-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8a6512d0b7e61064ca167ca0a9c95a3f49f8fa7216fe5e1d77eab01ce56a9414"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-discrecordingui" +version = "10.2" +description = "Wrappers for the framework DiscRecordingUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiscRecordingUI-10.2.tar.gz", hash = "sha256:afda9756a8f9e8ce1f83930eca3b1a263a29f48c1618269457f4aba63fc1644f"}, + {file = "pyobjc_framework_DiscRecordingUI-10.2-py2.py3-none-any.whl", hash = "sha256:e0423c548851cd9eb4ad7e9e085da4db2cde2420e1f3e05d46e649498edf97d8"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-DiscRecording = ">=10.2" + +[[package]] +name = "pyobjc-framework-diskarbitration" +version = "10.2" +description = "Wrappers for the framework DiskArbitration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiskArbitration-10.2.tar.gz", hash = "sha256:25b74db4f39a7128599e153533db0f88c680ad55f366c5ab6a6d7dede96eeb57"}, + {file = "pyobjc_framework_DiskArbitration-10.2-py2.py3-none-any.whl", hash = "sha256:dd14eb448865ca4c49e15a543f748f1ef6501ea0044eaa2cf04860547205c84f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-dvdplayback" +version = "10.2" +description = "Wrappers for the framework DVDPlayback on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DVDPlayback-10.2.tar.gz", hash = "sha256:0869a6e8da1c2d93713699785b4f0bbe5dd1b2820a0ff4a6adf06227b1bb96ac"}, + {file = "pyobjc_framework_DVDPlayback-10.2-py2.py3-none-any.whl", hash = "sha256:f3fb90eb3d616290d2ab652214ce682130cd19d1fd3205def6ab0ba295535dd9"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-eventkit" +version = "10.2" +description = "Wrappers for the framework Accounts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-EventKit-10.2.tar.gz", hash = "sha256:13c8262344f06096514d1e72d3c026fa4002d917846ce81217d4258acd861324"}, + {file = "pyobjc_framework_EventKit-10.2-py2.py3-none-any.whl", hash = "sha256:c9afa63fc2924281fdf1ef6c86cc2ba01b7b84a8545a826ddd89e4abd7077e81"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-exceptionhandling" +version = "10.2" +description = "Wrappers for the framework ExceptionHandling on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExceptionHandling-10.2.tar.gz", hash = "sha256:cf4cd143c24504d66ef9d4e67b4b88e2ac892716e6ead2aa9585a7d39278d943"}, + {file = "pyobjc_framework_ExceptionHandling-10.2-py2.py3-none-any.whl", hash = "sha256:fd7dfc197c29ccf187718dbb0b1dcd966a8c04ee6549ee9472959912e76a0609"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-executionpolicy" +version = "10.2" +description = "Wrappers for the framework ExecutionPolicy on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExecutionPolicy-10.2.tar.gz", hash = "sha256:8976c35a58c2e51d6574123ecfcd58459bbdb32b3992716119a3c001d3cc2bcf"}, + {file = "pyobjc_framework_ExecutionPolicy-10.2-py2.py3-none-any.whl", hash = "sha256:4d95d55f82a15286035bb5bc01b339d6c36103a1cbf7d6a3d7a9feac71663626"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-extensionkit" +version = "10.2" +description = "Wrappers for the framework ExtensionKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExtensionKit-10.2.tar.gz", hash = "sha256:343c17ec1696947cde6764b32f741d00d7424a620cdbaa91d9bcf47025b77718"}, + {file = "pyobjc_framework_ExtensionKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:69981d3a0f7146b57b16f1132c114419a2b89fa201677c7e240f861bc7e56670"}, + {file = "pyobjc_framework_ExtensionKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:30fa27de3f97436c867ca3e89d8e95f141337a9377f71be3c8a036795b5557fb"}, + {file = "pyobjc_framework_ExtensionKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:09e1402c9fd7c6fcacd662caa2198d79342b812665980fd9a66e906743bddf69"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-externalaccessory" +version = "10.2" +description = "Wrappers for the framework ExternalAccessory on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExternalAccessory-10.2.tar.gz", hash = "sha256:e62af0029b2fd7e07c17a4abe52b20495dba05cba45d7e901acbd43ad19c4cc3"}, + {file = "pyobjc_framework_ExternalAccessory-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b279672b05f0f8a11201a5ed8754bcea5b8d3e6226ec16c6b59127e2c6e25259"}, + {file = "pyobjc_framework_ExternalAccessory-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5fbe16bb4831a30659cba6a53b77dca94b72ff12bfd318c76f118f39557427c5"}, + {file = "pyobjc_framework_ExternalAccessory-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f446ce468a369650c4c49947bb7329c58c68cd44aee801506e60be1f26cd6265"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-fileprovider" +version = "10.2" +description = "Wrappers for the framework FileProvider on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FileProvider-10.2.tar.gz", hash = "sha256:1accc2965c59395152d04b2f4a096cb4a5364bca8094695ce2b60d2f794bff74"}, + {file = "pyobjc_framework_FileProvider-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e69d294b0ac9fdcafb28fbb1b9770e1e851cc5467dc0ae1d7b182882ce16d1d"}, + {file = "pyobjc_framework_FileProvider-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1d5cc02d43f2c6851934c8208cd4a66ad007daf0db673f72d1938677c90b1208"}, + {file = "pyobjc_framework_FileProvider-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0ae00a293e3ac511cc9eb54ee05b67583ea35d490b47f23f448a3da6652c189b"}, + {file = "pyobjc_framework_FileProvider-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fab7da3c7961e77b09f34cb71a876205ea8d73f9d10d5db78080f7282dd5066f"}, + {file = "pyobjc_framework_FileProvider-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:5d2b581c8cb1c15304676f5a77c42e430aaad886ac92d8b2d4e5cec57cb86be3"}, + {file = "pyobjc_framework_FileProvider-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c471c0d27d9d6a7bba3d06f679f14ac8d719ed3660d9a8e6788a31e1521e71d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-fileproviderui" +version = "10.2" +description = "Wrappers for the framework FileProviderUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FileProviderUI-10.2.tar.gz", hash = "sha256:a22204c1fad818e4c8d94ecb544fec59387e01a0074cbe2ca6e58de1a12c157e"}, + {file = "pyobjc_framework_FileProviderUI-10.2-py2.py3-none-any.whl", hash = "sha256:5fac2067c09a23a436708e05d71faf65d64f4c36b45ad254617720b1a682aad6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-FileProvider = ">=10.2" + +[[package]] +name = "pyobjc-framework-findersync" +version = "10.2" +description = "Wrappers for the framework FinderSync on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FinderSync-10.2.tar.gz", hash = "sha256:5ecbe9bf7fe77f28204fbe358ee541fdd2786fc076a631c4f11b74377d60ea05"}, + {file = "pyobjc_framework_FinderSync-10.2-py2.py3-none-any.whl", hash = "sha256:11d569492efe74a52883e6086038ca9d5a712a08db828f3ca43c03e756013801"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-fsevents" +version = "10.2" +description = "Wrappers for the framework FSEvents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FSEvents-10.2.tar.gz", hash = "sha256:3a75f38bb1d5d2cf6a0d3e92801b3510f32e96cf6443d81b9dd92a84d72eff0a"}, + {file = "pyobjc_framework_FSEvents-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:129f9654ab9074eff29ccb8dd09625e3740058744a38f9776d0349387f518715"}, + {file = "pyobjc_framework_FSEvents-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c71699f24482d99ee8f6b7a8d36c4c294655c670d8cbd0f3c6f146a2fda6283c"}, + {file = "pyobjc_framework_FSEvents-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a0ff7bb8c1a357181345ff3a90b7f808cd55c4757df60c723541f0f469323190"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-gamecenter" +version = "10.2" +description = "Wrappers for the framework GameCenter on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameCenter-10.2.tar.gz", hash = "sha256:43341b428cad2e50710cb974728924280e520e04ae9f750bc7beda5006457ae3"}, + {file = "pyobjc_framework_GameCenter-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f14ad00713519b508f4c956a8212bff01f6b6279b2a76e87d99a18262e61dfda"}, + {file = "pyobjc_framework_GameCenter-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b52932e90c6b6d90ce8c895b0ac878dc4e639d493724a5789fc990e1efec3d05"}, + {file = "pyobjc_framework_GameCenter-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:dc9de1b3d0db1921fb197ad964226ebc271744aee0cc792f9fe66afaf92b24f0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-gamecontroller" +version = "10.2" +description = "Wrappers for the framework GameController on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameController-10.2.tar.gz", hash = "sha256:81ad502346904995ec04b0580bab94ab32ca847fad06bca88cdf2ec6222b80ae"}, + {file = "pyobjc_framework_GameController-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:47e6dfcf10353a17adcfa7649d0f5d0cba4d4dc3ce3a66826d873574ae2afcb1"}, + {file = "pyobjc_framework_GameController-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ef6fcb5308c1c31d1de3969165a13750b74f52c80249b722383307fc558edff"}, + {file = "pyobjc_framework_GameController-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:89a7aac243b0347c3ef10fc2bcedcb1b2ae9eb14daabccb3f3cfe1cf12c7e572"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-gamekit" +version = "10.2" +description = "Wrappers for the framework GameKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameKit-10.2.tar.gz", hash = "sha256:0ef877db88e8888ecf682b09b9fb1ee6b879f23d521ce3a738a1b0fb2b885974"}, + {file = "pyobjc_framework_GameKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c23025087bec023a37fe0c84fcdc592cdc100d9187b49250446587f09571dbeb"}, + {file = "pyobjc_framework_GameKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6867159762db0a72046abe42df8dff080620c2f9cdf20927445eec28f3f04124"}, + {file = "pyobjc_framework_GameKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e7d91d28c4c8d240f0fbab80f84545efbeeb5a42db4c6fbd4ccb1f3face88c9c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-gameplaykit" +version = "10.2" +description = "Wrappers for the framework GameplayKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameplayKit-10.2.tar.gz", hash = "sha256:068ee6f3586f4033d25ed3b0451eab8f388b2970be1dfbe39be01accca7a9b2e"}, + {file = "pyobjc_framework_GameplayKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c9e87b8221a74599c813640b823f3a2546aa6076b04087f26fd3ecc8c78cbe01"}, + {file = "pyobjc_framework_GameplayKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a1f81c969347d63a1200818ae12350ad39353e85842f34040b9d997e55f7ec89"}, + {file = "pyobjc_framework_GameplayKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f438c4b98e1d00dec84fedc8796761063e99814f913151441bc7147ac8b23068"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-SpriteKit = ">=10.2" + +[[package]] +name = "pyobjc-framework-healthkit" +version = "10.2" +description = "Wrappers for the framework HealthKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-HealthKit-10.2.tar.gz", hash = "sha256:abcc4e6bd0e11eace7257887958b6cc5332f8aad4efa6b94e930425016540789"}, + {file = "pyobjc_framework_HealthKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:093687705413b88efe47097f09c7be84b6ccbb7ec0f9b943b4ad19fe9fbdc01c"}, + {file = "pyobjc_framework_HealthKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1fb83b08ed28b9adc9a8a2379dbf5f7515e01009160a86847e1a5f71b491a49c"}, + {file = "pyobjc_framework_HealthKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b84d3857c54076a63feea7072ecf98d925f68f96413ca40164d04b2fd865a4dc"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-imagecapturecore" +version = "10.2" +description = "Wrappers for the framework ImageCaptureCore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ImageCaptureCore-10.2.tar.gz", hash = "sha256:68f1f96982282e786c9c387c177c3b14202d560d68000136562eba1ed3f45a6e"}, + {file = "pyobjc_framework_ImageCaptureCore-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:69c19e235de32bc707a622fd2865fa53f6e7692b52851d559ea0c23664ee7665"}, + {file = "pyobjc_framework_ImageCaptureCore-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3bdbae9adf6456b4b4e2847135e5da214516545638dd715f01573ec6b6324af6"}, + {file = "pyobjc_framework_ImageCaptureCore-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:46b90bc950646b69b416949bb50ee7d2189b42b7aa77692e01d7c1b4062ddc19"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-inputmethodkit" +version = "10.2" +description = "Wrappers for the framework InputMethodKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InputMethodKit-10.2.tar.gz", hash = "sha256:294cf2c50cdbb4cdc8f06946924a01faf45a7356ef86652d73c1f310fc1ce99f"}, + {file = "pyobjc_framework_InputMethodKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f8bcb156dcd1dc77826f720ff70f9a12c72ad45e97d4faa7ca88e85fc2d7843a"}, + {file = "pyobjc_framework_InputMethodKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d96a18dd92dc19f631ed50c524355ab29f79975e081f516ad3cea2d902a277e7"}, + {file = "pyobjc_framework_InputMethodKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:fdea1320a3cf6e409ab8f602b90b167110f7ca58f44f95a52f188c6f59f08753"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-installerplugins" +version = "10.2" +description = "Wrappers for the framework InstallerPlugins on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InstallerPlugins-10.2.tar.gz", hash = "sha256:001e9ec6489e49fc22bbec1ef050518213292e8d56239ed004f98ed038b164e2"}, + {file = "pyobjc_framework_InstallerPlugins-10.2-py2.py3-none-any.whl", hash = "sha256:754b8fdf462b6e568f30249255af50f9bd3ac90edacfe6e02d0fe77f276c049b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-instantmessage" +version = "10.2" +description = "Wrappers for the framework InstantMessage on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InstantMessage-10.2.tar.gz", hash = "sha256:4aa7627697fa57120594477f1f287bc41836ec7a4107215d3060c26416cf72c9"}, + {file = "pyobjc_framework_InstantMessage-10.2-py2.py3-none-any.whl", hash = "sha256:65db5cb1f163700a6cb915506f8f7ae2f28d8d3f6464f7b122b0535b1694859a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-intents" +version = "10.2" +description = "Wrappers for the framework Intents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Intents-10.2.tar.gz", hash = "sha256:ec27d5d19212fcec180ff04e2bc617fee0a018e2eaf29b2590c5512da167aa6a"}, + {file = "pyobjc_framework_Intents-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6a4e2ba2b5319c15ceeabdfd06f258789174e7e31011a24eab489d685066ed69"}, + {file = "pyobjc_framework_Intents-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9a3c08ec0dd199305989786e6e3c68d27f40b9eae3050bbf0207f053190f3513"}, + {file = "pyobjc_framework_Intents-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3dc9233522564ea8850a02961398a591446e0a0a0e63cd42cf7820daa0242f6a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-intentsui" +version = "10.2" +description = "Wrappers for the framework Intents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IntentsUI-10.2.tar.gz", hash = "sha256:4b9ca6f868b6cb7945ef4c285e73d220433efc35dfcad6b4a356bfce55e96c09"}, + {file = "pyobjc_framework_IntentsUI-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ec579d0f25cba0e1225f7690f52ed092bef5e01962fbe83ffbb70ec39861674"}, + {file = "pyobjc_framework_IntentsUI-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:91331fec42522596500bd0a580c633b7b84831c6316b2ec7458425d60b37da9e"}, + {file = "pyobjc_framework_IntentsUI-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:81f9d337473b3cb51f2aa4aa98156d6e294778d24fe011f41f0123b2676d824c"}, + {file = "pyobjc_framework_IntentsUI-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1c52fa06e8d65a003e384afcc1322051f2fbbfeac2c91ab852b407c552fd5652"}, + {file = "pyobjc_framework_IntentsUI-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c514ecef1277ff00c07f78f7890e3a6cbe3c8fe44184f2f6da1a7b4b32851605"}, + {file = "pyobjc_framework_IntentsUI-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:22c40c11d5de5a866a5db2b4ba57e9663e79180c323928709eced30c5c03ac81"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Intents = ">=10.2" + +[[package]] +name = "pyobjc-framework-iobluetooth" +version = "10.2" +description = "Wrappers for the framework IOBluetooth on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOBluetooth-10.2.tar.gz", hash = "sha256:8c4d6a82d0f550c84dce72188369adb9347ad6ee1c8adef996ee1a8c376c51ee"}, + {file = "pyobjc_framework_IOBluetooth-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:15e8a35431740d3e4ee484d4af01afef0b6b8aee2bdfe7b6dbe6cf7c7cc563fa"}, + {file = "pyobjc_framework_IOBluetooth-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:03ee5ecc3a2d2f6a0b4de9b36bc1c56f820624e8176abca0014c9ef3c86b0cd0"}, + {file = "pyobjc_framework_IOBluetooth-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b91c0b370047b386e9b333ba3c12ac121089fa94291c721e8b1ad6945b5763dd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-iobluetoothui" +version = "10.2" +description = "Wrappers for the framework IOBluetoothUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOBluetoothUI-10.2.tar.gz", hash = "sha256:ed9f4cb62eeda769b3f530ce396fd332f82441c5d22b9cf7b58058670c262d10"}, + {file = "pyobjc_framework_IOBluetoothUI-10.2-py2.py3-none-any.whl", hash = "sha256:f833efa3b1636f7a6cf8b5b2d25fc566757c2c7c06ee7945023aeb992493d96e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-IOBluetooth = ">=10.2" + +[[package]] +name = "pyobjc-framework-iosurface" +version = "10.2" +description = "Wrappers for the framework IOSurface on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOSurface-10.2.tar.gz", hash = "sha256:f1412c2f029aa1d60add57abefe63ea4116b990892ef7530ae27a974efafdb42"}, + {file = "pyobjc_framework_IOSurface-10.2-py2.py3-none-any.whl", hash = "sha256:b571335a2150e865828d3e52e2a742531499c88dd85215c14d07e68e9bed70a7"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-ituneslibrary" +version = "10.2" +description = "Wrappers for the framework iTunesLibrary on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-iTunesLibrary-10.2.tar.gz", hash = "sha256:c60d1dc9eabb28b036b766b89ea7d18198e21deb8925fc5a5753777c905ecddf"}, + {file = "pyobjc_framework_iTunesLibrary-10.2-py2.py3-none-any.whl", hash = "sha256:4e6cf6073a902f77e0b0c33d2d52e3ab3f0c869cb339b7685b5e7f079df8ef4e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-kernelmanagement" +version = "10.2" +description = "Wrappers for the framework KernelManagement on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-KernelManagement-10.2.tar.gz", hash = "sha256:effd1d3230c8a3b8628e7fd315f0aac10fbf1ea99f2ed923999cb1ab787c317a"}, + {file = "pyobjc_framework_KernelManagement-10.2-py2.py3-none-any.whl", hash = "sha256:d8dca9dc1f756bfa894a32f56857ecefb4d188aec590433ee302529261dffb68"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-latentsemanticmapping" +version = "10.2" +description = "Wrappers for the framework LatentSemanticMapping on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LatentSemanticMapping-10.2.tar.gz", hash = "sha256:eb3ddd5e04c39b0151a64bd356f7de3c66062257e3802e8abea7a882e972ff21"}, + {file = "pyobjc_framework_LatentSemanticMapping-10.2-py2.py3-none-any.whl", hash = "sha256:dadd4352b9af681dd85d04712a6cf1d2c574acbf0b8178c35f42231ec8c5a6d1"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-launchservices" +version = "10.2" +description = "Wrappers for the framework LaunchServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LaunchServices-10.2.tar.gz", hash = "sha256:d9f78d702dea13a363de8a7c1c382e1ca872993980c164781cb2758ee49353d2"}, + {file = "pyobjc_framework_LaunchServices-10.2-py2.py3-none-any.whl", hash = "sha256:15b7c96e3059550c218ed5cb5de11dddc7aae21c67c0808b130a5d49b8f4cc0f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-CoreServices = ">=10.2" + +[[package]] +name = "pyobjc-framework-libdispatch" +version = "10.2" +description = "Wrappers for libdispatch on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-libdispatch-10.2.tar.gz", hash = "sha256:ae17602efbe628fa0432bcf436ee8137d2239a70669faefad420cd527e3ad567"}, + {file = "pyobjc_framework_libdispatch-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:955d3e3e5ee74f6707ab06cc76ad3fae27e78c180dea13f1b85e2659f9135889"}, + {file = "pyobjc_framework_libdispatch-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:011736d708067d9b21a4722bae0ed776cbf84c8625fc81648de26228ca093f6b"}, + {file = "pyobjc_framework_libdispatch-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:28c2a2ab2b4d2930f7c7865ad96c1157ad50ac93c58ffff64d889f769917a280"}, + {file = "pyobjc_framework_libdispatch-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6cb0879e1f6773ad0bbeb82d495ad0d76d8c24b196a314ac9a6eab8eed1736e0"}, + {file = "pyobjc_framework_libdispatch-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:aa921cd469a1c2e20d8ba9118989fe4e827cbb98e947fd11ae0392f36db3afcc"}, + {file = "pyobjc_framework_libdispatch-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6f3d57d24f81878d1b5dcb00a13f85465ede5b91589394f4f1b9dcf312f3bd99"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-libxpc" +version = "10.2" +description = "Wrappers for xpc on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-libxpc-10.2.tar.gz", hash = "sha256:04deac1f9dbd1c19c10d175846017f8e8e51d2b52a2674482638d6b289e883a6"}, + {file = "pyobjc_framework_libxpc-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b57089f792d51ad687c9933dd2d3669cd5e6f84d1f9213738ecc5833dba9aa8c"}, + {file = "pyobjc_framework_libxpc-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e31cb4f7fdb76defc53fe0b56c3f1db953c1dcf3519093835527f270c37315c3"}, + {file = "pyobjc_framework_libxpc-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:978cc2a9cc668e0c4aef13af81cec6129e7b98877b44c952232c0083a8fd352e"}, + {file = "pyobjc_framework_libxpc-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dd057a556398b48982fdae84f8e08ee9b69b6e5918b6782bd842ef9ad97820d"}, + {file = "pyobjc_framework_libxpc-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:df394dc08eab33430f565a2252906f27cd4f7c41fd431f75b4ae35d3a76f4eab"}, + {file = "pyobjc_framework_libxpc-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fd3608a32ebe65253c24b7590ad96977135aa847dd188e4c2168f0da9e74e47"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-linkpresentation" +version = "10.2" +description = "Wrappers for the framework LinkPresentation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LinkPresentation-10.2.tar.gz", hash = "sha256:4ccae5f593b58dfe9cb422645e0ccf5adab906ec008d3e20eb710cd62bbb4717"}, + {file = "pyobjc_framework_LinkPresentation-10.2-py2.py3-none-any.whl", hash = "sha256:1cada96d3eb03e51e1bbb7e7c10b9c08c80fd098132541b4e992234fe43cfa37"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-localauthentication" +version = "10.2" +description = "Wrappers for the framework LocalAuthentication on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LocalAuthentication-10.2.tar.gz", hash = "sha256:26e899e8b4a90632958eb323abbc06d7b55c64d894d4530a9cc92d49dc115a7e"}, + {file = "pyobjc_framework_LocalAuthentication-10.2-py2.py3-none-any.whl", hash = "sha256:442f6cae70300f29c9133ed7f2e01c294976b9aae55fe180c64983d5dee62254"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Security = ">=10.2" + +[[package]] +name = "pyobjc-framework-localauthenticationembeddedui" +version = "10.2" +description = "Wrappers for the framework LocalAuthenticationEmbeddedUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LocalAuthenticationEmbeddedUI-10.2.tar.gz", hash = "sha256:52acdef34ea38d1381a95de15b19c9543a607aeff11db603371d0224917a8830"}, + {file = "pyobjc_framework_LocalAuthenticationEmbeddedUI-10.2-py2.py3-none-any.whl", hash = "sha256:eafbbc321082ff012cdb14e38abae7ced94c6d962cb64af43d6d515da976e175"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-LocalAuthentication = ">=10.2" + +[[package]] +name = "pyobjc-framework-mailkit" +version = "10.2" +description = "Wrappers for the framework MailKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MailKit-10.2.tar.gz", hash = "sha256:8d8fceff5498df0cfa630b7088814f8daa8a25794a36d4b57cfde8c2c14cdc70"}, + {file = "pyobjc_framework_MailKit-10.2-py2.py3-none-any.whl", hash = "sha256:d8bc9e6649e7e500d2d4d4ab288304846d9bfa06952ebeee621fe095dc2f51eb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-mapkit" +version = "10.2" +description = "Wrappers for the framework MapKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MapKit-10.2.tar.gz", hash = "sha256:35dfe7aa5ec9e51abc47d6ceb0f83d3c2b5876258591a568e85e2db8218427c4"}, + {file = "pyobjc_framework_MapKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ce322299b04eef212706185764771041a1220f7a611606e33f95ac355d913238"}, + {file = "pyobjc_framework_MapKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:339a8c8181047fc9eb612eb47c51f017423a6b074e2a4838cd6b06e36af6c160"}, + {file = "pyobjc_framework_MapKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:85a110693198798234d3edbd3b606d9d9c9b4817e4ed70d2b2e18357422783c6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreLocation = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-mediaaccessibility" +version = "10.2" +description = "Wrappers for the framework MediaAccessibility on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaAccessibility-10.2.tar.gz", hash = "sha256:acce0baf11270c9276a219f5a0dfb6d8241e01ac775144bfe3a83e088dcd1308"}, + {file = "pyobjc_framework_MediaAccessibility-10.2-py2.py3-none-any.whl", hash = "sha256:55dbf7519028fadf3ac6cb1ef185156f6df649655075a015cf87cee370255e82"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-medialibrary" +version = "10.2" +description = "Wrappers for the framework MediaLibrary on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaLibrary-10.2.tar.gz", hash = "sha256:b3e1bd3e70f0013bbaccd0b43727a0f16ecf23f7d708ca81b8474faaa1f8e8fc"}, + {file = "pyobjc_framework_MediaLibrary-10.2-py2.py3-none-any.whl", hash = "sha256:98b9687f1399365889529c337d99d7f19edf3a94beb05884cf15a29f4fc178af"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-mediaplayer" +version = "10.2" +description = "Wrappers for the framework MediaPlayer on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaPlayer-10.2.tar.gz", hash = "sha256:4b6d296b084e01fb6e5c782b7b6308077db09f4051f50b0a6c3298ffbd1f1d70"}, + {file = "pyobjc_framework_MediaPlayer-10.2-py2.py3-none-any.whl", hash = "sha256:c501ea19380bfbf6b04fbe909fcfe9a78c5ff2a9b58dae87be259066b1ae3521"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-AVFoundation = ">=10.2" + +[[package]] +name = "pyobjc-framework-mediatoolbox" +version = "10.2" +description = "Wrappers for the framework MediaToolbox on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaToolbox-10.2.tar.gz", hash = "sha256:614ec0a28c810395274aa1d5348a447f67bae4629a3a8372d14162f38e2fc597"}, + {file = "pyobjc_framework_MediaToolbox-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ca7443bca94dfd9863d5290d2680247b7d577cf031dcfc854c414e5fdd9cdb03"}, + {file = "pyobjc_framework_MediaToolbox-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c34dca15560507286eb9ef045d6234ac1db1e50f22c63397662155a7f01ea9ac"}, + {file = "pyobjc_framework_MediaToolbox-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:696d6cadbb643f98750f5a791663ca264f0a0f4db2aeec7c8cf59c02face1683"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-metal" +version = "10.2" +description = "Wrappers for the framework Metal on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Metal-10.2.tar.gz", hash = "sha256:652050cf9f5627dba36b31ad134e56c49040d0dcfaf93a7026018ef17330a01e"}, + {file = "pyobjc_framework_Metal-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c68e4025c52e8c8b0fa584abeb058debe49ac3174e8c421408bf873e5951fd02"}, + {file = "pyobjc_framework_Metal-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:49f333f41556f08e28750bb4e09a7053ac55434f4a29a3e228ed4fd9bae8f57d"}, + {file = "pyobjc_framework_Metal-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0cb39a4f4a70f45f88f79c3641b00b6db0c9b9ed90bee21840a725a8d7c7eaca"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-metalfx" +version = "10.2" +description = "Wrappers for the framework MetalFX on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalFX-10.2.tar.gz", hash = "sha256:d98a0fd1f0d2d3ea54efa768e6817a8773566c820ae7a3a23497e1c492e11da7"}, + {file = "pyobjc_framework_MetalFX-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e7f1b50316db47ffb1e9505726dfe5bf552f32188d21b6ef979078fec9f58077"}, + {file = "pyobjc_framework_MetalFX-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:526687ac36b71b9822613bf552bff99930ee2620414b0b932f5e0d327d62809e"}, + {file = "pyobjc_framework_MetalFX-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a8c78a8f9c3ee59cb5ba96e4db56c3ab8cc78860f9d42ca5732168d8691cb17b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Metal = ">=10.2" + +[[package]] +name = "pyobjc-framework-metalkit" +version = "10.2" +description = "Wrappers for the framework MetalKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalKit-10.2.tar.gz", hash = "sha256:42fc61371d49c2b86828d2a668b7badb2418c0ecce7595fce790830607bd8040"}, + {file = "pyobjc_framework_MetalKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:abcdabdad3d9810730c67f493b70139254f7438ebba0149b5dcd848384a08a85"}, + {file = "pyobjc_framework_MetalKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7990b05194919d187a6af8be7fe51007ab666cfdb3512b6fb022da9049d9957d"}, + {file = "pyobjc_framework_MetalKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c26c2e2965ae6547edecbc8e250117401c26f62f9a55e351eca42f2e557721e7"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Metal = ">=10.2" + +[[package]] +name = "pyobjc-framework-metalperformanceshaders" +version = "10.2" +description = "Wrappers for the framework MetalPerformanceShaders on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalPerformanceShaders-10.2.tar.gz", hash = "sha256:66e6f671279b1f7edbaed1bea8ab1eb57f617e000c1e871c190b60ad60c1d727"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a65c201921fffb992955aa143ffcb36be3e7c5aee86334941d3214428f0c7ad8"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9fd437d0b1a83a3bdc866727ba17a00b49ee239205b2d14b617f5ca4f566c4f7"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0f862a65ffc0159e6b9ad46115b8d7ecbce5f56fe920c709b943982d4a70d63c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Metal = ">=10.2" + +[[package]] +name = "pyobjc-framework-metalperformanceshadersgraph" +version = "10.2" +description = "Wrappers for the framework MetalPerformanceShadersGraph on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalPerformanceShadersGraph-10.2.tar.gz", hash = "sha256:4fffad1c37e700fc38b2ca8eb006d7532b3b5cb700580ce7dfd31af35e0fb6e8"}, + {file = "pyobjc_framework_MetalPerformanceShadersGraph-10.2-py2.py3-none-any.whl", hash = "sha256:7fedd831f9fc58708f6b01888abd42a2f08151c86db47280fe47be0f709811bf"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-MetalPerformanceShaders = ">=10.2" + +[[package]] +name = "pyobjc-framework-metrickit" +version = "10.2" +description = "Wrappers for the framework MetricKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetricKit-10.2.tar.gz", hash = "sha256:14cb02fd8fc338f6f15df5fd14c95419871b768cc8f5f71b1e0e99fde46b4712"}, + {file = "pyobjc_framework_MetricKit-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:585a494a5126c5481afc34ac5bfdc28a1a2b7044d8b0e3427fbd5313e72c59fb"}, + {file = "pyobjc_framework_MetricKit-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b330ccffa45f4ccf2fc23c73112bf3b652515eb025fddeb3e2c81ca25f1a168"}, + {file = "pyobjc_framework_MetricKit-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fc336ff6db376bff4cab0bd7db962aae1ff11f4584026cd5c4d3f66283018ce7"}, + {file = "pyobjc_framework_MetricKit-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1403302d753686b49aa0d6fc0a4c05e6ead18aa1b9de9668322fd0e81c51f"}, + {file = "pyobjc_framework_MetricKit-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:82b01a838203000c262f9f52420b1387850505f0a7b742b29a73cc8c6a9e0c25"}, + {file = "pyobjc_framework_MetricKit-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:79971789bff04540200bd443ec3c6ae13f83eea827d2dab0f33bc9c6e6af9ab0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-mlcompute" +version = "10.2" +description = "Wrappers for the framework MLCompute on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MLCompute-10.2.tar.gz", hash = "sha256:6f5bff2317b2ae45c092a94a05e7831d0dc7a002fc68b03648abbac5a2ce33a3"}, + {file = "pyobjc_framework_MLCompute-10.2-py2.py3-none-any.whl", hash = "sha256:a191abf1c6aef061b4eab1aa8d4cf886fd6c98e53f6fedcd738ddd904571b933"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-modelio" +version = "10.2" +description = "Wrappers for the framework ModelIO on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ModelIO-10.2.tar.gz", hash = "sha256:8ae1444375260a346d1c77838f84e2c04dfabaf2769b2970a3588becb670431e"}, + {file = "pyobjc_framework_ModelIO-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6d226b059a4c99669ec3dc03c1dde9b0daeba392a198cdb36398394396512a26"}, + {file = "pyobjc_framework_ModelIO-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c2fff57596d54b95507a1c180a6df877e28e561e5e71941619d70ac67d5bec4d"}, + {file = "pyobjc_framework_ModelIO-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e6b119d66cefde55ce63e406c4fd12d626fb017ee88d9e01fdd25434f6ddc831"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-multipeerconnectivity" +version = "10.2" +description = "Wrappers for the framework MultipeerConnectivity on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MultipeerConnectivity-10.2.tar.gz", hash = "sha256:e3c1e5f39715621786f4ad5ecffa2cc9445a218e5ab3e94295c16fbcb754ee5a"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ce68b7b5030e95e78bc94e898adb09f1e3f30c738e7140101146c52c64ff5493"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f158aaaabcfd0d1e6d77585ec24797dbedf6bde640675b26dcfb4e2093d3a0ce"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0e02f9ecdbf2c4aacd5ab8cd019415584bed7fa1656d525c8f841466d6e58993"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-naturallanguage" +version = "10.2" +description = "Wrappers for the framework NaturalLanguage on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NaturalLanguage-10.2.tar.gz", hash = "sha256:eba7de67bea4a6a071e04e79c8a4de0547c25a09635fe3d4ee6cd58fb6aeaf65"}, + {file = "pyobjc_framework_NaturalLanguage-10.2-py2.py3-none-any.whl", hash = "sha256:0165735973a720f09bd5a2333f32e16aac52332fb595425480d7a2215472d4fb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-netfs" +version = "10.2" +description = "Wrappers for the framework NetFS on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NetFS-10.2.tar.gz", hash = "sha256:05de46b15d19abecbb9e7d04745ca27dba9ec121f16ea7bafc9dc87a12c0e828"}, + {file = "pyobjc_framework_NetFS-10.2-py2.py3-none-any.whl", hash = "sha256:e7a84497be6114ea2e47776efda640d9d8becaaa07214d712a204b5d446e3d95"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-network" +version = "10.2" +description = "Wrappers for the framework Network on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Network-10.2.tar.gz", hash = "sha256:b39bc26f89cf9fc56cc9c4a99099aef68c388d45b62dc1ec16772ee290b225d4"}, + {file = "pyobjc_framework_Network-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4f465400cd4402b7495a27de4c9099bcc127afa4d1cb587f75b987750c0ea032"}, + {file = "pyobjc_framework_Network-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:39966aa35d17b00973fa85e334b6360311cfd1a097d26d79b5957bc7cd7fad4a"}, + {file = "pyobjc_framework_Network-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5542660d0c7183dc4599bd20763ed3b59772cf17211ca3720a4175f886a8eada"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-networkextension" +version = "10.2" +description = "Wrappers for the framework NetworkExtension on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NetworkExtension-10.2.tar.gz", hash = "sha256:14f237bd96a822c55374584e99f2d79581b2d60570f34e4863800f934a44b82d"}, + {file = "pyobjc_framework_NetworkExtension-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:280dc76901628b2c9750766bb2424a29de3f1f49b41e5f29634701cfe0ab0524"}, + {file = "pyobjc_framework_NetworkExtension-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ce6cfdff6f65f512137ee382ba04ee2b52e0fb51deacb651e385daf5349d28b7"}, + {file = "pyobjc_framework_NetworkExtension-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ed2cf32a802ec872466c743013ce9ef17757e89e21a49cbeeeffddfaefb89fc4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-notificationcenter" +version = "10.2" +description = "Wrappers for the framework NotificationCenter on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NotificationCenter-10.2.tar.gz", hash = "sha256:3771c7a8b8e839d07c7cb51eef2e83666254bdd88bd873b0ba7e385245cda684"}, + {file = "pyobjc_framework_NotificationCenter-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f982ce1d0916f9ba3322ebbffd9b936b5b9aeb6d8ec21bd2c3c5245c467c1a12"}, + {file = "pyobjc_framework_NotificationCenter-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:dd1d8364d2212a671b2224ab6bf7785ba5b2aae46610ec46ae35d27c4d55cb15"}, + {file = "pyobjc_framework_NotificationCenter-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1e8aaaef40b6c0deaffd979b3741d1f9de7d804995b7b92fa88ba7839615230e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-opendirectory" +version = "10.2" +description = "Wrappers for the framework OpenDirectory on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OpenDirectory-10.2.tar.gz", hash = "sha256:ecca3346275e1ee7be812e428da7f243e37258d8152708a2baa246001b7f5996"}, + {file = "pyobjc_framework_OpenDirectory-10.2-py2.py3-none-any.whl", hash = "sha256:7996985a746f4cceee72233eb5671983e9ee9c9bce3fa9c2fd03d65e766a4efd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-osakit" +version = "10.2" +description = "Wrappers for the framework OSAKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OSAKit-10.2.tar.gz", hash = "sha256:6efba4a1733e9ab0bf0e7b4f2eb3e0c84b2a4af1b0b4bbc3a310ae041ccaf92d"}, + {file = "pyobjc_framework_OSAKit-10.2-py2.py3-none-any.whl", hash = "sha256:fbad23e47e31d795a005c18a20d84bff68d90d6dd0f87b6a343e46f87c00034a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-oslog" +version = "10.2" +description = "Wrappers for the framework OSLog on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OSLog-10.2.tar.gz", hash = "sha256:2377637a0de7dd60f610caab4bcd7efa165d23dba4ac896fd542f1fab2fc588a"}, + {file = "pyobjc_framework_OSLog-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ef1ddc15f98243be9b03f4f4bcb839318333fb135842085ba40499a58c8bd342"}, + {file = "pyobjc_framework_OSLog-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7958957503310ec8df90a0a036ae8a075b90610c0b797769ad117bf635b0caa6"}, + {file = "pyobjc_framework_OSLog-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:67796f02b77c1cc893b3f112f88c58714b1e16a38b59bc52748c25798db71c29"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreMedia = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-passkit" +version = "10.2" +description = "Wrappers for the framework PassKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PassKit-10.2.tar.gz", hash = "sha256:0c879d632f0f0bf586161a7abbbba3dad9ba9894a3edbce06f4160491c2c134c"}, + {file = "pyobjc_framework_PassKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:15891c8c1e23081961d652946d4750fd3cd1308efc953a1c77713394726798a6"}, + {file = "pyobjc_framework_PassKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d68061729743be30c66f7eb3cb649850ef12a24b1d1896233036a390e7d69aa7"}, + {file = "pyobjc_framework_PassKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7290369b34be3317463a32c9e78a0ed734db4793414851a9e73295413cf17317"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-pencilkit" +version = "10.2" +description = "Wrappers for the framework PencilKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PencilKit-10.2.tar.gz", hash = "sha256:2338ea384b9a9e67a7f34c300a898ccb997bcff9a2a27e5f9bf7642760c016a0"}, + {file = "pyobjc_framework_PencilKit-10.2-py2.py3-none-any.whl", hash = "sha256:d3e605f104548f26c708957ab7939a64147c422c35d45c4ff4c8d01b5c248c4d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-phase" +version = "10.2" +description = "Wrappers for the framework PHASE on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PHASE-10.2.tar.gz", hash = "sha256:047ba5b7a869ed93c3c7af2cf7e3ffc83299038275d47c8229e7c09006785402"}, + {file = "pyobjc_framework_PHASE-10.2-py2.py3-none-any.whl", hash = "sha256:f29cd40e5be860758d8444e761d43f313915e2750b8b03b8a080dd86260f6f91"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-AVFoundation = ">=10.2" + +[[package]] +name = "pyobjc-framework-photos" +version = "10.2" +description = "Wrappers for the framework Photos on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Photos-10.2.tar.gz", hash = "sha256:ba05d1208158e6de6d14782c182991c0d157254be7254b8d3bb0a9a53bf113fb"}, + {file = "pyobjc_framework_Photos-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4f2c2aa73f3ac331a84ee1f7b5e0edc26471776b2de2190640f041e3c1cc8ef3"}, + {file = "pyobjc_framework_Photos-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:17d69ce116a7f7db1d78ed12a8a81bec1b580735ad40611c0037d8c2977b2eb8"}, + {file = "pyobjc_framework_Photos-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e53d0759c26c7eac4ebfc7bd0018dfd7e3be8ab88a042684ee45e9184e0ac90e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-photosui" +version = "10.2" +description = "Wrappers for the framework PhotosUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PhotosUI-10.2.tar.gz", hash = "sha256:d0bbcae82b4cc652bb60d3221c557cc19be62ff430575ec8e6d233beb936f73b"}, + {file = "pyobjc_framework_PhotosUI-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a27607419652b45053e0be5ede2780b48e6a8dded2b365ded1732e80dafacea0"}, + {file = "pyobjc_framework_PhotosUI-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7eddf0343fae6c327a3dc941d0d7b216f5d186edb2e511d7c54668f6ff2be701"}, + {file = "pyobjc_framework_PhotosUI-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d6715ac72c7967761c33502f6cd552534ec0f727f009f22a2c273dc12076d52d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-preferencepanes" +version = "10.2" +description = "Wrappers for the framework PreferencePanes on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PreferencePanes-10.2.tar.gz", hash = "sha256:f1fba8727d172a3e9b58d764695702f7752dfb585d0378e588915f3d8363728c"}, + {file = "pyobjc_framework_PreferencePanes-10.2-py2.py3-none-any.whl", hash = "sha256:4da63d42bc2f2de547b6c817236e902ad6155efa05e5305daa38be830b70a19d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-pubsub" +version = "10.2" +description = "Wrappers for the framework PubSub on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PubSub-10.2.tar.gz", hash = "sha256:68ca9701b29c5e87f7837490cad3dab0b6cd539dfaff3ffe84b1f3f1bf4dc764"}, + {file = "pyobjc_framework_PubSub-10.2-py2.py3-none-any.whl", hash = "sha256:b44f7f87de3f92ce9655344c476672f8f7a912f86ab7a615fec30cebbe7a8827"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-pushkit" +version = "10.2" +description = "Wrappers for the framework PushKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PushKit-10.2.tar.gz", hash = "sha256:e30fc4926a9fcd3427701e48a8908f72e546720e52b1e0f457ba2fa017974917"}, + {file = "pyobjc_framework_PushKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1015e4473a8eac7eba09902807b8d8edd47c536e3a50a0b3fe7ab7211e454ad8"}, + {file = "pyobjc_framework_PushKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6f68b2630f84dc6d94046f7676d415e5342b2bb3f0368f3b9e81d0c5744c219b"}, + {file = "pyobjc_framework_PushKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5fe75738ea08c05e42460a58acbf0a8af67a3df26ca2a7bddd48d801b00772ed"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-quartz" +version = "10.2" +description = "Wrappers for the Quartz frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Quartz-10.2.tar.gz", hash = "sha256:9b947e081f5bd6cd01c99ab5d62c36500d2d6e8d3b87421c1cbb7f9c885555eb"}, + {file = "pyobjc_framework_Quartz-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc0ab739259a717d9d13a739434991b54eb8963ad7c27f9f6d04d68531fb479b"}, + {file = "pyobjc_framework_Quartz-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a74d00e933c1e1a1820839323dc5cf252bee8bb98e2a298d961f7ae7905ce71"}, + {file = "pyobjc_framework_Quartz-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3e8e33246d966c2bd7f5ee2cf3b431582fa434a6ec2b6dbe580045ebf1f55be5"}, + {file = "pyobjc_framework_Quartz-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6ca490eff1be0dd8dc7726edde79c97e21ec1afcf55f75962a79e27b4eb2961"}, + {file = "pyobjc_framework_Quartz-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d3d54d9fa50de09ee8994248151def58f30b4738eb20755b0bdd5ee1e1f5883d"}, + {file = "pyobjc_framework_Quartz-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:520c8031b2389110f80070b078dde1968caaecb10921f8070046c26132ac9286"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-quicklookthumbnailing" +version = "10.2" +description = "Wrappers for the framework QuickLookThumbnailing on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-QuickLookThumbnailing-10.2.tar.gz", hash = "sha256:91497a4dc601c99ccc11ad7976ff729b57f724d9eff071bc24c519940d129dca"}, + {file = "pyobjc_framework_QuickLookThumbnailing-10.2-py2.py3-none-any.whl", hash = "sha256:34349ff0b07b39ecfe5757eb80341a45f9d4426558b93946225f8b4fa2781c4c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-replaykit" +version = "10.2" +description = "Wrappers for the framework ReplayKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ReplayKit-10.2.tar.gz", hash = "sha256:12544028e59ef25ea5c96ebd451cee70d1833d6b5991d3a1b324c6d81ecfb49e"}, + {file = "pyobjc_framework_ReplayKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4e34b006879ed2e86df044e3dd36482d78e6297c954aeda29f60f4b9006c8114"}, + {file = "pyobjc_framework_ReplayKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a6e7ae17d41a381d379d10bd240e1681fc83664b89495999a4dd8d0f42d4b542"}, + {file = "pyobjc_framework_ReplayKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3c57b4019563aaae3c37a250d6c064cbcb5c0d3b227b5b4f1e18bf4a1effcf0e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-safariservices" +version = "10.2" +description = "Wrappers for the framework SafariServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SafariServices-10.2.tar.gz", hash = "sha256:723de09afb718b05d03cbbed42f90d36356294b038ca6422c88d50240047b067"}, + {file = "pyobjc_framework_SafariServices-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2cd4b4210bd3c05d74d41e5bf2760e841289927601184f0e6ca3ef85019aa5dd"}, + {file = "pyobjc_framework_SafariServices-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6c8aa0becaa7d4ce0d0d1ada4e14e1eae2bf8e5be7ef49cc1861a41d3a4eeade"}, + {file = "pyobjc_framework_SafariServices-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aecc109b096b3e995b896bfb97c09ef156600788e2a46c498bb4e2e355faa2bc"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-safetykit" +version = "10.2" +description = "Wrappers for the framework SafetyKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SafetyKit-10.2.tar.gz", hash = "sha256:b5822cda3b1dc0209fa58027551fa17457763275902c7d42fc23d5b13de9ee67"}, + {file = "pyobjc_framework_SafetyKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:947d42faf40b4ddd71bce75b8b913b7b67e0640fffa508562f4e502ca99426d4"}, + {file = "pyobjc_framework_SafetyKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65feaff614eeacceb8c405030ddd79f8eda2366d2a73f44ea595f48f7969bcf0"}, + {file = "pyobjc_framework_SafetyKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a6c3201dfb649523fa2f7569ca1274d1322527e210ee19d7c2395d0e3d18e0a2"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-scenekit" +version = "10.2" +description = "Wrappers for the framework SceneKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SceneKit-10.2.tar.gz", hash = "sha256:53d2ffac43684bb7834ae570d3537bd15f2a7711b77cc9e8b7b81f63a697ba03"}, + {file = "pyobjc_framework_SceneKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac80bf8c4cf957add63a0bd2f1811097fb62eafb4fc26192f4087cd7853e85fd"}, + {file = "pyobjc_framework_SceneKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:86c3d23b63b0bb4d8fea370cb08aac778bc3fdb64b639b8b9ea87dacc54fd1cf"}, + {file = "pyobjc_framework_SceneKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:00676f4e11f3069545b07357e51781054ecf4785ed24ea8747515e018db1618c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-screencapturekit" +version = "10.2" +description = "Wrappers for the framework ScreenCaptureKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenCaptureKit-10.2.tar.gz", hash = "sha256:86f64377be94db1b95e77ca53301ed94c0a7a82c0251c9e960bcae24b6a5841b"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e15b0af8a1155b7bc975ccd54192c5feae2706a8e17da6effa4273a3302d4dce"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bdcc687d022b8b6264dca74c1f72897c91528b0c701d76f1466faeead8030a11"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f53caee8edca7868449f2cce60574cedea4299c324fa692c944824a627b7b8a4"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4cdd6add328e2318550df325210c83d1de68774a634d3914da2bfbd1cb7d929f"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e51c6c632b1c6ff773cfcf7d3e2b349693e06d52259b8c8485cfaa6c6cd602b3"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b63d9dc8635e7a3e59163a4abc13a9014de702729a55d290a22518702f4679fc"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreMedia = ">=10.2" + +[[package]] +name = "pyobjc-framework-screensaver" +version = "10.2" +description = "Wrappers for the framework ScreenSaver on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenSaver-10.2.tar.gz", hash = "sha256:00c6a312467abb516fd2f19e3166c4609eed939edc0f2c888ccd8c9f0fdd30f1"}, + {file = "pyobjc_framework_ScreenSaver-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:830b2fc85ff7d48824eb6f12100915c2aa480a1a408b53c30f6b81906dc8b1ea"}, + {file = "pyobjc_framework_ScreenSaver-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1cb8a31bd2a0597727553d0459c91803bf02c52ffb5ac94aa5ad484ddc46d88d"}, + {file = "pyobjc_framework_ScreenSaver-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ca00a5c4cd89450e629962bfafe6a4a25b7bae93eb3fdd3ecb314c6c5755cbcf"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-screentime" +version = "10.2" +description = "Wrappers for the framework ScreenTime on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenTime-10.2.tar.gz", hash = "sha256:fd516f0dd7c16f15ab6ed3eeb8180460136f72b7eaa3d6e849d4a462438bfdf2"}, + {file = "pyobjc_framework_ScreenTime-10.2-py2.py3-none-any.whl", hash = "sha256:43afabfd0fd61eed91f11aba3de95091a4f05d7c7e63341f493026e5ff7b90e4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-scriptingbridge" +version = "10.2" +description = "Wrappers for the framework ScriptingBridge on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScriptingBridge-10.2.tar.gz", hash = "sha256:c02d88b4a4d48d54ce2260f5c7e1757e74cd91281352cdd32492a4c7ee4b0e7c"}, + {file = "pyobjc_framework_ScriptingBridge-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:466ad2d483edadf97dc38629c393902a790141547e145e83f6bd34351d10f4c9"}, + {file = "pyobjc_framework_ScriptingBridge-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1e3e0c19afd0f8189ebee5c57ab2b0c177dddccc9b56811c665ec6848007ac6a"}, + {file = "pyobjc_framework_ScriptingBridge-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:560dff883edd251f1e0bf86dde681c1e19845399720fd2434734c91120eafdd0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-searchkit" +version = "10.2" +description = "Wrappers for the framework SearchKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SearchKit-10.2.tar.gz", hash = "sha256:c1e16457e727c5282b620d20b2d764352947cd4509966475a874f2750a9c5d11"}, + {file = "pyobjc_framework_SearchKit-10.2-py2.py3-none-any.whl", hash = "sha256:ddd9e2f207ae578f04ec2358fdf485f26978d6de4909640b58486a8a9e4e639c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-CoreServices = ">=10.2" + +[[package]] +name = "pyobjc-framework-security" +version = "10.2" +description = "Wrappers for the framework Security on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Security-10.2.tar.gz", hash = "sha256:20ec8ebb41506037d54b40606590b90f66a89adceeddd9a40674b0c7ea7c8c82"}, + {file = "pyobjc_framework_Security-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5a9c1bf88db62ebe1186dbecb40c6fdf8dab2d614012b5da8e9b90ee3bd8575e"}, + {file = "pyobjc_framework_Security-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9119f8bad7bead85e5b57c8538d319ef19eb5159500a0e3677c11ddbb774a17a"}, + {file = "pyobjc_framework_Security-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:317add1dcbc6866ce2e9389ef5a2a46db82e042aca6e5fad9aa5ce17782493fe"}, + {file = "pyobjc_framework_Security-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:75f061f0d03c3099d01b7818409eb602b882f6a31b4381bbf289f10ce1cf7753"}, + {file = "pyobjc_framework_Security-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d99aeba0e3a7ee95bf5582b06885a5d6f8115ff3a2e47506562514117022f170"}, + {file = "pyobjc_framework_Security-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:186a97497209acdb8d56aa7bbd56ab8021663fff2fb83f0d0e1b4e1f57ac5bbb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-securityfoundation" +version = "10.2" +description = "Wrappers for the framework SecurityFoundation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SecurityFoundation-10.2.tar.gz", hash = "sha256:ed612afab0f70e24b29f2e2b3a31cfefb1ad17244b5c147e7bcad8dfc7e60bd1"}, + {file = "pyobjc_framework_SecurityFoundation-10.2-py2.py3-none-any.whl", hash = "sha256:296f7f9ff96a35c19e4aef7621a567c0efe584aafd20ac25a2839dd96bf46a04"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Security = ">=10.2" + +[[package]] +name = "pyobjc-framework-securityinterface" +version = "10.2" +description = "Wrappers for the framework SecurityInterface on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SecurityInterface-10.2.tar.gz", hash = "sha256:43930539fed05e74f3c692f5ee7848681e7e65c44387af300447514fe8e23ab6"}, + {file = "pyobjc_framework_SecurityInterface-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:70f2cb61261e84fb366f43a9a44fb19a19188cf650d3cf3f3e6ee3a16a73e62d"}, + {file = "pyobjc_framework_SecurityInterface-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:52a18a18af6d47f7fbdfeef898a038ff3ab7537a694c591ddcf8f895b9e55cce"}, + {file = "pyobjc_framework_SecurityInterface-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b2472e3714cc17b22e5bb0173887aac77c80ccc2188ec2c40d2b906bd2490f6b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Security = ">=10.2" + +[[package]] +name = "pyobjc-framework-sensitivecontentanalysis" +version = "10.2" +description = "Wrappers for the framework SensitiveContentAnalysis on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SensitiveContentAnalysis-10.2.tar.gz", hash = "sha256:ef111cb8a85bc86e47954cdb01e3ccb654aba64a3d855f17a0c786361859aef8"}, + {file = "pyobjc_framework_SensitiveContentAnalysis-10.2-py2.py3-none-any.whl", hash = "sha256:3c875856837e217c9eba68e5c2b4f5b862dee1bb64513b463a7af8c3e67e5a50"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-servicemanagement" +version = "10.2" +description = "Wrappers for the framework ServiceManagement on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ServiceManagement-10.2.tar.gz", hash = "sha256:62413cd911932cc16262710a3853061fdae341ed95e1aa0426b4ff0011d18c0c"}, + {file = "pyobjc_framework_ServiceManagement-10.2-py2.py3-none-any.whl", hash = "sha256:e5a1c1746788d0e125cc87cbe0749b2b824fb7a08bc4344c06c9ac6007859187"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-sharedwithyou" +version = "10.2" +description = "Wrappers for the framework SharedWithYou on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SharedWithYou-10.2.tar.gz", hash = "sha256:bc13756ef20af488cd3022c036a11a0f7572e1b286e9eb7d31c61a8cb7655c70"}, + {file = "pyobjc_framework_SharedWithYou-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b69169db01c78bef3178b8795fb5e2a9eccfa4c26b7de008e23a5aa6f0c709f0"}, + {file = "pyobjc_framework_SharedWithYou-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e4ec724f103b0904893212d473c68c462f8fbe46a470b0c9f88cb8330969a94e"}, + {file = "pyobjc_framework_SharedWithYou-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:04b477d42a6edd25c187fc61422ce62156fd5d8670b7007ff3f1a10723b1b4b8"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-SharedWithYouCore = ">=10.2" + +[[package]] +name = "pyobjc-framework-sharedwithyoucore" +version = "10.2" +description = "Wrappers for the framework SharedWithYouCore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SharedWithYouCore-10.2.tar.gz", hash = "sha256:cc8faa9f549f6c931be33cf99f49b8cde11db52cb542e3797c3a27f98e5e9a2a"}, + {file = "pyobjc_framework_SharedWithYouCore-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:275b50a6b9205b1c0a08632c2ede98293b26df28d6c35bc34714ec9d5a7065d6"}, + {file = "pyobjc_framework_SharedWithYouCore-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:55aaac1bea38e566e70084cbe348b2af0f5cda782c8da54c6bbbd70345a50b27"}, + {file = "pyobjc_framework_SharedWithYouCore-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b118ba79e7bb2fab26369927316b90aa952795976a29e7dc49dcb47a87f7924c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-shazamkit" +version = "10.2" +description = "Wrappers for the framework ShazamKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ShazamKit-10.2.tar.gz", hash = "sha256:f3359be7a0ffe0084d047b8813dd9e9b5339a0970baecad89cbe85513e838e74"}, + {file = "pyobjc_framework_ShazamKit-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a33d2ad28cc7731e67906eccf324c441383ba741399c88e993b5375e734509ba"}, + {file = "pyobjc_framework_ShazamKit-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13976c21722389e81d9e10ab419dfb0904f48cec639f0932aada0f039d78dac3"}, + {file = "pyobjc_framework_ShazamKit-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:380992e9da3000ebefe45b50f65ed3bf88ba87574c4a6486a29553cfbfc04c22"}, + {file = "pyobjc_framework_ShazamKit-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e8494bfcf6ceb8b59e1bf2678073e00155f6dd2afbec01eaefd2128d3a4f5c76"}, + {file = "pyobjc_framework_ShazamKit-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:71cb7db481c791a52d261b924063431b72c4c288afd14a00cf7106274596a1c3"}, + {file = "pyobjc_framework_ShazamKit-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0a537e1f86f47ddde742fd0491173c669e6cda6b9edddbe72e56a148a40111f8"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-social" +version = "10.2" +description = "Wrappers for the framework Social on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Social-10.2.tar.gz", hash = "sha256:34995cd0c0f6c4adbe7bfa9049463058c7a8676d34d3d5b9de37f87416f22a0a"}, + {file = "pyobjc_framework_Social-10.2-py2.py3-none-any.whl", hash = "sha256:76ed463e3a77c58e5b527c37eb8b2dd60658dd736ba243cfa24b4704580b58c4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-soundanalysis" +version = "10.2" +description = "Wrappers for the framework SoundAnalysis on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SoundAnalysis-10.2.tar.gz", hash = "sha256:960434f16a130da4fe5cd86ceac832b7eb17831a1e739472f7636aceea65e018"}, + {file = "pyobjc_framework_SoundAnalysis-10.2-py2.py3-none-any.whl", hash = "sha256:a09b49acca76a3c161b937002e5d034cf32c33d033677a8143d446eb53ca941d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-speech" +version = "10.2" +description = "Wrappers for the framework Speech on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Speech-10.2.tar.gz", hash = "sha256:4cb3445ff31a3f8d50492d420941723e07967b4fc4fc46c336403d8ca245c086"}, + {file = "pyobjc_framework_Speech-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:175195f945d89d2382c0f6052b798ef3ee41384b8bfa4954c16add126dc181f6"}, + {file = "pyobjc_framework_Speech-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c65cbda4b8d357b80d41695b1b505a759d3be8b63bca9dd7675053876878577"}, + {file = "pyobjc_framework_Speech-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b139f64cc3636f1cfdc78a4071068d34e9ea70283201fd7a821e41d5bbbcf306"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-spritekit" +version = "10.2" +description = "Wrappers for the framework SpriteKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SpriteKit-10.2.tar.gz", hash = "sha256:31b3e639a617c456574df8f3ce18275eff613cf49e98ea8df974cda05d13a7fc"}, + {file = "pyobjc_framework_SpriteKit-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7b312677a70a7fe684af8726a39837e935fd6660f0271246885934f60d773506"}, + {file = "pyobjc_framework_SpriteKit-10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a8e015451fa701c7d9b383a95315809208145790d8e68a542def9fb10d6c2ce2"}, + {file = "pyobjc_framework_SpriteKit-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:803138edacb0c5bbc40bfeb964c70521259a7fb9c0dd31a79824b36be3942f59"}, + {file = "pyobjc_framework_SpriteKit-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ae44f20076286dd0d600f9b4c8c31f144abe1f44dbd37ca96ecdba98732bfb4a"}, + {file = "pyobjc_framework_SpriteKit-10.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9f5b65ac9fd0a40e28673a15c6c7785208402c02422a1e150f713b2c82681b51"}, + {file = "pyobjc_framework_SpriteKit-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8f2e140ad818d6891f654369853f9439d0f280302bf5750c28df8a4fcc019ec"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-storekit" +version = "10.2" +description = "Wrappers for the framework StoreKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-StoreKit-10.2.tar.gz", hash = "sha256:44cf0b5fe605b9e5dc6aed2ae9e09d807d04d5f2eaf78afb8c04e3f109a0d680"}, + {file = "pyobjc_framework_StoreKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f9e511ebf2d954f10999c2a46e5ecffee0235e0c35eda24c8fcfdb433768935d"}, + {file = "pyobjc_framework_StoreKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e22a701666a787d4df9f45bf1507cf41e45357b22c55ad79c608b24a506981e1"}, + {file = "pyobjc_framework_StoreKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:44c0a65bd39e64e276d8a7b991d93b59b149b3b886cadddb6a38253d48b123e5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-symbols" +version = "10.2" +description = "Wrappers for the framework Symbols on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Symbols-10.2.tar.gz", hash = "sha256:b1874e79fdcaf65deaadda35a3c9dbd24eb92d7dc8aa4db5d7f14f2b06d8a312"}, + {file = "pyobjc_framework_Symbols-10.2-py2.py3-none-any.whl", hash = "sha256:3b5fa1e162acb04eab092e0e1dbe686e2fb61cf648850953e15314edb56fb05f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-syncservices" +version = "10.2" +description = "Wrappers for the framework SyncServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SyncServices-10.2.tar.gz", hash = "sha256:1c76073484924201336e6aab40f10358573bc640a92ed4066b8062c748957576"}, + {file = "pyobjc_framework_SyncServices-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf40e4194bd42bb447212037876ca3e90e0e5a7aa21e59a6987f300209a83fb7"}, + {file = "pyobjc_framework_SyncServices-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4e438c0cf74aecb95c2a86db9c39236fee3edf0a91814255e2aff18bf24e7e82"}, + {file = "pyobjc_framework_SyncServices-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:dffc9ddf9235176c1f1575095beae97d6d2ffa9cffe9c195f815c46f69070787"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreData = ">=10.2" + +[[package]] +name = "pyobjc-framework-systemconfiguration" +version = "10.2" +description = "Wrappers for the framework SystemConfiguration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SystemConfiguration-10.2.tar.gz", hash = "sha256:e9ec946ca56514a68e28040c55c79ba105c9a70b56698635767250e629c37e49"}, + {file = "pyobjc_framework_SystemConfiguration-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d25ff4b8525f087fc004ece9518b38d365ef6bbc06e4c0f847d70cb72ca961df"}, + {file = "pyobjc_framework_SystemConfiguration-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2fc0a9d1c1c6a5d5b9d9289ee8e5de0d4ef8cb4c9bc03e8a33513217580a307b"}, + {file = "pyobjc_framework_SystemConfiguration-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d33ebea6881c2e4b9ddd03f8def7495dc884b7e53fe3d6e1340d9f9cc7441878"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-systemextensions" +version = "10.2" +description = "Wrappers for the framework SystemExtensions on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SystemExtensions-10.2.tar.gz", hash = "sha256:883c41cb257fb2b5baadafa4213dc0f0fffc97edb35ebaf6ed95a185a786eb85"}, + {file = "pyobjc_framework_SystemExtensions-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8a85c71121abe33a83742b84d046684e30e5400c5d2bbb4bca4322c4e9d5506b"}, + {file = "pyobjc_framework_SystemExtensions-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3374105ebc3992e8898b46ba85e860ac1f2f24985c640834bf2b9da26a8f40a7"}, + {file = "pyobjc_framework_SystemExtensions-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5b699c94a05a7253d803fb75b6ea5c67d2c59eb906deceb7f3d0a44f42b5d7a8"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-threadnetwork" +version = "10.2" +description = "Wrappers for the framework ThreadNetwork on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ThreadNetwork-10.2.tar.gz", hash = "sha256:864ebabdb187cef16e1fba0f5439a73b1ed9a4e66b888f7954b12150c323c0f8"}, + {file = "pyobjc_framework_ThreadNetwork-10.2-py2.py3-none-any.whl", hash = "sha256:f7ad31b4a67f9ed00097a21c7bbd48ffa4ce2c22174a52ac508beedf7cb2aa9e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-uniformtypeidentifiers" +version = "10.2" +description = "Wrappers for the framework UniformTypeIdentifiers on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UniformTypeIdentifiers-10.2.tar.gz", hash = "sha256:4d3e7add89766fe7abc6fd6e29387e92d7b38343b37d365607c9d287c5e758f6"}, + {file = "pyobjc_framework_UniformTypeIdentifiers-10.2-py2.py3-none-any.whl", hash = "sha256:25b72005063a88c5e67bf91d1355973f4bbf3dd7c1b3fb8eb00503020a837b33"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-usernotifications" +version = "10.2" +description = "Wrappers for the framework UserNotifications on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UserNotifications-10.2.tar.gz", hash = "sha256:3a1b7d77c95dff109f904451525752ece3c38f38cfa0825fd01735388c2b0264"}, + {file = "pyobjc_framework_UserNotifications-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f4ce290d1874d8b4ec36b2fae9181fa230e6ce0dced3aeb0fd0d88b7cda6a75a"}, + {file = "pyobjc_framework_UserNotifications-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8bc0c9599d7bbf35bb79eb661d537d6ea506859d2f1332ae2ee34b140bd937ef"}, + {file = "pyobjc_framework_UserNotifications-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:12d1ea6683af36813e3bdbdb065c28d71d01dfed7ea4deedeb3585e55179cbbb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-usernotificationsui" +version = "10.2" +description = "Wrappers for the framework UserNotificationsUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UserNotificationsUI-10.2.tar.gz", hash = "sha256:f476d4a9f5b0746beda3d06ed6eb8a1b072372e644c707e675f4e11703528a81"}, + {file = "pyobjc_framework_UserNotificationsUI-10.2-py2.py3-none-any.whl", hash = "sha256:b0909b11655a7ae14e54ba6f80f1c6d34d46de5e8b565d0a51c22f87604ad3d3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-UserNotifications = ">=10.2" + +[[package]] +name = "pyobjc-framework-videosubscriberaccount" +version = "10.2" +description = "Wrappers for the framework VideoSubscriberAccount on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-VideoSubscriberAccount-10.2.tar.gz", hash = "sha256:26ea7fe843ba316eea90c488ed3ff46651b94b51b6e3bd87db2ff93f9fa8e496"}, + {file = "pyobjc_framework_VideoSubscriberAccount-10.2-py2.py3-none-any.whl", hash = "sha256:300c9f419821aab400ab9798bed9fc659984f19eb8577934e6faae0428b89096"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-videotoolbox" +version = "10.2" +description = "Wrappers for the framework VideoToolbox on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-VideoToolbox-10.2.tar.gz", hash = "sha256:347259a8e920dbc3dd1fada5ab0d829485cef3165166fa65f78c23ada4f9b80a"}, + {file = "pyobjc_framework_VideoToolbox-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb8147d673defdb02526b80b1584369f94b94721016950bb12425b2309b92c88"}, + {file = "pyobjc_framework_VideoToolbox-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6527180eede44301c21790baa7e1a5f5429893e3995e61640f3941c3b6fb08f9"}, + {file = "pyobjc_framework_VideoToolbox-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:de55d7629a9659439901a16d6074e9fc9b229e93a555097a1c92e0df6cfb5cdb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreMedia = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-virtualization" +version = "10.2" +description = "Wrappers for the framework Virtualization on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Virtualization-10.2.tar.gz", hash = "sha256:49eb8d0ec3017c2194620f0698e95ccf20b8b706c73ab3b1b50902c57f0f86ff"}, + {file = "pyobjc_framework_Virtualization-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:492daa384cf3117749ff35127f81313bd1ea9bbd09385c2a882b82ca4ca0797e"}, + {file = "pyobjc_framework_Virtualization-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1bea91b57d419d91e76865f9621ba4762793e05f7a694cefe73206f3a19b4eda"}, + {file = "pyobjc_framework_Virtualization-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c1198bcd31e4711c6a0c6816c77483361217a1ed2f0ad69608f9ba5633efc144"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyobjc-framework-vision" +version = "10.2" +description = "Wrappers for the framework Vision on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Vision-10.2.tar.gz", hash = "sha256:722e0a6da64738b5fc3c763a102445cad5892c0af94597637e89455099da397e"}, + {file = "pyobjc_framework_Vision-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:42b7383c317c2076edcb44f7ad8ed4a6e675250a3fd20e87eef8e0e4233b1b58"}, + {file = "pyobjc_framework_Vision-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5e3adb56fca35d41a4bb113f3eadbe45e9667d8e3edf64908da3d6b130e14a8c"}, + {file = "pyobjc_framework_Vision-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e424d106052112897c8aa882d8334ac984e12509f9a473a285827ba47bfbcc9a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" +pyobjc-framework-CoreML = ">=10.2" +pyobjc-framework-Quartz = ">=10.2" + +[[package]] +name = "pyobjc-framework-webkit" +version = "10.2" +description = "Wrappers for the framework WebKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-WebKit-10.2.tar.gz", hash = "sha256:3717104dbc901a1bd46d97886c5adb6eb32798ff4451c4544e04740e41706083"}, + {file = "pyobjc_framework_WebKit-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0d128a9e053b3dfaa71857eba6e6aadfbde88347382e1e58e288b5e410b71226"}, + {file = "pyobjc_framework_WebKit-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d20344d55c3cb4aa27314e096f59db5cefa70539112d8c1658f2a2076df58612"}, + {file = "pyobjc_framework_WebKit-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f7dcf2e51964406cc2440e556c855d087c4706289c5f53464e8ffb0fba37adda"}, +] + +[package.dependencies] +pyobjc-core = ">=10.2" +pyobjc-framework-Cocoa = ">=10.2" + +[[package]] +name = "pyparsing" +version = "3.1.2" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pypiwin32" +version = "223" +description = "" +optional = false +python-versions = "*" +files = [ + {file = "pypiwin32-223-py3-none-any.whl", hash = "sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775"}, + {file = "pypiwin32-223.tar.gz", hash = "sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a"}, +] + +[package.dependencies] +pywin32 = ">=223" + +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +optional = false +python-versions = "*" +files = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] + +[package.extras] +cp2110 = ["hidapi"] + +[[package]] +name = "pyttsx3" +version = "2.90" +description = "Text to Speech (TTS) library for Python 2 and 3. Works without internet connection or delay. Supports multiple TTS engines, including Sapi5, nsss, and espeak." +optional = false +python-versions = "*" +files = [ + {file = "pyttsx3-2.90-py3-none-any.whl", hash = "sha256:a585b6d8cffc19bd92db1e0ccbd8aa9c6528dd2baa5a47045d6fed542a44aa19"}, +] + +[package.dependencies] +comtypes = {version = "*", markers = "platform_system == \"Windows\""} +pyobjc = {version = ">=2.4", markers = "platform_system == \"Darwin\""} +pypiwin32 = {version = "*", markers = "platform_system == \"Windows\""} +pywin32 = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, +] + +[[package]] +name = "requests" +version = "2.26.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "sanic" +version = "21.9.3" +description = "A web server and web framework that's written to go fast. Build fast. Run fast." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sanic-21.9.3-py3-none-any.whl", hash = "sha256:354fbc9e5382df23989752067a57d042aef99bf5a8e85593a3e0112276bff9e8"}, + {file = "sanic-21.9.3.tar.gz", hash = "sha256:5edb41d0d30cf47a25cf991b7465f53ea161b43e3cd20d8985e68ecf7fb7ad24"}, +] + +[package.dependencies] +aiofiles = ">=0.6.0" +httptools = ">=0.0.10" +multidict = ">=5.0,<6.0" +sanic-routing = ">=0.7,<1.0" +ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} +uvloop = {version = ">=0.5.3", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} +websockets = ">=10.0" + +[package.extras] +all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "m2r2", "mypy (>=0.901)", "pygments", "pytest (==5.2.1)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901)", "pygments", "pytest (==5.2.1)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +docs = ["docutils", "m2r2", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] +test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901)", "pygments", "pytest (==5.2.1)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "types-ujson", "uvicorn (<0.15.0)"] + +[[package]] +name = "sanic-cors" +version = "2.0.1" +description = "A Sanic extension adding a decorator for CORS support. Based on flask-cors by Cory Dolphin." +optional = false +python-versions = "*" +files = [ + {file = "Sanic-Cors-2.0.1.tar.gz", hash = "sha256:4d2f26333d49db428217814c66e89fc3df20fc62a5ab518a71fa22e2e249e19d"}, + {file = "Sanic_Cors-2.0.1-py2.py3-none-any.whl", hash = "sha256:0c8132bed394ba86f93c03bef52787183652d96b70add4ea13c25eb98f344343"}, +] + +[package.dependencies] +sanic = ">=21.9.3" + +[[package]] +name = "sanic-routing" +version = "0.7.2" +description = "Core routing component for Sanic" +optional = false +python-versions = "*" +files = [ + {file = "sanic-routing-0.7.2.tar.gz", hash = "sha256:139ce88b3f054e7aa336e2ecc8459837092b103b275d3a97609a34092c55374d"}, + {file = "sanic_routing-0.7.2-py3-none-any.whl", hash = "sha256:523034ffd07aca056040e08de438269c9a880722eee1ace3a32e4f74b394d9aa"}, +] + +[[package]] +name = "setproctitle" +version = "1.2.2" +description = "A Python module to customize the process title" +optional = false +python-versions = ">=3.6" +files = [ + {file = "setproctitle-1.2.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9106bcbacae534b6f82955b176723f1b2ca6514518aab44dffec05a583f8dca8"}, + {file = "setproctitle-1.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:30bc7a769a4451639a0adcbc97bdf7a6e9ac0ef3ddad8d63eb1e338edb3ebeda"}, + {file = "setproctitle-1.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e8ef655eab26e83ec105ce79036bb87e5f2bf8ba2d6f48afdd9595ef7647fcf4"}, + {file = "setproctitle-1.2.2-cp36-cp36m-win32.whl", hash = "sha256:0df728d0d350e6b1ad8436cc7add052faebca6f4d03257182d427d86d4422065"}, + {file = "setproctitle-1.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:5260e8700c5793d48e79c5e607e8e552e795b698491a4b9bb9111eb74366a450"}, + {file = "setproctitle-1.2.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba1fb32e7267330bd9f72e69e076777a877f1cb9be5beac5e62d1279e305f37f"}, + {file = "setproctitle-1.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e696c93d93c23f377ccd2d72e38908d3dbfc90e45561602b805f53f2627d42ea"}, + {file = "setproctitle-1.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:fbf914179dc4540ee6bfd8228b4cc1f1f6fb12dad66b72b5c9b955b222403220"}, + {file = "setproctitle-1.2.2-cp37-cp37m-win32.whl", hash = "sha256:28b884e1cb9a53974e15838864283f9bad774b5c7db98c9609416bd123cb9fd1"}, + {file = "setproctitle-1.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a11d329f33221443317e2aeaee9442f22fcae25be3aa4fb8489e4f7b1f65cdd2"}, + {file = "setproctitle-1.2.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e13a5c1d9c369cb11cdfc4b75be432b83eb3205c95a69006008ffd4366f87b9e"}, + {file = "setproctitle-1.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c611f65bc9de5391a1514de556f71101e6531bb0715d240efd3e9732626d5c9e"}, + {file = "setproctitle-1.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:bc4393576ed3ac87ddac7d1bd0faaa2fab24840a025cc5f3c21d14cf0c9c8a12"}, + {file = "setproctitle-1.2.2-cp38-cp38-win32.whl", hash = "sha256:17598f38be9ef499d74f2380bf76b558be72e87da75d66b153350e586649171b"}, + {file = "setproctitle-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:0d160d46c8f3567e0aa27b26b1f36e03122e3de475aacacc14a92b8fe45b648a"}, + {file = "setproctitle-1.2.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:077943272d0490b3f43d17379432d5e49c263f608fdf4cf624b419db762ca72b"}, + {file = "setproctitle-1.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:970798d948f0c90a3eb0f8750f08cb215b89dcbee1b55ffb353ad62d9361daeb"}, + {file = "setproctitle-1.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3f6136966c81daaf5b4b010613fe33240a045a4036132ef040b623e35772d998"}, + {file = "setproctitle-1.2.2-cp39-cp39-win32.whl", hash = "sha256:249526a06f16d493a2cb632abc1b1fdfaaa05776339a50dd9f27c941f6ff1383"}, + {file = "setproctitle-1.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:4fc5bebd34f451dc87d2772ae6093adea1ea1dc29afc24641b250140decd23bb"}, + {file = "setproctitle-1.2.2.tar.gz", hash = "sha256:7dfb472c8852403d34007e01d6e3c68c57eb66433fb8a5c77b13b89a160d97df"}, +] + +[package.extras] +test = ["pytest (>=6.1,<6.2)"] + +[[package]] +name = "setuptools" +version = "69.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, + {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "sounddevice" +version = "0.4.2" +description = "Play and Record Sound with Python" +optional = false +python-versions = ">=3" +files = [ + {file = "sounddevice-0.4.2-py3-none-any.whl", hash = "sha256:7820da4db09c83b5f8ff49a1caebd6a7a883c53e05df7edd09ca792bf1928b94"}, + {file = "sounddevice-0.4.2-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:ef5478d67e2dc9855c25b87dd3d5ef74c7ba25d21e6721419900c5bc8e4a9637"}, + {file = "sounddevice-0.4.2-py3-none-win32.whl", hash = "sha256:18bfd7efa121872c923b983ab253d6985cba988c331eab87029e48b51e739dbb"}, + {file = "sounddevice-0.4.2-py3-none-win_amd64.whl", hash = "sha256:22a5f72a9877ccba02f98f86dede44b7dc93a0fd468df6e8c0c47c2656274140"}, + {file = "sounddevice-0.4.2.tar.gz", hash = "sha256:1c9b07cff59c837d258002ed806ee134ed367ef11042bd7d283d6ce407bf889c"}, +] + +[package.dependencies] +CFFI = ">=1.0" + +[package.extras] +numpy = ["NumPy"] + +[[package]] +name = "syncer" +version = "1.3.0" +description = "Async to sync converter" +optional = false +python-versions = "*" +files = [ + {file = "syncer-1.3.0.tar.gz", hash = "sha256:6eb756f345388e08763f9695e7b5071b7657f0a7e809e486e3f9ca3fb4cd4d2c"}, +] + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +optional = false +python-versions = "*" +files = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] + +[[package]] +name = "ujson" +version = "5.9.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ujson-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab71bf27b002eaf7d047c54a68e60230fbd5cd9da60de7ca0aa87d0bccead8fa"}, + {file = "ujson-5.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a365eac66f5aa7a7fdf57e5066ada6226700884fc7dce2ba5483538bc16c8c5"}, + {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e015122b337858dba5a3dc3533af2a8fc0410ee9e2374092f6a5b88b182e9fcc"}, + {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779a2a88c53039bebfbccca934430dabb5c62cc179e09a9c27a322023f363e0d"}, + {file = "ujson-5.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10ca3c41e80509fd9805f7c149068fa8dbee18872bbdc03d7cca928926a358d5"}, + {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a566e465cb2fcfdf040c2447b7dd9718799d0d90134b37a20dff1e27c0e9096"}, + {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f833c529e922577226a05bc25b6a8b3eb6c4fb155b72dd88d33de99d53113124"}, + {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b68a0caab33f359b4cbbc10065c88e3758c9f73a11a65a91f024b2e7a1257106"}, + {file = "ujson-5.9.0-cp310-cp310-win32.whl", hash = "sha256:7cc7e605d2aa6ae6b7321c3ae250d2e050f06082e71ab1a4200b4ae64d25863c"}, + {file = "ujson-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6d3f10eb8ccba4316a6b5465b705ed70a06011c6f82418b59278fbc919bef6f"}, + {file = "ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b"}, + {file = "ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0"}, + {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae"}, + {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d"}, + {file = "ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e"}, + {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908"}, + {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b"}, + {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d"}, + {file = "ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120"}, + {file = "ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99"}, + {file = "ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c"}, + {file = "ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f"}, + {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399"}, + {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e"}, + {file = "ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320"}, + {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164"}, + {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01"}, + {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c"}, + {file = "ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437"}, + {file = "ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c"}, + {file = "ujson-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d581db9db9e41d8ea0b2705c90518ba623cbdc74f8d644d7eb0d107be0d85d9c"}, + {file = "ujson-5.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff741a5b4be2d08fceaab681c9d4bc89abf3c9db600ab435e20b9b6d4dfef12e"}, + {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdcb02cabcb1e44381221840a7af04433c1dc3297af76fde924a50c3054c708c"}, + {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e208d3bf02c6963e6ef7324dadf1d73239fb7008491fdf523208f60be6437402"}, + {file = "ujson-5.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4b3917296630a075e04d3d07601ce2a176479c23af838b6cf90a2d6b39b0d95"}, + {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0c4d6adb2c7bb9eb7c71ad6f6f612e13b264942e841f8cc3314a21a289a76c4e"}, + {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0b159efece9ab5c01f70b9d10bbb77241ce111a45bc8d21a44c219a2aec8ddfd"}, + {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0cb4a7814940ddd6619bdce6be637a4b37a8c4760de9373bac54bb7b229698b"}, + {file = "ujson-5.9.0-cp38-cp38-win32.whl", hash = "sha256:dc80f0f5abf33bd7099f7ac94ab1206730a3c0a2d17549911ed2cb6b7aa36d2d"}, + {file = "ujson-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:506a45e5fcbb2d46f1a51fead991c39529fc3737c0f5d47c9b4a1d762578fc30"}, + {file = "ujson-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0fd2eba664a22447102062814bd13e63c6130540222c0aa620701dd01f4be81"}, + {file = "ujson-5.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bdf7fc21a03bafe4ba208dafa84ae38e04e5d36c0e1c746726edf5392e9f9f36"}, + {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f909bc08ce01f122fd9c24bc6f9876aa087188dfaf3c4116fe6e4daf7e194f"}, + {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd4ea86c2afd41429751d22a3ccd03311c067bd6aeee2d054f83f97e41e11d8f"}, + {file = "ujson-5.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63fb2e6599d96fdffdb553af0ed3f76b85fda63281063f1cb5b1141a6fcd0617"}, + {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:32bba5870c8fa2a97f4a68f6401038d3f1922e66c34280d710af00b14a3ca562"}, + {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37ef92e42535a81bf72179d0e252c9af42a4ed966dc6be6967ebfb929a87bc60"}, + {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f69f16b8f1c69da00e38dc5f2d08a86b0e781d0ad3e4cc6a13ea033a439c4844"}, + {file = "ujson-5.9.0-cp39-cp39-win32.whl", hash = "sha256:3382a3ce0ccc0558b1c1668950008cece9bf463ebb17463ebf6a8bfc060dae34"}, + {file = "ujson-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:6adef377ed583477cf005b58c3025051b5faa6b8cc25876e594afbb772578f21"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ffdfebd819f492e48e4f31c97cb593b9c1a8251933d8f8972e81697f00326ff1"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4eec2ddc046360d087cf35659c7ba0cbd101f32035e19047013162274e71fcf"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbb90aa5c23cb3d4b803c12aa220d26778c31b6e4b7a13a1f49971f6c7d088e"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0823cb70866f0d6a4ad48d998dd338dce7314598721bc1b7986d054d782dfd"}, + {file = "ujson-5.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4e35d7885ed612feb6b3dd1b7de28e89baaba4011ecdf995e88be9ac614765e9"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b048aa93eace8571eedbd67b3766623e7f0acbf08ee291bef7d8106210432427"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:323279e68c195110ef85cbe5edce885219e3d4a48705448720ad925d88c9f851"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ac92d86ff34296f881e12aa955f7014d276895e0e4e868ba7fddebbde38e378"}, + {file = "ujson-5.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6eecbd09b316cea1fd929b1e25f70382917542ab11b692cb46ec9b0a26c7427f"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:473fb8dff1d58f49912323d7cb0859df5585cfc932e4b9c053bf8cf7f2d7c5c4"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f91719c6abafe429c1a144cfe27883eace9fb1c09a9c5ef1bcb3ae80a3076a4e"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1c0991c4fe256f5fdb19758f7eac7f47caac29a6c57d0de16a19048eb86bad"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea0f55a1396708e564595aaa6696c0d8af532340f477162ff6927ecc46e21"}, + {file = "ujson-5.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:07e0cfdde5fd91f54cd2d7ffb3482c8ff1bf558abf32a8b953a5d169575ae1cd"}, + {file = "ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532"}, +] + +[[package]] +name = "urllib3" +version = "1.26.18" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, +] + +[package.extras] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvloop" +version = "0.19.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, + {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, +] + +[package.extras] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "websockets" +version = "10.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc"}, + {file = "websockets-10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60"}, + {file = "websockets-10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3477146d1f87ead8df0f27e8960249f5248dceb7c2741e8bbec9aa5338d0c053"}, + {file = "websockets-10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb01ea7b5f52e7125bdc3c5807aeaa2d08a0553979cf2d96a8b7803ea33e15e7"}, + {file = "websockets-10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9fd62c6dc83d5d35fb6a84ff82ec69df8f4657fff05f9cd6c7d9bec0dd57f0f6"}, + {file = "websockets-10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbf080f3892ba1dc8838786ec02899516a9d227abe14a80ef6fd17d4fb57127"}, + {file = "websockets-10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5560558b0dace8312c46aa8915da977db02738ac8ecffbc61acfbfe103e10155"}, + {file = "websockets-10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:667c41351a6d8a34b53857ceb8343a45c85d438ee4fd835c279591db8aeb85be"}, + {file = "websockets-10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:468f0031fdbf4d643f89403a66383247eb82803430b14fa27ce2d44d2662ca37"}, + {file = "websockets-10.1-cp310-cp310-win32.whl", hash = "sha256:d0d81b46a5c87d443e40ce2272436da8e6092aa91f5fbeb60d1be9f11eff5b4c"}, + {file = "websockets-10.1-cp310-cp310-win_amd64.whl", hash = "sha256:b68b6caecb9a0c6db537aa79750d1b592a841e4f1a380c6196091e65b2ad35f9"}, + {file = "websockets-10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a249139abc62ef333e9e85064c27fefb113b16ffc5686cefc315bdaef3eefbc8"}, + {file = "websockets-10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8877861e3dee38c8d302eee0d5dbefa6663de3b46dc6a888f70cd7e82562d1f7"}, + {file = "websockets-10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e3872ae57acd4306ecf937d36177854e218e999af410a05c17168cd99676c512"}, + {file = "websockets-10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b66e6d514f12c28d7a2d80bb2a48ef223342e99c449782d9831b0d29a9e88a17"}, + {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9f304a22ece735a3da8a51309bc2c010e23961a8f675fae46fdf62541ed62123"}, + {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:189ed478395967d6a98bb293abf04e8815349e17456a0a15511f1088b6cb26e4"}, + {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:08a42856158307e231b199671c4fce52df5786dd3d703f36b5d8ac76b206c485"}, + {file = "websockets-10.1-cp37-cp37m-win32.whl", hash = "sha256:3ef6f73854cded34e78390dbdf40dfdcf0b89b55c0e282468ef92646fce8d13a"}, + {file = "websockets-10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:89e985d40d407545d5f5e2e58e1fdf19a22bd2d8cd54d20a882e29f97e930a0a"}, + {file = "websockets-10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:002071169d2e44ce8eb9e5ebac9fbce142ba4b5146eef1cfb16b177a27662657"}, + {file = "websockets-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfae282c2aa7f0c4be45df65c248481f3509f8c40ca8b15ed96c35668ae0ff69"}, + {file = "websockets-10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:97b4b68a2ddaf5c4707ae79c110bfd874c5be3c6ac49261160fb243fa45d8bbb"}, + {file = "websockets-10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c9407719f42cb77049975410490c58a705da6af541adb64716573e550e5c9db"}, + {file = "websockets-10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d858fb31e5ac992a2cdf17e874c95f8a5b1e917e1fb6b45ad85da30734b223f"}, + {file = "websockets-10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7bdd3d26315db0a9cf8a0af30ca95e0aa342eda9c1377b722e71ccd86bc5d1dd"}, + {file = "websockets-10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e259be0863770cb91b1a6ccf6907f1ac2f07eff0b7f01c249ed751865a70cb0d"}, + {file = "websockets-10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6b014875fae19577a392372075e937ebfebf53fd57f613df07b35ab210f31534"}, + {file = "websockets-10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:98de71f86bdb29430fd7ba9997f47a6b10866800e3ea577598a786a785701bb0"}, + {file = "websockets-10.1-cp38-cp38-win32.whl", hash = "sha256:3a02ab91d84d9056a9ee833c254895421a6333d7ae7fff94b5c68e4fa8095519"}, + {file = "websockets-10.1-cp38-cp38-win_amd64.whl", hash = "sha256:7d6673b2753f9c5377868a53445d0c321ef41ff3c8e3b6d57868e72054bfce5f"}, + {file = "websockets-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ddab2dc69ee5ae27c74dbfe9d7bb6fee260826c136dca257faa1a41d1db61a89"}, + {file = "websockets-10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14e9cf68a08d1a5d42109549201aefba473b1d925d233ae19035c876dd845da9"}, + {file = "websockets-10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4819c6fb4f336fd5388372cb556b1f3a165f3f68e66913d1a2fc1de55dc6f58"}, + {file = "websockets-10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05e7f098c76b0a4743716590bb8f9706de19f1ef5148d61d0cf76495ec3edb9c"}, + {file = "websockets-10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bb6256de5a4fb1d42b3747b4e2268706c92965d75d0425be97186615bf2f24f"}, + {file = "websockets-10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:888a5fa2a677e0c2b944f9826c756475980f1b276b6302e606f5c4ff5635be9e"}, + {file = "websockets-10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6fdec1a0b3e5630c58e3d8704d2011c678929fce90b40908c97dfc47de8dca72"}, + {file = "websockets-10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:531d8eb013a9bc6b3ad101588182aa9b6dd994b190c56df07f0d84a02b85d530"}, + {file = "websockets-10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0d93b7cadc761347d98da12ec1930b5c71b2096f1ceed213973e3cda23fead9c"}, + {file = "websockets-10.1-cp39-cp39-win32.whl", hash = "sha256:d9b245db5a7e64c95816e27d72830e51411c4609c05673d1ae81eb5d23b0be54"}, + {file = "websockets-10.1-cp39-cp39-win_amd64.whl", hash = "sha256:882c0b8bdff3bf1bd7f024ce17c6b8006042ec4cceba95cf15df57e57efa471c"}, + {file = "websockets-10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:10edd9d7d3581cfb9ff544ac09fc98cab7ee8f26778a5a8b2d5fd4b0684c5ba5"}, + {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa83174390c0ff4fc1304fbe24393843ac7a08fdd59295759c4b439e06b1536"}, + {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:483edee5abed738a0b6a908025be47f33634c2ad8e737edd03ffa895bd600909"}, + {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:816ae7dac2c6522cfa620947ead0ca95ac654916eebf515c94d7c28de5601a6e"}, + {file = "websockets-10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1dafe98698ece09b8ccba81b910643ff37198e43521d977be76caf37709cf62b"}, + {file = "websockets-10.1.tar.gz", hash = "sha256:181d2b25de5a437b36aefedaf006ecb6fa3aa1328ec0236cdde15f32f9d3ff6d"}, +] + +[[package]] +name = "wheel" +version = "0.43.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"}, + {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + +[[package]] +name = "whichcraft" +version = "0.6.1" +description = "This package provides cross-platform cross-python shutil.which functionality." +optional = false +python-versions = "*" +files = [ + {file = "whichcraft-0.6.1-py2.py3-none-any.whl", hash = "sha256:deda9266fbb22b8c64fd3ee45c050d61139cd87419765f588e37c8d23e236dd9"}, + {file = "whichcraft-0.6.1.tar.gz", hash = "sha256:acdbb91b63d6a15efbd6430d1d7b2d36e44a71697e93e19b7ded477afd9fce87"}, +] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zope-event" +version = "5.0" +description = "Very basic event publishing system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, + {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx"] +test = ["zope.testrunner"] + +[[package]] +name = "zope-interface" +version = "6.2" +description = "Interfaces for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zope.interface-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:506f5410b36e5ba494136d9fa04c548eaf1a0d9c442b0b0e7a0944db7620e0ab"}, + {file = "zope.interface-6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b386b8b9d2b6a5e1e4eadd4e62335571244cb9193b7328c2b6e38b64cfda4f0e"}, + {file = "zope.interface-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb0b3f2cb606981c7432f690db23506b1db5899620ad274e29dbbbdd740e797"}, + {file = "zope.interface-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7916380abaef4bb4891740879b1afcba2045aee51799dfd6d6ca9bdc71f35f"}, + {file = "zope.interface-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b240883fb43160574f8f738e6d09ddbdbf8fa3e8cea051603d9edfd947d9328"}, + {file = "zope.interface-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8af82afc5998e1f307d5e72712526dba07403c73a9e287d906a8aa2b1f2e33dd"}, + {file = "zope.interface-6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d45d2ba8195850e3e829f1f0016066a122bfa362cc9dc212527fc3d51369037"}, + {file = "zope.interface-6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76e0531d86523be7a46e15d379b0e975a9db84316617c0efe4af8338dc45b80c"}, + {file = "zope.interface-6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59f7374769b326a217d0b2366f1c176a45a4ff21e8f7cebb3b4a3537077eff85"}, + {file = "zope.interface-6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25e0af9663eeac6b61b231b43c52293c2cb7f0c232d914bdcbfd3e3bd5c182ad"}, + {file = "zope.interface-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e02a6fc1772b458ebb6be1c276528b362041217b9ca37e52ecea2cbdce9fac"}, + {file = "zope.interface-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:02adbab560683c4eca3789cc0ac487dcc5f5a81cc48695ec247f00803cafe2fe"}, + {file = "zope.interface-6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8f5d2c39f3283e461de3655e03faf10e4742bb87387113f787a7724f32db1e48"}, + {file = "zope.interface-6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:75d2ec3d9b401df759b87bc9e19d1b24db73083147089b43ae748aefa63067ef"}, + {file = "zope.interface-6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa994e8937e8ccc7e87395b7b35092818905cf27c651e3ff3e7f29729f5ce3ce"}, + {file = "zope.interface-6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ede888382882f07b9e4cd942255921ffd9f2901684198b88e247c7eabd27a000"}, + {file = "zope.interface-6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2606955a06c6852a6cff4abeca38346ed01e83f11e960caa9a821b3626a4467b"}, + {file = "zope.interface-6.2-cp312-cp312-win_amd64.whl", hash = "sha256:ac7c2046d907e3b4e2605a130d162b1b783c170292a11216479bb1deb7cadebe"}, + {file = "zope.interface-6.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:febceb04ee7dd2aef08c2ff3d6f8a07de3052fc90137c507b0ede3ea80c21440"}, + {file = "zope.interface-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fc711acc4a1c702ca931fdbf7bf7c86f2a27d564c85c4964772dadf0e3c52f5"}, + {file = "zope.interface-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:396f5c94654301819a7f3a702c5830f0ea7468d7b154d124ceac823e2419d000"}, + {file = "zope.interface-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd374927c00764fcd6fe1046bea243ebdf403fba97a937493ae4be2c8912c2b"}, + {file = "zope.interface-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a3046e8ab29b590d723821d0785598e0b2e32b636a0272a38409be43e3ae0550"}, + {file = "zope.interface-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de125151a53ecdb39df3cb3deb9951ed834dd6a110a9e795d985b10bb6db4532"}, + {file = "zope.interface-6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f444de0565db46d26c9fa931ca14f497900a295bd5eba480fc3fad25af8c763e"}, + {file = "zope.interface-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2fefad268ff5c5b314794e27e359e48aeb9c8bb2cbb5748a071757a56f6bb8f"}, + {file = "zope.interface-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97785604824981ec8c81850dd25c8071d5ce04717a34296eeac771231fbdd5cd"}, + {file = "zope.interface-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7b2bed4eea047a949296e618552d3fed00632dc1b795ee430289bdd0e3717f3"}, + {file = "zope.interface-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:d54f66c511ea01b9ef1d1a57420a93fbb9d48a08ec239f7d9c581092033156d0"}, + {file = "zope.interface-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5ee9789a20b0081dc469f65ff6c5007e67a940d5541419ca03ef20c6213dd099"}, + {file = "zope.interface-6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af27b3fe5b6bf9cd01b8e1c5ddea0a0d0a1b8c37dc1c7452f1e90bf817539c6d"}, + {file = "zope.interface-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bce517b85f5debe07b186fc7102b332676760f2e0c92b7185dd49c138734b70"}, + {file = "zope.interface-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ae9793f114cee5c464cc0b821ae4d36e1eba961542c6086f391a61aee167b6f"}, + {file = "zope.interface-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e87698e2fea5ca2f0a99dff0a64ce8110ea857b640de536c76d92aaa2a91ff3a"}, + {file = "zope.interface-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:b66335bbdbb4c004c25ae01cc4a54fd199afbc1fd164233813c6d3c2293bb7e1"}, + {file = "zope.interface-6.2.tar.gz", hash = "sha256:3b6c62813c63c543a06394a636978b22dffa8c5410affc9331ce6cdb5bfa8565"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx_rtd_theme"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.11,<3.13" +content-hash = "7a879a23f8a0512a896434c5adb1e2dc72ee35d4efb38c7d4e44bf758ab081b8" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2b7a91a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[tool.poetry] +name = "bapsicle" +version = "3.1.0" +description = "" +authors = ["University Radio York"] +readme = "README.md" +package-mode = false + +[tool.poetry.dependencies] +python = ">=3.11,<3.13" +wheel = "^0.43.0" +pygame = "2.5.2" +sanic = "21.9.3" +sanic-cors = "2.0.1" +syncer = "1.3.0" +aiohttp = "3.7.4" +mutagen = "1.45.1" +sounddevice = "0.4.2" +setproctitle = "1.2.2" +pyttsx3 = "2.90" +websockets = "10.1" +typing-extensions = "3.10.0.0" +pyserial = "3.5" +requests = "2.26.0" +jinja2 = "3.0.1" +pydub = "0.25.1" +psutil = "^5.9.8" + +[tool.poetry.group.dev.dependencies] +pyinstaller = [ + {version = "^6.5.0", platform = "linux"}, + {version = "^6.5.0", platform = "darwin"} +] +auto-py-to-exe = {version = "^2.43.3", platform = "win32"} +pywin32 = {version = "^306", platform = "win32"} +autopep8 = "^2.1.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 54097583b3446ffcb9e66debecb6a09d02d57c29 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Fri, 29 Mar 2024 16:38:10 +0000 Subject: [PATCH 145/156] fix(ci): use poetry --- .github/workflows/build.yaml | 4 ++++ build/build-exe.py | 4 ++-- build/build-linux.sh | 7 ++----- build/build-macos.sh | 9 +++------ build/build-windows.bat | 11 ++--------- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ab96de7..804a502 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -55,6 +55,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Setup poetry + uses: Gr1N/setup-poetry@v8 - uses: actions/checkout@v2 - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v2 @@ -91,6 +93,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Setup poetry + uses: Gr1N/setup-poetry@v8 - uses: actions/checkout@v2 - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v2 diff --git a/build/build-exe.py b/build/build-exe.py index 8a46cd8..ec8ac82 100644 --- a/build/build-exe.py +++ b/build/build-exe.py @@ -6,9 +6,9 @@ file.close() if isLinux(): - cmd_str = "python3 -m PyInstaller " + cmd_str = "poetry run python3 -m PyInstaller " else: - cmd_str = "pyinstaller " + cmd_str = "poetry run pyinstaller " json_dests = ["icon_file", "clean_build"] pyi_dests = ["icon", "clean"] diff --git a/build/build-linux.sh b/build/build-linux.sh index 6e3b4b1..df39674 100755 --- a/build/build-linux.sh +++ b/build/build-linux.sh @@ -11,16 +11,13 @@ echo "BRANCH: str = \"$build_branch\"" >> ../build.py sudo apt install libportaudio2 sudo apt install python3-pip python3-venv ffmpeg -python3 -m venv ../venv -source ../venv/bin/activate - poetry install -python3 ./generate-build-exe-config.py +poetry run python3 ./generate-build-exe-config.py chmod +x output/BAPSicle -python3 ./build-exe.py +poetry run python3 ./build-exe.py bash ./build-exe-pyinstaller-command.sh diff --git a/build/build-macos.sh b/build/build-macos.sh index 9d3bb98..5c48efd 100755 --- a/build/build-macos.sh +++ b/build/build-macos.sh @@ -8,21 +8,18 @@ build_branch="$(git branch --show-current)" echo "BUILD: str = \"$build_commit\"" > ../build.py echo "BRANCH: str = \"$build_branch\"" >> ../build.py -python3 -m venv ../venv -source ../venv/bin/activate - poetry install -python3 ./generate-build-exe-config.py +poetry run python3 ./generate-build-exe-config.py -python3 ./build-exe.py +poetry run python3 ./build-exe.py bash ./build-exe-pyinstaller-command.sh rm ./*.spec cd ../ -python3 build/generate-platypus-config.py +poetry run python3 build/generate-platypus-config.py cd build brew install platypus diff --git a/build/build-windows.bat b/build/build-windows.bat index ef1d25b..b925c9b 100644 --- a/build/build-windows.bat +++ b/build/build-windows.bat @@ -10,21 +10,14 @@ SET build_branch=%%F echo BUILD: str = "%build_commit%"> ..\build.py echo BRANCH: str = "%build_branch%">> ..\build.py -if "%1" == "no-venv" goto skip-venv - - py -m venv ..\venv - ..\venv\Scripts\activate - -:skip-venv - poetry install : Generate the json config in case you wanted to use the gui to regenerate the command below manually. -python generate-build-exe-config.py +poetry run python generate-build-exe-config.py : auto-py-to-exe -c build-exe-config.json -o ../install -python build-exe.py +poetry run python build-exe.py build-exe-pyinstaller-command.bat From 7018f913963c997f8b6dee64cf68868d81a12a25 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Fri, 29 Mar 2024 16:51:32 +0000 Subject: [PATCH 146/156] fix: include BAPS as a package, loosen python version requirement --- poetry.lock | 57 +++++++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 4 ++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index b809c0a..0704cda 100644 --- a/poetry.lock +++ b/poetry.lock @@ -138,6 +138,7 @@ files = [ [package.dependencies] pycodestyle = ">=2.11.0" +tomli = {version = "*", markers = "python_version < \"3.11\""} [[package]] name = "bottle" @@ -358,7 +359,10 @@ files = [ [package.dependencies] cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""} +greenlet = [ + {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}, + {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""}, +] "zope.event" = "*" "zope.interface" = "*" @@ -513,6 +517,25 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "importlib-metadata" +version = "7.1.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + [[package]] name = "jinja2" version = "3.0.1" @@ -877,6 +900,7 @@ files = [ [package.dependencies] altgraph = "*" +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} @@ -900,6 +924,7 @@ files = [ ] [package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} packaging = ">=22.0" setuptools = ">=42.0.0" @@ -3905,6 +3930,17 @@ files = [ {file = "syncer-1.3.0.tar.gz", hash = "sha256:6eb756f345388e08763f9695e7b5071b7657f0a7e809e486e3f9ca3fb4cd4d2c"}, ] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "typing-extensions" version = "3.10.0.0" @@ -4236,6 +4272,21 @@ files = [ idna = ">=2.0" multidict = ">=4.0" +[[package]] +name = "zipp" +version = "3.18.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, + {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + [[package]] name = "zope-event" version = "5.0" @@ -4309,5 +4360,5 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" -python-versions = ">=3.11,<3.13" -content-hash = "7a879a23f8a0512a896434c5adb1e2dc72ee35d4efb38c7d4e44bf758ab081b8" +python-versions = ">=3.8,<3.13" +content-hash = "f9a6c80bce20fd5641715e8d7ab4308022ec24c0f2701ab32629bd1a8436a6f0" diff --git a/pyproject.toml b/pyproject.toml index 2b7a91a..9a5e7fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,10 @@ version = "3.1.0" description = "" authors = ["University Radio York"] readme = "README.md" -package-mode = false +packages = [{include = "__init__.py"}] [tool.poetry.dependencies] -python = ">=3.11,<3.13" +python = ">=3.8,<3.13" wheel = "^0.43.0" pygame = "2.5.2" sanic = "21.9.3" From ee93a452084bbbaa9250474139527aa4a22ec781 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 16:56:27 +0100 Subject: [PATCH 147/156] chore: general dependency updates (no breaking changes) --- build/requirements-dev.txt | 4 - build/requirements-linux.txt | 1 - build/requirements-macos.txt | 1 - build/requirements-windows.txt | 2 - build/requirements.txt | 17 - poetry.lock | 688 ++++++++++++++++++++++++--------- pyproject.toml | 30 +- 7 files changed, 511 insertions(+), 232 deletions(-) delete mode 100644 build/requirements-dev.txt delete mode 100644 build/requirements-linux.txt delete mode 100644 build/requirements-macos.txt delete mode 100644 build/requirements-windows.txt delete mode 100644 build/requirements.txt diff --git a/build/requirements-dev.txt b/build/requirements-dev.txt deleted file mode 100644 index db4ee3f..0000000 --- a/build/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ -wheel -flake8 -black -autopep8 diff --git a/build/requirements-linux.txt b/build/requirements-linux.txt deleted file mode 100644 index ef376ca..0000000 --- a/build/requirements-linux.txt +++ /dev/null @@ -1 +0,0 @@ -pyinstaller diff --git a/build/requirements-macos.txt b/build/requirements-macos.txt deleted file mode 100644 index ef376ca..0000000 --- a/build/requirements-macos.txt +++ /dev/null @@ -1 +0,0 @@ -pyinstaller diff --git a/build/requirements-windows.txt b/build/requirements-windows.txt deleted file mode 100644 index 8af44a0..0000000 --- a/build/requirements-windows.txt +++ /dev/null @@ -1,2 +0,0 @@ -pywin32 -auto-py-to-exe \ No newline at end of file diff --git a/build/requirements.txt b/build/requirements.txt deleted file mode 100644 index cfd24d3..0000000 --- a/build/requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -wheel -pygame==2.5.2 -sanic==21.9.3 -sanic-Cors==2.0.1 -syncer==1.3.0 -aiohttp==3.7.4 -mutagen==1.45.1 -sounddevice==0.4.2 -setproctitle==1.2.2 -pyttsx3==2.90 -websockets==10.1 -typing_extensions==3.10.0.0 -pyserial==3.5 -requests==2.26.0 -Jinja2==3.0.1 -pydub==0.25.1 -psutil diff --git a/poetry.lock b/poetry.lock index 0704cda..86fb368 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,60 +13,113 @@ files = [ [[package]] name = "aiohttp" -version = "3.7.4" +version = "3.9.3" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.7.4-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076"}, - {file = "aiohttp-3.7.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895"}, - {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d"}, - {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54"}, - {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9"}, - {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c"}, - {file = "aiohttp-3.7.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b"}, - {file = "aiohttp-3.7.4-cp36-cp36m-win32.whl", hash = "sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329"}, - {file = "aiohttp-3.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6"}, - {file = "aiohttp-3.7.4-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe"}, - {file = "aiohttp-3.7.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2"}, - {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0"}, - {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de"}, - {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40"}, - {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb"}, - {file = "aiohttp-3.7.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242"}, - {file = "aiohttp-3.7.4-cp37-cp37m-win32.whl", hash = "sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9"}, - {file = "aiohttp-3.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9"}, - {file = "aiohttp-3.7.4-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2"}, - {file = "aiohttp-3.7.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907"}, - {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536"}, - {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297"}, - {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212"}, - {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9"}, - {file = "aiohttp-3.7.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0"}, - {file = "aiohttp-3.7.4-cp38-cp38-win32.whl", hash = "sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb"}, - {file = "aiohttp-3.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc"}, - {file = "aiohttp-3.7.4-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf"}, - {file = "aiohttp-3.7.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d"}, - {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a"}, - {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d"}, - {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4"}, - {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7"}, - {file = "aiohttp-3.7.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81"}, - {file = "aiohttp-3.7.4-cp39-cp39-win32.whl", hash = "sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0"}, - {file = "aiohttp-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e"}, - {file = "aiohttp-3.7.4.tar.gz", hash = "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de"}, -] - -[package.dependencies] -async-timeout = ">=3.0,<4.0" + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" -chardet = ">=2.0,<4.0" +frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -typing-extensions = ">=3.6.5" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["aiodns", "brotlipy", "cchardet"] +speedups = ["Brotli", "aiodns", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" [[package]] name = "altgraph" @@ -81,13 +134,13 @@ files = [ [[package]] name = "async-timeout" -version = "3.0.1" +version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.5.3" +python-versions = ">=3.7" files = [ - {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, - {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [[package]] @@ -240,31 +293,105 @@ files = [ [package.dependencies] pycparser = "*" -[[package]] -name = "chardet" -version = "3.0.4" -description = "Universal encoding detector for Python 2 and 3" -optional = false -python-versions = "*" -files = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] - [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.5.0" -files = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] -[package.extras] -unicode-backport = ["unicodedata2"] - [[package]] name = "comtypes" version = "1.3.1" @@ -296,6 +423,92 @@ whichcraft = "*" [package.extras] jinja2 = ["jinja2 (>=2.10)"] +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + [[package]] name = "future" version = "1.0.0" @@ -538,13 +751,13 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", [[package]] name = "jinja2" -version = "3.0.1" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, - {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -719,13 +932,13 @@ files = [ [[package]] name = "mutagen" -version = "1.45.1" +version = "1.47.0" description = "read and write audio tags for many formats" optional = false -python-versions = ">=3.5, <4" +python-versions = ">=3.7" files = [ - {file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"}, - {file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"}, + {file = "mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719"}, + {file = "mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"}, ] [[package]] @@ -791,13 +1004,13 @@ files = [ [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] @@ -3781,34 +3994,34 @@ files = [ [[package]] name = "requests" -version = "2.26.0" +version = "2.31.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sanic" -version = "21.9.3" +version = "21.12.2" description = "A web server and web framework that's written to go fast. Build fast. Run fast." optional = false python-versions = ">=3.7" files = [ - {file = "sanic-21.9.3-py3-none-any.whl", hash = "sha256:354fbc9e5382df23989752067a57d042aef99bf5a8e85593a3e0112276bff9e8"}, - {file = "sanic-21.9.3.tar.gz", hash = "sha256:5edb41d0d30cf47a25cf991b7465f53ea161b43e3cd20d8985e68ecf7fb7ad24"}, + {file = "sanic-21.12.2-py3-none-any.whl", hash = "sha256:ef4edddfba46f2f8728400470f84deb91d9e5fc21cf2531512acf70638699620"}, + {file = "sanic-21.12.2.tar.gz", hash = "sha256:c426e15aac6984860c6d1221329be17e02e2dfed4ce0abf8532ab096026ee5e3"}, ] [package.dependencies] @@ -3821,23 +4034,25 @@ uvloop = {version = ">=0.5.3", markers = "sys_platform != \"win32\" and implemen websockets = ">=10.0" [package.extras] -all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "m2r2", "mypy (>=0.901)", "pygments", "pytest (==5.2.1)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] -dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901)", "pygments", "pytest (==5.2.1)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] -docs = ["docutils", "m2r2", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] -test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901)", "pygments", "pytest (==5.2.1)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "types-ujson", "uvicorn (<0.15.0)"] +all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "cryptography", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "m2r2", "mistune (<2.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==6.2.5)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "cryptography", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==6.2.5)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +docs = ["docutils", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] +ext = ["sanic-ext"] +test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==6.2.5)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "types-ujson", "uvicorn (<0.15.0)"] [[package]] name = "sanic-cors" -version = "2.0.1" +version = "2.2.0" description = "A Sanic extension adding a decorator for CORS support. Based on flask-cors by Cory Dolphin." optional = false python-versions = "*" files = [ - {file = "Sanic-Cors-2.0.1.tar.gz", hash = "sha256:4d2f26333d49db428217814c66e89fc3df20fc62a5ab518a71fa22e2e249e19d"}, - {file = "Sanic_Cors-2.0.1-py2.py3-none-any.whl", hash = "sha256:0c8132bed394ba86f93c03bef52787183652d96b70add4ea13c25eb98f344343"}, + {file = "Sanic-Cors-2.2.0.tar.gz", hash = "sha256:f8d7515da4c8b837871d422c66314c4b5704396a78894b59c50e26aa72a95873"}, + {file = "Sanic_Cors-2.2.0-py2.py3-none-any.whl", hash = "sha256:c3b133ff1f0bb609a53db35f727f5c371dc4ebeb6be4cc2c37c19dd8b9301115"}, ] [package.dependencies] +packaging = ">=21.3" sanic = ">=21.9.3" [[package]] @@ -3853,36 +4068,103 @@ files = [ [[package]] name = "setproctitle" -version = "1.2.2" +version = "1.3.3" description = "A Python module to customize the process title" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "setproctitle-1.2.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9106bcbacae534b6f82955b176723f1b2ca6514518aab44dffec05a583f8dca8"}, - {file = "setproctitle-1.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:30bc7a769a4451639a0adcbc97bdf7a6e9ac0ef3ddad8d63eb1e338edb3ebeda"}, - {file = "setproctitle-1.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e8ef655eab26e83ec105ce79036bb87e5f2bf8ba2d6f48afdd9595ef7647fcf4"}, - {file = "setproctitle-1.2.2-cp36-cp36m-win32.whl", hash = "sha256:0df728d0d350e6b1ad8436cc7add052faebca6f4d03257182d427d86d4422065"}, - {file = "setproctitle-1.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:5260e8700c5793d48e79c5e607e8e552e795b698491a4b9bb9111eb74366a450"}, - {file = "setproctitle-1.2.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba1fb32e7267330bd9f72e69e076777a877f1cb9be5beac5e62d1279e305f37f"}, - {file = "setproctitle-1.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e696c93d93c23f377ccd2d72e38908d3dbfc90e45561602b805f53f2627d42ea"}, - {file = "setproctitle-1.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:fbf914179dc4540ee6bfd8228b4cc1f1f6fb12dad66b72b5c9b955b222403220"}, - {file = "setproctitle-1.2.2-cp37-cp37m-win32.whl", hash = "sha256:28b884e1cb9a53974e15838864283f9bad774b5c7db98c9609416bd123cb9fd1"}, - {file = "setproctitle-1.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a11d329f33221443317e2aeaee9442f22fcae25be3aa4fb8489e4f7b1f65cdd2"}, - {file = "setproctitle-1.2.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e13a5c1d9c369cb11cdfc4b75be432b83eb3205c95a69006008ffd4366f87b9e"}, - {file = "setproctitle-1.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c611f65bc9de5391a1514de556f71101e6531bb0715d240efd3e9732626d5c9e"}, - {file = "setproctitle-1.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:bc4393576ed3ac87ddac7d1bd0faaa2fab24840a025cc5f3c21d14cf0c9c8a12"}, - {file = "setproctitle-1.2.2-cp38-cp38-win32.whl", hash = "sha256:17598f38be9ef499d74f2380bf76b558be72e87da75d66b153350e586649171b"}, - {file = "setproctitle-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:0d160d46c8f3567e0aa27b26b1f36e03122e3de475aacacc14a92b8fe45b648a"}, - {file = "setproctitle-1.2.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:077943272d0490b3f43d17379432d5e49c263f608fdf4cf624b419db762ca72b"}, - {file = "setproctitle-1.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:970798d948f0c90a3eb0f8750f08cb215b89dcbee1b55ffb353ad62d9361daeb"}, - {file = "setproctitle-1.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3f6136966c81daaf5b4b010613fe33240a045a4036132ef040b623e35772d998"}, - {file = "setproctitle-1.2.2-cp39-cp39-win32.whl", hash = "sha256:249526a06f16d493a2cb632abc1b1fdfaaa05776339a50dd9f27c941f6ff1383"}, - {file = "setproctitle-1.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:4fc5bebd34f451dc87d2772ae6093adea1ea1dc29afc24641b250140decd23bb"}, - {file = "setproctitle-1.2.2.tar.gz", hash = "sha256:7dfb472c8852403d34007e01d6e3c68c57eb66433fb8a5c77b13b89a160d97df"}, + {file = "setproctitle-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:897a73208da48db41e687225f355ce993167079eda1260ba5e13c4e53be7f754"}, + {file = "setproctitle-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c331e91a14ba4076f88c29c777ad6b58639530ed5b24b5564b5ed2fd7a95452"}, + {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbbd6c7de0771c84b4aa30e70b409565eb1fc13627a723ca6be774ed6b9d9fa3"}, + {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c05ac48ef16ee013b8a326c63e4610e2430dbec037ec5c5b58fcced550382b74"}, + {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1342f4fdb37f89d3e3c1c0a59d6ddbedbde838fff5c51178a7982993d238fe4f"}, + {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc74e84fdfa96821580fb5e9c0b0777c1c4779434ce16d3d62a9c4d8c710df39"}, + {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9617b676b95adb412bb69645d5b077d664b6882bb0d37bfdafbbb1b999568d85"}, + {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6a249415f5bb88b5e9e8c4db47f609e0bf0e20a75e8d744ea787f3092ba1f2d0"}, + {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:38da436a0aaace9add67b999eb6abe4b84397edf4a78ec28f264e5b4c9d53cd5"}, + {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:da0d57edd4c95bf221b2ebbaa061e65b1788f1544977288bdf95831b6e44e44d"}, + {file = "setproctitle-1.3.3-cp310-cp310-win32.whl", hash = "sha256:a1fcac43918b836ace25f69b1dca8c9395253ad8152b625064415b1d2f9be4fb"}, + {file = "setproctitle-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:200620c3b15388d7f3f97e0ae26599c0c378fdf07ae9ac5a13616e933cbd2086"}, + {file = "setproctitle-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:334f7ed39895d692f753a443102dd5fed180c571eb6a48b2a5b7f5b3564908c8"}, + {file = "setproctitle-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:950f6476d56ff7817a8fed4ab207727fc5260af83481b2a4b125f32844df513a"}, + {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:195c961f54a09eb2acabbfc90c413955cf16c6e2f8caa2adbf2237d1019c7dd8"}, + {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f05e66746bf9fe6a3397ec246fe481096664a9c97eb3fea6004735a4daf867fd"}, + {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5901a31012a40ec913265b64e48c2a4059278d9f4e6be628441482dd13fb8b5"}, + {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64286f8a995f2cd934082b398fc63fca7d5ffe31f0e27e75b3ca6b4efda4e353"}, + {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:184239903bbc6b813b1a8fc86394dc6ca7d20e2ebe6f69f716bec301e4b0199d"}, + {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:664698ae0013f986118064b6676d7dcd28fefd0d7d5a5ae9497cbc10cba48fa5"}, + {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e5119a211c2e98ff18b9908ba62a3bd0e3fabb02a29277a7232a6fb4b2560aa0"}, + {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:417de6b2e214e837827067048f61841f5d7fc27926f2e43954567094051aff18"}, + {file = "setproctitle-1.3.3-cp311-cp311-win32.whl", hash = "sha256:6a143b31d758296dc2f440175f6c8e0b5301ced3b0f477b84ca43cdcf7f2f476"}, + {file = "setproctitle-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a680d62c399fa4b44899094027ec9a1bdaf6f31c650e44183b50d4c4d0ccc085"}, + {file = "setproctitle-1.3.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d4460795a8a7a391e3567b902ec5bdf6c60a47d791c3b1d27080fc203d11c9dc"}, + {file = "setproctitle-1.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bdfd7254745bb737ca1384dee57e6523651892f0ea2a7344490e9caefcc35e64"}, + {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477d3da48e216d7fc04bddab67b0dcde633e19f484a146fd2a34bb0e9dbb4a1e"}, + {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ab2900d111e93aff5df9fddc64cf51ca4ef2c9f98702ce26524f1acc5a786ae7"}, + {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088b9efc62d5aa5d6edf6cba1cf0c81f4488b5ce1c0342a8b67ae39d64001120"}, + {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6d50252377db62d6a0bb82cc898089916457f2db2041e1d03ce7fadd4a07381"}, + {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:87e668f9561fd3a457ba189edfc9e37709261287b52293c115ae3487a24b92f6"}, + {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:287490eb90e7a0ddd22e74c89a92cc922389daa95babc833c08cf80c84c4df0a"}, + {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe1c49486109f72d502f8be569972e27f385fe632bd8895f4730df3c87d5ac8"}, + {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4a6ba2494a6449b1f477bd3e67935c2b7b0274f2f6dcd0f7c6aceae10c6c6ba3"}, + {file = "setproctitle-1.3.3-cp312-cp312-win32.whl", hash = "sha256:2df2b67e4b1d7498632e18c56722851ba4db5d6a0c91aaf0fd395111e51cdcf4"}, + {file = "setproctitle-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f38d48abc121263f3b62943f84cbaede05749047e428409c2c199664feb6abc7"}, + {file = "setproctitle-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:816330675e3504ae4d9a2185c46b573105d2310c20b19ea2b4596a9460a4f674"}, + {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f960bc22d8d8e4ac886d1e2e21ccbd283adcf3c43136161c1ba0fa509088e0"}, + {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e6e7adff74796ef12753ff399491b8827f84f6c77659d71bd0b35870a17d8f"}, + {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53bc0d2358507596c22b02db079618451f3bd720755d88e3cccd840bafb4c41c"}, + {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6d20f9541f5f6ac63df553b6d7a04f313947f550eab6a61aa758b45f0d5657"}, + {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c1c84beab776b0becaa368254801e57692ed749d935469ac10e2b9b825dbdd8e"}, + {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:507e8dc2891021350eaea40a44ddd887c9f006e6b599af8d64a505c0f718f170"}, + {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b1067647ac7aba0b44b591936118a22847bda3c507b0a42d74272256a7a798e9"}, + {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e71f6365744bf53714e8bd2522b3c9c1d83f52ffa6324bd7cbb4da707312cd8"}, + {file = "setproctitle-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:7f1d36a1e15a46e8ede4e953abb104fdbc0845a266ec0e99cc0492a4364f8c44"}, + {file = "setproctitle-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9a402881ec269d0cc9c354b149fc29f9ec1a1939a777f1c858cdb09c7a261df"}, + {file = "setproctitle-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ff814dea1e5c492a4980e3e7d094286077054e7ea116cbeda138819db194b2cd"}, + {file = "setproctitle-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:accb66d7b3ccb00d5cd11d8c6e07055a4568a24c95cf86109894dcc0c134cc89"}, + {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554eae5a5b28f02705b83a230e9d163d645c9a08914c0ad921df363a07cf39b1"}, + {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a911b26264dbe9e8066c7531c0591cfab27b464459c74385b276fe487ca91c12"}, + {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2982efe7640c4835f7355fdb4da313ad37fb3b40f5c69069912f8048f77b28c8"}, + {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df3f4274b80709d8bcab2f9a862973d453b308b97a0b423a501bcd93582852e3"}, + {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:af2c67ae4c795d1674a8d3ac1988676fa306bcfa1e23fddb5e0bd5f5635309ca"}, + {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:af4061f67fd7ec01624c5e3c21f6b7af2ef0e6bab7fbb43f209e6506c9ce0092"}, + {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:37a62cbe16d4c6294e84670b59cf7adcc73faafe6af07f8cb9adaf1f0e775b19"}, + {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a83ca086fbb017f0d87f240a8f9bbcf0809f3b754ee01cec928fff926542c450"}, + {file = "setproctitle-1.3.3-cp38-cp38-win32.whl", hash = "sha256:059f4ce86f8cc92e5860abfc43a1dceb21137b26a02373618d88f6b4b86ba9b2"}, + {file = "setproctitle-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ab92e51cd4a218208efee4c6d37db7368fdf182f6e7ff148fb295ecddf264287"}, + {file = "setproctitle-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c7951820b77abe03d88b114b998867c0f99da03859e5ab2623d94690848d3e45"}, + {file = "setproctitle-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5bc94cf128676e8fac6503b37763adb378e2b6be1249d207630f83fc325d9b11"}, + {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f5d9027eeda64d353cf21a3ceb74bb1760bd534526c9214e19f052424b37e42"}, + {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e4a8104db15d3462e29d9946f26bed817a5b1d7a47eabca2d9dc2b995991503"}, + {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c32c41ace41f344d317399efff4cffb133e709cec2ef09c99e7a13e9f3b9483c"}, + {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf16381c7bf7f963b58fb4daaa65684e10966ee14d26f5cc90f07049bfd8c1e"}, + {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e18b7bd0898398cc97ce2dfc83bb192a13a087ef6b2d5a8a36460311cb09e775"}, + {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69d565d20efe527bd8a9b92e7f299ae5e73b6c0470f3719bd66f3cd821e0d5bd"}, + {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ddedd300cd690a3b06e7eac90ed4452348b1348635777ce23d460d913b5b63c3"}, + {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:415bfcfd01d1fbf5cbd75004599ef167a533395955305f42220a585f64036081"}, + {file = "setproctitle-1.3.3-cp39-cp39-win32.whl", hash = "sha256:21112fcd2195d48f25760f0eafa7a76510871bbb3b750219310cf88b04456ae3"}, + {file = "setproctitle-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:5a740f05d0968a5a17da3d676ce6afefebeeeb5ce137510901bf6306ba8ee002"}, + {file = "setproctitle-1.3.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6b9e62ddb3db4b5205c0321dd69a406d8af9ee1693529d144e86bd43bcb4b6c0"}, + {file = "setproctitle-1.3.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e3b99b338598de0bd6b2643bf8c343cf5ff70db3627af3ca427a5e1a1a90dd9"}, + {file = "setproctitle-1.3.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ae9a02766dad331deb06855fb7a6ca15daea333b3967e214de12cfae8f0ef5"}, + {file = "setproctitle-1.3.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:200ede6fd11233085ba9b764eb055a2a191fb4ffb950c68675ac53c874c22e20"}, + {file = "setproctitle-1.3.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0d3a953c50776751e80fe755a380a64cb14d61e8762bd43041ab3f8cc436092f"}, + {file = "setproctitle-1.3.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e08e232b78ba3ac6bc0d23ce9e2bee8fad2be391b7e2da834fc9a45129eb87"}, + {file = "setproctitle-1.3.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1da82c3e11284da4fcbf54957dafbf0655d2389cd3d54e4eaba636faf6d117a"}, + {file = "setproctitle-1.3.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:aeaa71fb9568ebe9b911ddb490c644fbd2006e8c940f21cb9a1e9425bd709574"}, + {file = "setproctitle-1.3.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:59335d000c6250c35989394661eb6287187854e94ac79ea22315469ee4f4c244"}, + {file = "setproctitle-1.3.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3ba57029c9c50ecaf0c92bb127224cc2ea9fda057b5d99d3f348c9ec2855ad3"}, + {file = "setproctitle-1.3.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d876d355c53d975c2ef9c4f2487c8f83dad6aeaaee1b6571453cb0ee992f55f6"}, + {file = "setproctitle-1.3.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:224602f0939e6fb9d5dd881be1229d485f3257b540f8a900d4271a2c2aa4e5f4"}, + {file = "setproctitle-1.3.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d7f27e0268af2d7503386e0e6be87fb9b6657afd96f5726b733837121146750d"}, + {file = "setproctitle-1.3.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5e7266498cd31a4572378c61920af9f6b4676a73c299fce8ba93afd694f8ae7"}, + {file = "setproctitle-1.3.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33c5609ad51cd99d388e55651b19148ea99727516132fb44680e1f28dd0d1de9"}, + {file = "setproctitle-1.3.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:eae8988e78192fd1a3245a6f4f382390b61bce6cfcc93f3809726e4c885fa68d"}, + {file = "setproctitle-1.3.3.tar.gz", hash = "sha256:c913e151e7ea01567837ff037a23ca8740192880198b7fbb90b16d181607caae"}, ] [package.extras] -test = ["pytest (>=6.1,<6.2)"] +test = ["pytest"] [[package]] name = "setuptools" @@ -3902,16 +4184,16 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar [[package]] name = "sounddevice" -version = "0.4.2" +version = "0.4.6" description = "Play and Record Sound with Python" optional = false -python-versions = ">=3" +python-versions = ">=3.7" files = [ - {file = "sounddevice-0.4.2-py3-none-any.whl", hash = "sha256:7820da4db09c83b5f8ff49a1caebd6a7a883c53e05df7edd09ca792bf1928b94"}, - {file = "sounddevice-0.4.2-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:ef5478d67e2dc9855c25b87dd3d5ef74c7ba25d21e6721419900c5bc8e4a9637"}, - {file = "sounddevice-0.4.2-py3-none-win32.whl", hash = "sha256:18bfd7efa121872c923b983ab253d6985cba988c331eab87029e48b51e739dbb"}, - {file = "sounddevice-0.4.2-py3-none-win_amd64.whl", hash = "sha256:22a5f72a9877ccba02f98f86dede44b7dc93a0fd468df6e8c0c47c2656274140"}, - {file = "sounddevice-0.4.2.tar.gz", hash = "sha256:1c9b07cff59c837d258002ed806ee134ed367ef11042bd7d283d6ce407bf889c"}, + {file = "sounddevice-0.4.6-py3-none-any.whl", hash = "sha256:5de768ba6fe56ad2b5aaa2eea794b76b73e427961c95acad2ee2ed7f866a4b20"}, + {file = "sounddevice-0.4.6-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:8b0b806c205dd3e3cd5a97262b2482624fd21db7d47083b887090148a08051c8"}, + {file = "sounddevice-0.4.6-py3-none-win32.whl", hash = "sha256:e3ba6e674ffa8f79a591d744a1d4ab922fe5bdfd4faf8b25069a08e051010b7b"}, + {file = "sounddevice-0.4.6-py3-none-win_amd64.whl", hash = "sha256:7830d4f8f8570f2e5552942f81d96999c5fcd9a0b682d6fc5d5c5529df23be2c"}, + {file = "sounddevice-0.4.6.tar.gz", hash = "sha256:3236b78f15f0415bdf006a620cef073d0c0522851d66f4a961ed6d8eb1482fe9"}, ] [package.dependencies] @@ -3943,14 +4225,14 @@ files = [ [[package]] name = "typing-extensions" -version = "3.10.0.0" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" optional = false python-versions = "*" files = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] [[package]] @@ -4029,19 +4311,20 @@ files = [ [[package]] name = "urllib3" -version = "1.26.18" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.8" files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvloop" @@ -4089,59 +4372,80 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)" [[package]] name = "websockets" -version = "10.1" +version = "10.4" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.7" files = [ - {file = "websockets-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc"}, - {file = "websockets-10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60"}, - {file = "websockets-10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3477146d1f87ead8df0f27e8960249f5248dceb7c2741e8bbec9aa5338d0c053"}, - {file = "websockets-10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb01ea7b5f52e7125bdc3c5807aeaa2d08a0553979cf2d96a8b7803ea33e15e7"}, - {file = "websockets-10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9fd62c6dc83d5d35fb6a84ff82ec69df8f4657fff05f9cd6c7d9bec0dd57f0f6"}, - {file = "websockets-10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbf080f3892ba1dc8838786ec02899516a9d227abe14a80ef6fd17d4fb57127"}, - {file = "websockets-10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5560558b0dace8312c46aa8915da977db02738ac8ecffbc61acfbfe103e10155"}, - {file = "websockets-10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:667c41351a6d8a34b53857ceb8343a45c85d438ee4fd835c279591db8aeb85be"}, - {file = "websockets-10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:468f0031fdbf4d643f89403a66383247eb82803430b14fa27ce2d44d2662ca37"}, - {file = "websockets-10.1-cp310-cp310-win32.whl", hash = "sha256:d0d81b46a5c87d443e40ce2272436da8e6092aa91f5fbeb60d1be9f11eff5b4c"}, - {file = "websockets-10.1-cp310-cp310-win_amd64.whl", hash = "sha256:b68b6caecb9a0c6db537aa79750d1b592a841e4f1a380c6196091e65b2ad35f9"}, - {file = "websockets-10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a249139abc62ef333e9e85064c27fefb113b16ffc5686cefc315bdaef3eefbc8"}, - {file = "websockets-10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8877861e3dee38c8d302eee0d5dbefa6663de3b46dc6a888f70cd7e82562d1f7"}, - {file = "websockets-10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e3872ae57acd4306ecf937d36177854e218e999af410a05c17168cd99676c512"}, - {file = "websockets-10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b66e6d514f12c28d7a2d80bb2a48ef223342e99c449782d9831b0d29a9e88a17"}, - {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9f304a22ece735a3da8a51309bc2c010e23961a8f675fae46fdf62541ed62123"}, - {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:189ed478395967d6a98bb293abf04e8815349e17456a0a15511f1088b6cb26e4"}, - {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:08a42856158307e231b199671c4fce52df5786dd3d703f36b5d8ac76b206c485"}, - {file = "websockets-10.1-cp37-cp37m-win32.whl", hash = "sha256:3ef6f73854cded34e78390dbdf40dfdcf0b89b55c0e282468ef92646fce8d13a"}, - {file = "websockets-10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:89e985d40d407545d5f5e2e58e1fdf19a22bd2d8cd54d20a882e29f97e930a0a"}, - {file = "websockets-10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:002071169d2e44ce8eb9e5ebac9fbce142ba4b5146eef1cfb16b177a27662657"}, - {file = "websockets-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfae282c2aa7f0c4be45df65c248481f3509f8c40ca8b15ed96c35668ae0ff69"}, - {file = "websockets-10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:97b4b68a2ddaf5c4707ae79c110bfd874c5be3c6ac49261160fb243fa45d8bbb"}, - {file = "websockets-10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c9407719f42cb77049975410490c58a705da6af541adb64716573e550e5c9db"}, - {file = "websockets-10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d858fb31e5ac992a2cdf17e874c95f8a5b1e917e1fb6b45ad85da30734b223f"}, - {file = "websockets-10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7bdd3d26315db0a9cf8a0af30ca95e0aa342eda9c1377b722e71ccd86bc5d1dd"}, - {file = "websockets-10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e259be0863770cb91b1a6ccf6907f1ac2f07eff0b7f01c249ed751865a70cb0d"}, - {file = "websockets-10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6b014875fae19577a392372075e937ebfebf53fd57f613df07b35ab210f31534"}, - {file = "websockets-10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:98de71f86bdb29430fd7ba9997f47a6b10866800e3ea577598a786a785701bb0"}, - {file = "websockets-10.1-cp38-cp38-win32.whl", hash = "sha256:3a02ab91d84d9056a9ee833c254895421a6333d7ae7fff94b5c68e4fa8095519"}, - {file = "websockets-10.1-cp38-cp38-win_amd64.whl", hash = "sha256:7d6673b2753f9c5377868a53445d0c321ef41ff3c8e3b6d57868e72054bfce5f"}, - {file = "websockets-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ddab2dc69ee5ae27c74dbfe9d7bb6fee260826c136dca257faa1a41d1db61a89"}, - {file = "websockets-10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14e9cf68a08d1a5d42109549201aefba473b1d925d233ae19035c876dd845da9"}, - {file = "websockets-10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4819c6fb4f336fd5388372cb556b1f3a165f3f68e66913d1a2fc1de55dc6f58"}, - {file = "websockets-10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05e7f098c76b0a4743716590bb8f9706de19f1ef5148d61d0cf76495ec3edb9c"}, - {file = "websockets-10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bb6256de5a4fb1d42b3747b4e2268706c92965d75d0425be97186615bf2f24f"}, - {file = "websockets-10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:888a5fa2a677e0c2b944f9826c756475980f1b276b6302e606f5c4ff5635be9e"}, - {file = "websockets-10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6fdec1a0b3e5630c58e3d8704d2011c678929fce90b40908c97dfc47de8dca72"}, - {file = "websockets-10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:531d8eb013a9bc6b3ad101588182aa9b6dd994b190c56df07f0d84a02b85d530"}, - {file = "websockets-10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0d93b7cadc761347d98da12ec1930b5c71b2096f1ceed213973e3cda23fead9c"}, - {file = "websockets-10.1-cp39-cp39-win32.whl", hash = "sha256:d9b245db5a7e64c95816e27d72830e51411c4609c05673d1ae81eb5d23b0be54"}, - {file = "websockets-10.1-cp39-cp39-win_amd64.whl", hash = "sha256:882c0b8bdff3bf1bd7f024ce17c6b8006042ec4cceba95cf15df57e57efa471c"}, - {file = "websockets-10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:10edd9d7d3581cfb9ff544ac09fc98cab7ee8f26778a5a8b2d5fd4b0684c5ba5"}, - {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa83174390c0ff4fc1304fbe24393843ac7a08fdd59295759c4b439e06b1536"}, - {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:483edee5abed738a0b6a908025be47f33634c2ad8e737edd03ffa895bd600909"}, - {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:816ae7dac2c6522cfa620947ead0ca95ac654916eebf515c94d7c28de5601a6e"}, - {file = "websockets-10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1dafe98698ece09b8ccba81b910643ff37198e43521d977be76caf37709cf62b"}, - {file = "websockets-10.1.tar.gz", hash = "sha256:181d2b25de5a437b36aefedaf006ecb6fa3aa1328ec0236cdde15f32f9d3ff6d"}, + {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, + {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, + {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, + {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, + {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, + {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, + {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, + {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, + {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, + {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, + {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, + {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, + {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, + {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, + {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, + {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, + {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, + {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, + {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, + {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, + {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, + {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, + {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, + {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, ] [[package]] @@ -4361,4 +4665,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.13" -content-hash = "f9a6c80bce20fd5641715e8d7ab4308022ec24c0f2701ab32629bd1a8436a6f0" +content-hash = "e767e639664c98edec535b6759890827381f5b3dbef5f4ba861e59eeaf0f4b8e" diff --git a/pyproject.toml b/pyproject.toml index 9a5e7fe..a98a07b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,21 +9,21 @@ packages = [{include = "__init__.py"}] [tool.poetry.dependencies] python = ">=3.8,<3.13" wheel = "^0.43.0" -pygame = "2.5.2" -sanic = "21.9.3" -sanic-cors = "2.0.1" -syncer = "1.3.0" -aiohttp = "3.7.4" -mutagen = "1.45.1" -sounddevice = "0.4.2" -setproctitle = "1.2.2" -pyttsx3 = "2.90" -websockets = "10.1" -typing-extensions = "3.10.0.0" -pyserial = "3.5" -requests = "2.26.0" -jinja2 = "3.0.1" -pydub = "0.25.1" +pygame = "^2.5.2" +sanic = "^21.9.3" +sanic-cors = "^2.0.1" +syncer = "^1.3.0" +aiohttp = "^3.7.4" +mutagen = "^1.45.1" +sounddevice = "^0.4.2" +setproctitle = "^1.2.2" +pyttsx3 = "^2.90" +websockets = "^10.1" +typing-extensions = "^3.10.0.0" +pyserial = "^3.5" +requests = "^2.26.0" +jinja2 = "^3.0.1" +pydub = "^0.25.1" psutil = "^5.9.8" [tool.poetry.group.dev.dependencies] From 1e26fc8838859d4881e488b5bd76a5974b7460fe Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 17:45:17 +0100 Subject: [PATCH 148/156] fix(ci): update to actions/upload-artifact@v4 and set unique names for each build --- .github/workflows/build.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 804a502..ab93a90 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -71,9 +71,9 @@ jobs: run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" id: extract_branch - name: Archive Build - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-Ubuntu + name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-Ubuntu-python${{matrix.python-version}}-node${{matrix.node-version}} path: | build/output/BAPSicle @@ -109,9 +109,9 @@ jobs: run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" id: extract_branch - name: Archive Build - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-Windows + name: BAPSicle-${{ steps.extract_branch.outputs.branch }}-${{github.sha}}-Windows-python${{matrix.python-version}}-node${{matrix.node-version}} path: | build/output/BAPSicle.exe install/ From 45a3568a9be9aceafdf4e21534b2b8728e9f5918 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 18:01:09 +0100 Subject: [PATCH 149/156] fix(ci): hopefully fix the test workflow --- .github/workflows/test.yaml | 11 +-- poetry.lock | 161 ++++++++++++++++++++++++++++++++++-- pyproject.toml | 6 +- 3 files changed, 159 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a4b120c..b5edbef 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -19,14 +19,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r build/requirements-dev.txt - pip install -r build/requirements.txt - pip install -r build/requirements-macos.txt - - name: Install bapsicle as module - run: | - pip install -e . + run: poetry install --with=dev - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names @@ -41,7 +34,7 @@ jobs: python -m unittest - name: Archive test logs if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Logs - Python ${{ matrix.python-version }} path: | diff --git a/poetry.lock b/poetry.lock index 86fb368..bdb2ab3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -193,6 +193,52 @@ files = [ pycodestyle = ">=2.11.0" tomli = {version = "*", markers = "python_version < \"3.11\""} +[[package]] +name = "black" +version = "24.3.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "bottle" version = "0.12.25" @@ -392,6 +438,31 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + [[package]] name = "comtypes" version = "1.3.1" @@ -423,6 +494,22 @@ whichcraft = "*" [package.extras] jinja2 = ["jinja2 (>=2.10)"] +[[package]] +name = "flake8" +version = "7.0.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, + {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.2.0,<3.3.0" + [[package]] name = "frozenlist" version = "1.4.1" @@ -849,6 +936,17 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "multidict" version = "5.2.0" @@ -941,6 +1039,17 @@ files = [ {file = "mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" version = "24.0" @@ -952,6 +1061,17 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + [[package]] name = "pefile" version = "2023.2.7" @@ -963,6 +1083,21 @@ files = [ {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, ] +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + [[package]] name = "psutil" version = "5.9.8" @@ -1024,6 +1159,17 @@ files = [ {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"}, ] +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + [[package]] name = "pygame" version = "2.5.2" @@ -4225,14 +4371,13 @@ files = [ [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] @@ -4664,5 +4809,5 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" -python-versions = ">=3.8,<3.13" -content-hash = "e767e639664c98edec535b6759890827381f5b3dbef5f4ba861e59eeaf0f4b8e" +python-versions = ">=3.8.1,<3.13" +content-hash = "db5bd468bbc2c8a82ba3d837964115d6107c0b07a70479477cc74abfd95f2d6b" diff --git a/pyproject.toml b/pyproject.toml index a98a07b..0208996 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" packages = [{include = "__init__.py"}] [tool.poetry.dependencies] -python = ">=3.8,<3.13" +python = ">=3.8.1,<3.13" wheel = "^0.43.0" pygame = "^2.5.2" sanic = "^21.9.3" @@ -19,7 +19,7 @@ sounddevice = "^0.4.2" setproctitle = "^1.2.2" pyttsx3 = "^2.90" websockets = "^10.1" -typing-extensions = "^3.10.0.0" +typing-extensions = "^4.10.0" pyserial = "^3.5" requests = "^2.26.0" jinja2 = "^3.0.1" @@ -34,6 +34,8 @@ pyinstaller = [ auto-py-to-exe = {version = "^2.43.3", platform = "win32"} pywin32 = {version = "^306", platform = "win32"} autopep8 = "^2.1.0" +flake8 = "^7.0.0" +black = "^24.3.0" [build-system] requires = ["poetry-core"] From c2795b84bb293910a9fc178ae9040ac7ed413796 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 18:02:57 +0100 Subject: [PATCH 150/156] fix(ci): install poetry --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b5edbef..b54a30c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -18,6 +18,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Setup poetry + uses: Gr1N/setup-poetry@v8 - name: Install Python dependencies run: poetry install --with=dev - name: Lint with flake8 From 046fd3e86ad8be9a2062746ec502090e365fdf82 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 18:04:39 +0100 Subject: [PATCH 151/156] fix(ci): run python commands with poetry --- .github/workflows/test.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b54a30c..630c816 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,15 +25,15 @@ jobs: - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics + poetry run flake8 . --count --ignore=E402,E226,E24,W50,W690 --max-complexity=25 --max-line-length=127 --statistics - name: Test with unittest if: ${{ always() }} timeout-minutes: 10 run: | - python -m sounddevice - python -m unittest + poetry run python -m sounddevice + poetry run python -m unittest - name: Archive test logs if: ${{ always() }} uses: actions/upload-artifact@v4 From 7a2145353d3f07ac08e30203fd7524b1c98ebb97 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 17:55:12 +0100 Subject: [PATCH 152/156] feat: update sanic to latest release --- poetry.lock | 225 ++++++++++++++++++++++++++++++------------------- pyproject.toml | 4 +- web_server.py | 5 +- 3 files changed, 142 insertions(+), 92 deletions(-) diff --git a/poetry.lock b/poetry.lock index bdb2ab3..6a21415 100644 --- a/poetry.lock +++ b/poetry.lock @@ -758,6 +758,17 @@ files = [ docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] +[[package]] +name = "html5tagger" +version = "1.3.0" +description = "Pythonic HTML generation/templating (no template files)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "html5tagger-1.3.0-py3-none-any.whl", hash = "sha256:ce14313515edffec8ed8a36c5890d023922641171b4e6e5774ad1a74998f5351"}, + {file = "html5tagger-1.3.0.tar.gz", hash = "sha256:84fa3dfb49e5c83b79bbd856ab7b1de8e2311c3bb46a8be925f119e3880a8da9"}, +] + [[package]] name = "httptools" version = "0.6.1" @@ -949,83 +960,101 @@ files = [ [[package]] name = "multidict" -version = "5.2.0" +version = "6.0.5" description = "multidict implementation" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"}, - {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"}, - {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"}, - {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"}, - {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"}, - {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"}, - {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"}, - {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"}, - {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"}, - {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"}, - {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"}, - {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"}, - {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"}, - {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"}, - {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"}, - {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] @@ -4161,30 +4190,34 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sanic" -version = "21.12.2" +version = "23.12.1" description = "A web server and web framework that's written to go fast. Build fast. Run fast." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "sanic-21.12.2-py3-none-any.whl", hash = "sha256:ef4edddfba46f2f8728400470f84deb91d9e5fc21cf2531512acf70638699620"}, - {file = "sanic-21.12.2.tar.gz", hash = "sha256:c426e15aac6984860c6d1221329be17e02e2dfed4ce0abf8532ab096026ee5e3"}, + {file = "sanic-23.12.1-py3-none-any.whl", hash = "sha256:e292293b2663a7afeb380bdc48ab93978468b27deae46ad9561513941eb0311f"}, + {file = "sanic-23.12.1.tar.gz", hash = "sha256:2528ca81d2bdc58ea67d93c500df1a9c58404904b0bc3442425b464c72b4bb84"}, ] [package.dependencies] aiofiles = ">=0.6.0" +html5tagger = ">=1.2.1" httptools = ">=0.0.10" -multidict = ">=5.0,<6.0" -sanic-routing = ">=0.7,<1.0" +multidict = ">=5.0,<7.0" +sanic-routing = ">=23.12.0" +tracerite = ">=1.0.0" +typing-extensions = ">=4.4.0" ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} -uvloop = {version = ">=0.5.3", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} +uvloop = {version = ">=0.15.0", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} websockets = ">=10.0" [package.extras] -all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "cryptography", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "m2r2", "mistune (<2.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==6.2.5)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] -dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "cryptography", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==6.2.5)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] -docs = ["docutils", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] +all = ["autodocsumm (>=0.2.11)", "bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +dev = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +docs = ["autodocsumm (>=0.2.11)", "docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] ext = ["sanic-ext"] -test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage (==5.3)", "docutils", "flake8", "gunicorn (==20.0.4)", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==6.2.5)", "pytest-benchmark", "pytest-cov", "pytest-sanic", "pytest-sugar", "sanic-testing (>=0.7.0)", "types-ujson", "uvicorn (<0.15.0)"] +http3 = ["aioquic"] +test = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "docutils", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn (<0.15.0)"] [[package]] name = "sanic-cors" @@ -4203,13 +4236,13 @@ sanic = ">=21.9.3" [[package]] name = "sanic-routing" -version = "0.7.2" +version = "23.12.0" description = "Core routing component for Sanic" optional = false python-versions = "*" files = [ - {file = "sanic-routing-0.7.2.tar.gz", hash = "sha256:139ce88b3f054e7aa336e2ecc8459837092b103b275d3a97609a34092c55374d"}, - {file = "sanic_routing-0.7.2-py3-none-any.whl", hash = "sha256:523034ffd07aca056040e08de438269c9a880722eee1ace3a32e4f74b394d9aa"}, + {file = "sanic-routing-23.12.0.tar.gz", hash = "sha256:1dcadc62c443e48c852392dba03603f9862b6197fc4cba5bbefeb1ace0848b04"}, + {file = "sanic_routing-23.12.0-py3-none-any.whl", hash = "sha256:1558a72afcb9046ed3134a5edae02fc1552cff08f0fff2e8d5de0877ea43ed73"}, ] [[package]] @@ -4369,6 +4402,20 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tracerite" +version = "1.1.1" +description = "Human-readable HTML tracebacks for Python exceptions" +optional = false +python-versions = "*" +files = [ + {file = "tracerite-1.1.1-py3-none-any.whl", hash = "sha256:3a787a9ecb1a136ea9ce17e6328e414ec414a4f644130af4e1e330bec2dece29"}, + {file = "tracerite-1.1.1.tar.gz", hash = "sha256:6400a35a187747189e4bb8d4a8e471bd86d14dbdcc94bcad23f4eda023f41356"}, +] + +[package.dependencies] +html5tagger = ">=1.2.1" + [[package]] name = "typing-extensions" version = "4.10.0" @@ -4810,4 +4857,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<3.13" -content-hash = "db5bd468bbc2c8a82ba3d837964115d6107c0b07a70479477cc74abfd95f2d6b" +content-hash = "2cfe1c9226070a1eca74c8612b33f790628bee81ea3a727f4ea68dece6328743" diff --git a/pyproject.toml b/pyproject.toml index 0208996..1d3217f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ packages = [{include = "__init__.py"}] python = ">=3.8.1,<3.13" wheel = "^0.43.0" pygame = "^2.5.2" -sanic = "^21.9.3" -sanic-cors = "^2.0.1" +sanic = "^23.12.1" +sanic-cors = "^2.2.0" syncer = "^1.3.0" aiohttp = "^3.7.4" mutagen = "^1.45.1" diff --git a/web_server.py b/web_server.py index 1189abe..ccc3979 100644 --- a/web_server.py +++ b/web_server.py @@ -88,8 +88,10 @@ }, ) -app = Sanic("BAPSicle-WebServer", log_config=LOGGING_CONFIG) +# https://sanic.dev/en/guide/running/manager.html#overcoming-a-coderuntimeerrorcode +Sanic.START_METHOD_SET = True +app = Sanic("BAPSicle-WebServer", log_config=LOGGING_CONFIG) def render_template(file, data, status=200): template = env.get_template(file) @@ -553,6 +555,7 @@ def WebServer(player_to: List[Queue], player_from: Queue, state: StateManager): host=server_state.get()["host"], port=server_state.get()["port"], auto_reload=False, + single_process=True, debug=not package.BETA, access_log=not package.BETA, ) From 5e489a6408fffaa97443ad1f1a6d83920e337dae Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 18:21:13 +0100 Subject: [PATCH 153/156] chore: bump minimum versions of other packages --- poetry.lock | 151 +++++++++++++++++++++++++------------------------ pyproject.toml | 16 +++--- 2 files changed, 85 insertions(+), 82 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6a21415..b3b69e8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4383,12 +4383,12 @@ numpy = ["NumPy"] [[package]] name = "syncer" -version = "1.3.0" +version = "2.0.3" description = "Async to sync converter" optional = false python-versions = "*" files = [ - {file = "syncer-1.3.0.tar.gz", hash = "sha256:6eb756f345388e08763f9695e7b5071b7657f0a7e809e486e3f9ca3fb4cd4d2c"}, + {file = "syncer-2.0.3.tar.gz", hash = "sha256:4340eb54b54368724a78c5c0763824470201804fe9180129daf3635cb500550f"}, ] [[package]] @@ -4564,80 +4564,83 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)" [[package]] name = "websockets" -version = "10.4" +version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, - {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, - {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, - {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, - {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, - {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, - {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, - {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, - {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, - {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, - {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, - {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, - {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, - {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, - {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, - {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, - {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, - {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, - {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, - {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, - {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, - {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, - {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, - {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, - {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, - {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, - {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, - {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, - {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, - {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, ] [[package]] @@ -4857,4 +4860,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<3.13" -content-hash = "2cfe1c9226070a1eca74c8612b33f790628bee81ea3a727f4ea68dece6328743" +content-hash = "809e9580926300dec48298b4a01a26f731ec9a7fa8069bfce555f8d71d37893b" diff --git a/pyproject.toml b/pyproject.toml index 1d3217f..cd86827 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,17 +12,17 @@ wheel = "^0.43.0" pygame = "^2.5.2" sanic = "^23.12.1" sanic-cors = "^2.2.0" -syncer = "^1.3.0" -aiohttp = "^3.7.4" -mutagen = "^1.45.1" -sounddevice = "^0.4.2" -setproctitle = "^1.2.2" +syncer = "^2.0.3" +aiohttp = "^3.9.3" +mutagen = "^1.47.0" +sounddevice = "^0.4.6" +setproctitle = "^1.3.3" pyttsx3 = "^2.90" -websockets = "^10.1" +websockets = "^12.0" typing-extensions = "^4.10.0" pyserial = "^3.5" -requests = "^2.26.0" -jinja2 = "^3.0.1" +requests = "^2.31.0" +jinja2 = "^3.1.3" pydub = "^0.25.1" psutil = "^5.9.8" From f04e1ad8a9d3acf96ae8f5124514d5abf1e43f00 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 31 Mar 2024 18:22:29 +0100 Subject: [PATCH 154/156] docs: tested up to python 3.11 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25a91ba..f71303a 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Starting and stopping the server, as well as UI links, are available in the Syst On all platforms: -- Python 3.8 - 3.9 Tested +- Python 3.8 - 3.11 Tested - Git (Obviously) On MacOS: From 99933083e7863e3360f141ad86986baba0cb8191 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Wed, 3 Apr 2024 19:16:45 +0100 Subject: [PATCH 155/156] fix: include resources from tracerite package in final build --- build/build-exe-config.template.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/build-exe-config.template.json b/build/build-exe-config.template.json index c40e3c2..7f99378 100644 --- a/build/build-exe-config.template.json +++ b/build/build-exe-config.template.json @@ -88,6 +88,10 @@ { "optionDest": "collect-all", "value": "setproctitle" + }, + { + "optionDest": "collect-all", + "value": "tracerite" } ], "nonPyinstallerOptions": { From a10c33a25c98f76ff2f9a7eb3e76a9ce57547522 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Wed, 3 Apr 2024 19:28:40 +0100 Subject: [PATCH 156/156] chore: bump to 3.1.1 --- package.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2d3161e..0dac03d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bapsicle", "nice_name": "BAPSicle", - "version": "3.1.0", + "version": "3.1.1", "description": "BAPS3, the third generation of University Radio York's Broadcast and Presenting Suite. This package includes the Server (BAPSicle) and Presenter (WebStudio)", "main": "index.js", "directories": { diff --git a/pyproject.toml b/pyproject.toml index cd86827..af74792 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bapsicle" -version = "3.1.0" +version = "3.1.1" description = "" authors = ["University Radio York"] readme = "README.md"