From cec39c58af94c04e90c0511cff4d08731648fd8a Mon Sep 17 00:00:00 2001 From: maykar <25127328+maykar@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:00:27 -0500 Subject: [PATCH] rapidfuzz, title caching, cleanup --- custom_components/plex_assistant/__init__.py | 19 ++++--- .../plex_assistant/config_flow.py | 4 +- custom_components/plex_assistant/helpers.py | 56 +++++++++---------- .../plex_assistant/manifest.json | 2 +- .../plex_assistant/plex_assistant.py | 19 ++++--- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/custom_components/plex_assistant/__init__.py b/custom_components/plex_assistant/__init__.py index 8eac19b..318ff03 100644 --- a/custom_components/plex_assistant/__init__.py +++ b/custom_components/plex_assistant/__init__.py @@ -39,8 +39,10 @@ async def async_setup(hass: HomeAssistant, config: Config): if DOMAIN in config: changes_url = "https://github.com/maykar/plex_assistant/blob/master/ver_one_update.md" - message = "Configuration is now handled in the UI, please read the %s for how to migrate " \ - "to the new version and more info.%s " + message = ( + "Configuration is now handled in the UI, please read the %s for how to migrate " + "to the new version and more info.%s " + ) service_data = { "title": "Plex Assistant Breaking Changes", "message": message % (f"[change log]({changes_url})", "."), @@ -71,17 +73,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): def pa_executor(_server, start_script_keys): _pa = PlexAssistant(_server, start_script_keys) get_devices(hass, _pa) + _LOGGER.debug(f"Media titles: {len(_pa.media['all_titles'])}") return _pa pa = await hass.async_add_executor_job(pa_executor, server, list(start_script.keys())) - ifttt_listener = await listeners(hass) - hass.data[DOMAIN][entry.entry_id] = {"remove_listener": ifttt_listener} - tts_dir = hass.config.path() + "/www/plex_assist_tts/" if tts_errors and not os.path.exists(tts_dir): os.makedirs(tts_dir, mode=0o777) + ifttt_listener = await listeners(hass) + hass.data[DOMAIN][entry.entry_id] = {"remove_listener": ifttt_listener} entry.add_update_listener(async_reload_entry) def handle_input(call): @@ -108,7 +110,8 @@ def handle_input(call): return if pa.media["updated"] < pa.library.search(sort="addedAt:desc", limit=1)[0].addedAt: - pa.update_libraries() + type(pa).media.fget.cache_clear() + _LOGGER.debug(f"Updated Library: {pa.media['updated']}") device = fuzzy(command["device"] or default_device, pa.device_names) device = run_start_script(hass, pa, command, start_script, device, default_device) @@ -135,12 +138,12 @@ def handle_input(call): play_tts_error(hass, tts_dir, device["entity_id"], error, lang) return - _LOGGER.debug("Media: %s", str(media)) + _LOGGER.debug("Media: %s", str(media.items)) payload = '%s{"playqueue_id": %s, "type": "%s"}' % ( "plex://" if device["device_type"] in ["cast", "sonos"] else "", media.playQueueID, - media.playQueueType + media.playQueueType, ) media_service(hass, device["entity_id"], "play_media", payload) diff --git a/custom_components/plex_assistant/config_flow.py b/custom_components/plex_assistant/config_flow.py index cf29e4a..7e0c28e 100644 --- a/custom_components/plex_assistant/config_flow.py +++ b/custom_components/plex_assistant/config_flow.py @@ -14,7 +14,7 @@ def get_devices(_self): if "plex" in info or "cast" in info: try: devices.append(_self.hass.states.get(entity.entity_id).attributes.get("friendly_name")) - except: + except AttributeError: continue else: continue @@ -24,7 +24,7 @@ def get_devices(_self): def get_servers(_self): try: return [x.title for x in _self.hass.config_entries.async_entries("plex")] - except: + except (KeyError, AttributeError): return [] diff --git a/custom_components/plex_assistant/helpers.py b/custom_components/plex_assistant/helpers.py index 5ba6da8..8ad0eea 100644 --- a/custom_components/plex_assistant/helpers.py +++ b/custom_components/plex_assistant/helpers.py @@ -1,13 +1,13 @@ import re import time import uuid -import json import pychromecast -from fuzzywuzzy import fuzz -from fuzzywuzzy import process +from rapidfuzz import fuzz, process from gtts import gTTS +from json import JSONDecodeError, loads from homeassistant.components.plex.services import get_plex_server +from homeassistant.exceptions import HomeAssistantError, ServiceNotFound from homeassistant.core import Context from pychromecast.controllers.plex import PlexController @@ -16,38 +16,34 @@ def fuzzy(media, lib, scorer=fuzz.QRatio): if isinstance(lib, list) and len(lib) > 0: - return process.extractOne(media, lib, scorer=scorer) + return process.extractOne(media, lib, scorer=scorer) or ["", 0] return ["", 0] -def process_config_item(options, item_type): - item = options.get(item_type) - if item: - try: - item = json.loads("{" + item + "}") - for i in item.keys(): - _LOGGER.debug(f"{item_type} {i}: {item[i]}") - except Exception: - item = {} - return item - return {} +def process_config_item(options, option_type): + option = options.get(option_type) + if not option: + return {} + try: + option = loads("{" + option + "}") + for i in option.keys(): + _LOGGER.debug(f"{option_type} {i}: {option[i]}") + except (TypeError, AttributeError, KeyError, JSONDecodeError): + _LOGGER.warning(f"There is a formatting error in the {option_type.replace('_', ' ')} config.") + option = {} + return option async def get_server(hass, config, server_name): try: await hass.helpers.discovery.async_discover(None, None, "plex", config) return get_plex_server(hass, server_name)._plex_server - except Exception as ex: - if ex.args[0] == "No Plex servers available": - server_name_str = ", the server_name is correct," if server_name else "" - _LOGGER.warning( - "Plex Assistant: Plex server not found. Ensure that you've setup the HA " - f"Plex integration{server_name_str} and the server is reachable. " - ) - else: - template = "An exception of type {0} occurred. Arguments:\n{1!r}" - message = template.format(type(ex).__name__, ex.args) - _LOGGER.warning(message) + except HomeAssistantError as error: + server_name_str = ", the server_name is correct," if server_name else "" + _LOGGER.warning( + f"Plex Assistant: {error.args[0]}. Ensure that you've setup the HA " + f"Plex integration{server_name_str} and the server is reachable. " + ) def get_devices(hass, pa): @@ -58,7 +54,7 @@ def get_devices(hass, pa): continue try: name = hass.states.get(entity.entity_id).attributes.get("friendly_name") - except Exception: + except AttributeError: continue pa.devices[name] = {"entity_id": entity.entity_id, "device_type": dev_type} @@ -81,7 +77,7 @@ def ifttt_webhook_callback(event): listener = hass.bus.async_listen("ifttt_webhook_received", ifttt_webhook_callback) try: await hass.services.async_call("conversation", "process", {"text": "tell plex to initialize_plex_intent"}) - except Exception: + except ServiceNotFound: pass return listener @@ -253,14 +249,14 @@ def filter_media(pa, command, media, library): media = unwatched if unwatched and not command["random"] else media.episodes()[:30] elif getattr(media, "TYPE", None) == "episode": episodes = media.show().episodes() - episodes = episodes[episodes.index(media):episodes.index(media) + 30] + episodes = episodes[episodes.index(media) : episodes.index(media) + 30] media = pa.server.createPlayQueue(episodes, shuffle=int(command["random"])) elif getattr(media, "TYPE", None) in ["artist", "album"]: tracks = media.tracks() media = pa.server.createPlayQueue(tracks, shuffle=int(command["random"])) elif getattr(media, "TYPE", None) == "track": tracks = media.album().tracks() - tracks = tracks[tracks.index(media):] + tracks = tracks[tracks.index(media) :] media = pa.server.createPlayQueue(tracks, shuffle=int(command["random"])) if getattr(media, "TYPE", None) != "playqueue" and media: diff --git a/custom_components/plex_assistant/manifest.json b/custom_components/plex_assistant/manifest.json index b89b4da..4b6cdba 100644 --- a/custom_components/plex_assistant/manifest.json +++ b/custom_components/plex_assistant/manifest.json @@ -9,7 +9,7 @@ "requirements": [ "gTTs>=2.2.1", "pychromecast>=8.0.0", - "fuzzywuzzy==0.18.0", + "rapidfuzz==1.1.1", "plexapi>=4.3.0", "awesomeversion>=21.2.2" ] diff --git a/custom_components/plex_assistant/plex_assistant.py b/custom_components/plex_assistant/plex_assistant.py index 8c337d2..03f0760 100644 --- a/custom_components/plex_assistant/plex_assistant.py +++ b/custom_components/plex_assistant/plex_assistant.py @@ -1,4 +1,5 @@ from datetime import datetime +from functools import lru_cache class PlexAssistant: @@ -6,9 +7,7 @@ def __init__(self, server, start_script_keys): self.server = server self.library = self.server.library self.devices = {} - self.media = {} self.start_script_keys = start_script_keys - self.update_libraries() self.tv_id = self.get_section_id("show") self.movie_id = self.get_section_id("movie") self.music_id = self.get_section_id("artist") @@ -30,14 +29,16 @@ def section_id(self): "track": self.music_id, } - def update_libraries(self): - self.library.reload() - self.media["all_titles"] = [] + @property + @lru_cache() + def media(self): + media_items = {"all_titles": []} for item in ["show", "movie", "artist", "album", "track"]: - self.media[f"{item}_titles"] = [x.title for x in self.library.search(libtype=item, sort="addedAt:desc")] - self.media["all_titles"] += self.media[f"{item}_titles"] - self.media["playlist_titles"] = [x.title for x in self.server.playlists()] - self.media["updated"] = datetime.now() + media_items[f"{item}_titles"] = [x.title for x in self.library.search(libtype=item, sort="addedAt:desc")] + media_items["all_titles"] += media_items[f"{item}_titles"] + media_items["playlist_titles"] = [x.title for x in self.server.playlists()] + media_items["updated"] = datetime.now() + return media_items def get_section_id(self, section): section = self.library.search(libtype=section, limit=1)