diff --git a/.github/workflows/develop-arm7.yml b/.github/workflows/develop-arm7.yml deleted file mode 100644 index 742da0c3..00000000 --- a/.github/workflows/develop-arm7.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Docker Develop Release Arm7 - -on: - push: - branches: [ develop ] - pull_request: - branches: [ develop ] - -jobs: - - docker-develop: - runs-on: ubuntu-latest - - steps: - - - name: Check Out Repo - uses: actions/checkout@v2 - with: - ref: develop - - - name: Login to Docker Hub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@master - with: - platforms: all - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - context: ./ - file: ./Dockerfile - platforms: linux/arm/v7 - push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/plex-meta-manager:develop diff --git a/.gitignore b/.gitignore index bde577dc..66015f42 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ __pycache__/ # Distribution / packaging .idea .Python -/test.py +/test* logs/ config/* !config/overlays/ diff --git a/README.md b/README.md index faa64381..56f9d454 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,9 @@ Before posting on GitHub about an enhancement, error, or configuration question - [MyAnimeList Attributes](https://github.com/meisnate12/Plex-Meta-Manager/wiki/MyAnimeList-Attributes) - [Metadata and Playlist Files](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Metadata-and-Playlist-Files) - Metadata - - [Movies Metadata](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Movies-Metadata) - - [Shows Metadata](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Shows-Metadata) - - [Artists Metadata](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Artists-Metadata) + - [Movie Library Metadata](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Movie-Library-Metadata) + - [TV Show Library Metadata](https://github.com/meisnate12/Plex-Meta-Manager/wiki/TV-Show-Library-Metadata) + - [Music Library Metadata](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Music-Library-Metadata) - [Templates](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Templates) - [Filters](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Filters) - Builders @@ -93,6 +93,7 @@ Before posting on GitHub about an enhancement, error, or configuration question - [IMDb Builders](https://github.com/meisnate12/Plex-Meta-Manager/wiki/IMDb-Builders) - [Trakt Builders](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Trakt-Builders) - [Tautulli Builders](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Tautulli-Builders) + - [MdbList Builders](https://github.com/meisnate12/Plex-Meta-Manager/wiki/MdbList-Builders) - [Letterboxd Builders](https://github.com/meisnate12/Plex-Meta-Manager/wiki/Letterboxd-Builders) - [ICheckMovies Builders](https://github.com/meisnate12/Plex-Meta-Manager/wiki/ICheckMovies-Builders) - [FlixPatrol Builders](https://github.com/meisnate12/Plex-Meta-Manager/wiki/FlixPatrol-Builders) diff --git a/VERSION b/VERSION index 8fab5479..795d8700 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.0-develop220122 \ No newline at end of file +1.15.1 \ No newline at end of file diff --git a/config/config.yml.template b/config/config.yml.template index 81ca2ede..e4a58450 100644 --- a/config/config.yml.template +++ b/config/config.yml.template @@ -26,6 +26,8 @@ settings: # Can be individually specified dimensional_asset_rename: false download_url_assets: false show_missing_season_assets: false + show_missing_episode_assets: false + show_asset_not_needed: true sync_mode: append minimum_items: 1 default_collection_order: @@ -43,6 +45,7 @@ settings: # Can be individually specified tvdb_language: eng ignore_ids: ignore_imdb_ids: + item_refresh_delay: 0 playlist_sync_to_user: all verify_ssl: true webhooks: # Can be individually specified per library as well diff --git a/modules/builder.py b/modules/builder.py index a5b088fd..0acca81c 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -109,7 +109,7 @@ sonarr_details = [ "sonarr_add_missing", "sonarr_add_existing", "sonarr_folder", "sonarr_monitor", "sonarr_language", "sonarr_series", "sonarr_quality", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_tag" ] -album_details = ["item_label", "item_album_sorting"] +album_details = ["non_item_remove_label", "item_label", "item_album_sorting"] filters_by_type = { "movie_show_season_episode_artist_album_track": ["title", "summary", "collection", "has_collection", "added", "last_played", "user_rating", "plays"], "movie_show_season_episode_album_track": ["year"], @@ -165,7 +165,7 @@ custom_sort_builders = [ "flixpatrol_url", "flixpatrol_demographics", "flixpatrol_popular", "flixpatrol_top", "trakt_recommended_daily", "trakt_recommended_weekly", "trakt_recommended_monthly", "trakt_recommended_yearly", "trakt_recommended_all", "trakt_watched_daily", "trakt_watched_weekly", "trakt_watched_monthly", "trakt_watched_yearly", "trakt_watched_all", - "tautulli_popular", "tautulli_watched", "letterboxd_list", "icheckmovies_list", + "tautulli_popular", "tautulli_watched", "mdblist_list", "letterboxd_list", "icheckmovies_list", "anilist_top_rated", "anilist_popular", "anilist_trending", "anilist_search", "mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_movie", "mal_ova", "mal_special", "mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio" @@ -182,7 +182,7 @@ playlist_attributes = [ "server_preroll", "changes_webhooks", "minimum_items", ] + custom_sort_builders + summary_details + poster_details + radarr_details + sonarr_details music_attributes = [ - "item_label", "item_assets", "item_lock_background", "item_lock_poster", "item_lock_title", + "non_item_remove_label", "item_label", "item_assets", "item_lock_background", "item_lock_poster", "item_lock_title", "item_refresh", "item_refresh_delay", "plex_search", "plex_all", "filters" ] + details + summary_details + poster_details + background_details @@ -1090,7 +1090,7 @@ class CollectionBuilder: self.builders.append((method_name, self._parse(method_name, method_data, "bool"))) def _mdblist(self, method_name, method_data): - for mdb_dict in self.config.Mdblist.validate_mdb_lists(method_data, self.language): + for mdb_dict in self.config.Mdblist.validate_mdblist_lists(method_data): self.builders.append((method_name, mdb_dict)) def _tautulli(self, method_name, method_data): @@ -1356,7 +1356,7 @@ class CollectionBuilder: if tvdb_id not in self.missing_shows: self.missing_shows.append(tvdb_id) except Failed as e: - logger.error(e) + logger.warning(e) elif show_id not in self.missing_shows: self.missing_shows.append(show_id) else: @@ -1374,7 +1374,7 @@ class CollectionBuilder: try: input_id = self.config.Convert.tmdb_to_tvdb(input_id, fail=True) except Failed as e: - logger.error(e) + logger.warning(e) continue if input_id not in self.ignore_ids: if input_id in self.library.show_map: @@ -1396,7 +1396,7 @@ class CollectionBuilder: if tvdb_id not in self.missing_shows: self.missing_shows.append(tvdb_id) except Failed as e: - logger.error(e) + logger.warning(e) continue if not isinstance(rating_keys, list): rating_keys = [rating_keys] @@ -2514,4 +2514,4 @@ class CollectionBuilder: logger.info(f"{name} {self.Type} | ? | {title} (TVDb: {missing_id})") logger.info(f"{len(self.run_again_shows)} Show{'s' if len(self.run_again_shows) > 1 else ''} Missing") - return amount_added \ No newline at end of file + return amount_added diff --git a/modules/config.py b/modules/config.py index f5058238..7b39024e 100644 --- a/modules/config.py +++ b/modules/config.py @@ -54,7 +54,7 @@ class ConfigFile: self.run_hour = datetime.strptime(attrs["time"], "%H:%M").hour self.requested_collections = util.get_list(attrs["collections"]) if "collections" in attrs else None self.requested_libraries = util.get_list(attrs["libraries"]) if "libraries" in attrs else None - self.requested_metadata = util.get_list(attrs["metadata"]) if "metadata" in attrs else None + self.requested_metadata_files = util.get_list(attrs["metadata_files"]) if "metadata_files" in attrs else None self.resume_from = attrs["resume"] if "resume" in attrs else None yaml.YAML().allow_duplicate_keys = True @@ -667,11 +667,14 @@ class ConfigFile: if lib["operations"]["genre_mapper"] and isinstance(lib["operations"]["genre_mapper"], dict): params["genre_mapper"] = {} for new_genre, old_genres in lib["operations"]["genre_mapper"].items(): - for old_genre in util.get_list(old_genres): - if old_genre == new_genre: - logger.error("Config Error: genres cannot be mapped to themselves") - else: - params["genre_mapper"][old_genre] = new_genre + if old_genres is None: + params["genre_mapper"][new_genre] = old_genres + else: + for old_genre in util.get_list(old_genres): + if old_genre == new_genre: + logger.error("Config Error: genres cannot be mapped to themselves") + else: + params["genre_mapper"][old_genre] = new_genre else: logger.error("Config Error: genre_mapper is blank") if "genre_collections" in lib["operations"]: diff --git a/modules/convert.py b/modules/convert.py index 5fd95f2f..109ec85d 100644 --- a/modules/convert.py +++ b/modules/convert.py @@ -75,9 +75,9 @@ class Convert: elif anidb_id in self.anidb_to_tvdb: ids.append((self.anidb_to_tvdb[anidb_id], "tvdb")) elif anidb_id in self.anidb_ids: - logger.error(f"Convert Error: No TVDb ID or IMDb ID found for AniDB ID: {anidb_id}") + logger.warning(f"Convert Error: No TVDb ID or IMDb ID found for AniDB ID: {anidb_id}") else: - logger.error(f"Convert Error: AniDB ID: {anidb_id} not found") + logger.warning(f"Convert Error: AniDB ID: {anidb_id} not found") return ids def anilist_to_ids(self, anilist_ids, library): @@ -86,7 +86,7 @@ class Convert: if anilist_id in self.anilist_to_anidb: anidb_ids.append(self.anilist_to_anidb[anilist_id]) else: - logger.error(f"Convert Error: AniDB ID not found for AniList ID: {anilist_id}") + logger.warning(f"Convert Error: AniDB ID not found for AniList ID: {anilist_id}") return self.anidb_to_ids(anidb_ids, library) def myanimelist_to_ids(self, mal_ids, library): @@ -97,7 +97,7 @@ class Convert: elif int(mal_id) in self.mal_to_anidb: ids.extend(self.anidb_to_ids(self.mal_to_anidb[int(mal_id)], library)) else: - logger.error(f"Convert Error: AniDB ID not found for MyAnimeList ID: {mal_id}") + logger.warning(f"Convert Error: AniDB ID not found for MyAnimeList ID: {mal_id}") return ids def tmdb_to_imdb(self, tmdb_id, is_movie=True, fail=False): diff --git a/modules/library.py b/modules/library.py index fb8b81c7..a7277140 100644 --- a/modules/library.py +++ b/modules/library.py @@ -1,4 +1,4 @@ -import logging, os, requests, shutil, time +import logging, os, shutil, time from abc import ABC, abstractmethod from modules import util from modules.meta import MetadataFile diff --git a/modules/mdblist.py b/modules/mdblist.py index 2645bb75..934e1fd4 100644 --- a/modules/mdblist.py +++ b/modules/mdblist.py @@ -1,19 +1,20 @@ import logging from modules import util from modules.util import Failed +from urllib.parse import urlparse logger = logging.getLogger("Plex Meta Manager") builders = ["mdblist_list"] base_url = "https://mdblist.com/lists" -headers = { 'User-Agent': 'Plex-Meta-Manager' } +headers = {"User-Agent": "Plex-Meta-Manager"} class Mdblist: def __init__(self, config): self.config = config - def validate_mdb_lists(self, mdb_lists, language): + def validate_mdblist_lists(self, mdb_lists): valid_lists = [] for mdb_dict in util.get_list(mdb_lists, split=False): if not isinstance(mdb_dict, dict): @@ -49,7 +50,9 @@ class Mdblist: if method == "mdblist_list": limit_status = f" Limit at: {data['limit']} items" if data['limit'] > 0 else '' logger.info(f"Processing Mdblist.com List: {data['url']}{limit_status}") - url = f"{data['url']}?limit={data['limit']}" - return [(i["imdb_id"], "imdb") for i in self.config.get_json(url,headers=headers)] + parsed_url = urlparse(data["url"]) + url_base = parsed_url._replace(query=None).geturl() + params = {"limit": data["limit"]} if data["limit"] > 0 else None + return [(i["imdb_id"], "imdb") for i in self.config.get_json(url_base, headers=headers, params=params)] else: raise Failed(f"Mdblist Error: Method {method} not supported") diff --git a/modules/meta.py b/modules/meta.py index 1d210ad3..59461d7a 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -31,18 +31,18 @@ def get_dict(attribute, attr_data, check_list=None): new_dict = {} for _name, _data in attr_data[attribute].items(): if _name in check_list: - logger.error(f"Config Warning: Skipping duplicate {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name}") + logger.warning(f"Config Warning: Skipping duplicate {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name}") elif _data is None: - logger.error(f"Config Warning: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} has no data") + logger.error(f"Config Error: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} has no data") elif not isinstance(_data, dict): - logger.error(f"Config Warning: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} must be a dictionary") + logger.error(f"Config Error: {attribute[:-1] if attribute[-1] == 's' else attribute}: {_name} must be a dictionary") else: new_dict[str(_name)] = _data return new_dict else: - logger.warning(f"Config Warning: {attribute} must be a dictionary") + logger.error(f"Config Error: {attribute} must be a dictionary") else: - logger.warning(f"Config Warning: {attribute} attribute is blank") + logger.error(f"Config Error: {attribute} attribute is blank") return None @@ -238,7 +238,7 @@ class MetadataFile(DataFile): logger.info("") logger.info(f"Loading Metadata {file_type}: {path}") data = self.load_file() - self.metadata = get_dict("metadata", data, library.metadatas) + self.metadata = get_dict("metadata", data, library.metadata_files) self.templates = get_dict("templates", data) self.collections = get_dict("collections", data, library.collections) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 4fdd5136..5d8751cd 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -34,7 +34,7 @@ parser.add_argument("-lo", "--library-only", "--libraries-only", dest="library_o parser.add_argument("-lf", "--library-first", "--libraries-first", dest="library_first", help="Run library operations before collections", 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("-rm", "-m", "--metadata", "--metadatas", "--run-metadata", "--run-metadatas", dest="metadata", help="Process only specified Metadata files (comma-separated list)", type=str) +parser.add_argument("-rm", "-m", "--metadata", "--metadata-files", "--run-metadata-files", dest="metadata", help="Process only specified Metadata files (comma-separated list)", type=str) parser.add_argument("-dc", "--delete", "--delete-collections", dest="delete", help="Deletes all Collections in the Plex Library before running", 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("-nm", "--no-missing", dest="no_missing", help="Run without running the missing section", action="store_true", default=False) @@ -70,7 +70,7 @@ library_only = get_arg("PMM_LIBRARIES_ONLY", args.library_only, arg_bool=True) library_first = get_arg("PMM_LIBRARIES_FIRST", args.library_first, arg_bool=True) collections = get_arg("PMM_COLLECTIONS", args.collections) libraries = get_arg("PMM_LIBRARIES", args.libraries) -metadatas = get_arg("PMM_METADATA", args.metadata) +metadata_files = get_arg("PMM_METADATA_FILES", args.metadata) delete = get_arg("PMM_DELETE_COLLECTIONS", args.delete, arg_bool=True) resume = get_arg("PMM_RESUME", args.resume) no_countdown = get_arg("PMM_NO_COUNTDOWN", args.no_countdown, arg_bool=True) @@ -160,7 +160,7 @@ def start(attrs): logger.debug(f"--libraries-first (PMM_LIBRARIES_FIRST): {library_first}") logger.debug(f"--run-collections (PMM_COLLECTIONS): {collections}") logger.debug(f"--run-libraries (PMM_LIBRARIES): {libraries}") - logger.debug(f"--run-metadata (PMM_METADATA): {metadatas}") + logger.debug(f"--run-metadata-files (PMM_METADATA_FILES): {metadata_files}") logger.debug(f"--ignore-schedules (PMM_IGNORE_SCHEDULES): {ignore_schedules}") logger.debug(f"--delete-collections (PMM_DELETE_COLLECTIONS): {delete}") logger.debug(f"--resume (PMM_RESUME): {resume}") @@ -262,7 +262,7 @@ def update_libraries(config): library.map_guids() for metadata in library.metadata_files: metadata_name = metadata.get_file_name() - if config.requested_metadata and metadata_name not in config.requested_metadata: + if config.requested_metadata_files and metadata_name not in config.requested_metadata_files: continue logger.info("") util.separator(f"Running {metadata_name} Metadata File\n{metadata.path}") @@ -1072,7 +1072,7 @@ def run_playlists(config): try: input_id = config.Convert.tmdb_to_tvdb(input_id, fail=True) except Failed as e: - logger.error(e) + logger.warning(e) continue if input_id not in builder.ignore_ids: found = False @@ -1121,7 +1121,7 @@ def run_playlists(config): if tvdb_id not in builder.missing_shows: builder.missing_shows.append(tvdb_id) except Failed as e: - logger.error(e) + logger.warning(e) continue if not isinstance(rating_keys, list): rating_keys = [rating_keys] @@ -1225,7 +1225,7 @@ def run_playlists(config): return status, stats try: - if run or test or collections or libraries or metadatas or resume: + if run or test or collections or libraries or metadata_files or resume: start({ "config_file": config_file, "test": test, @@ -1233,7 +1233,7 @@ try: "ignore_schedules": ignore_schedules, "collections": collections, "libraries": libraries, - "metadata": metadatas, + "metadata_files": metadata_files, "library_first": library_first, "resume": resume, "trace": trace