From d8d1730fc808bd9bfd7a0f02317ad635bef2028d Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 4 May 2022 12:27:17 -0400 Subject: [PATCH] [61] #738 Expand Summary --- VERSION | 2 +- docs/metadata/builders/radarr.md | 4 +- docs/metadata/builders/sonarr.md | 4 +- modules/builder.py | 84 ++++++++++++++---------- modules/library.py | 2 + modules/operations.py | 9 ++- plex_meta_manager.py | 106 +++++++++++++++++-------------- 7 files changed, 122 insertions(+), 89 deletions(-) diff --git a/VERSION b/VERSION index f7ce55d3..19f03be9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.16.5-develop60 +1.16.5-develop61 diff --git a/docs/metadata/builders/radarr.md b/docs/metadata/builders/radarr.md index 7b203c81..0219549c 100644 --- a/docs/metadata/builders/radarr.md +++ b/docs/metadata/builders/radarr.md @@ -27,8 +27,8 @@ Set the attribute to the tag you want to search for. Multiple values are support ```yaml collections: - Radarr Movies Without Tags: - radarr_taglist: action, drama + Radarr Tag1 and Tag2 Movies: + radarr_taglist: tag1, tag2 ``` If no tag is specified then it gets every Movie without a tag. diff --git a/docs/metadata/builders/sonarr.md b/docs/metadata/builders/sonarr.md index 4ba18693..59c8462d 100644 --- a/docs/metadata/builders/sonarr.md +++ b/docs/metadata/builders/sonarr.md @@ -27,8 +27,8 @@ Set the attribute to the tag you want to search for. Multiple values are support ```yaml collections: - Sonarr Series Without Tags: - sonarr_taglist: action, drama + Sonarr Tag1 and Tag2 Series: + sonarr_taglist: tag1, tag2 ``` If no tag is specified then it gets every Movie without a tag. diff --git a/modules/builder.py b/modules/builder.py index 79da38c1..3c0f03b2 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -2438,45 +2438,44 @@ class CollectionBuilder: self.created = True def update_details(self): + updated_details = [] logger.info("") logger.separator(f"Updating Details of {self.name} {self.Type}", space=False, border=False) logger.info("") if self.smart_url and self.smart_url != self.library.smart_filter(self.obj): self.library.update_smart_collection(self.obj, self.smart_url) logger.info(f"Detail: Smart Filter updated to {self.smart_url}") - - def get_summary(summary_method, summaries): - logger.info(f"Detail: {summary_method} will update {self.Type} Summary") - return summaries[summary_method] - if "summary" in self.summaries: summary = get_summary("summary", self.summaries) - elif "tmdb_description" in self.summaries: summary = get_summary("tmdb_description", self.summaries) - elif "letterboxd_description" in self.summaries: summary = get_summary("letterboxd_description", self.summaries) - elif "tmdb_summary" in self.summaries: summary = get_summary("tmdb_summary", self.summaries) - elif "tvdb_summary" in self.summaries: summary = get_summary("tvdb_summary", self.summaries) - elif "tmdb_biography" in self.summaries: summary = get_summary("tmdb_biography", self.summaries) - elif "tmdb_person" in self.summaries: summary = get_summary("tmdb_person", self.summaries) - elif "tmdb_collection_details" in self.summaries: summary = get_summary("tmdb_collection_details", self.summaries) - elif "trakt_list_details" in self.summaries: summary = get_summary("trakt_list_details", self.summaries) - elif "tmdb_list_details" in self.summaries: summary = get_summary("tmdb_list_details", self.summaries) - elif "letterboxd_list_details" in self.summaries: summary = get_summary("letterboxd_list_details", self.summaries) - elif "icheckmovies_list_details" in self.summaries: summary = get_summary("icheckmovies_list_details", self.summaries) - elif "tmdb_actor_details" in self.summaries: summary = get_summary("tmdb_actor_details", self.summaries) - elif "tmdb_crew_details" in self.summaries: summary = get_summary("tmdb_crew_details", self.summaries) - elif "tmdb_director_details" in self.summaries: summary = get_summary("tmdb_director_details", self.summaries) - elif "tmdb_producer_details" in self.summaries: summary = get_summary("tmdb_producer_details", self.summaries) - elif "tmdb_writer_details" in self.summaries: summary = get_summary("tmdb_writer_details", self.summaries) - elif "tmdb_movie_details" in self.summaries: summary = get_summary("tmdb_movie_details", self.summaries) - elif "tvdb_movie_details" in self.summaries: summary = get_summary("tvdb_movie_details", self.summaries) - elif "tvdb_show_details" in self.summaries: summary = get_summary("tvdb_show_details", self.summaries) - elif "tmdb_show_details" in self.summaries: summary = get_summary("tmdb_show_details", self.summaries) + updated_details.append("Smart Filter") + if "summary" in self.summaries: summary = ("summary", self.summaries["summary"]) + elif "tmdb_description" in self.summaries: summary = ("tmdb_description", self.summaries["tmdb_description"]) + elif "letterboxd_description" in self.summaries: summary = ("letterboxd_description", self.summaries["letterboxd_description"]) + elif "tmdb_summary" in self.summaries: summary = ("tmdb_summary", self.summaries["tmdb_summary"]) + elif "tvdb_summary" in self.summaries: summary = ("tvdb_summary", self.summaries["tvdb_summary"]) + elif "tmdb_biography" in self.summaries: summary = ("tmdb_biography", self.summaries["tmdb_biography"]) + elif "tmdb_person" in self.summaries: summary = ("tmdb_person", self.summaries["tmdb_person"]) + elif "tmdb_collection_details" in self.summaries: summary = ("tmdb_collection_details", self.summaries["tmdb_collection_details"]) + elif "trakt_list_details" in self.summaries: summary = ("trakt_list_details", self.summaries["trakt_list_details"]) + elif "tmdb_list_details" in self.summaries: summary = ("tmdb_list_details", self.summaries["tmdb_list_details"]) + elif "letterboxd_list_details" in self.summaries: summary = ("letterboxd_list_details", self.summaries["letterboxd_list_details"]) + elif "icheckmovies_list_details" in self.summaries: summary = ("icheckmovies_list_details", self.summaries["icheckmovies_list_details"]) + elif "tmdb_actor_details" in self.summaries: summary = ("tmdb_actor_details", self.summaries["tmdb_actor_details"]) + elif "tmdb_crew_details" in self.summaries: summary = ("tmdb_crew_details", self.summaries["tmdb_crew_details"]) + elif "tmdb_director_details" in self.summaries: summary = ("tmdb_director_details", self.summaries["tmdb_director_details"]) + elif "tmdb_producer_details" in self.summaries: summary = ("tmdb_producer_details", self.summaries["tmdb_producer_details"]) + elif "tmdb_writer_details" in self.summaries: summary = ("tmdb_writer_details", self.summaries["tmdb_writer_details"]) + elif "tmdb_movie_details" in self.summaries: summary = ("tmdb_movie_details", self.summaries["tmdb_movie_details"]) + elif "tvdb_movie_details" in self.summaries: summary = ("tvdb_movie_details", self.summaries["tvdb_movie_details"]) + elif "tvdb_show_details" in self.summaries: summary = ("tvdb_show_details", self.summaries["tvdb_show_details"]) + elif "tmdb_show_details" in self.summaries: summary = ("tmdb_show_details", self.summaries["tmdb_show_details"]) else: summary = None if self.playlist: - if summary and str(summary) != str(self.obj.summary): + if summary and str(summary[1]) != str(self.obj.summary): try: - self.obj.edit(summary=str(summary)) - logger.info(f"Summary | {summary:<25}") + self.obj.edit(summary=str(summary[1])) + logger.info(f"Summary ({summary[0]}) | {summary[1]:<25}") logger.info("Details: have been updated") + updated_details.append("Metadata") except NotFound: logger.error("Details: Failed to Update Please delete the collection and run again") logger.info("") @@ -2484,9 +2483,9 @@ class CollectionBuilder: self.obj.batchEdits() batch_display = "Collection Metadata Edits" - if summary and str(summary) != str(self.obj.summary): - self.obj.editSummary(summary) - batch_display += f"\nSummary | {summary:<25}" + if summary and str(summary[1]) != str(self.obj.summary): + self.obj.editSummary(summary[1]) + batch_display += f"\nSummary ({summary[0]}) | {summary[1]:<25}" if "sort_title" in self.details and str(self.details["sort_title"]) != str(self.obj.titleSort): self.obj.editSortTitle(self.details["sort_title"]) @@ -2506,21 +2505,29 @@ class CollectionBuilder: try: self.obj.saveEdits() logger.info("Details: have been updated") + updated_details.append("Metadata") except NotFound: logger.error("Details: Failed to Update Please delete the collection and run again") logger.info("") + advance_update = False if "collection_mode" in self.details: - self.library.collection_mode_query(self.obj, self.details["collection_mode"]) + if int(self.obj.collectionMode) not in plex.collection_mode_keys \ + or plex.collection_mode_keys[int(self.obj.collectionMode)] != self.details["collection_mode"]: + self.library.collection_mode_query(self.obj, self.details["collection_mode"]) + logger.info(f"Collection Mode | {self.details['collection_mode']}") + advance_update = True if "collection_filtering" in self.details: self.library.edit_query(self.obj, {"collectionFilterBasedOnUser": 0 if self.details["collection_filtering"] == "admin" else 1}, advanced=True) + advance_update = True if "collection_order" in self.details: - if int(self.obj.collectionSort) not in plex.collection_order_keys\ + if int(self.obj.collectionSort) not in plex.collection_order_keys \ or plex.collection_order_keys[int(self.obj.collectionSort)] != self.details["collection_order"]: self.library.collection_order_query(self.obj, self.details["collection_order"]) logger.info(f"Collection Order | {self.details['collection_order']}") + advance_update = True if "visible_library" in self.details or "visible_home" in self.details or "visible_shared" in self.details: visibility = self.library.collection_visibility(self.obj) @@ -2539,8 +2546,12 @@ class CollectionBuilder: if visible_library is not None or visible_home is not None or visible_shared is not None: self.library.collection_visibility_update(self.obj, visibility=visibility, library=visible_library, home=visible_home, shared=visible_shared) + advance_update = True logger.info("Collection Visibility Updated") + if advance_update and "Metadata" not in updated_details: + updated_details.append("Metadata") + poster_image = None background_image = None asset_location = None @@ -2633,12 +2644,15 @@ class CollectionBuilder: logger.info(f"No background {self.type} detail or asset folder found") if self.collection_poster or self.collection_background: - self.library.upload_images(self.obj, poster=self.collection_poster, background=self.collection_background) + pu, bu = self.library.upload_images(self.obj, poster=self.collection_poster, background=self.collection_background) + if pu or bu: + updated_details.append("Image") - if self.url_theme: + if self.url_theme: # TODO: cache theme path to not constantly upload self.library.upload_theme(self.obj, url=self.url_theme) elif self.file_theme: self.library.upload_theme(self.obj, filepath=self.file_theme) + return updated_details def sort_collection(self): logger.info("") diff --git a/modules/library.py b/modules/library.py index ee0a36e2..0dc31fbc 100644 --- a/modules/library.py +++ b/modules/library.py @@ -184,6 +184,8 @@ class Library(ABC): if background_uploaded: self.config.Cache.update_image_map(item.ratingKey, f"{self.image_table_name}_backgrounds", item.art, background.compare) + return poster_uploaded, background_uploaded + @abstractmethod def notify(self, text, collection=None, critical=True): pass diff --git a/modules/operations.py b/modules/operations.py index af5a03cb..6796b0b3 100644 --- a/modules/operations.py +++ b/modules/operations.py @@ -1,5 +1,5 @@ import os, re -from modules import util +from modules import plex, util from modules.util import Failed from plexapi.audio import Artist from plexapi.video import Show @@ -439,10 +439,13 @@ class Operations: if self.library.mass_collection_mode: logger.info("") - logger.separator(f"Unmanaged Mass Collection Mode for {self.library.name} Library", space=False, border=False) + logger.separator(f"Unmanaged Mass Collection Mode to {self.library.mass_collection_mode} for {self.library.name} Library", space=False, border=False) logger.info("") for col in unmanaged_collections: - self.library.collection_mode_query(col, self.library.mass_collection_mode) + if int(col.collectionMode) not in plex.collection_mode_keys \ + or plex.collection_mode_keys[int(col.collectionMode)] != self.library.mass_collection_mode: + self.library.collection_mode_query(col, self.library.mass_collection_mode) + logger.info(f"{col.title} Collection Mode Updated") if self.library.metadata_backup: logger.info("") diff --git a/plex_meta_manager.py b/plex_meta_manager.py index f7e7da7d..af293634 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -396,11 +396,11 @@ def update_libraries(config): logger.info("") logger.separator(f"{section} Summary", space=False, border=False) logger.info("") - logger.info(f"{'Title':^{longest}} | + | = | - | {'Status':^13}") + logger.info(f"{'Title':^{longest}} | + | = | - | Run Time | {'Status'}") breaker = f"{logger.separating_character * longest}|{logger.separating_character * 5}|{logger.separating_character * 5}|{logger.separating_character * 5}|" logger.separator(breaker, space=False, border=False, side_space=False, left=True) for name, data in status.items(): - logger.info(f"{name:<{longest}} | {data['added']:^3} | {data['unchanged']:^3} | {data['removed']:^3} | {data['status']}") + logger.info(f"{name:<{longest}} | {data['added']:^3} | {data['unchanged']:^3} | {data['removed']:^3} | {data['run_time']:>8} | {data['status']}") if data["errors"]: for error in data["errors"]: logger.info(error) @@ -467,7 +467,7 @@ def run_collection(config, library, metadata, requested_collections): else: collection_log_name, output_str = util.validate_filename(mapping_name) logger.add_collection_handler(library.mapping_name, collection_log_name) - library.status[mapping_name] = {"status": "", "errors": [], "created": False, "modified": False, "deleted": False, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} + library.status[mapping_name] = {"status": "Unchanged", "errors": [], "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} try: builder = CollectionBuilder(config, metadata, mapping_name, collection_attrs, library=library, extra=output_str) @@ -528,11 +528,14 @@ def run_collection(config, library, metadata, requested_collections): ): logger.info("") logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection") + delete_status = f"Minimum {builder.minimum} Not Met" valid = False if builder.details["delete_below_minimum"] and builder.obj: logger.info("") logger.info(builder.delete()) - builder.deleted = True + library.stats["deleted"] += 1 + delete_status = f"Deleted; {delete_status}" + library.status[mapping_name]["status"] = delete_status run_item_details = True if valid and builder.build_collection and (builder.builders or builder.smart_url or builder.blank_collection): @@ -540,28 +543,29 @@ def run_collection(config, library, metadata, requested_collections): builder.load_collection() if builder.created: library.stats["created"] += 1 - library.status[mapping_name]["created"] = True + library.status[mapping_name]["status"] = "Created" elif items_added > 0 or items_removed > 0: library.stats["modified"] += 1 - library.status[mapping_name]["modified"] = True + library.status[mapping_name]["status"] = "Modified" except Failed: logger.stacktrace() run_item_details = False logger.info("") logger.separator("No Collection to Update", space=False, border=False) else: - builder.update_details() - - if builder.deleted: - library.stats["deleted"] += 1 - library.status[mapping_name]["deleted"] = True + details_list = builder.update_details() + if details_list: + pre = "" + if library.status[mapping_name]["status"] != "Unchanged": + pre = f"{library.status[mapping_name]['status']} and " + library.status[mapping_name]["status"] = f"{pre}Updated {', '.join(details_list)}" if builder.server_preroll is not None: library.set_server_preroll(builder.server_preroll) logger.info("") logger.info(f"Plex Server Movie pre-roll video updated to {builder.server_preroll}") - if (builder.item_details or builder.custom_sort) and run_item_details and builder.builders: + if valid and run_item_details and builder.builders and (builder.item_details or builder.custom_sort): try: builder.load_collection_items() except Failed: @@ -578,17 +582,13 @@ def run_collection(config, library, metadata, requested_collections): if builder.run_again and (len(builder.run_again_movies) > 0 or len(builder.run_again_shows) > 0): library.run_again.append(builder) - if library.status[mapping_name]["created"]: - library.status[mapping_name]["status"] = "Created" - elif library.status[mapping_name]["deleted"]: - library.status[mapping_name]["status"] = "Deleted" - elif library.status[mapping_name]["modified"]: - library.status[mapping_name]["status"] = "Modified" - else: - library.status[mapping_name]["status"] = "Unchanged" except NotScheduled as e: logger.info(e) - library.status[mapping_name]["status"] = "Not Scheduled" + if str(e).endswith("and was deleted"): + library.stats["deleted"] += 1 + library.status[mapping_name]["status"] = "Deleted Not Scheduled" + else: + library.status[mapping_name]["status"] = "Not Scheduled" except Failed as e: library.notify(e, collection=mapping_name) logger.stacktrace() @@ -601,8 +601,10 @@ def run_collection(config, library, metadata, requested_collections): logger.error(f"Unknown Error: {e}") library.status[mapping_name]["status"] = "Unknown Error" library.status[mapping_name]["errors"].append(e) + collection_run_time = str(datetime.now() - collection_start).split('.')[0] + library.status[mapping_name]["run_time"] = collection_run_time logger.info("") - logger.separator(f"Finished {mapping_name} Collection\nCollection Run Time: {str(datetime.now() - collection_start).split('.')[0]}") + logger.separator(f"Finished {mapping_name} Collection\nCollection Run Time: {collection_run_time}") logger.remove_collection_handler(library.mapping_name, collection_log_name) def run_playlists(config): @@ -634,7 +636,7 @@ def run_playlists(config): else: playlist_log_name, output_str = util.validate_filename(mapping_name) logger.add_playlist_handler(playlist_log_name) - status[mapping_name] = {"status": "", "errors": [], "created": False, "modified": False, "deleted": False, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} + status[mapping_name] = {"status": "Unchanged", "errors": [], "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} server_name = None library_names = None try: @@ -677,7 +679,7 @@ def run_playlists(config): for filter_key, filter_value in builder.tmdb_filters: logger.info(f"Playlist Filter {filter_key}: {filter_value}") - if len(builder.added_items) >= builder.minimum: + if len(builder.added_items) > 0 and len(builder.added_items) + builder.beginning_count >= builder.minimum: items_added, items_unchanged = builder.add_to_collection() stats["added"] += items_added status[mapping_name]["added"] += items_added @@ -691,11 +693,14 @@ def run_playlists(config): elif len(builder.added_items) < builder.minimum: logger.info("") logger.info(f"Playlist Minimum: {builder.minimum} not met for {mapping_name} Playlist") + delete_status = f"Minimum {builder.minimum} Not Met" valid = False if builder.details["delete_below_minimum"] and builder.obj: logger.info("") logger.info(builder.delete()) - builder.deleted = True + stats["deleted"] += 1 + delete_status = f"Deleted; {delete_status}" + status[mapping_name]["status"] = delete_status if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0): radarr_add, sonarr_add = builder.run_missing() @@ -705,25 +710,27 @@ def run_playlists(config): status[mapping_name]["sonarr"] += sonarr_add run_item_details = True - try: - builder.load_collection() - if builder.created: - stats["created"] += 1 - status[mapping_name]["created"] = True - elif items_added > 0 or items_removed > 0: - stats["modified"] += 1 - status[mapping_name]["modified"] = True - except Failed: - logger.stacktrace() - run_item_details = False - logger.info("") - logger.separator("No Playlist to Update", space=False, border=False) - else: - builder.update_details() - - if builder.deleted: - stats["deleted"] += 1 - status[mapping_name]["deleted"] = True + if valid and builder.builders: + try: + builder.load_collection() + if builder.created: + stats["created"] += 1 + status[mapping_name]["status"] = "Created" + elif items_added > 0 or items_removed > 0: + stats["modified"] += 1 + status[mapping_name]["status"] = "Modified" + except Failed: + logger.stacktrace() + run_item_details = False + logger.info("") + logger.separator("No Playlist to Update", space=False, border=False) + else: + details_list = builder.update_details() + if details_list: + pre = "" + if status[mapping_name]["status"] != "Unchanged": + pre = f"{status[mapping_name]['status']} and " + status[mapping_name]["status"] = f"{pre}Updated {', '.join(details_list)}" if valid and run_item_details and builder.builders and (builder.item_details or builder.custom_sort): try: @@ -744,7 +751,11 @@ def run_playlists(config): except NotScheduled as e: logger.info(e) - status[mapping_name]["status"] = "Not Scheduled" + if str(e).endswith("and was deleted"): + stats["deleted"] += 1 + status[mapping_name]["status"] = "Deleted Not Scheduled" + else: + status[mapping_name]["status"] = "Not Scheduled" except Failed as e: config.notify(e, server=server_name, library=library_names, playlist=mapping_name) logger.stacktrace() @@ -758,7 +769,10 @@ def run_playlists(config): status[mapping_name]["status"] = "Unknown Error" status[mapping_name]["errors"].append(e) logger.info("") - logger.separator(f"Finished {mapping_name} Playlist\nPlaylist Run Time: {str(datetime.now() - playlist_start).split('.')[0]}") + playlist_run_time = str(datetime.now() - playlist_start).split('.')[0] + status[mapping_name]["run_time"] = playlist_run_time + logger.info("") + logger.separator(f"Finished {mapping_name} Playlist\nPlaylist Run Time: {playlist_run_time}") logger.remove_playlist_handler(playlist_log_name) return status, stats