From 8519342eb347c16ab4418d2164f3ded2d2d96e1c Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 20 May 2021 15:26:56 -0400 Subject: [PATCH 01/14] fix for tag.remove --- modules/builder.py | 37 ++++++++------------- modules/meta.py | 81 +++++++++++++++++++++------------------------- modules/plex.py | 29 +++++++++++++++++ 3 files changed, 80 insertions(+), 67 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 59f81217..028d95d5 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1603,12 +1603,12 @@ class CollectionBuilder: if "label" in self.details or "label.remove" in self.details or "label.sync" in self.details: item_labels = [label.tag for label in self.obj.labels] - labels = self.details["label" if "label" in self.details else "label.sync"] - if "label.sync" in self.details: - for label in (la for la in item_labels if la not in labels): - self.library.query_data(self.obj.removeLabel, label) - logger.info(f"Detail: Label {label} removed") if "label" in self.details or "label.sync" in self.details: + labels = self.details["label" if "label" in self.details else "label.sync"] + if "label.sync" in self.details: + for label in (la for la in item_labels if la not in labels): + self.library.query_data(self.obj.removeLabel, label) + logger.info(f"Detail: Label {label} removed") for label in (la for la in labels if la not in item_labels): self.library.query_data(self.obj.addLabel, label) logger.info(f"Detail: Label {label} added") @@ -1618,26 +1618,17 @@ class CollectionBuilder: self.library.query_data(self.obj.removeLabel, label) logger.info(f"Detail: Label {label} removed") + add_tags = self.details["label"] if "label" in self.details else None + remove_tags = self.details["label.remove"] if "label.remove" in self.details else None + sync_tags = self.details["label.sync"] if "label.sync" in self.details else None + self.library.edit_tags("label", self.obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags) + if len(self.item_details) > 0: - labels = None - if "item_label" in self.item_details or "item_label.remove" in self.item_details or "item_label.sync" in self.item_details: - labels = self.item_details["item_label" if "item_label" in self.item_details else "item_label.sync"] + add_tags = self.item_details["item_label"] if "item_label" in self.item_details else None + remove_tags = self.item_details["item_label.remove"] if "item_label.remove" in self.item_details else None + sync_tags = self.item_details["item_label.sync"] if "item_label.sync" in self.item_details else None for item in self.library.get_collection_items(self.obj, self.smart_label_collection): - if labels is not None: - item_labels = [label.tag for label in item.labels] - if "item_label.sync" in self.item_details: - for label in (la for la in item_labels if la not in labels): - self.library.query_data(item.removeLabel, label) - logger.info(f"Detail: Label {label} removed from {item.title}") - if "item_label" in self.item_details or "item_label.sync" in self.item_details: - for label in (la for la in labels if la not in item_labels): - self.library.query_data(item.addLabel, label) - logger.info(f"Detail: Label {label} added to {item.title}") - if "item_label.remove" in self.item_details: - for label in self.item_details["item_label.remove"]: - if label in item_labels: - self.library.query_data(self.obj.removeLabel, label) - logger.info(f"Detail: Label {label} removed from {item.title}") + self.library.edit_tags("label", item, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags) advance_edits = {} for method_name, method_data in self.item_details.items(): if method_name in plex.item_advance_keys: diff --git a/modules/meta.py b/modules/meta.py index 9efc9a09..3a1330a2 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -119,8 +119,6 @@ class Metadata: logger.error(f"Metadata Error: {attr} attribute is blank") def edit_tags(attr, obj, group, alias, key=None, extra=None, movie_library=False): - if key is None: - key = f"{attr}s" if movie_library and not self.library.is_movie: logger.error(f"Metadata Error: {attr} attribute only works for movie libraries") elif attr in alias and f"{attr}.sync" in alias: @@ -134,33 +132,16 @@ class Metadata: elif f"{attr}.sync" in alias and group[alias[f"{attr}.sync"]] is None: logger.error(f"Metadata Error: {attr}.sync attribute is blank") elif attr in alias or f"{attr}.remove" in alias or f"{attr}.sync" in alias: - attr_key = attr if attr in alias else f"{attr}.sync" - item_tags = [item_tag.tag for item_tag in getattr(obj, key)] - input_tags = [] - if group[alias[attr_key]]: - input_tags.extend(util.get_list(group[alias[attr_key]])) + add_tags = group[alias[attr]] if attr in alias else None if extra: - input_tags.extend(extra) - if f"{attr}.sync" in alias: - remove_method = getattr(obj, f"remove{attr.capitalize()}") - for tag in (t for t in item_tags if t not in input_tags): - updated = True - self.library.query_data(remove_method, tag) - logger.info(f"Detail: {attr.capitalize()} {tag} removed") - if attr in alias or f"{attr}.sync" in alias: - add_method = getattr(obj, f"add{attr.capitalize()}") - for tag in (t for t in input_tags if t not in item_tags): - updated = True - self.library.query_data(add_method, tag) - logger.info(f"Detail: {attr.capitalize()} {tag} added") - if f"{attr}.remove" in alias: - remove_method = getattr(obj, f"remove{attr.capitalize()}") - for tag in util.get_list(group[alias[f"{attr}.remove"]]): - if tag in item_tags: - self.library.query_data(remove_method, tag) - logger.info(f"Detail: {attr.capitalize()} {tag} removed") - else: - logger.error(f"Metadata Error: {attr} attribute is blank") + if add_tags: + add_tags.extend(extra) + else: + add_tags = extra + remove_tags = group[alias[f"{attr}.remove"]] if f"{attr}.remove" in alias else None + sync_tags = group[alias[f"{attr}.sync"]] if f"{attr}.sync" in alias else None + return self.library.edit_tags("attr", obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags, key=key) + return False def set_image(attr, obj, group, alias, poster=True, url=True): if group[alias[attr]]: @@ -262,8 +243,7 @@ class Metadata: edits = {} add_edit("title", item.title, meta, methods, value=title) add_edit("sort_title", item.titleSort, meta, methods, key="titleSort") - add_edit("originally_available", str(item.originallyAvailableAt)[:-9], meta, methods, - key="originallyAvailableAt", value=originally_available, var_type="date") + add_edit("originally_available", str(item.originallyAvailableAt)[:-9], meta, methods, key="originallyAvailableAt", value=originally_available, var_type="date") add_edit("critic_rating", item.rating, meta, methods, value=rating, key="rating", var_type="float") add_edit("audience_rating", item.audienceRating, meta, methods, key="audienceRating", var_type="float") add_edit("content_rating", item.contentRating, meta, methods, key="contentRating") @@ -271,7 +251,8 @@ class Metadata: add_edit("studio", item.studio, meta, methods, value=studio) add_edit("tagline", item.tagline, meta, methods, value=tagline) add_edit("summary", item.summary, meta, methods, value=summary) - self.library.edit_item(item, mapping_name, item_type, edits) + if self.library.edit_item(item, mapping_name, item_type, edits): + updated = True advance_edits = {} add_advanced_edit("episode_sorting", item, meta, methods, show_library=True) @@ -281,15 +262,23 @@ class Metadata: add_advanced_edit("episode_ordering", item, meta, methods, show_library=True) add_advanced_edit("metadata_language", item, meta, methods, new_agent=True) add_advanced_edit("use_original_title", item, meta, methods, new_agent=True) - self.library.edit_item(item, mapping_name, item_type, advance_edits, advanced=True) + if self.library.edit_item(item, mapping_name, item_type, advance_edits, advanced=True): + updated = True - edit_tags("genre", item, meta, methods, extra=genres) - edit_tags("label", item, meta, methods) - edit_tags("collection", item, meta, methods) - edit_tags("country", item, meta, methods, key="countries", movie_library=True) - edit_tags("director", item, meta, methods, movie_library=True) - edit_tags("producer", item, meta, methods, movie_library=True) - edit_tags("writer", item, meta, methods, movie_library=True) + if edit_tags("genre", item, meta, methods, extra=genres): + updated = True + if edit_tags("label", item, meta, methods): + updated = True + if edit_tags("collection", item, meta, methods): + updated = True + if edit_tags("country", item, meta, methods, key="countries", movie_library=True): + updated = True + if edit_tags("director", item, meta, methods, movie_library=True): + updated = True + if edit_tags("producer", item, meta, methods, movie_library=True): + updated = True + if edit_tags("writer", item, meta, methods, movie_library=True): + updated = True logger.info(f"{item_type}: {mapping_name} Details Update {'Complete' if updated else 'Not Needed'}") @@ -330,7 +319,8 @@ class Metadata: edits = {} add_edit("title", season.title, season_dict, season_methods, value=title) add_edit("summary", season.summary, season_dict, season_methods) - self.library.edit_item(season, season_id, "Season", edits) + if self.library.edit_item(season, season_id, "Season", edits): + updated = True set_images(season, season_dict, season_methods) else: logger.error(f"Metadata Error: Season: {season_id} invalid, it must be an integer") @@ -380,11 +370,14 @@ class Metadata: add_edit("originally_available", str(episode.originallyAvailableAt)[:-9], episode_dict, episode_methods, key="originallyAvailableAt") add_edit("summary", episode.summary, episode_dict, episode_methods) - self.library.edit_item(episode, f"{season_id} Episode: {episode_id}", "Season", edits) - edit_tags("director", episode, episode_dict, episode_methods) - edit_tags("writer", episode, episode_dict, episode_methods) + if self.library.edit_item(episode, f"{season_id} Episode: {episode_id}", "Season", edits): + updated = True + if edit_tags("director", episode, episode_dict, episode_methods): + updated = True + if edit_tags("writer", episode, episode_dict, episode_methods): + updated = True set_images(episode, episode_dict, episode_methods) - logger.info(f"Episode S{episode_id}E{season_id} of {mapping_name} Details Update {'Complete' if updated else 'Not Needed'}") + logger.info(f"Episode S{episode_id}E{season_id} of {mapping_name} Details Update {'Complete' if updated else 'Not Needed'}") else: logger.error(f"Metadata Error: episode {episode_str} invalid must have S##E## format") else: diff --git a/modules/plex.py b/modules/plex.py index 978f4c28..6f39f423 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -668,9 +668,38 @@ class PlexAPI: if advanced and "languageOverride" in edits: self.query(item.refresh) logger.info(f"{item_type}: {name}{' Advanced' if advanced else ''} Details Update Successful") + return True except BadRequest: util.print_stacktrace() logger.error(f"{item_type}: {name}{' Advanced' if advanced else ''} Details Update Failed") + return False + + def edit_tags(self, attr, obj, add_tags=None, remove_tags=None, sync_tags=None, key=None): + updated = False + if key is None: + key = f"{attr}s" + if add_tags or remove_tags or sync_tags: + item_tags = [item_tag.tag for item_tag in getattr(obj, key)] + input_tags = [] + if add_tags: + input_tags.extend(add_tags) + if sync_tags: + input_tags.extend(sync_tags) + if sync_tags or remove_tags: + remove_method = getattr(obj, f"remove{attr.capitalize()}") + for tag in item_tags: + if (sync_tags and tag not in sync_tags) or (remove_tags and tag in remove_tags): + updated = True + self.query_data(remove_method, tag) + logger.info(f"Detail: {attr.capitalize()} {tag} removed") + if input_tags: + add_method = getattr(obj, f"add{attr.capitalize()}") + for tag in input_tags: + if tag not in item_tags: + updated = True + self.query_data(add_method, tag) + logger.info(f"Detail: {attr.capitalize()} {tag} added") + return updated def update_item_from_assets(self, item, collection_mode=False, upload=True, dirs=None, name=None): if dirs is None: From 1758dad7caa00ecf380b761fb2f9ae98be2f7764 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 20 May 2021 16:38:48 -0400 Subject: [PATCH 02/14] added Collection Run Time --- modules/builder.py | 17 ----------------- modules/plex.py | 4 +++- modules/util.py | 5 ++--- plex_meta_manager.py | 12 ++++++++---- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 028d95d5..c2499477 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1601,23 +1601,6 @@ class CollectionBuilder: self.library.collection_order_query(self.obj, self.details["collection_order"]) logger.info(f"Detail: collection_order updated Collection Order to {self.details['collection_order']}") - if "label" in self.details or "label.remove" in self.details or "label.sync" in self.details: - item_labels = [label.tag for label in self.obj.labels] - if "label" in self.details or "label.sync" in self.details: - labels = self.details["label" if "label" in self.details else "label.sync"] - if "label.sync" in self.details: - for label in (la for la in item_labels if la not in labels): - self.library.query_data(self.obj.removeLabel, label) - logger.info(f"Detail: Label {label} removed") - for label in (la for la in labels if la not in item_labels): - self.library.query_data(self.obj.addLabel, label) - logger.info(f"Detail: Label {label} added") - if "label.remove" in self.details: - for label in self.details["label.remove"]: - if label in item_labels: - self.library.query_data(self.obj.removeLabel, label) - logger.info(f"Detail: Label {label} removed") - add_tags = self.details["label"] if "label" in self.details else None remove_tags = self.details["label.remove"] if "label.remove" in self.details else None sync_tags = self.details["label.sync"] if "label.sync" in self.details else None diff --git a/modules/plex.py b/modules/plex.py index 6f39f423..ac8e55fd 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -324,7 +324,9 @@ class PlexAPI: self.Sonarr = None self.Tautulli = None self.name = params["name"] - self.mapping_name = util.validate_filename(params["mapping_name"]) + self.mapping_name, output = util.validate_filename(params["mapping_name"]) + if output: + logger.info(output) self.missing_path = os.path.join(params["default_dir"], f"{self.name}_missing.yml") self.metadata_path = params["metadata_path"] self.asset_directory = params["asset_directory"] diff --git a/modules/util.py b/modules/util.py index 993b444b..df95f7d0 100644 --- a/modules/util.py +++ b/modules/util.py @@ -393,8 +393,7 @@ def print_end(length, text=None): def validate_filename(filename): if is_valid_filename(filename): - return filename + return filename, None else: mapping_name = sanitize_filename(filename) - logger.info(f"Folder Name: {filename} is invalid using {mapping_name}") - return mapping_name + return mapping_name, f"Log Folder Name: {filename} is invalid using {mapping_name}" diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 6cf0be6d..2d4300bf 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -115,7 +115,7 @@ def start(config_path, is_test, daily, requested_collections, requested_librarie logger.critical(e) logger.info("") util.separator(f"Finished {start_type}Run\nRun Time: {str(datetime.now() - start_time).split('.')[0]}") - logger.addHandler(file_handler) + logger.removeHandler(file_handler) def update_libraries(config, is_test, requested_collections, resume_from): for library in config.libraries: @@ -372,10 +372,10 @@ def mass_metadata(config, library, movie_map, show_map): except Failed as e: logger.error(e) - def run_collection(config, library, metadata, requested_collections, is_test, resume_from, movie_map, show_map): logger.info("") for mapping_name, collection_attrs in requested_collections.items(): + collection_start = datetime.now() if is_test and ("test" not in collection_attrs or collection_attrs["test"] is not True): no_template_test = True if "template" in collection_attrs and collection_attrs["template"]: @@ -399,9 +399,9 @@ def run_collection(config, library, metadata, requested_collections, is_test, re util.separator(f"Resuming Collections") if "name_mapping" in collection_attrs and collection_attrs["name_mapping"]: - collection_log_name = util.validate_filename(collection_attrs["name_mapping"]) + collection_log_name, output_str = util.validate_filename(collection_attrs["name_mapping"]) else: - collection_log_name = util.validate_filename(mapping_name) + collection_log_name, output_str = util.validate_filename(mapping_name) collection_log_folder = os.path.join(default_dir, "logs", library.mapping_name, "collections", collection_log_name) os.makedirs(collection_log_folder, exist_ok=True) col_file_logger = os.path.join(collection_log_folder, f"collection.log") @@ -415,6 +415,9 @@ def run_collection(config, library, metadata, requested_collections, is_test, re try: util.separator(f"{mapping_name} Collection") logger.info("") + if output_str: + logger.info(output_str) + logger.info("") builder = CollectionBuilder(config, library, metadata, mapping_name, collection_attrs) @@ -453,6 +456,7 @@ def run_collection(config, library, metadata, requested_collections, is_test, re util.print_stacktrace() logger.error(f"Unknown Error: {e}") logger.info("") + util.separator(f"Finished {mapping_name} Collection\nCollection Run Time: {str(datetime.now() - collection_start).split('.')[0]}") logger.removeHandler(collection_handler) return resume_from From f1e56a5abcbd90b73de2b04cd546a655ca3ae505 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Fri, 21 May 2021 10:30:23 -0400 Subject: [PATCH 03/14] speed up IMDb #267 --- modules/builder.py | 6 +++--- modules/convert.py | 10 +++++----- modules/imdb.py | 26 ++++++++++++-------------- modules/letterboxd.py | 2 +- modules/plex.py | 2 +- modules/util.py | 5 ++--- plex_meta_manager.py | 18 +++++++++--------- 7 files changed, 33 insertions(+), 36 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index c2499477..56ea1ea5 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1315,7 +1315,7 @@ class CollectionBuilder: elif "anilist" in method: check_map(self.config.AniList.get_items(method, value)) elif "mal" in method: check_map(self.config.MyAnimeList.get_items(method, value)) elif "tvdb" in method: check_map(self.config.TVDb.get_items(method, value, self.library.Plex.language)) - elif "imdb" in method: check_map(self.config.IMDb.get_items(method, value, self.library.Plex.language)) + elif "imdb" in method: check_map(self.config.IMDb.get_items(method, value, self.library.Plex.language, self.library.is_movie)) elif "letterboxd" in method: check_map(self.config.Letterboxd.get_items(method, value, self.library.Plex.language)) elif "tmdb" in method: check_map(self.config.TMDb.get_items(method, value, self.library.is_movie)) elif "trakt" in method: check_map(self.config.Trakt.get_items(method, value, self.library.is_movie)) @@ -1432,7 +1432,7 @@ class CollectionBuilder: break length = util.print_return(length, f"Filtering {(' ' * (max_length - len(str(i)))) + str(i)}/{total} {current.title}") if match: - util.print_end(length, f"{name} Collection | {'=' if current in collection_items else '+'} | {current.title}") + logger.info(util.adjust_space(length, f"{name} Collection | {'=' if current in collection_items else '+'} | {current.title}")) if current in collection_items: self.plex_map[current.ratingKey] = None elif self.smart_label_collection: @@ -1442,7 +1442,7 @@ class CollectionBuilder: elif self.details["show_filtered"] is True: logger.info(f"{name} Collection | X | {current.title}") media_type = f"{'Movie' if self.library.is_movie else 'Show'}{'s' if total > 1 else ''}" - util.print_end(length, f"{total} {media_type} Processed") + logger.info(util.adjust_space(length, f"{total} {media_type} Processed")) def run_missing(self): logger.info("") diff --git a/modules/convert.py b/modules/convert.py index ea6ad4ca..043185cb 100644 --- a/modules/convert.py +++ b/modules/convert.py @@ -214,7 +214,7 @@ class Convert: return cache_id imdb_id = None try: - imdb_id = self.tmdb_to_imdb(self.tvdb_to_tmdb(tvdb_id), False) + imdb_id = self.tmdb_to_imdb(self.tvdb_to_tmdb(tvdb_id, fail=True), is_movie=False, fail=True) except Failed: if self.config.Trakt: try: @@ -235,7 +235,7 @@ class Convert: return cache_id tvdb_id = None try: - tvdb_id = self.tmdb_to_tvdb(self.imdb_to_tmdb(imdb_id, False)) + tvdb_id = self.tmdb_to_tvdb(self.imdb_to_tmdb(imdb_id, is_movie=False, fail=True), fail=True) except Failed: if self.config.Trakt: try: @@ -343,7 +343,7 @@ class Convert: def update_cache(cache_ids, id_type, guid_type): if self.config.Cache: cache_ids = util.compile_list(cache_ids) - util.print_end(length, f" Cache | {'^' if expired else '+'} | {item.guid:<46} | {id_type} ID: {cache_ids:<6} | {item.title}") + logger.info(util.adjust_space(length, f" Cache | {'^' if expired else '+'} | {item.guid:<46} | {id_type} ID: {cache_ids:<6} | {item.title}")) self.config.Cache.update_guid_map(guid_type, item.guid, cache_ids, expired) if tmdb_id and library.is_movie: @@ -358,8 +358,8 @@ class Convert: else: raise Failed(f"No ID to convert") except Failed as e: - util.print_end(length, f"Mapping Error | {item.guid:<46} | {e} for {item.title}") + logger.info(util.adjust_space(length, f"Mapping Error | {item.guid:<46} | {e} for {item.title}")) except BadRequest: util.print_stacktrace() - util.print_end(length, f"Mapping Error: | {item.guid} for {item.title} not found") + logger.info(util.adjust_space(length, f"Mapping Error: | {item.guid} for {item.title} not found")) return None, None diff --git a/modules/imdb.py b/modules/imdb.py index c9d19449..15ac801a 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -91,34 +91,32 @@ class IMDbAPI: def _request(self, url, header): return html.fromstring(requests.get(url, headers=header).content) - def get_items(self, method, data, language): + def get_items(self, method, data, language, is_movie): pretty = util.pretty_names[method] if method in util.pretty_names else method logger.debug(f"Data: {data}") show_ids = [] movie_ids = [] - if method == "imdb_id": - logger.info(f"Processing {pretty}: {data}") - tmdb_id = self.config.Convert.imdb_to_tmdb(data) - tvdb_id = self.config.Convert.imdb_to_tvdb(data) + def run_convert(imdb_id): + tmdb_id = self.config.Convert.imdb_to_tmdb(imdb_id) + tvdb_id = self.config.Convert.imdb_to_tvdb(imdb_id) if not is_movie else None if not tmdb_id and not tvdb_id: - logger.error(f"Convert Error: No TMDb ID or TVDb ID found for IMDb: {data}") + logger.error(f"Convert Error: No TMDb ID or TVDb ID found for IMDb: {imdb_id}") if tmdb_id: movie_ids.append(tmdb_id) if tvdb_id: show_ids.append(tvdb_id) + + if method == "imdb_id": + logger.info(f"Processing {pretty}: {data}") + run_convert(data) elif method == "imdb_list": status = f"{data['limit']} Items at " if data['limit'] > 0 else '' logger.info(f"Processing {pretty}: {status}{data['url']}") imdb_ids = self._ids_from_url(data["url"], language, data["limit"]) total_ids = len(imdb_ids) length = 0 - for i, imdb_id in enumerate(imdb_ids, 1): + for i, imdb in enumerate(imdb_ids, 1): length = util.print_return(length, f"Converting IMDb ID {i}/{total_ids}") - tmdb_id = self.config.Convert.imdb_to_tmdb(imdb_id) - tvdb_id = self.config.Convert.imdb_to_tvdb(imdb_id) - if not tmdb_id and not tvdb_id: - logger.error(f"Convert Error: No TMDb ID or TVDb ID found for IMDb: {imdb_id}") - if tmdb_id: movie_ids.append(tmdb_id) - if tvdb_id: show_ids.append(tvdb_id) - util.print_end(length, f"Processed {total_ids} IMDb IDs") + run_convert(imdb) + logger.info(util.adjust_space(length, f"Processed {total_ids} IMDb IDs")) else: raise Failed(f"IMDb Error: Method {method} not supported") logger.debug(f"TMDb IDs Found: {movie_ids}") diff --git a/modules/letterboxd.py b/modules/letterboxd.py index bdef073f..6f8f6e01 100644 --- a/modules/letterboxd.py +++ b/modules/letterboxd.py @@ -66,7 +66,7 @@ class LetterboxdAPI: if self.config.Cache: self.config.Cache.update_letterboxd_map(expired, letterboxd_id, tmdb_id) movie_ids.append(tmdb_id) - util.print_end(length, f"Processed {total_items} TMDb IDs") + logger.info(util.adjust_space(length, f"Processed {total_items} TMDb IDs")) else: logger.error(f"Letterboxd Error: No List Items found in {data}") logger.debug(f"TMDb IDs Found: {movie_ids}") diff --git a/modules/plex.py b/modules/plex.py index ac8e55fd..7a1c44c4 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -617,7 +617,7 @@ class PlexAPI: break if add_item: items.append(item) - util.print_end(length, f"Processed {len(all_items)} {'Movies' if self.is_movie else 'Shows'}") + logger.info(util.adjust_space(length, f"Processed {len(all_items)} {'Movies' if self.is_movie else 'Shows'}")) else: raise Failed(f"Plex Error: Method {method} not supported") if len(items) > 0: diff --git a/modules/util.py b/modules/util.py index df95f7d0..637fe53a 100644 --- a/modules/util.py +++ b/modules/util.py @@ -387,9 +387,8 @@ def print_return(length, text): print(adjust_space(length, f"| {text}"), end="\r") return len(text) + 2 -def print_end(length, text=None): - if text: logger.info(adjust_space(length, text)) - else: print(adjust_space(length, " "), end="\r") +def print_end(length): + print(adjust_space(length, " "), end="\r") def validate_filename(filename): if is_valid_filename(filename): diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 2d4300bf..36cbffd1 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -251,7 +251,7 @@ def map_guids(config, library): for m in main_id: if m in show_map: show_map[m].append(item.ratingKey) else: show_map[m] = [item.ratingKey] - util.print_end(length, f"Processed {len(items)} {'Movies' if library.is_movie else 'Shows'}") + logger.info(util.adjust_space(length, f"Processed {len(items)} {'Movies' if library.is_movie else 'Shows'}")) return movie_map, show_map def mass_metadata(config, library, movie_map, show_map): @@ -298,9 +298,9 @@ def mass_metadata(config, library, movie_map, show_map): try: tmdb_item = config.TMDb.get_movie(tmdb_id) if library.is_movie else config.TMDb.get_show(tmdb_id) except Failed as e: - util.print_end(length, str(e)) + logger.info(util.adjust_space(length, str(e))) else: - util.print_end(length, f"{item.title[:25]:<25} | No TMDb ID for Guid: {item.guid}") + logger.info(util.adjust_space(length, f"{item.title[:25]:<25} | No TMDb ID for Guid: {item.guid}")) omdb_item = None if library.mass_genre_update in ["omdb", "imdb"] or library.mass_audience_rating_update in ["omdb", "imdb"] or library.mass_critic_rating_update in ["omdb", "imdb"]: @@ -313,9 +313,9 @@ def mass_metadata(config, library, movie_map, show_map): try: omdb_item = config.OMDb.get_omdb(imdb_id) except Failed as e: - util.print_end(length, str(e)) + logger.info(util.adjust_space(length, str(e))) else: - util.print_end(length, f"{item.title[:25]:<25} | No IMDb ID for Guid: {item.guid}") + logger.info(util.adjust_space(length, f"{item.title[:25]:<25} | No IMDb ID for Guid: {item.guid}")) if not tmdb_item and not omdb_item: continue @@ -337,7 +337,7 @@ def mass_metadata(config, library, movie_map, show_map): library.query_data(item.addGenre, genre) display_str += f"{', ' if len(display_str) > 0 else ''}+{genre}" if len(display_str) > 0: - util.print_end(length, f"{item.title[:25]:<25} | Genres | {display_str}") + logger.info(util.adjust_space(length, f"{item.title[:25]:<25} | Genres | {display_str}")) except Failed: pass if library.mass_audience_rating_update or library.mass_critic_rating_update: @@ -349,14 +349,14 @@ def mass_metadata(config, library, movie_map, show_map): else: raise Failed if new_rating is None: - util.print_end(length, f"{item.title[:25]:<25} | No Rating Found") + logger.info(util.adjust_space(length, f"{item.title[:25]:<25} | No Rating Found")) else: if library.mass_audience_rating_update and str(item.audienceRating) != str(new_rating): library.edit_query(item, {"audienceRating.value": new_rating, "audienceRating.locked": 1}) - util.print_end(length, f"{item.title[:25]:<25} | Audience Rating | {new_rating}") + logger.info(util.adjust_space(length, f"{item.title[:25]:<25} | Audience Rating | {new_rating}")) if library.mass_critic_rating_update and str(item.rating) != str(new_rating): library.edit_query(item, {"rating.value": new_rating, "rating.locked": 1}) - util.print_end(length, f"{item.title[:25]:<25} | Critic Rating | {new_rating}") + logger.info(util.adjust_space(length, f"{item.title[:25]:<25} | Critic Rating | {new_rating}")) except Failed: pass From 4264dbbdb2a4909f09a00a8dc6035ab8fdbca80d Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sat, 22 May 2021 00:45:03 -0400 Subject: [PATCH 04/14] speed up IMDb for shows --- modules/imdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imdb.py b/modules/imdb.py index 15ac801a..6b037032 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -97,8 +97,8 @@ class IMDbAPI: show_ids = [] movie_ids = [] def run_convert(imdb_id): - tmdb_id = self.config.Convert.imdb_to_tmdb(imdb_id) tvdb_id = self.config.Convert.imdb_to_tvdb(imdb_id) if not is_movie else None + tmdb_id = self.config.Convert.imdb_to_tmdb(imdb_id) if tvdb_id is None else None if not tmdb_id and not tvdb_id: logger.error(f"Convert Error: No TMDb ID or TVDb ID found for IMDb: {imdb_id}") if tmdb_id: movie_ids.append(tmdb_id) From e946a4b6af70f411b32d76c11f03cbe89d2281df Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sat, 22 May 2021 22:42:00 -0400 Subject: [PATCH 05/14] #268 Added smart collection validation --- modules/plex.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/plex.py b/modules/plex.py index 7a1c44c4..7b333e04 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -458,8 +458,14 @@ class PlexAPI: sort_type = movie_smart_sorts[sort] if self.is_movie else show_smart_sorts[sort] return smart_type, f"?type={smart_type}&sort={sort_type}&label={labels[title]}" + def test_smart_filter(self, uri_args): + logger.debug(f"Smart Collection Test: {uri_args}") + test_items = self.get_filter_items(uri_args) + if len(test_items) < 1: + raise Failed(f"Plex Error: No items for smart filter: {uri_args}") + def create_smart_collection(self, title, smart_type, uri_args): - logger.debug(f"Smart Collection Created: {uri_args}") + self.test_smart_filter(uri_args) args = { "type": smart_type, "title": title, @@ -478,6 +484,7 @@ class PlexAPI: return f"server://{self.PlexServer.machineIdentifier}/com.plexapp.plugins.library/library/sections/{self.Plex.key}/all{uri_args}" def update_smart_collection(self, collection, uri_args): + self.test_smart_filter(uri_args) self._query(f"/library/collections/{collection.ratingKey}/items{utils.joinArgs({'uri': self.build_smart_filter(uri_args)})}", put=True) def smart(self, collection): @@ -645,13 +652,16 @@ class PlexAPI: return self.get_labeled_items(collection.title if isinstance(collection, Collections) else str(collection)) elif isinstance(collection, Collections): if self.smart(collection): - key = f"/library/sections/{self.Plex.key}/all{self.smart_filter(collection)}" - return self.Plex._search(key, None, 0, plexapi.X_PLEX_CONTAINER_SIZE) + return self.get_filter_items(self.smart_filter(collection)) else: return self.query(collection.items) else: return [] + def get_filter_items(self, uri_args): + key = f"/library/sections/{self.Plex.key}/all{uri_args}" + return self.Plex._search(key, None, 0, plexapi.X_PLEX_CONTAINER_SIZE) + def get_collection_name_and_items(self, collection, smart_label_collection): name = collection.title if isinstance(collection, Collections) else str(collection) return name, self.get_collection_items(collection, smart_label_collection) From de4515126a2d91939ac4bb6fb81e56a91f44ad31 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sun, 23 May 2021 23:38:46 -0400 Subject: [PATCH 06/14] updated how the log looks --- modules/anidb.py | 2 +- modules/anilist.py | 2 +- modules/builder.py | 39 ++++++++++++++++-------------- modules/config.py | 42 ++++++++++++++++++++++---------- modules/imdb.py | 4 ++-- modules/letterboxd.py | 1 + modules/mal.py | 2 +- modules/meta.py | 8 +++---- modules/plex.py | 1 - modules/radarr.py | 2 ++ modules/sonarr.py | 2 ++ modules/tmdb.py | 2 +- modules/trakttv.py | 2 +- modules/tvdb.py | 1 + modules/util.py | 27 +++++++++++++-------- plex_meta_manager.py | 56 ++++++++++++++++++++++++++++++------------- 16 files changed, 123 insertions(+), 70 deletions(-) diff --git a/modules/anidb.py b/modules/anidb.py index 785ec679..b1beb49d 100644 --- a/modules/anidb.py +++ b/modules/anidb.py @@ -49,7 +49,6 @@ class AniDBAPI: def get_items(self, method, data, language): pretty = util.pretty_names[method] if method in util.pretty_names else method - logger.debug(f"Data: {data}") anidb_ids = [] if method == "anidb_popular": logger.info(f"Processing {pretty}: {data} Anime") @@ -60,6 +59,7 @@ class AniDBAPI: elif method == "anidb_relation": anidb_ids.extend(self._relations(data, language)) else: raise Failed(f"AniDB Error: Method {method} not supported") movie_ids, show_ids = self.config.Convert.anidb_to_ids(anidb_ids) + logger.debug("") logger.debug(f"AniDB IDs Found: {anidb_ids}") logger.debug(f"TMDb IDs Found: {movie_ids}") logger.debug(f"TVDb IDs Found: {show_ids}") diff --git a/modules/anilist.py b/modules/anilist.py index 3c366e93..7301327f 100644 --- a/modules/anilist.py +++ b/modules/anilist.py @@ -218,7 +218,6 @@ class AniListAPI: raise Failed(f"AniList Error: No valid AniList IDs in {anilist_ids}") def get_items(self, method, data): - logger.debug(f"Data: {data}") pretty = util.pretty_names[method] if method in util.pretty_names else method if method == "anilist_id": anilist_id, name = self._validate(data) @@ -243,6 +242,7 @@ class AniListAPI: else: raise Failed(f"AniList Error: Method {method} not supported") movie_ids, show_ids = self.config.Convert.anilist_to_ids(anilist_ids) + logger.debug("") logger.debug(f"AniList IDs Found: {anilist_ids}") logger.debug(f"Shows Found: {show_ids}") logger.debug(f"Movies Found: {movie_ids}") diff --git a/modules/builder.py b/modules/builder.py index 56ea1ea5..a606b048 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -384,8 +384,6 @@ class CollectionBuilder: if skip_collection: raise Failed(f"{self.schedule}\n\nCollection {self.name} not scheduled to run") - logger.info(f"Scanning {self.name} Collection") - self.run_again = "run_again" in methods self.collectionless = "plex_collectionless" in methods @@ -432,8 +430,9 @@ class CollectionBuilder: else: raise Failed("Collection Error: smart_url attribute is blank") + self.smart_filter_details = "" if "smart_filter" in methods: - logger.info("") + filter_details = "\n" smart_filter = self.data[methods["smart_filter"]] if smart_filter is None: raise Failed(f"Collection Error: smart_filter attribute is blank") @@ -453,7 +452,7 @@ class CollectionBuilder: smart_type = "shows" else: smart_type = "movies" - logger.info(f"Smart {smart_type.capitalize()[:-1]} Filter") + filter_details += f"Smart {smart_type.capitalize()[:-1]} Filter\n" self.smart_type_key, smart_sorts = plex.smart_types[smart_type] smart_sort = "random" @@ -463,7 +462,7 @@ class CollectionBuilder: if smart_filter[smart_methods["sort_by"]] not in smart_sorts: raise Failed(f"Collection Error: sort_by: {smart_filter[smart_methods['sort_by']]} is invalid") smart_sort = smart_filter[smart_methods["sort_by"]] - logger.info(f"Sort By: {smart_sort}") + filter_details += f"Sort By: {smart_sort}\n" limit = None if "limit" in smart_methods: @@ -472,7 +471,7 @@ class CollectionBuilder: if not isinstance(smart_filter[smart_methods["limit"]], int) or smart_filter[smart_methods["limit"]] < 1: raise Failed("Collection Error: limit attribute must be an integer greater then 0") limit = smart_filter[smart_methods["limit"]] - logger.info(f"Limit: {limit}") + filter_details += f"Limit: {limit}\n" validate = True if "validate" in smart_methods: @@ -481,7 +480,7 @@ class CollectionBuilder: if not isinstance(smart_filter[smart_methods["validate"]], bool): raise Failed("Collection Error: validate attribute must be either true or false") validate = smart_filter[smart_methods["validate"]] - logger.info(f"Validate: {validate}") + filter_details += f"Validate: {validate}\n" def _filter(filter_dict, fail, is_all=True, level=1): output = "" @@ -590,7 +589,7 @@ class CollectionBuilder: if not isinstance(smart_filter[smart_methods[base]], dict): raise Failed(f"Collection Error: {base} must be a dictionary: {smart_filter[smart_methods[base]]}") built_filter, filter_text = _filter(smart_filter[smart_methods[base]], validate, is_all=base_all) - util.print_multiline(f"Filter:{filter_text}") + self.smart_filter_details = f"{filter_details}Filter:{filter_text}" final_filter = built_filter[:-1] if base_all else f"push=1&{built_filter}pop=1" self.smart_url = f"?type={self.smart_type_key}&{f'limit={limit}&' if limit else ''}sort={smart_sorts[smart_sort]}&{final_filter}" @@ -1277,6 +1276,8 @@ class CollectionBuilder: else: self.sync = False self.run_again = False + logger.info("") + logger.info("Validation Successful") def collect_rating_keys(self, movie_map, show_map): def add_rating_keys(keys): @@ -1442,10 +1443,11 @@ class CollectionBuilder: elif self.details["show_filtered"] is True: logger.info(f"{name} Collection | X | {current.title}") media_type = f"{'Movie' if self.library.is_movie else 'Show'}{'s' if total > 1 else ''}" - logger.info(util.adjust_space(length, f"{total} {media_type} Processed")) + util.print_end(length) + logger.info("") + logger.info(f"{total} {media_type} Processed") def run_missing(self): - logger.info("") arr_filters = [] for filter_method, filter_data in self.filters: if (filter_method.startswith("original_language") and self.library.is_movie) or filter_method.startswith("tmdb_vote_count"): @@ -1472,6 +1474,7 @@ class CollectionBuilder: logger.info(f"{self.name} Collection | ? | {movie.title} (TMDb: {missing_id})") elif self.details["show_filtered"] is True: logger.info(f"{self.name} Collection | X | {movie.title} (TMDb: {missing_id})") + logger.info("") logger.info(f"{len(missing_movies_with_names)} Movie{'s' if len(missing_movies_with_names) > 1 else ''} Missing") if self.details["save_missing"] is True: self.library.add_missing(self.name, missing_movies_with_names, True) @@ -1506,6 +1509,7 @@ class CollectionBuilder: logger.info(f"{self.name} Collection | ? | {title} (TVDB: {missing_id})") elif self.details["show_filtered"] is True: logger.info(f"{self.name} Collection | X | {title} (TVDb: {missing_id})") + logger.info("") logger.info(f"{len(missing_shows_with_names)} Show{'s' if len(missing_shows_with_names) > 1 else ''} Missing") if self.details["save_missing"] is True: self.library.add_missing(self.name, missing_shows_with_names, False) @@ -1520,17 +1524,22 @@ class CollectionBuilder: self.run_again_shows.extend(missing_tvdb_ids) def sync_collection(self): - logger.info("") count_removed = 0 for ratingKey, item in self.plex_map.items(): if item is not None: + if count_removed == 0: + logger.info("") + util.separator(f"Removed from {self.name} Collection", space=False, border=False) + logger.info("") logger.info(f"{self.name} Collection | - | {item.title}") if self.smart_label_collection: self.library.query_data(item.removeLabel, self.name) else: self.library.query_data(item.removeCollection, self.name) count_removed += 1 - logger.info(f"{count_removed} {'Movie' if self.library.is_movie else 'Show'}{'s' if count_removed == 1 else ''} Removed") + if count_removed > 0: + logger.info("") + logger.info(f"{count_removed} {'Movie' if self.library.is_movie else 'Show'}{'s' if count_removed == 1 else ''} Removed") def update_details(self): if not self.obj and self.smart_url: @@ -1644,9 +1653,6 @@ class CollectionBuilder: except BadRequest: logger.error(f"Detail: {image_method} failed to update {message}") - if len(self.posters) > 0: - logger.info("") - if len(self.posters) > 1: logger.info(f"{len(self.posters)} posters found:") for p in self.posters: @@ -1671,9 +1677,6 @@ class CollectionBuilder: elif "tmdb_show_details" in self.posters: set_image("tmdb_show_details", self.posters) else: logger.info("No poster to update") - if len(self.backgrounds) > 0: - logger.info("") - if len(self.backgrounds) > 1: logger.info(f"{len(self.backgrounds)} backgrounds found:") for b in self.backgrounds: diff --git a/modules/config.py b/modules/config.py index 716c1e70..994a2810 100644 --- a/modules/config.py +++ b/modules/config.py @@ -318,14 +318,17 @@ class Config: continue util.separator() params = {} - logger.info("") + params["mapping_name"] = str(library_name) if lib and "library_name" in lib and lib["library_name"]: params["name"] = str(lib["library_name"]) - logger.info(f"Connecting to {params['name']} ({library_name}) Library...") + display_name = f"{params['name']} ({params['mapping_name']})" else: - params["name"] = str(library_name) - logger.info(f"Connecting to {params['name']} Library...") - params["mapping_name"] = str(library_name) + params["name"] = params["mapping_name"] + display_name = params["mapping_name"] + + util.separator(f"{display_name} Configuration") + logger.info("") + logger.info(f"Connecting to {display_name} Library...") params["asset_directory"] = check_for_attribute(lib, "asset_directory", parent="settings", var_type="list_path", default=self.general["asset_directory"], default_is_none=True, save=False) if params["asset_directory"] is None: @@ -436,15 +439,19 @@ class Config: params["plex"]["empty_trash"] = check_for_attribute(lib, "empty_trash", parent="plex", var_type="bool", default=self.general["plex"]["empty_trash"], save=False) params["plex"]["optimize"] = check_for_attribute(lib, "optimize", parent="plex", var_type="bool", default=self.general["plex"]["optimize"], save=False) library = PlexAPI(params, self.TMDb, self.TVDb) - logger.info(f"{params['name']} Library Connection Successful") + logger.info("") + logger.info(f"{display_name} Library Connection Successful") except Failed as e: util.print_multiline(e, error=True) - logger.info(f"{params['name']} Library Connection Failed") + logger.info(f"{display_name} Library Connection Failed") continue if self.general["radarr"]["url"] or (lib and "radarr" in lib): logger.info("") - logger.info(f"Connecting to {params['name']} library's Radarr...") + util.separator("Radarr Configuration", space=False, border=False) + logger.info("") + logger.info(f"Connecting to {display_name} library's Radarr...") + logger.info("") radarr_params = {} try: radarr_params["url"] = check_for_attribute(lib, "url", parent="radarr", default=self.general["radarr"]["url"], req_default=True, save=False) @@ -460,11 +467,15 @@ class Config: library.Radarr = RadarrAPI(radarr_params) except Failed as e: util.print_multiline(e, error=True) - logger.info(f"{params['name']} library's Radarr Connection {'Failed' if library.Radarr is None else 'Successful'}") + logger.info("") + logger.info(f"{display_name} library's Radarr Connection {'Failed' if library.Radarr is None else 'Successful'}") if self.general["sonarr"]["url"] or (lib and "sonarr" in lib): logger.info("") - logger.info(f"Connecting to {params['name']} library's Sonarr...") + util.separator("Sonarr Configuration", space=False, border=False) + logger.info("") + logger.info(f"Connecting to {display_name} library's Sonarr...") + logger.info("") sonarr_params = {} try: sonarr_params["url"] = check_for_attribute(lib, "url", parent="sonarr", default=self.general["sonarr"]["url"], req_default=True, save=False) @@ -486,11 +497,15 @@ class Config: library.Sonarr = SonarrAPI(sonarr_params, library.Plex.language) except Failed as e: util.print_multiline(e, error=True) - logger.info(f"{params['name']} library's Sonarr Connection {'Failed' if library.Sonarr is None else 'Successful'}") + logger.info("") + logger.info(f"{display_name} library's Sonarr Connection {'Failed' if library.Sonarr is None else 'Successful'}") if self.general["tautulli"]["url"] or (lib and "tautulli" in lib): logger.info("") - logger.info(f"Connecting to {params['name']} library's Tautulli...") + util.separator("Tautulli Configuration", space=False, border=False) + logger.info("") + logger.info(f"Connecting to {display_name} library's Tautulli...") + logger.info("") tautulli_params = {} try: tautulli_params["url"] = check_for_attribute(lib, "url", parent="tautulli", default=self.general["tautulli"]["url"], req_default=True, save=False) @@ -498,7 +513,8 @@ class Config: library.Tautulli = TautulliAPI(tautulli_params) except Failed as e: util.print_multiline(e, error=True) - logger.info(f"{params['name']} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}") + logger.info("") + logger.info(f"{display_name} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}") logger.info("") self.libraries.append(library) diff --git a/modules/imdb.py b/modules/imdb.py index 6b037032..728c79a9 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -93,14 +93,13 @@ class IMDbAPI: def get_items(self, method, data, language, is_movie): pretty = util.pretty_names[method] if method in util.pretty_names else method - logger.debug(f"Data: {data}") show_ids = [] movie_ids = [] def run_convert(imdb_id): tvdb_id = self.config.Convert.imdb_to_tvdb(imdb_id) if not is_movie else None tmdb_id = self.config.Convert.imdb_to_tmdb(imdb_id) if tvdb_id is None else None if not tmdb_id and not tvdb_id: - logger.error(f"Convert Error: No TMDb ID or TVDb ID found for IMDb: {imdb_id}") + logger.error(f"Convert Error: No {'' if is_movie else 'TVDb ID or '}TMDb ID found for IMDb: {imdb_id}") if tmdb_id: movie_ids.append(tmdb_id) if tvdb_id: show_ids.append(tvdb_id) @@ -119,6 +118,7 @@ class IMDbAPI: logger.info(util.adjust_space(length, f"Processed {total_ids} IMDb IDs")) else: raise Failed(f"IMDb Error: Method {method} not supported") + logger.debug("") logger.debug(f"TMDb IDs Found: {movie_ids}") logger.debug(f"TVDb IDs Found: {show_ids}") return movie_ids, show_ids diff --git a/modules/letterboxd.py b/modules/letterboxd.py index 6f8f6e01..681ff2ae 100644 --- a/modules/letterboxd.py +++ b/modules/letterboxd.py @@ -69,5 +69,6 @@ class LetterboxdAPI: logger.info(util.adjust_space(length, f"Processed {total_items} TMDb IDs")) else: logger.error(f"Letterboxd Error: No List Items found in {data}") + logger.debug("") logger.debug(f"TMDb IDs Found: {movie_ids}") return movie_ids, [] diff --git a/modules/mal.py b/modules/mal.py index 5be31e4a..eda320b4 100644 --- a/modules/mal.py +++ b/modules/mal.py @@ -194,7 +194,6 @@ class MyAnimeListAPI: return self._parse_request(url) def get_items(self, method, data): - logger.debug(f"Data: {data}") pretty = util.pretty_names[method] if method in util.pretty_names else method if method == "mal_id": mal_ids = [data] @@ -214,6 +213,7 @@ class MyAnimeListAPI: else: raise Failed(f"MyAnimeList Error: Method {method} not supported") movie_ids, show_ids = self.config.Convert.myanimelist_to_ids(mal_ids) + logger.debug("") logger.debug(f"MyAnimeList IDs Found: {mal_ids}") logger.debug(f"Shows Found: {show_ids}") logger.debug(f"Movies Found: {movie_ids}") diff --git a/modules/meta.py b/modules/meta.py index 3a1330a2..965fbcf1 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -66,11 +66,11 @@ class Metadata: return self.collections def update_metadata(self, TMDb, test): - logger.info("") - util.separator(f"Running Metadata") - logger.info("") if not self.metadata: - raise Failed("No metadata to edit") + return None + logger.info("") + util.separator("Running Metadata") + logger.info("") for mapping_name, meta in self.metadata.items(): methods = {mm.lower(): mm for mm in meta} if test and ("test" not in methods or meta[methods["test"]] is not True): diff --git a/modules/plex.py b/modules/plex.py index 7b333e04..104bbd76 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -530,7 +530,6 @@ class PlexAPI: return valid_collections def get_items(self, method, data): - logger.debug(f"Data: {data}") pretty = util.pretty_names[method] if method in util.pretty_names else method media_type = "Movie" if self.is_movie else "Show" items = [] diff --git a/modules/radarr.py b/modules/radarr.py index 9f211251..1f7e3203 100644 --- a/modules/radarr.py +++ b/modules/radarr.py @@ -66,6 +66,8 @@ class RadarrAPI: raise Failed(f"Sonarr Error: TMDb ID: {tmdb_id} not found") def add_tmdb(self, tmdb_ids, **options): + logger.info("") + util.separator(f"Adding to Radarr", space=False, border=False) logger.info("") logger.debug(f"TMDb IDs: {tmdb_ids}") tag_nums = [] diff --git a/modules/sonarr.py b/modules/sonarr.py index de7c0c5b..4a631216 100644 --- a/modules/sonarr.py +++ b/modules/sonarr.py @@ -86,6 +86,8 @@ class SonarrAPI: raise Failed(f"Sonarr Error: TVDb ID: {tvdb_id} not found") def add_tvdb(self, tvdb_ids, **options): + logger.info("") + util.separator(f"Adding to Sonarr", space=False, border=False) logger.info("") logger.debug(f"TVDb IDs: {tvdb_ids}") tag_nums = [] diff --git a/modules/tmdb.py b/modules/tmdb.py index 819583e6..e493915e 100644 --- a/modules/tmdb.py +++ b/modules/tmdb.py @@ -292,7 +292,6 @@ class TMDbAPI: return tmdb_id def get_items(self, method, data, is_movie): - logger.debug(f"Data: {data}") pretty = util.pretty_names[method] if method in util.pretty_names else method media_type = "Movie" if is_movie else "Show" movie_ids = [] @@ -362,6 +361,7 @@ class TMDbAPI: logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(movie_ids)} Movie{'' if len(movie_ids) == 1 else 's'})") if not is_movie and len(show_ids) > 0: logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(show_ids)} Show{'' if len(show_ids) == 1 else 's'})") + logger.debug("") logger.debug(f"TMDb IDs Found: {movie_ids}") logger.debug(f"TVDb IDs Found: {show_ids}") return movie_ids, show_ids diff --git a/modules/trakttv.py b/modules/trakttv.py index 37d82b13..8ed2a9d3 100644 --- a/modules/trakttv.py +++ b/modules/trakttv.py @@ -157,7 +157,6 @@ class TraktAPI: return trakt_values def get_items(self, method, data, is_movie): - logger.debug(f"Data: {data}") pretty = self.aliases[method] if method in self.aliases else method media_type = "Movie" if is_movie else "Show" if method in ["trakt_trending", "trakt_popular", "trakt_recommended", "trakt_watched", "trakt_collected"]: @@ -181,6 +180,7 @@ class TraktAPI: elif (isinstance(trakt_item, (Season, Episode))) and trakt_item.show.pk[1] not in show_ids: show_ids.append(int(trakt_item.show.pk[1])) logger.debug(f"Trakt {media_type} Found: {trakt_items}") + logger.debug("") logger.debug(f"TMDb IDs Found: {movie_ids}") logger.debug(f"TVDb IDs Found: {show_ids}") return movie_ids, show_ids diff --git a/modules/tvdb.py b/modules/tvdb.py index 8024dac8..93970232 100644 --- a/modules/tvdb.py +++ b/modules/tvdb.py @@ -163,6 +163,7 @@ class TVDbAPI: show_ids.extend(tvdb_ids) else: raise Failed(f"TVDb Error: Method {method} not supported") + logger.debug("") logger.debug(f"TMDb IDs Found: {movie_ids}") logger.debug(f"TVDb IDs Found: {show_ids}") return movie_ids, show_ids diff --git a/modules/util.py b/modules/util.py index 637fe53a..62676d8d 100644 --- a/modules/util.py +++ b/modules/util.py @@ -352,28 +352,35 @@ def regex_first_int(data, id_type, default=None): else: raise Failed(f"Regex Error: Failed to parse {id_type} from {data}") -def centered(text, do_print=True): +def centered(text, sep=" "): if len(text) > screen_width - 2: raise Failed("text must be shorter then screen_width") space = screen_width - len(text) - 2 + text = f" {text} " if space % 2 == 1: - text += " " + text += sep space -= 1 - side = int(space / 2) - final_text = f"{' ' * side}{text}{' ' * side}" - if do_print: - logger.info(final_text) + side = int(space / 2) - 1 + final_text = f"{sep * side}{text}{sep * side}" return final_text -def separator(text=None): +def separator(text=None, space=True, border=True, debug=False): + sep = " " if space else separating_character for handler in logger.handlers: apply_formatter(handler, border=False) - logger.info(f"|{separating_character * screen_width}|") + border_text = f"|{separating_character * screen_width}|" + if border and debug: + logger.debug(border_text) + elif border: + logger.info(border_text) if text: text_list = text.split("\n") for t in text_list: - logger.info(f"| {centered(t, do_print=False)} |") - logger.info(f"|{separating_character * screen_width}|") + logger.info(f"|{sep}{centered(t, sep=sep)}{sep}|") + if border and debug: + logger.debug(border_text) + elif border: + logger.info(border_text) for handler in logger.handlers: apply_formatter(handler) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 36cbffd1..e4df9845 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -91,15 +91,14 @@ def start(config_path, is_test, daily, requested_collections, requested_librarie file_handler.doRollover() logger.addHandler(file_handler) util.separator() - util.centered(" ") - util.centered(" ____ _ __ __ _ __ __ ") - util.centered("| _ \\| | _____ __ | \\/ | ___| |_ __ _ | \\/ | __ _ _ __ __ _ __ _ ___ _ __ ") - util.centered("| |_) | |/ _ \\ \\/ / | |\\/| |/ _ \\ __/ _` | | |\\/| |/ _` | '_ \\ / _` |/ _` |/ _ \\ '__|") - util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ") - util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ") - util.centered(" |___/ ") - util.centered(" Version: 1.9.2 ") - util.separator() + logger.info(util.centered(" ")) + logger.info(util.centered(" ____ _ __ __ _ __ __ ")) + logger.info(util.centered("| _ \\| | _____ __ | \\/ | ___| |_ __ _ | \\/ | __ _ _ __ __ _ __ _ ___ _ __ ")) + logger.info(util.centered("| |_) | |/ _ \\ \\/ / | |\\/| |/ _ \\ __/ _` | | |\\/| |/ _` | '_ \\ / _` |/ _` |/ _ \\ '__|")) + logger.info(util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ")) + logger.info(util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ")) + logger.info(util.centered(" |___/ ")) + logger.info(util.centered(" Version: 1.9.2 ")) if daily: start_type = "Daily " elif is_test: start_type = "Test " elif requested_collections: start_type = "Collections " @@ -132,7 +131,7 @@ def update_libraries(config, is_test, requested_collections, resume_from): logger.info("") util.separator(f"{library.name} Library") logger.info("") - util.separator(f"Mapping {library.name} Library") + util.separator(f"Mapping {library.name} Library", space=False, border=False) logger.info("") movie_map, show_map = map_guids(config, library) if not is_test and not resume_from and not collection_only and library.mass_update: @@ -145,18 +144,22 @@ def update_libraries(config, is_test, requested_collections, resume_from): metadata.update_metadata(config.TMDb, is_test) except Failed as e: logger.error(e) - logger.info("") - util.separator(f"{'Test ' if is_test else ''}Collections") collections_to_run = metadata.get_collections(requested_collections) if resume_from and resume_from not in collections_to_run: + logger.info("") logger.warning(f"Collection: {resume_from} not in Metadata File: {metadata.path}") continue if collections_to_run and not library_only: + logger.info("") + util.separator(f"{'Test ' if is_test else ''}Collections") logger.removeHandler(library_handler) resume_from = run_collection(config, library, metadata, collections_to_run, is_test, resume_from, movie_map, show_map) logger.addHandler(library_handler) - if not is_test and not requested_collections: + if not is_test and not requested_collections and ((library.show_unmanaged and not library_only) or (library.assets_for_all and not collection_only)): + logger.info("") + util.separator(f"Other {library.name} Library Operations") + logger.info("") unmanaged_collections = [] for col in library.get_all_collections(): if col.title not in library.collections: @@ -164,15 +167,16 @@ def update_libraries(config, is_test, requested_collections, resume_from): if library.show_unmanaged and not library_only: logger.info("") - util.separator(f"Unmanaged Collections in {library.name} Library") + util.separator(f"Unmanaged Collections in {library.name} Library", space=False, border=False) logger.info("") for col in unmanaged_collections: logger.info(col.title) + logger.info("") logger.info(f"{len(unmanaged_collections)} Unmanaged Collections") if library.assets_for_all and not collection_only: logger.info("") - util.separator(f"All {'Movies' if library.is_movie else 'Shows'} Assets Check for {library.name} Library") + util.separator(f"All {'Movies' if library.is_movie else 'Shows'} Assets Check for {library.name} Library", space=False, border=False) logger.info("") for col in unmanaged_collections: library.update_item_from_assets(col, collection_mode=True) @@ -236,6 +240,7 @@ def map_guids(config, library): show_map = {} length = 0 logger.info(f"Mapping {'Movie' if library.is_movie else 'Show'} Library: {library.name}") + logger.info("") items = library.Plex.all() for i, item in enumerate(items, 1): length = util.print_return(length, f"Processing: {i}/{len(items)} {item.title}") @@ -251,6 +256,7 @@ def map_guids(config, library): for m in main_id: if m in show_map: show_map[m].append(item.ratingKey) else: show_map[m] = [item.ratingKey] + logger.info("") logger.info(util.adjust_space(length, f"Processed {len(items)} {'Movies' if library.is_movie else 'Shows'}")) return movie_map, show_map @@ -419,11 +425,19 @@ def run_collection(config, library, metadata, requested_collections, is_test, re logger.info(output_str) logger.info("") + util.separator(f"Validating {mapping_name} Attributes", space=False, border=False) + builder = CollectionBuilder(config, library, metadata, mapping_name, collection_attrs) + logger.info("") + + util.separator(f"Building {mapping_name} Collection", space=False, border=False) if len(builder.schedule) > 0: util.print_multiline(builder.schedule, info=True) + if len(builder.smart_filter_details) > 0: + util.print_multiline(builder.smart_filter_details, info=True) + if not builder.smart_url: logger.info("") logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}") @@ -434,16 +448,24 @@ def run_collection(config, library, metadata, requested_collections, is_test, re logger.info(f"Collection Filter {filter_key}: {filter_value}") builder.collect_rating_keys(movie_map, show_map) - logger.info("") + if len(builder.rating_keys) > 0 and builder.build_collection: + logger.info("") + util.separator(f"Adding to {mapping_name} Collection", space=False, border=False) + logger.info("") builder.add_to_collection(movie_map) if len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0: + logger.info("") + util.separator(f"Missing from Library", space=False, border=False) + logger.info("") builder.run_missing() if builder.sync and len(builder.rating_keys) > 0 and builder.build_collection: builder.sync_collection() - logger.info("") if builder.build_collection: + logger.info("") + util.separator(f"Updating Details of {mapping_name} Collection", space=False, border=False) + logger.info("") builder.update_details() if builder.run_again and (len(builder.run_again_movies) > 0 or len(builder.run_again_shows) > 0): From 85a0db091a5b89a6b1325c28ccd5aa0efac4bd11 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 24 May 2021 11:23:21 -0400 Subject: [PATCH 07/14] log display changes --- modules/builder.py | 193 +++++++++++++++++++++++++------------------ plex_meta_manager.py | 1 - 2 files changed, 113 insertions(+), 81 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index a606b048..2163520f 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -220,11 +220,14 @@ class CollectionBuilder: methods = {m.lower(): m for m in self.data} if "template" in methods: + logger.info("") + logger.info("Validating Method: template") if not self.metadata.templates: raise Failed("Collection Error: No templates found") elif not self.data[methods["template"]]: raise Failed("Collection Error: template attribute is blank") else: + logger.debug(f"Value: {self.data[methods['template']]}") for variables in util.get_list(self.data[methods["template"]], split=False): if not isinstance(variables, dict): raise Failed("Collection Error: template attribute is not a dictionary") @@ -329,66 +332,105 @@ class CollectionBuilder: except Failed: continue - skip_collection = True - if "schedule" not in methods: - skip_collection = False - elif not self.data[methods["schedule"]]: - logger.error("Collection Error: schedule attribute is blank. Running daily") - skip_collection = False - else: - schedule_list = util.get_list(self.data[methods["schedule"]]) - next_month = current_time.replace(day=28) + timedelta(days=4) - last_day = next_month - timedelta(days=next_month.day) - for schedule in schedule_list: - run_time = str(schedule).lower() - if run_time.startswith("day") or run_time.startswith("daily"): - skip_collection = False - elif run_time.startswith("week") or run_time.startswith("month") or run_time.startswith("year"): - match = re.search("\\(([^)]+)\\)", run_time) - if match: - param = match.group(1) - if run_time.startswith("week"): - if param.lower() in util.days_alias: - weekday = util.days_alias[param.lower()] - self.schedule += f"\nScheduled weekly on {util.pretty_days[weekday]}" - if weekday == current_time.weekday(): - skip_collection = False - else: - logger.error(f"Collection Error: weekly schedule attribute {schedule} invalid must be a day of the week i.e. weekly(Monday)") - elif run_time.startswith("month"): - try: - if 1 <= int(param) <= 31: - self.schedule += f"\nScheduled monthly on the {util.make_ordinal(param)}" - if current_time.day == int(param) or (current_time.day == last_day.day and int(param) > last_day.day): + if "schedule" in methods: + logger.info("") + logger.info("Validating Method: schedule") + if not self.data[methods["schedule"]]: + raise Failed("Collection Error: schedule attribute is blank") + else: + logger.debug(f"Value: {self.data[methods['schedule']]}") + skip_collection = True + schedule_list = util.get_list(self.data[methods["schedule"]]) + next_month = current_time.replace(day=28) + timedelta(days=4) + last_day = next_month - timedelta(days=next_month.day) + for schedule in schedule_list: + run_time = str(schedule).lower() + if run_time.startswith("day") or run_time.startswith("daily"): + skip_collection = False + elif run_time.startswith("week") or run_time.startswith("month") or run_time.startswith("year"): + match = re.search("\\(([^)]+)\\)", run_time) + if match: + param = match.group(1) + if run_time.startswith("week"): + if param.lower() in util.days_alias: + weekday = util.days_alias[param.lower()] + self.schedule += f"\nScheduled weekly on {util.pretty_days[weekday]}" + if weekday == current_time.weekday(): skip_collection = False else: - logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be between 1 and 31") - except ValueError: - logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be an integer") - elif run_time.startswith("year"): - match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) - if match: - month = int(match.group(1)) - day = int(match.group(2)) - self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}" - if current_time.month == month and (current_time.day == day or (current_time.day == last_day.day and day > last_day.day)): - skip_collection = False - else: - logger.error(f"Collection Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)") + logger.error(f"Collection Error: weekly schedule attribute {schedule} invalid must be a day of the week i.e. weekly(Monday)") + elif run_time.startswith("month"): + try: + if 1 <= int(param) <= 31: + self.schedule += f"\nScheduled monthly on the {util.make_ordinal(param)}" + if current_time.day == int(param) or (current_time.day == last_day.day and int(param) > last_day.day): + skip_collection = False + else: + logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be between 1 and 31") + except ValueError: + logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be an integer") + elif run_time.startswith("year"): + match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) + if match: + month = int(match.group(1)) + day = int(match.group(2)) + self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}" + if current_time.month == month and (current_time.day == day or (current_time.day == last_day.day and day > last_day.day)): + skip_collection = False + else: + logger.error(f"Collection Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)") + else: + logger.error(f"Collection Error: failed to parse schedule: {schedule}") else: - logger.error(f"Collection Error: failed to parse schedule: {schedule}") - else: - logger.error(f"Collection Error: schedule attribute {schedule} invalid") - if len(self.schedule) == 0: - skip_collection = False - if skip_collection: - raise Failed(f"{self.schedule}\n\nCollection {self.name} not scheduled to run") + logger.error(f"Collection Error: schedule attribute {schedule} invalid") + if len(self.schedule) == 0: + skip_collection = False + if skip_collection: + raise Failed(f"{self.schedule}\n\nCollection {self.name} not scheduled to run") self.run_again = "run_again" in methods self.collectionless = "plex_collectionless" in methods + self.run_again = False + if "run_again" in methods: + logger.info("") + logger.info("Validating Method: run_again") + if not self.data[methods["run_again"]]: + logger.warning(f"Collection Warning: run_again attribute is blank defaulting to false") + else: + logger.debug(f"Value: {self.data[methods['run_again']]}") + self.run_again = util.get_bool("run_again", self.data[methods["run_again"]]) + + self.sync = self.library.sync_mode == "sync" + if "sync_mode" in methods: + logger.info("") + logger.info("Validating Method: sync_mode") + if not self.data[methods["sync_mode"]]: + logger.warning(f"Collection Warning: sync_mode attribute is blank using general: {self.library.sync_mode}") + else: + logger.debug(f"Value: {self.data[methods['sync_mode']]}") + if self.data[methods["sync_mode"]].lower() not in ["append", "sync"]: + logger.warning(f"Collection Warning: {self.data[methods['sync_mode']]} sync_mode invalid using general: {self.library.sync_mode}") + else: + self.sync = self.data[methods["sync_mode"]].lower() == "sync" + + self.build_collection = True + if "build_collection" in methods: + logger.info("") + logger.info("Validating Method: build_collection") + if not self.data[methods["build_collection"]]: + logger.warning(f"Collection Warning: build_collection attribute is blank defaulting to true") + else: + logger.debug(f"Value: {self.data[methods['build_collection']]}") + self.build_collection = util.get_bool("build_collection", self.data[methods["build_collection"]]) + if "tmdb_person" in methods: - if self.data[methods["tmdb_person"]]: + logger.info("") + logger.info("Validating Method: build_collection") + if not self.data[methods["tmdb_person"]]: + raise Failed("Collection Error: tmdb_person attribute is blank") + else: + logger.debug(f"Value: {self.data[methods['tmdb_person']]}") valid_names = [] for tmdb_id in util.get_int_list(self.data[methods["tmdb_person"]], "TMDb Person ID"): person = config.TMDb.get_person(tmdb_id) @@ -401,43 +443,48 @@ class CollectionBuilder: self.details["tmdb_person"] = valid_names else: raise Failed(f"Collection Error: No valid TMDb Person IDs in {self.data[methods['tmdb_person']]}") - else: - raise Failed("Collection Error: tmdb_person attribute is blank") self.smart_sort = "random" self.smart_label_collection = False if "smart_label" in methods: + logger.info("") + logger.info("Validating Method: smart_label") self.smart_label_collection = True - if self.data[methods["smart_label"]]: + if not self.data[methods["smart_label"]]: + logger.warning("Collection Error: smart_label attribute is blank defaulting to random") + else: + logger.debug(f"Value: {self.data[methods['smart_label']]}") if (self.library.is_movie and str(self.data[methods["smart_label"]]).lower() in plex.movie_smart_sorts) \ or (self.library.is_show and str(self.data[methods["smart_label"]]).lower() in plex.show_smart_sorts): self.smart_sort = str(self.data[methods["smart_label"]]).lower() else: - logger.info("") logger.warning(f"Collection Error: smart_label attribute: {self.data[methods['smart_label']]} is invalid defaulting to random") - else: - logger.info("") - logger.warning("Collection Error: smart_label attribute is blank defaulting to random") self.smart_url = None self.smart_type_key = None if "smart_url" in methods: - if self.data[methods["smart_url"]]: + logger.info("") + logger.info("Validating Method: smart_url") + if not self.data[methods["smart_url"]]: + raise Failed("Collection Error: smart_url attribute is blank") + else: + logger.debug(f"Value: {self.data[methods['smart_url']]}") try: self.smart_url, self.smart_type_key = library.get_smart_filter_from_uri(self.data[methods["smart_url"]]) except ValueError: raise Failed("Collection Error: smart_url is incorrectly formatted") - else: - raise Failed("Collection Error: smart_url attribute is blank") self.smart_filter_details = "" if "smart_filter" in methods: + logger.info("") + logger.info("Validating Method: smart_filter") filter_details = "\n" smart_filter = self.data[methods["smart_filter"]] if smart_filter is None: raise Failed(f"Collection Error: smart_filter attribute is blank") if not isinstance(smart_filter, dict): raise Failed(f"Collection Error: smart_filter must be a dictionary: {smart_filter}") + logger.debug(f"Value: {self.data[methods['smart_filter']]}") smart_methods = {m.lower(): m for m in smart_filter} if "any" in smart_methods and "all" in smart_methods: raise Failed(f"Collection Error: Cannot have more then one base") @@ -611,6 +658,10 @@ class CollectionBuilder: self.smart = self.smart_url or self.smart_label_collection for method_key, method_data in self.data.items(): + if method_key.lower() in ignored_details: + continue + logger.info("") + logger.info(f"Validating Method: {method_key}") if "trakt" in method_key.lower() and not config.Trakt: raise Failed(f"Collection Error: {method_key} requires Trakt todo be configured") elif "imdb" in method_key.lower() and not config.IMDb: raise Failed(f"Collection Error: {method_key} requires TMDb or Trakt to be configured") elif "radarr" in method_key.lower() and not self.library.Radarr: raise Failed(f"Collection Error: {method_key} requires Radarr to be configured") @@ -618,8 +669,6 @@ class CollectionBuilder: elif "tautulli" in method_key.lower() and not self.library.Tautulli: raise Failed(f"Collection Error: {method_key} requires Tautulli to be configured") elif "mal" in method_key.lower() and not config.MyAnimeList: raise Failed(f"Collection Error: {method_key} requires MyAnimeList to be configured") elif method_data is not None: - logger.debug("") - logger.debug(f"Validating Method: {method_key}") logger.debug(f"Value: {method_data}") if method_key.lower() in method_alias: method_name = method_alias[method_key.lower()] @@ -1226,15 +1275,6 @@ class CollectionBuilder: else: logger.warning(f"Collection Warning: {method_key} attribute is blank") - self.sync = self.library.sync_mode == "sync" - if "sync_mode" in methods: - if not self.data[methods["sync_mode"]]: - logger.warning(f"Collection Warning: sync_mode attribute is blank using general: {self.library.sync_mode}") - elif self.data[methods["sync_mode"]].lower() not in ["append", "sync"]: - logger.warning(f"Collection Warning: {self.data[methods['sync_mode']]} sync_mode invalid using general: {self.library.sync_mode}") - else: - self.sync = self.data[methods["sync_mode"]].lower() == "sync" - if self.add_to_radarr is None: self.add_to_radarr = self.library.Radarr.add if self.library.Radarr else False if self.add_to_sonarr is None: @@ -1250,13 +1290,6 @@ class CollectionBuilder: self.details["collection_mode"] = "hide" self.sync = True - self.build_collection = True - if "build_collection" in methods: - if not self.data[methods["build_collection"]]: - logger.warning(f"Collection Warning: build_collection attribute is blank defaulting to true") - else: - self.build_collection = util.get_bool("build_collection", self.data[methods["build_collection"]]) - if self.build_collection: try: self.obj = library.get_collection(self.name) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index e4df9845..623b934c 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -159,7 +159,6 @@ def update_libraries(config, is_test, requested_collections, resume_from): if not is_test and not requested_collections and ((library.show_unmanaged and not library_only) or (library.assets_for_all and not collection_only)): logger.info("") util.separator(f"Other {library.name} Library Operations") - logger.info("") unmanaged_collections = [] for col in library.get_all_collections(): if col.title not in library.collections: From 6d5fa9d52519168347a174c5e69ebf9883f66d58 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Mon, 24 May 2021 18:16:19 -0400 Subject: [PATCH 08/14] #274 added --no-countdown --- modules/builder.py | 4 ++-- modules/convert.py | 3 ++- modules/plex.py | 2 +- plex_meta_manager.py | 21 ++++++++++++--------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 2163520f..2ddf7135 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1392,8 +1392,8 @@ class CollectionBuilder: if movie is None: logger.warning(f"Filter Error: No TMDb ID found for {current.title}") continue - if (modifier == ".not" and movie.original_language in filter_data) or ( - modifier != ".not" and movie.original_language not in filter_data): + if (modifier == ".not" and movie.original_language in filter_data) \ + or (modifier != ".not" and movie.original_language not in filter_data): match = False break elif method_name == "audio_track_title": diff --git a/modules/convert.py b/modules/convert.py index 043185cb..1124dc0c 100644 --- a/modules/convert.py +++ b/modules/convert.py @@ -275,6 +275,7 @@ class Convert: elif url_parsed.scheme == "imdb": imdb_id.append(url_parsed.netloc) elif url_parsed.scheme == "tmdb": tmdb_id.append(int(url_parsed.netloc)) except requests.exceptions.ConnectionError: + library.query(item.refresh) util.print_stacktrace() raise Failed("No External GUIDs found") if not tvdb_id and not imdb_id and not tmdb_id: @@ -361,5 +362,5 @@ class Convert: logger.info(util.adjust_space(length, f"Mapping Error | {item.guid:<46} | {e} for {item.title}")) except BadRequest: util.print_stacktrace() - logger.info(util.adjust_space(length, f"Mapping Error: | {item.guid} for {item.title} not found")) + logger.info(util.adjust_space(length, f"Mapping Error | {item.guid:<46} | Bad Request for {item.title}")) return None, None diff --git a/modules/plex.py b/modules/plex.py index 104bbd76..9105d8c2 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -403,7 +403,7 @@ class PlexAPI: @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex) def get_guids(self, item): item.reload(checkFiles=False, includeAllConcerts=False, includeBandwidths=False, includeChapters=False, - includeChildren=False, includeConcerts=False, includeExternalMedia=False, inclueExtras=False, + includeChildren=False, includeConcerts=False, includeExternalMedia=False, includeExtras=False, includeFields='', includeGeolocation=False, includeLoudnessRamps=False, includeMarkers=False, includeOnDeck=False, includePopularLeaves=False, includePreferences=False, includeRelated=False, includeRelatedCount=0, includeReviews=False, includeStations=False) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 623b934c..11ba6865 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -21,6 +21,7 @@ parser.add_argument("-co", "--collection-only", "--collections-only", dest="coll parser.add_argument("-lo", "--library-only", "--libraries-only", dest="library_only", help="Run only library operations", action="store_true", default=False) parser.add_argument("-rc", "-cl", "--collection", "--collections", "--run-collection", "--run-collections", dest="collections", help="Process only specified collections (comma-separated list)", type=str) parser.add_argument("-rl", "-l", "--library", "--libraries", "--run-library", "--run-libraries", dest="libraries", help="Process only specified libraries (comma-separated list)", type=str) +parser.add_argument("-nc", "--no-countdown", dest="no_countdown", help="Run without displaying countdown", action="store_true", default=False) parser.add_argument("-d", "--divider", dest="divider", help="Character that divides the sections (Default: '=')", default="=", type=str) parser.add_argument("-w", "--width", dest="width", help="Screen Width (Default: 100)", default=100, type=int) args = parser.parse_args() @@ -40,6 +41,7 @@ def check_bool(env_str, default): test = check_bool("PMM_TEST", args.test) debug = check_bool("PMM_DEBUG", args.debug) run = check_bool("PMM_RUN", args.run) +no_countdown = check_bool("PMM_NO_COUNTDOWN", args.no_countdown) library_only = check_bool("PMM_LIBRARIES_ONLY", args.library_only) collection_only = check_bool("PMM_COLLECTIONS_ONLY", args.collection_only) collections = os.environ.get("PMM_COLLECTIONS") if os.environ.get("PMM_COLLECTIONS") else args.collections @@ -489,16 +491,17 @@ try: schedule.every().day.at(time_to_run).do(start, config_file, False, True, None, None, None) while True: schedule.run_pending() - current = datetime.now().strftime("%H:%M") - seconds = (datetime.strptime(time_to_run, "%H:%M") - datetime.strptime(current, "%H:%M")).total_seconds() - hours = int(seconds // 3600) - if hours < 0: - hours += 24 - minutes = int((seconds % 3600) // 60) - time_str = f"{hours} Hour{'s' if hours > 1 else ''} and " if hours > 0 else "" - time_str += f"{minutes} Minute{'s' if minutes > 1 else ''}" + if not no_countdown: + current = datetime.now().strftime("%H:%M") + seconds = (datetime.strptime(time_to_run, "%H:%M") - datetime.strptime(current, "%H:%M")).total_seconds() + hours = int(seconds // 3600) + if hours < 0: + hours += 24 + minutes = int((seconds % 3600) // 60) + time_str = f"{hours} Hour{'s' if hours > 1 else ''} and " if hours > 0 else "" + time_str += f"{minutes} Minute{'s' if minutes > 1 else ''}" - time_length = util.print_return(time_length, f"Current Time: {current} | {time_str} until the daily run at {time_to_run}") + time_length = util.print_return(time_length, f"Current Time: {current} | {time_str} until the daily run at {time_to_run}") time.sleep(1) except KeyboardInterrupt: util.separator("Exiting Plex Meta Manager") From d94a9fc44afb3836af3eeffa925c16e52a5f5a09 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 25 May 2021 00:16:58 -0400 Subject: [PATCH 09/14] fix for #275 --- modules/meta.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/meta.py b/modules/meta.py index 965fbcf1..bb41ab97 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -119,7 +119,7 @@ class Metadata: logger.error(f"Metadata Error: {attr} attribute is blank") def edit_tags(attr, obj, group, alias, key=None, extra=None, movie_library=False): - if movie_library and not self.library.is_movie: + if movie_library and not self.library.is_movie and (attr in alias or f"{attr}.sync" in alias or f"{attr}.remove" in alias): logger.error(f"Metadata Error: {attr} attribute only works for movie libraries") elif attr in alias and f"{attr}.sync" in alias: logger.error(f"Metadata Error: Cannot use {attr} and {attr}.sync together") @@ -140,7 +140,7 @@ class Metadata: add_tags = extra remove_tags = group[alias[f"{attr}.remove"]] if f"{attr}.remove" in alias else None sync_tags = group[alias[f"{attr}.sync"]] if f"{attr}.sync" in alias else None - return self.library.edit_tags("attr", obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags, key=key) + return self.library.edit_tags(attr, obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags, key=key) return False def set_image(attr, obj, group, alias, poster=True, url=True): From 4fe06ec64a24e314e1bb3092048140c0ae9a5b84 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 25 May 2021 00:58:43 -0400 Subject: [PATCH 10/14] #275 fix for tags --- modules/meta.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/meta.py b/modules/meta.py index bb41ab97..cec47994 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -132,14 +132,11 @@ class Metadata: elif f"{attr}.sync" in alias and group[alias[f"{attr}.sync"]] is None: logger.error(f"Metadata Error: {attr}.sync attribute is blank") elif attr in alias or f"{attr}.remove" in alias or f"{attr}.sync" in alias: - add_tags = group[alias[attr]] if attr in alias else None + add_tags = util.get_list(group[alias[attr]]) if attr in alias else [] if extra: - if add_tags: - add_tags.extend(extra) - else: - add_tags = extra - remove_tags = group[alias[f"{attr}.remove"]] if f"{attr}.remove" in alias else None - sync_tags = group[alias[f"{attr}.sync"]] if f"{attr}.sync" in alias else None + add_tags.extend(extra) + remove_tags = util.get_list(group[alias[f"{attr}.remove"]]) if f"{attr}.remove" in alias else None + sync_tags = util.get_list(group[alias[f"{attr}.sync"]]) if f"{attr}.sync" in alias else None return self.library.edit_tags(attr, obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags, key=key) return False From 00b128a9677f62440310c7e938713362c78f59b8 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 25 May 2021 01:29:25 -0400 Subject: [PATCH 11/14] upped beta version --- plex_meta_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 11ba6865..ba17638f 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -100,7 +100,7 @@ def start(config_path, is_test, daily, requested_collections, requested_librarie logger.info(util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ")) logger.info(util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ")) logger.info(util.centered(" |___/ ")) - logger.info(util.centered(" Version: 1.9.2 ")) + logger.info(util.centered(" Version: 1.9.3-beta1 ")) if daily: start_type = "Daily " elif is_test: start_type = "Test " elif requested_collections: start_type = "Collections " From f28c09bf4fc5b9b320869514e7c3cac0c2a2e1ef Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 25 May 2021 18:22:59 -0400 Subject: [PATCH 12/14] add ability to run multiple times per day --- config/config.yml.template | 3 ++ modules/builder.py | 76 ++++++++++++++++++--------------- modules/config.py | 14 +++++-- modules/util.py | 3 +- plex_meta_manager.py | 86 ++++++++++++++++++++++---------------- 5 files changed, 110 insertions(+), 72 deletions(-) diff --git a/config/config.yml.template b/config/config.yml.template index 8f291cc1..9ee8c3de 100644 --- a/config/config.yml.template +++ b/config/config.yml.template @@ -33,6 +33,9 @@ plex: # Can be individually specified url: http://192.168.1.12:32400 token: #################### timeout: 60 + clean_bundles: false + empty_trash: false + optimize: false tmdb: apikey: ################################ language: en diff --git a/modules/builder.py b/modules/builder.py index 2ddf7135..67616d0a 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -345,42 +345,52 @@ class CollectionBuilder: last_day = next_month - timedelta(days=next_month.day) for schedule in schedule_list: run_time = str(schedule).lower() - if run_time.startswith("day") or run_time.startswith("daily"): + if run_time.startswith(("day", "daily")): skip_collection = False - elif run_time.startswith("week") or run_time.startswith("month") or run_time.startswith("year"): + elif run_time.startswith(("hour", "week", "month", "year")): match = re.search("\\(([^)]+)\\)", run_time) - if match: - param = match.group(1) - if run_time.startswith("week"): - if param.lower() in util.days_alias: - weekday = util.days_alias[param.lower()] - self.schedule += f"\nScheduled weekly on {util.pretty_days[weekday]}" - if weekday == current_time.weekday(): - skip_collection = False - else: - logger.error(f"Collection Error: weekly schedule attribute {schedule} invalid must be a day of the week i.e. weekly(Monday)") - elif run_time.startswith("month"): - try: - if 1 <= int(param) <= 31: - self.schedule += f"\nScheduled monthly on the {util.make_ordinal(param)}" - if current_time.day == int(param) or (current_time.day == last_day.day and int(param) > last_day.day): - skip_collection = False - else: - logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be between 1 and 31") - except ValueError: - logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be an integer") - elif run_time.startswith("year"): - match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) - if match: - month = int(match.group(1)) - day = int(match.group(2)) - self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}" - if current_time.month == month and (current_time.day == day or (current_time.day == last_day.day and day > last_day.day)): - skip_collection = False - else: - logger.error(f"Collection Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)") - else: + if not match: logger.error(f"Collection Error: failed to parse schedule: {schedule}") + continue + param = match.group(1) + if run_time.startswith("hour"): + try: + if 0 <= int(param) <= 23: + self.schedule += f"\nScheduled to run only on the {util.make_ordinal(param)} hour" + if config.run_hour == int(param): + skip_collection = False + else: + raise ValueError + except ValueError: + logger.error(f"Collection Error: hourly schedule attribute {schedule} invalid must be an integer between 0 and 23") + elif run_time.startswith("week"): + if param.lower() not in util.days_alias: + logger.error(f"Collection Error: weekly schedule attribute {schedule} invalid must be a day of the week i.e. weekly(Monday)") + continue + weekday = util.days_alias[param.lower()] + self.schedule += f"\nScheduled weekly on {util.pretty_days[weekday]}" + if weekday == current_time.weekday(): + skip_collection = False + elif run_time.startswith("month"): + try: + if 1 <= int(param) <= 31: + self.schedule += f"\nScheduled monthly on the {util.make_ordinal(param)}" + if current_time.day == int(param) or (current_time.day == last_day.day and int(param) > last_day.day): + skip_collection = False + else: + raise ValueError + except ValueError: + logger.error(f"Collection Error: monthly schedule attribute {schedule} invalid must be an integer between 1 and 31") + elif run_time.startswith("year"): + match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param) + if not match: + logger.error(f"Collection Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)") + continue + month = int(match.group(1)) + day = int(match.group(2)) + self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}" + if current_time.month == month and (current_time.day == day or (current_time.day == last_day.day and day > last_day.day)): + skip_collection = False else: logger.error(f"Collection Error: schedule attribute {schedule} invalid") if len(self.schedule) == 0: diff --git a/modules/config.py b/modules/config.py index 994a2810..349d08bd 100644 --- a/modules/config.py +++ b/modules/config.py @@ -1,4 +1,5 @@ import logging, os +from datetime import datetime from modules import util from modules.anidb import AniDBAPI from modules.anilist import AniListAPI @@ -48,7 +49,7 @@ mass_update_options = {"tmdb": "Use TMDb Metadata", "omdb": "Use IMDb Metadata t library_types = {"movie": "For Movie Libraries", "show": "For Show Libraries"} class Config: - def __init__(self, default_dir, config_path=None, libraries_to_run=None): + def __init__(self, default_dir, config_path=None, is_test=False, time_scheduled=None, requested_collections=None, requested_libraries=None, resume_from=None): logger.info("Locating config...") if config_path and os.path.exists(config_path): self.config_path = os.path.abspath(config_path) elif config_path and not os.path.exists(config_path): raise Failed(f"Config Error: config not found at {os.path.abspath(config_path)}") @@ -56,6 +57,13 @@ class Config: else: raise Failed(f"Config Error: config not found at {os.path.abspath(default_dir)}") logger.info(f"Using {self.config_path} as config") + self.test_mode = is_test + self.run_start_time = time_scheduled + self.run_hour = datetime.strptime(time_scheduled, "%H:%M").hour + self.requested_collections = util.get_list(requested_collections) + self.requested_libraries = util.get_list(requested_libraries) + self.resume_from = resume_from + yaml.YAML().allow_duplicate_keys = True try: new_config, ind, bsi = yaml.util.load_yaml_guess_indent(open(self.config_path, encoding="utf-8")) @@ -312,9 +320,9 @@ class Config: self.libraries = [] try: libs = check_for_attribute(self.data, "libraries", throw=True) except Failed as e: raise Failed(e) - requested_libraries = util.get_list(libraries_to_run) if libraries_to_run else None + for library_name, lib in libs.items(): - if requested_libraries and library_name not in requested_libraries: + if self.requested_libraries and library_name not in self.requested_libraries: continue util.separator() params = {} diff --git a/modules/util.py b/modules/util.py index 62676d8d..bb3b425b 100644 --- a/modules/util.py +++ b/modules/util.py @@ -222,7 +222,8 @@ def compile_list(data): return data def get_list(data, lower=False, split=True, int_list=False): - if isinstance(data, list): return data + if data is None: return None + elif isinstance(data, list): return data elif isinstance(data, dict): return [data] elif split is False: return [str(data)] elif lower is True: return [d.strip().lower() for d in str(data).split(",")] diff --git a/plex_meta_manager.py b/plex_meta_manager.py index ba17638f..aecc392d 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -7,13 +7,17 @@ try: from modules.config import Config from modules.util import Failed except ModuleNotFoundError: - print("Error: Requirements are not installed") + print("Requirements Error: Requirements are not installed") + sys.exit(0) + +if sys.version_info[0] != 3 or sys.version_info[1] < 6: + print("Version Error: Version: %s.%s.%s incompatible please use Python 3.6+" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])) sys.exit(0) parser = argparse.ArgumentParser() parser.add_argument("-db", "--debug", dest="debug", help=argparse.SUPPRESS, action="store_true", default=False) parser.add_argument("-c", "--config", dest="config", help="Run with desired *.yml file", type=str) -parser.add_argument("-t", "--time", dest="time", help="Time to update each day use format HH:MM (Default: 03:00)", default="03:00", type=str) +parser.add_argument("-t", "--time", dest="time", help="Times to update each day use format HH:MM (Default: 03:00) (comma-separated list)", default="03:00", type=str) parser.add_argument("-re", "--resume", dest="resume", help="Resume collection run from a specific collection", type=str) parser.add_argument("-r", "--run", dest="run", help="Run without the scheduler", action="store_true", default=False) parser.add_argument("-rt", "--test", "--tests", "--run-test", "--run-tests", dest="test", help="Run in debug mode with only collections that have test: true", action="store_true", default=False) @@ -48,9 +52,10 @@ collections = os.environ.get("PMM_COLLECTIONS") if os.environ.get("PMM_COLLECTIO libraries = os.environ.get("PMM_LIBRARIES") if os.environ.get("PMM_LIBRARIES") else args.libraries resume = os.environ.get("PMM_RESUME") if os.environ.get("PMM_RESUME") else args.resume -time_to_run = os.environ.get("PMM_TIME") if os.environ.get("PMM_TIME") else args.time -if not re.match("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$", time_to_run): - raise util.Failed(f"Argument Error: time argument invalid: {time_to_run} must be in the HH:MM format") +times_to_run = util.get_list(os.environ.get("PMM_TIME") if os.environ.get("PMM_TIME") else args.time) +for time_to_run in times_to_run: + if not re.match("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$", time_to_run): + raise util.Failed(f"Argument Error: time argument invalid: {time_to_run} must be in the HH:MM format") util.separating_character = os.environ.get("PMM_DIVIDER")[0] if os.environ.get("PMM_DIVIDER") else args.divider[0] @@ -83,7 +88,7 @@ logger.addHandler(cmd_handler) sys.excepthook = util.my_except_hook -def start(config_path, is_test, daily, requested_collections, requested_libraries, resume_from): +def start(config_path, is_test=False, time_scheduled=None, requested_collections=None, requested_libraries=None, resume_from=None): file_logger = os.path.join(default_dir, "logs", "meta.log") should_roll_over = os.path.isfile(file_logger) file_handler = logging.handlers.RotatingFileHandler(file_logger, delay=True, mode="w", backupCount=10, encoding="utf-8") @@ -101,16 +106,20 @@ def start(config_path, is_test, daily, requested_collections, requested_librarie logger.info(util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ")) logger.info(util.centered(" |___/ ")) logger.info(util.centered(" Version: 1.9.3-beta1 ")) - if daily: start_type = "Daily " + if time_scheduled: start_type = f"{time_scheduled} " elif is_test: start_type = "Test " elif requested_collections: start_type = "Collections " elif requested_libraries: start_type = "Libraries " else: start_type = "" start_time = datetime.now() + if time_scheduled is None: + time_scheduled = start_time.strftime("%H:%M") util.separator(f"Starting {start_type}Run") try: - config = Config(default_dir, config_path, requested_libraries) - update_libraries(config, is_test, requested_collections, resume_from) + config = Config(default_dir, config_path=config_path, is_test=is_test, + time_scheduled=time_scheduled, requested_collections=requested_collections, + requested_libraries=requested_libraries, resume_from=resume_from) + update_libraries(config) except Exception as e: util.print_stacktrace() logger.critical(e) @@ -118,7 +127,7 @@ def start(config_path, is_test, daily, requested_collections, requested_librarie util.separator(f"Finished {start_type}Run\nRun Time: {str(datetime.now() - start_time).split('.')[0]}") logger.removeHandler(file_handler) -def update_libraries(config, is_test, requested_collections, resume_from): +def update_libraries(config): for library in config.libraries: os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True) col_file_logger = os.path.join(default_dir, "logs", library.mapping_name, "library.log") @@ -136,29 +145,29 @@ def update_libraries(config, is_test, requested_collections, resume_from): util.separator(f"Mapping {library.name} Library", space=False, border=False) logger.info("") movie_map, show_map = map_guids(config, library) - if not is_test and not resume_from and not collection_only and library.mass_update: + if not config.test_mode and not config.resume_from and not collection_only and library.mass_update: mass_metadata(config, library, movie_map, show_map) for metadata in library.metadata_files: logger.info("") util.separator(f"Running Metadata File\n{metadata.path}") - if not is_test and not resume_from and not collection_only: + if not config.test_mode and not config.resume_from and not collection_only: try: - metadata.update_metadata(config.TMDb, is_test) + metadata.update_metadata(config.TMDb, config.test_mode) except Failed as e: logger.error(e) - collections_to_run = metadata.get_collections(requested_collections) - if resume_from and resume_from not in collections_to_run: + collections_to_run = metadata.get_collections(config.requested_collections) + if config.resume_from and config.resume_from not in collections_to_run: logger.info("") - logger.warning(f"Collection: {resume_from} not in Metadata File: {metadata.path}") + logger.warning(f"Collection: {config.resume_from} not in Metadata File: {metadata.path}") continue if collections_to_run and not library_only: logger.info("") - util.separator(f"{'Test ' if is_test else ''}Collections") + util.separator(f"{'Test ' if config.test_mode else ''}Collections") logger.removeHandler(library_handler) - resume_from = run_collection(config, library, metadata, collections_to_run, is_test, resume_from, movie_map, show_map) + run_collection(config, library, metadata, movie_map, show_map) logger.addHandler(library_handler) - if not is_test and not requested_collections and ((library.show_unmanaged and not library_only) or (library.assets_for_all and not collection_only)): + if not config.test_mode and not config.requested_collections and ((library.show_unmanaged and not library_only) or (library.assets_for_all and not collection_only)): logger.info("") util.separator(f"Other {library.name} Library Operations") unmanaged_collections = [] @@ -240,9 +249,11 @@ def map_guids(config, library): movie_map = {} show_map = {} length = 0 - logger.info(f"Mapping {'Movie' if library.is_movie else 'Show'} Library: {library.name}") + logger.info(f"Loading {'Movie' if library.is_movie else 'Show'} Library: {library.name}") logger.info("") items = library.Plex.all() + logger.info(f"Mapping {'Movie' if library.is_movie else 'Show'} Library: {library.name}") + logger.info("") for i, item in enumerate(items, 1): length = util.print_return(length, f"Processing: {i}/{len(items)} {item.title}") id_type, main_id = config.Convert.get_id(item, library, length) @@ -379,11 +390,11 @@ def mass_metadata(config, library, movie_map, show_map): except Failed as e: logger.error(e) -def run_collection(config, library, metadata, requested_collections, is_test, resume_from, movie_map, show_map): +def run_collection(config, library, metadata, movie_map, show_map): logger.info("") - for mapping_name, collection_attrs in requested_collections.items(): + for mapping_name, collection_attrs in config.requested_collections.items(): collection_start = datetime.now() - if is_test and ("test" not in collection_attrs or collection_attrs["test"] is not True): + if config.test_mode and ("test" not in collection_attrs or collection_attrs["test"] is not True): no_template_test = True if "template" in collection_attrs and collection_attrs["template"]: for data_template in util.get_list(collection_attrs["template"], split=False): @@ -398,10 +409,10 @@ def run_collection(config, library, metadata, requested_collections, is_test, re if no_template_test: continue - if resume_from and resume_from != mapping_name: + if config.resume_from and config.resume_from != mapping_name: continue - elif resume_from == mapping_name: - resume_from = None + elif config.resume_from == mapping_name: + config.resume_from = None logger.info("") util.separator(f"Resuming Collections") @@ -481,27 +492,32 @@ def run_collection(config, library, metadata, requested_collections, is_test, re logger.info("") util.separator(f"Finished {mapping_name} Collection\nCollection Run Time: {str(datetime.now() - collection_start).split('.')[0]}") logger.removeHandler(collection_handler) - return resume_from try: if run or test or collections or libraries or resume: - start(config_file, test, False, collections, libraries, resume) + start(config_file, is_test=test, requested_collections=collections, requested_libraries=libraries, resume_from=resume) else: time_length = 0 - schedule.every().day.at(time_to_run).do(start, config_file, False, True, None, None, None) + for time_to_run in times_to_run: + schedule.every().day.at(time_to_run).do(start, config_file, time_scheduled=time_to_run) while True: schedule.run_pending() if not no_countdown: current = datetime.now().strftime("%H:%M") - seconds = (datetime.strptime(time_to_run, "%H:%M") - datetime.strptime(current, "%H:%M")).total_seconds() + seconds = None + og_time_str = "" + for time_to_run in times_to_run: + new_seconds = (datetime.strptime(time_to_run, "%H:%M") - datetime.strptime(current, "%H:%M")).total_seconds() + if new_seconds < 0: + new_seconds += 86400 + if (seconds is None or new_seconds < seconds) and new_seconds > 0: + seconds = new_seconds + og_time_str = time_to_run hours = int(seconds // 3600) - if hours < 0: - hours += 24 minutes = int((seconds % 3600) // 60) time_str = f"{hours} Hour{'s' if hours > 1 else ''} and " if hours > 0 else "" time_str += f"{minutes} Minute{'s' if minutes > 1 else ''}" - - time_length = util.print_return(time_length, f"Current Time: {current} | {time_str} until the daily run at {time_to_run}") - time.sleep(1) + time_length = util.print_return(time_length, f"Current Time: {current} | {time_str} until the next run at {og_time_str} {times_to_run}") + time.sleep(60) except KeyboardInterrupt: util.separator("Exiting Plex Meta Manager") From de956845322c2c15d7f02dbeb207d51247d65bea Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 26 May 2021 00:35:54 -0400 Subject: [PATCH 13/14] None fix --- modules/builder.py | 4 ++-- plex_meta_manager.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/builder.py b/modules/builder.py index 67616d0a..8f110382 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -28,7 +28,7 @@ method_alias = { "writers": "writer", "years": "year" } -filter_alias = { +filter_translation = { "actor": "actors", "audience_rating": "audienceRating", "collection": "collections", @@ -1384,7 +1384,7 @@ class CollectionBuilder: for filter_method, filter_data in self.filters: modifier = filter_method[-4:] method = filter_method[:-4] if modifier in [".not", ".lte", ".gte"] else filter_method - method_name = filter_alias[method] if method in filter_alias else method + method_name = filter_translation[method] if method in filter_translation else method if method_name == "max_age": threshold_date = datetime.now() - timedelta(days=filter_data) if current.originallyAvailableAt is None or current.originallyAvailableAt < threshold_date: diff --git a/plex_meta_manager.py b/plex_meta_manager.py index aecc392d..a43d46b8 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -164,7 +164,7 @@ def update_libraries(config): logger.info("") util.separator(f"{'Test ' if config.test_mode else ''}Collections") logger.removeHandler(library_handler) - run_collection(config, library, metadata, movie_map, show_map) + run_collection(config, library, metadata, collections_to_run, movie_map, show_map) logger.addHandler(library_handler) if not config.test_mode and not config.requested_collections and ((library.show_unmanaged and not library_only) or (library.assets_for_all and not collection_only)): @@ -390,9 +390,9 @@ def mass_metadata(config, library, movie_map, show_map): except Failed as e: logger.error(e) -def run_collection(config, library, metadata, movie_map, show_map): +def run_collection(config, library, metadata, requested_collections, movie_map, show_map): logger.info("") - for mapping_name, collection_attrs in config.requested_collections.items(): + for mapping_name, collection_attrs in requested_collections.items(): collection_start = datetime.now() if config.test_mode and ("test" not in collection_attrs or collection_attrs["test"] is not True): no_template_test = True From 895d1f5573ef18ba702b7b2f2ca5aa80ee31f32f Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Wed, 26 May 2021 02:03:00 -0400 Subject: [PATCH 14/14] v1.9.3 --- README.md | 2 +- plex_meta_manager.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 38fff992..1fa414f7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Plex Meta Manager -#### Version 1.9.2 +#### Version 1.9.3 The original concept for Plex Meta Manager is [Plex Auto Collections](https://github.com/mza921/Plex-Auto-Collections), but this is rewritten from the ground up to be able to include a scheduler, metadata edits, multiple libraries, and logging. Plex Meta Manager is a Python 3 script that can be continuously run using YAML configuration files to update on a schedule the metadata of the movies, shows, and collections in your libraries as well as automatically build collections based on various methods all detailed in the wiki. Some collection examples that the script can automatically build and update daily include Plex Based Searches like actor, genre, or studio collections or Collections based on TMDb, IMDb, Trakt, TVDb, AniDB, or MyAnimeList lists and various other services. diff --git a/plex_meta_manager.py b/plex_meta_manager.py index a43d46b8..862fa470 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -25,7 +25,7 @@ parser.add_argument("-co", "--collection-only", "--collections-only", dest="coll parser.add_argument("-lo", "--library-only", "--libraries-only", dest="library_only", help="Run only library operations", action="store_true", default=False) parser.add_argument("-rc", "-cl", "--collection", "--collections", "--run-collection", "--run-collections", dest="collections", help="Process only specified collections (comma-separated list)", type=str) parser.add_argument("-rl", "-l", "--library", "--libraries", "--run-library", "--run-libraries", dest="libraries", help="Process only specified libraries (comma-separated list)", type=str) -parser.add_argument("-nc", "--no-countdown", dest="no_countdown", help="Run without displaying countdown", action="store_true", default=False) +parser.add_argument("-nc", "--no-countdown", dest="no_countdown", help="Run without displaying the countdown", action="store_true", default=False) parser.add_argument("-d", "--divider", dest="divider", help="Character that divides the sections (Default: '=')", default="=", type=str) parser.add_argument("-w", "--width", dest="width", help="Screen Width (Default: 100)", default=100, type=int) args = parser.parse_args() @@ -105,7 +105,7 @@ def start(config_path, is_test=False, time_scheduled=None, requested_collections logger.info(util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | ")) logger.info(util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| ")) logger.info(util.centered(" |___/ ")) - logger.info(util.centered(" Version: 1.9.3-beta1 ")) + logger.info(util.centered(" Version: 1.9.3 ")) if time_scheduled: start_type = f"{time_scheduled} " elif is_test: start_type = "Test " elif requested_collections: start_type = "Collections "