From e2de5ae19e9a7c393051ed988876219f57fe42fc Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 27 Jan 2022 10:22:58 -0500 Subject: [PATCH] #468 added metadata_backup library operation --- modules/config.py | 13 +++++ modules/library.py | 6 ++- modules/meta.py | 88 ++++++++-------------------------- modules/plex.py | 112 +++++++++++++++++++++++++++++++++++++++++-- modules/tmdb.py | 17 +++++++ modules/util.py | 12 +++++ plex_meta_manager.py | 54 +++++++++++---------- requirements.txt | 2 +- 8 files changed, 203 insertions(+), 101 deletions(-) diff --git a/modules/config.py b/modules/config.py index 7b39024e..320090ad 100644 --- a/modules/config.py +++ b/modules/config.py @@ -558,6 +558,7 @@ class ConfigFile: "radarr_remove_by_tag": None, "sonarr_remove_by_tag": None, "mass_collection_mode": None, + "metadata_backup": None, "genre_collections": None } display_name = f"{params['name']} ({params['mapping_name']})" if lib and "library_name" in lib and lib["library_name"] else params["mapping_name"] @@ -641,6 +642,18 @@ class ConfigFile: params["mass_collection_mode"] = util.check_collection_mode(lib["operations"]["mass_collection_mode"]) except Failed as e: logger.error(e) + if "metadata_backup" in lib["operations"]: + params["metadata_backup"] = { + "path": os.path.join(default_dir, f"{str(library_name)}_Metadata_Backup.yml"), + "exclude": [], + "sync_tags": False, + "add_blank_entries": True + } + if lib["operations"]["metadata_backup"] and isinstance(lib["operations"]["metadata_backup"], dict): + params["metadata_backup"]["path"] = check_for_attribute(lib["operations"]["metadata_backup"], "path", var_type="path", default=params["metadata_backup"]["path"], save=False) + params["metadata_backup"]["exclude"] = check_for_attribute(lib["operations"]["metadata_backup"], "exclude", var_type="comma_list", default_is_none=True, save=False) + params["metadata_backup"]["sync_tags"] = check_for_attribute(lib["operations"]["metadata_backup"], "sync_tags", var_type="bool", default=False, save=False) + params["metadata_backup"]["add_blank_entries"] = check_for_attribute(lib["operations"]["metadata_backup"], "add_blank_entries", var_type="bool", default=True, save=False) if "tmdb_collections" in lib["operations"]: params["tmdb_collections"] = { "exclude_ids": [], diff --git a/modules/library.py b/modules/library.py index a7277140..bde05072 100644 --- a/modules/library.py +++ b/modules/library.py @@ -76,6 +76,7 @@ class Library(ABC): self.sonarr_add_all_existing = params["sonarr_add_all_existing"] self.sonarr_remove_by_tag = params["sonarr_remove_by_tag"] self.mass_collection_mode = params["mass_collection_mode"] + self.metadata_backup = params["metadata_backup"] self.tmdb_collections = params["tmdb_collections"] self.genre_collections = params["genre_collections"] self.genre_mapper = params["genre_mapper"] @@ -89,8 +90,9 @@ class Library(ABC): self.status = {} self.items_library_operation = self.assets_for_all or self.mass_genre_update or self.mass_audience_rating_update \ - or self.mass_critic_rating_update or self.mass_trakt_rating_update or self.genre_mapper \ - or self.tmdb_collections or self.radarr_add_all_existing or self.sonarr_add_all_existing + or self.mass_critic_rating_update or self.mass_trakt_rating_update or self.genre_mapper \ + or self.tmdb_collections or self.radarr_add_all_existing or self.sonarr_add_all_existing \ + or self.metadata_backup self.library_operation = self.items_library_operation or self.delete_unmanaged_collections or self.delete_collections_with_less \ or self.radarr_remove_by_tag or self.sonarr_remove_by_tag or self.mass_collection_mode \ or self.genre_collections or self.show_unmanaged diff --git a/modules/meta.py b/modules/meta.py index ccac3821..09e33ede 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -9,19 +9,6 @@ logger = logging.getLogger("Plex Meta Manager") github_base = "https://raw.githubusercontent.com/meisnate12/Plex-Meta-Manager-Configs/master/" -advance_tags_to_edit = { - "Movie": ["metadata_language", "use_original_title"], - "Show": ["episode_sorting", "keep_episodes", "delete_episodes", "season_display", "episode_ordering", - "metadata_language", "use_original_title"], - "Artist": ["album_sorting"] -} - -tags_to_edit = { - "Movie": ["genre", "label", "collection", "country", "director", "producer", "writer"], - "Show": ["genre", "label", "collection"], - "Artist": ["genre", "style", "mood", "country", "collection", "similar_artist"] -} - def get_dict(attribute, attr_data, check_list=None): if check_list is None: check_list = [] @@ -33,9 +20,9 @@ def get_dict(attribute, attr_data, check_list=None): if _name in check_list: logger.warning(f"Config Warning: Skipping duplicate {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name}") elif _data is None: - logger.error(f"Config Error: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} has no data") + logger.warning(f"Config Warning: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} has no data") elif not isinstance(_data, dict): - logger.error(f"Config Error: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} must be a dictionary") + logger.warning(f"Config Warning: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} must be a dictionary") else: new_dict[str(_name)] = _data return new_dict @@ -435,11 +422,11 @@ class MetadataFile(DataFile): edits = {} add_edit("title", item, meta, methods, value=title) add_edit("sort_title", item, meta, methods, key="titleSort") + add_edit("user_rating", item, meta, methods, key="userRating", var_type="float") if not self.library.is_music: add_edit("originally_available", item, meta, methods, key="originallyAvailableAt", value=originally_available, var_type="date") add_edit("critic_rating", item, meta, methods, value=rating, key="rating", var_type="float") add_edit("audience_rating", item, meta, methods, key="audienceRating", var_type="float") - add_edit("user_rating", item, meta, methods, key="userRating", var_type="float") add_edit("content_rating", item, meta, methods, key="contentRating") add_edit("original_title", item, meta, methods, key="originalTitle", value=original_title) add_edit("studio", item, meta, methods, value=studio) @@ -450,12 +437,12 @@ class MetadataFile(DataFile): advance_edits = {} prefs = [p.id for p in item.preferences()] - for advance_edit in advance_tags_to_edit[self.library.type]: - key, options = plex.item_advance_keys[f"item_{advance_edit}"] + for advance_edit in util.advance_tags_to_edit[self.library.type]: if advance_edit in methods: if advance_edit in ["metadata_language", "use_original_title"] and self.library.agent not in plex.new_plex_agents: logger.error(f"Metadata Error: {advance_edit} attribute only works for with the New Plex Movie Agent and New Plex TV Agent") elif meta[methods[advance_edit]]: + key, options = plex.item_advance_keys[f"item_{advance_edit}"] method_data = str(meta[methods[advance_edit]]).lower() if method_data not in options: logger.error(f"Metadata Error: {meta[methods[advance_edit]]} {advance_edit} attribute invalid") @@ -467,7 +454,7 @@ class MetadataFile(DataFile): if self.library.edit_item(item, mapping_name, self.library.type, advance_edits, advanced=True): updated = True - for tag_edit in tags_to_edit[self.library.type]: + for tag_edit in util.tags_to_edit[self.library.type]: if self.edit_tags(tag_edit, item, meta, methods, extra=genres if tag_edit == "genre" else None): updated = True @@ -495,24 +482,10 @@ class MetadataFile(DataFile): logger.error(f"Metadata Error: Season: {season_id} not found") continue season_methods = {sm.lower(): sm for sm in season_dict} - - if "title" in season_methods and season_dict[season_methods["title"]]: - title = season_dict[season_methods["title"]] - else: - title = season.title - if "sub" in season_methods: - if season_dict[season_methods["sub"]] is None: - logger.error("Metadata Error: sub attribute is blank") - elif season_dict[season_methods["sub"]] is True and "(SUB)" not in title: - title = f"{title} (SUB)" - elif season_dict[season_methods["sub"]] is False and title.endswith(" (SUB)"): - title = title[:-6] - else: - logger.error("Metadata Error: sub attribute must be True or False") - edits = {} - add_edit("title", season, season_dict, season_methods, value=title) + add_edit("title", season, season_dict, season_methods) add_edit("summary", season, season_dict, season_methods) + add_edit("user_rating", season, season_dict, season_methods, key="userRating", var_type="float") if self.library.edit_item(season, season_id, "Season", edits): updated = True self.set_images(season, season_dict, season_methods) @@ -538,24 +511,12 @@ class MetadataFile(DataFile): logger.error(f"Metadata Error: Episode {episode_str} in Season {season_id} not found") continue episode_methods = {em.lower(): em for em in episode_dict} - - if "title" in episode_methods and episode_dict[episode_methods["title"]]: - title = episode_dict[episode_methods["title"]] - else: - title = episode.title - if "sub" in episode_dict: - if episode_dict[episode_methods["sub"]] is None: - logger.error("Metadata Error: sub attribute is blank") - elif episode_dict[episode_methods["sub"]] is True and "(SUB)" not in title: - title = f"{title} (SUB)" - elif episode_dict[episode_methods["sub"]] is False and title.endswith(" (SUB)"): - title = title[:-6] - else: - logger.error("Metadata Error: sub attribute must be True or False") edits = {} - add_edit("title", episode, episode_dict, episode_methods, value=title) + add_edit("title", episode, episode_dict, episode_methods) add_edit("sort_title", episode, episode_dict, episode_methods, key="titleSort") - add_edit("rating", episode, episode_dict, episode_methods, var_type="float") + add_edit("critic_rating", episode, episode_dict, episode_methods, key="rating", var_type="float") + add_edit("audience_rating", episode, episode_dict, episode_methods, key="audienceRating", var_type="float") + add_edit("user_rating", episode, episode_dict, episode_methods, key="userRating", var_type="float") add_edit("originally_available", episode, episode_dict, episode_methods, key="originallyAvailableAt", var_type="date") add_edit("summary", episode, episode_dict, episode_methods) if self.library.edit_item(episode, f"{episode_str} in Season: {season_id}", "Episode", edits): @@ -589,24 +550,12 @@ class MetadataFile(DataFile): logger.error(f"Metadata Error: episode {episode_id} of season {season_id} not found") continue episode_methods = {em.lower(): em for em in episode_dict} - - if "title" in episode_methods and episode_dict[episode_methods["title"]]: - title = episode_dict[episode_methods["title"]] - else: - title = episode.title - if "sub" in episode_dict: - if episode_dict[episode_methods["sub"]] is None: - logger.error("Metadata Error: sub attribute is blank") - elif episode_dict[episode_methods["sub"]] is True and "(SUB)" not in title: - title = f"{title} (SUB)" - elif episode_dict[episode_methods["sub"]] is False and title.endswith(" (SUB)"): - title = title[:-6] - else: - logger.error("Metadata Error: sub attribute must be True or False") edits = {} - add_edit("title", episode, episode_dict, episode_methods, value=title) + add_edit("title", episode, episode_dict, episode_methods) add_edit("sort_title", episode, episode_dict, episode_methods, key="titleSort") - add_edit("rating", episode, episode_dict, episode_methods, var_type="float") + add_edit("critic_rating", episode, episode_dict, episode_methods, key="rating", var_type="float") + add_edit("audience_rating", episode, episode_dict, episode_methods, key="audienceRating", var_type="float") + add_edit("user_rating", episode, episode_dict, episode_methods, key="userRating", var_type="float") add_edit("originally_available", episode, episode_dict, episode_methods, key="originallyAvailableAt", var_type="date") add_edit("summary", episode, episode_dict, episode_methods) if self.library.edit_item(episode, f"{season_id} Episode: {episode_id}", "Season", edits): @@ -643,7 +592,8 @@ class MetadataFile(DataFile): edits = {} add_edit("title", album, album_dict, album_methods, value=title) add_edit("sort_title", album, album_dict, album_methods, key="titleSort") - add_edit("rating", album, album_dict, album_methods, var_type="float") + add_edit("critic_rating", album, album_dict, album_methods, key="rating", var_type="float") + add_edit("user_rating", album, album_dict, album_methods, key="userRating", var_type="float") add_edit("originally_available", album, album_dict, album_methods, key="originallyAvailableAt", var_type="date") add_edit("record_label", album, album_dict, album_methods, key="studio") add_edit("summary", album, album_dict, album_methods) @@ -684,7 +634,7 @@ class MetadataFile(DataFile): title = track.title edits = {} add_edit("title", track, track_dict, track_methods, value=title) - add_edit("rating", track, track_dict, track_methods, var_type="float") + add_edit("user_rating", track, track_dict, track_methods, key="userRating", var_type="float") add_edit("track", track, track_dict, track_methods, key="index", var_type="int") add_edit("disc", track, track_dict, track_methods, key="parentIndex", var_type="int") add_edit("original_artist", track, track_dict, track_methods, key="originalTitle") diff --git a/modules/plex.py b/modules/plex.py index 9348520a..b99ef73d 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -1,15 +1,16 @@ import logging, os, plexapi, requests +from datetime import datetime from modules import builder, util from modules.library import Library from modules.util import Failed, ImageData from PIL import Image from plexapi import utils -from plexapi.audio import Artist +from plexapi.audio import Artist, Track, Album from plexapi.exceptions import BadRequest, NotFound, Unauthorized from plexapi.collection import Collection from plexapi.playlist import Playlist from plexapi.server import PlexServer -from plexapi.video import Movie, Show +from plexapi.video import Movie, Show, Season, Episode from retrying import retry from urllib import parse from xml.etree.ElementTree import ParseError @@ -111,8 +112,8 @@ modifier_translation = { "": "", ".not": "!", ".is": "%3D", ".isnot": "!%3D", ".gt": "%3E%3E", ".gte": "%3E", ".lt": "%3C%3C", ".lte": "%3C", ".before": "%3C%3C", ".after": "%3E%3E", ".begins": "%3C", ".ends": "%3E" } -episode_sorting_options = {"default": "-1", "oldest": "0", "newest": "1"} album_sorting_options = {"default": -1, "newest": 0, "oldest": 1, "name": 2} +episode_sorting_options = {"default": -1, "oldest": 0, "newest": 1} keep_episodes_options = {"all": 0, "5_latest": 5, "3_latest": 3, "latest": 1, "past_3": -3, "past_7": -7, "past_30": -30} delete_episodes_options = {"never": 0, "day": 1, "week": 7, "refresh": 100} season_display_options = {"default": -1, "show": 0, "hide": 1} @@ -987,3 +988,108 @@ class Plex(Library): elif isinstance(item, (Movie, Show)) and not poster and not background and self.show_missing_assets: logger.warning(f"Asset Warning: No poster or background found in an assets folder for '{name}'") return None, None, found_folder + + def get_ids(self, item): + tmdb_id = None + tvdb_id = None + imdb_id = None + if self.config.Cache: + t_id, i_id, guid_media_type, _ = self.config.Cache.query_guid_map(item.guid) + if t_id: + if "movie" in guid_media_type: + tmdb_id = t_id[0] + else: + tvdb_id = t_id[0] + if i_id: + imdb_id = i_id[0] + if not tmdb_id and not tvdb_id: + tmdb_id = self.get_tmdb_from_map(item) + if not tmdb_id and not tvdb_id and self.is_show: + tvdb_id = self.get_tvdb_from_map(item) + return tmdb_id, tvdb_id, imdb_id + + def get_locked_attributes(self, item, titles=None): + attrs = {} + fields = {f.name: f for f in item.fields if f.locked} + if isinstance(item, (Movie, Show)) and titles and titles.count(item.title) > 1: + map_key = f"{item.title} ({item.year})" + attrs["title"] = item.title + attrs["year"] = item.year + elif isinstance(item, (Season, Episode, Track)) and item.index: + map_key = int(item.index) + else: + map_key = item.title + + if "title" in fields: + if isinstance(item, (Movie, Show)): + tmdb_id, tvdb_id, imdb_id = self.get_ids(item) + tmdb_item = self.config.TMDb.get_item(item, tmdb_id, tvdb_id, imdb_id, is_movie=isinstance(item, Movie)) + if tmdb_item: + attrs["alt_title"] = tmdb_item.title + elif isinstance(item, (Season, Episode, Track)): + attrs["title"] = item.title + + def check_field(plex_key, pmm_key, var_key=None): + if plex_key in fields and pmm_key not in self.metadata_backup["exclude"]: + if not var_key: + var_key = plex_key + if hasattr(item, var_key): + plex_value = getattr(item, var_key) + if isinstance(plex_value, list): + plex_tags = [t.tag for t in plex_value] + if len(plex_tags) > 0 or self.metadata_backup["sync_tags"]: + attrs[f"{pmm_key}.sync" if self.metadata_backup["sync_tags"] else pmm_key] = None if not plex_tags else plex_tags[0] if len(plex_tags) == 1 else plex_tags + elif isinstance(plex_value, datetime): + attrs[pmm_key] = datetime.strftime(plex_value, "%Y-%m-%d") + else: + attrs[pmm_key] = plex_value + + check_field("titleSort", "sort_title") + check_field("originalTitle", "original_artist" if self.is_music else "original_title") + check_field("originallyAvailableAt", "originally_available") + check_field("contentRating", "content_rating") + check_field("userRating", "user_rating") + check_field("audienceRating", "audience_rating") + check_field("rating", "critic_rating") + check_field("studio", "record_label" if self.is_music else "studio") + check_field("tagline", "tagline") + check_field("summary", "summary") + check_field("index", "track") + check_field("parentIndex", "disc") + check_field("director", "director", var_key="directors") + check_field("country", "country", var_key="countries") + check_field("genre", "genre", var_key="genres") + check_field("writer", "writer", var_key="writers") + check_field("producer", "producer", var_key="producers") + check_field("collection", "collection", var_key="collections") + check_field("label", "label", var_key="labels") + check_field("mood", "mood", var_key="moods") + check_field("style", "style", var_key="styles") + check_field("similar", "similar_artist") + for advance_edit in util.advance_tags_to_edit[self.type]: + key, options = item_advance_keys[f"item_{advance_edit}"] + if advance_edit in self.metadata_backup["exclude"] or not hasattr(item, key): + continue + keys = {v: k for k, v in options.items()} + if keys[getattr(item, key)] not in ["default", "all", "never"]: + attrs[advance_edit] = keys[getattr(item, key)] + + def _recur(sub): + sub_items = {} + for sub_item in getattr(item, sub)(): + sub_item_key, sub_item_attrs = self.get_locked_attributes(sub_item) + if sub_item_attrs: + sub_items[sub_item_key] = sub_item_attrs + if sub_items: + attrs[sub] = sub_items + + if isinstance(item, Show): + _recur("seasons") + elif isinstance(item, Season): + _recur("episodes") + elif isinstance(item, Artist): + _recur("albums") + elif isinstance(item, Album): + _recur("tracks") + + return map_key, attrs if attrs else None diff --git a/modules/tmdb.py b/modules/tmdb.py index 61062d49..a68fa93f 100644 --- a/modules/tmdb.py +++ b/modules/tmdb.py @@ -238,3 +238,20 @@ class TMDb: if len(ids) > 0: logger.info(f"Processing {pretty}: ({tmdb_id}) {tmdb_name} ({len(ids)} Item{'' if len(ids) == 1 else 's'})") return ids + + def get_item(self, item, tmdb_id, tvdb_id, imdb_id, is_movie=True): + tmdb_item = None + if tvdb_id and not tmdb_id: + tmdb_id = self.config.Convert.tvdb_to_tmdb(tvdb_id) + if imdb_id and not tmdb_id: + _id, _type = self.config.Convert.imdb_to_tmdb(imdb_id) + if _id and ((_type == "movie" and is_movie) or (_type == "show" and not is_movie)): + tmdb_id = _id + if tmdb_id: + try: + tmdb_item = self.get_movie(tmdb_id) if is_movie else self.get_show(tmdb_id) + except Failed as e: + logger.error(util.adjust_space(str(e))) + else: + logger.info(util.adjust_space(f"{item.title[:25]:<25} | No TMDb ID for Guid: {item.guid}")) + return tmdb_item diff --git a/modules/util.py b/modules/util.py index 38c64cbd..51984ea8 100644 --- a/modules/util.py +++ b/modules/util.py @@ -75,6 +75,18 @@ collection_mode_options = { "hide_items": "hideItems", "hideitems": "hideItems", "show_items": "showItems", "showitems": "showItems" } +advance_tags_to_edit = { + "Movie": ["metadata_language", "use_original_title"], + "Show": ["episode_sorting", "keep_episodes", "delete_episodes", "season_display", "episode_ordering", + "metadata_language", "use_original_title"], + "Artist": ["album_sorting"] +} + +tags_to_edit = { + "Movie": ["genre", "label", "collection", "country", "director", "producer", "writer"], + "Show": ["genre", "label", "collection"], + "Artist": ["genre", "style", "mood", "country", "collection", "similar_artist"] +} def tab_new_lines(data): return str(data).replace("\n", "\n ") if "\n" in str(data) else str(data) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 84df27d5..c4b7c67c 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -12,6 +12,7 @@ try: from modules.util import Failed, NotScheduled from plexapi.exceptions import NotFound from plexapi.video import Show, Season + from ruamel import yaml except ModuleNotFoundError: print("Requirements Error: Requirements are not installed") sys.exit(0) @@ -445,7 +446,8 @@ def library_operations(config, library): logger.debug(f"TMDb Collections: {library.tmdb_collections}") logger.debug(f"Genre Collections: {library.genre_collections}") logger.debug(f"Genre Mapper: {library.genre_mapper}") - logger.debug(f"TMDb Operation: {library.items_library_operation}") + logger.debug(f"Metadata Backup: {library.metadata_backup}") + logger.debug(f"Item Operation: {library.items_library_operation}") if library.split_duplicates: items = library.search(**{"duplicate": True}) @@ -469,22 +471,7 @@ def library_operations(config, library): util.print_return(f"Processing: {i}/{len(items)} {item.title}") if library.assets_for_all: library.find_assets(item) - tmdb_id = None - tvdb_id = None - imdb_id = None - if config.Cache: - t_id, i_id, guid_media_type, _ = config.Cache.query_guid_map(item.guid) - if t_id: - if "movie" in guid_media_type: - tmdb_id = t_id[0] - else: - tvdb_id = t_id[0] - if i_id: - imdb_id = i_id[0] - if not tmdb_id and not tvdb_id: - tmdb_id = library.get_tmdb_from_map(item) - if not tmdb_id and not tvdb_id and library.is_show: - tvdb_id = library.get_tvdb_from_map(item) + tmdb_id, tvdb_id, imdb_id = library.get_ids(item) if library.mass_trakt_rating_update: try: @@ -512,15 +499,7 @@ def library_operations(config, library): tmdb_item = None if library.tmdb_collections or library.mass_genre_update == "tmdb" or library.mass_audience_rating_update == "tmdb" or library.mass_critic_rating_update == "tmdb": - if tvdb_id and not tmdb_id: - tmdb_id = config.Convert.tvdb_to_tmdb(tvdb_id) - if tmdb_id: - try: - tmdb_item = config.TMDb.get_movie(tmdb_id) if library.is_movie else config.TMDb.get_show(tmdb_id) - except Failed as e: - logger.error(util.adjust_space(str(e))) - else: - logger.info(util.adjust_space(f"{item.title[:25]:<25} | No TMDb ID for Guid: {item.guid}")) + tmdb_item = config.TMDb.get_item(item, tmdb_id, tvdb_id, imdb_id, is_movie=library.is_movie) 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"]: @@ -713,6 +692,29 @@ def library_operations(config, library): for col in unmanaged_collections: library.find_assets(col) + if library.metadata_backup: + logger.info("") + util.separator(f"Metadata Backup for {library.name} Library", space=False, border=False) + logger.info("") + logger.info(f"Metadata Backup Path: {library.metadata_backup['path']}") + logger.info("") + meta = {} + items = library.get_all() + titles = [i.title for i in items] + for i, item in enumerate(items, 1): + util.print_return(f"Processing: {i}/{len(items)} {item.title}") + map_key, attrs = library.get_locked_attributes(item, titles) + if attrs or library.metadata_backup["add_blank_entries"]: + meta[map_key] = attrs + util.print_end() + with open(library.metadata_backup["path"], "w"): + pass + try: + yaml.round_trip_dump({"metadata": meta}, open(library.metadata_backup["path"], "w", encoding="utf-8")) + logger.info(f"{len(meta)} {library.type.capitalize()}{'s' if len(meta) > 1 else ''} Backed Up") + except yaml.scanner.ScannerError as e: + util.print_multiline(f"YAML Error: {util.tab_new_lines(e)}", error=True) + def run_collection(config, library, metadata, requested_collections): logger.info("") for mapping_name, collection_attrs in requested_collections.items(): diff --git a/requirements.txt b/requirements.txt index 1893dde8..1e817ab0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ PlexAPI==4.9.1 -tmdbapis==0.1.8 +tmdbapis==0.1.9 arrapi==1.3.1 lxml==4.7.1 requests==2.27.1