From b617f4a2589fde66b67f22e406c624848479a45a Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 31 May 2022 09:29:37 -0400 Subject: [PATCH] [6] removed plex_search `type` now use `collection_level` --- VERSION | 2 +- docs/_static/heart.png | Bin 0 -> 8505 bytes docs/config/configuration.md | 53 +++++++++++++++++++--------- docs/metadata/builders/plex.md | 3 +- docs/metadata/builders/smart.md | 3 +- modules/builder.py | 59 ++++++++++++++------------------ modules/plex.py | 16 +++++---- modules/util.py | 37 +++++++++++--------- 8 files changed, 95 insertions(+), 78 deletions(-) create mode 100644 docs/_static/heart.png diff --git a/VERSION b/VERSION index 455946ae..7aed998e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.17.0-develop5 +1.17.0-develop6 diff --git a/docs/_static/heart.png b/docs/_static/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a634804d33c698deb1eb5a0bf519cc92604c15 GIT binary patch literal 8505 zcmeHMYfuwc6ut!HDHTLJKr1ebI%*}!Mo36Zq#;X`8A1^$GLBY4vOo&SuE}B&P(X?h zJB3ka>Z8S`;#jBEN~l&C5JBjG9j8WHt%z3RlxnL|R6wK#?F|C$kJ4ZMXouaIeVm(f z&OP_L-?_UdcYAWu3V+{FUjTrA!b&^^01u(-^!9`;XR9vFg0~PlKAm1oX3<8ao&Zuc znMoiCT4gqoLMYYdjo%Vt0Nm$mQq$>lMWRSWYS~Jc4%?)qpfv#ESQDjG=|fG6Hi@7EEkP?0lQvIh5Sb+C zs9zC$b~STQWK@ODm7p(zN>?N!GEz?<0yY+@;&Qo&K*-MIX2oO@LhcfT$K_!hjK{&E zBYBvJ7cIh~k+B1X{pro{JW}FJE3i$Q;5If{YS@!R5L|zQV0W?uUEmT%ZhMyMTum3LP?YQRFcda$1ypF zq)9^#Ng=$YY#y?3jYg*?jfO>|1`35JL1&w2&u=;tHg)aa){JuoZA8wRN#UOTnITLa*6GsO5T6i;Rw0 zq?tSxd{Td7w(3b^;m1#E%YnggTr>N(nI3O~^l?2-Fc%6F+$VI9@OsE>TVvH70Oov= zfJ;+N-S@B6y*pb2%5Q}TkD}d{%8EQ?IJ&FjeA)xg>&+D>)|&m(>ITE21V~r+A)jy$ zKfm_6?K8XL`IXN*3ePqQYwhlLSdY#)m-g@4*BKlHv+V-kZg@tnb1p7kShnaxTZcV# z0kz!ye73{6j^_q{Pe$~vr1U|(;B^NhFc?e~9{2^FSpRgO+k4~9FiAA)=;ufJ-U&T3 zJUzxbJl8q$)GD{k6c$zbnbHsZ_Wgc;fik^skK^G;`_AxcdrAL3>#yC1uNdazM%59h=KfnLoh3>lY_plvLNAF4S0TE&ONPxURv2 z0yzLND`2MWKV2Nd!w2Vdc|f?{+fT|M5*7sVQBSuu^ zOLj)+j#eGM{J1K-X1D#5U^n21b%ZpIRN;HLPnKZN^F9f|$^`8s`>`$8t=T-)seD1s)mDX8qX?*Co;T-NQl;M$y7mR=W z@{RYZJ@%p_y>noLRf4fvr`-;_J{Uon%F73P46`Ash_nY@!A$%WMqn_QN<6rYbwt^Z z&#%!*igL;tYMZ>;eJ{Efm<^53Gta$vPX%l;_8kmtdu(m_sN-%|z$!u5_5RXXLomN0 zV19vny#wdA2iCByBWc%`m0Q*>FU&04P?FnlZ-s-J;n`r?d<=dBGNllM!PKmR+r%Av zZ<|lzm%i+rQ3-PI-~0WHEpEW`)G6Ul#dUe!Qol**XJ;4na96i&+tC^B_L7U6vLim=*_Dy}C2!DMZd&W&uJjH+aI#vnaoW7_)PEd ```yaml -libraries: - Movies - 4K: +libraries: # This is called out once within the config.yml file + Movies: # Each library must match the Plex library name metadata_path: - - file: config/Movies.yml - - git: meisnate12/MovieCharts - TV Shows: + - file: config/Movies.yml # This is a local file on the system + - folder: config/Movies/ # This is a local directory on the system + - git: PMM/chart/basic # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository + - git: PMM/chart/imdb # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository + overlay_path: + - remove_overlays: false # Set this to true to remove all overlays + - file: config/Overlays.yml # This is a local file on the system + - git: PMM/overlays/imdb_top_250 # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository + TV Shows: metadata_path: - file: config/TVShows.yml - folder: config/TV Shows/ - - git: meisnate12/ShowCharts - Animé: + - git: PMM/chart/basic # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository + - git: PMM/chart/imdb # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository + overlay_path: + - remove_overlays: false # Set this to true to remove all overlays + - file: config/Overlays.yml # This is a local file on the system + - git: PMM/overlays/imdb_top_250 # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository + Anime: metadata_path: - file: config/Anime.yml + - git: PMM/chart/basic # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository + - git: PMM/chart/anilist # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository Music: metadata_path: - file: config/Music.yml playlist_files: - - file: config/playlists.yml - - git: meisnate12/Playlists + - file: config/playlists.yml + - git: PMM/playlist # This is a file within the https://github.com/meisnate12/Plex-Meta-Manager-Configs Repository settings: cache: true cache_expiration: 60 @@ -183,7 +196,8 @@ As can be seen in the original config.yml example, there are three metadata_path metadata_path: - file: config/TVShows.yml - folder: config/TV Shows/ - - git: meisnate12/ShowCharts + - git: PMM/chart/basic + - git: PMM/chart/imdb ``` These path types are outlined as follows: @@ -194,17 +208,22 @@ These path types are outlined as follows: * `- git:` refers to a YAML file which is hosted on the [GitHub Configs Repo](https://github.com/meisnate12/Plex-Meta-Manager-Configs) unless the user has specified a custom repository within the settings section of the config.yml file. Within the above example, PMM will: -* First, look within the root of the PMM directory (also known as `config/`) for a metadata file named `Movies.yml`. If this file does not exist, PMM will skip the entry and move to the next one in the list. +* First, look within the root of the PMM directory (also known as `config/`) for a metadata file named `TVShows.yml`. If this file does not exist, PMM will skip the entry and move to the next one in the list. + * Then, look within the root of the PMM directory (also known as `config/`) for a directory called `TV Shows`, and then load any metadata/YAML files within that directory. -* Finally, look at the [meisnate12 folder](https://github.com/meisnate12/Plex-Meta-Manager-Configs/tree/master/meisnate12) within the GitHub Configs Repo for a file called `MovieCharts.yml` which it finds [here](https://github.com/meisnate12/Plex-Meta-Manager-Configs/blob/master/meisnate12/MovieCharts.yml). +* After that, look at the [PMM/chart folder](https://github.com/meisnate12/Plex-Meta-Manager-Configs/tree/master/PMM/chart) within the GitHub Configs Repo for a file called `basic.yml` which it finds [here](https://github.com/meisnate12/Plex-Meta-Manager-Configs/blob/master/PMM/chart/basic.yml). + +* Finally, look at the [PMM/chart folder](https://github.com/meisnate12/Plex-Meta-Manager-Configs/tree/master/PMM/chart) within the GitHub Configs Repo for a file called `imdb.yml` which it finds [here](https://github.com/meisnate12/Plex-Meta-Manager-Configs/blob/master/PMM/chart/imdb.yml). -It should be noted that whilst the user should be able to edit any metadata files which are `- file:` or `- folder:` based, they have little to no control over `- git:` metadata files **unless a copy of the YAML file is downloaded and ran locally**. In the above example, if the user downloaded the [MovieCharts.yml file](https://github.com/meisnate12/Plex-Meta-Manager-Configs/blob/master/meisnate12/MovieCharts.yml) from the [GitHub Configs Repo](https://github.com/meisnate12/Plex-Meta-Manager-Configs) and placed it in the root directory of PMM (`config/`), then the metadata_path mapping would be updated to reflect this as follows: +It should be noted that whilst the user should be able to edit any metadata files which are `- file:` or `- folder:` based, they have little to no control over `- git:` metadata files **unless a copy of the YAML file is downloaded and ran locally**. In the above example, if the user downloaded the [basic.yml file](https://github.com/meisnate12/Plex-Meta-Manager-Configs/blob/master/PMM/chart/basic.yml) from the [GitHub Configs Repo](https://github.com/meisnate12/Plex-Meta-Manager-Configs) and placed it in the root directory of PMM (`config/`), then the metadata_path mapping would be updated to reflect this as follows: ```yaml - Movies - 4K: + TV Shows: metadata_path: - - file: config/Movies.yml - - file: config/MovieCharts.yml <------ HERE + - file: config/TVShows.yml + - folder: config/TV Shows/ + - file: PMM/chart/basic # <------ HERE + - git: PMM/chart/imdb ``` ## Playlists (`playlist_files:` mappings) @@ -213,7 +232,7 @@ Playlists can be seen as an extension of Libraries in that they are both handled ```yaml playlist_files: - file: config/playlists.yml - - git: meisnate12/Playlists + - git: PMM/playlists ``` As with `libraries:`, YAML files are defined to create the Playlists. It should be noted that whilst in `libraries:` when working with `playlist_files:` you call out the libraries being connected to within the Metadata/YAML file as Playlists can combine media from multiple libraries. You can view an example playlists.yml file as follows: diff --git a/docs/metadata/builders/plex.md b/docs/metadata/builders/plex.md index acc27200..f0a27c5f 100644 --- a/docs/metadata/builders/plex.md +++ b/docs/metadata/builders/plex.md @@ -77,13 +77,14 @@ like Plex's [Advanced Filters](https://support.plex.tv/articles/201273953-collec Inside the base attribute you can use any search below or nest more `any` or `all`. You can have as many nested `any` or `all` next to each other as you want. If using multiple `any` or `all` you will have to do so in the form of a list. +**Note: To search by `season`, `episode`, `album`, or `track` you must use the `collection_level` [Detail](../details/metadata) to change the type of items the collection holds.** + There are a couple other attributes you can have at the top level only along with the base attribute are: ## Special Attributes | Attribute | Description & Values | |:-----------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `type` | **Description:** The Type of items inside this collection/playlist.
**Default:**
`movies` for Movies Libraries
`shows` for Show Libraries
`artists` for Music Libraries
**Values:** `movies`, `shows`, `seasons`, `episodes`, `artists`, `albums`, or `tracks` | | `limit` | **Description:** The max number of item for the search.
**Default:** `all`
**Values:** `all` or a number greater than 0 | | `sort_by` | **Description:** This will control how the filter is sorted in your library.
**Default:** `random`
**Values:** Any sort options for your search type in the [Sorts Options Table](#sort-options) | | `validate` | **Description:** Determines if a collection/playlist will fail on a validation error
**Default:** `true`
**Values**: `true` or `false` | diff --git a/docs/metadata/builders/smart.md b/docs/metadata/builders/smart.md index e477cc50..0fda35f9 100644 --- a/docs/metadata/builders/smart.md +++ b/docs/metadata/builders/smart.md @@ -46,13 +46,14 @@ like Plex's [Advanced Filters](https://support.plex.tv/articles/201273953-collec Inside the base attribute you can use any filter below or nest more `any` or `all`. You can have as many nested `any` or `all` next to each other as you want. If using multiple `any` or `all` you will have to do so in the form of a list. +**Note: To search by `season`, `episode`, `album`, or `track` you must use the `collection_level` [Detail](../details/metadata) to change the type of items the collection holds.** + There are a couple other attributes you can have at the top level only along with the base attribute are: ## Special Attributes | Attribute | Description & Values | |:-----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `type` | **Description:** The Type of items inside this collection.
**Default:**
`movies` for Movies Libraries
`shows` for Show Libraries
`artists` for Music Libraries
**Values:** `movies`, `shows`, `seasons`, `episodes`, `artists`, `albums`, or `tracks` | | `limit` | **Description:** The max number of item for the filter.
**Default:** `all`
**Values:** `all` or a number greater than 0 | | `sort_by` | **Description:** This will control how the filter is sorted in your library.
**Default:** `random`
**Values:** Any sort options for your filter type in the [Sorts Options Table](#sort-options) | | `validate` | **Description:** Determines if a collection will fail on a validation error
**Default:** `true`
**Values**: `true` or `false` | diff --git a/modules/builder.py b/modules/builder.py index b8385df5..06909293 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -454,17 +454,18 @@ class CollectionBuilder: logger.debug("Validating Method: collection_level") level = self.data[methods["collection_level"]] if level is None: - raise Failed(f"{self.Type} Error: collection_level attribute is blank") - logger.debug(f"Value: {level}") - level = level.lower() - if (self.library.is_show and level in plex.collection_level_show_options) or (self.library.is_music and level in plex.collection_level_music_options): - self.collection_level = level - elif (self.library.is_show and level != "show") or (self.library.is_music and level != "artist"): - if self.library.is_show: - options = "\n\tseason (Collection at the Season Level)\n\tepisode (Collection at the Episode Level)" - else: - options = "\n\talbum (Collection at the Album Level)\n\ttrack (Collection at the Track Level)" - raise Failed(f"{self.Type} Error: {self.data[methods['collection_level']]} collection_level invalid{options}") + logger.error(f"{self.Type} Error: collection_level attribute is blank") + else: + logger.debug(f"Value: {level}") + level = level.lower() + if (self.library.is_show and level in plex.collection_level_show_options) or (self.library.is_music and level in plex.collection_level_music_options): + self.collection_level = level + elif (self.library.is_show and level != "show") or (self.library.is_music and level != "artist"): + if self.library.is_show: + options = "\n\tseason (Collection at the Season Level)\n\tepisode (Collection at the Episode Level)" + else: + options = "\n\talbum (Collection at the Album Level)\n\ttrack (Collection at the Track Level)" + raise Failed(f"{self.Type} Error: {self.data[methods['collection_level']]} collection_level invalid{options}") self.parts_collection = self.collection_level in plex.collection_level_options if "tmdb_person" in methods: @@ -1232,19 +1233,15 @@ class CollectionBuilder: elif method_name in ["plex_search", "plex_collectionless"]: for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"): dict_methods = {dm.lower(): dm for dm in dict_data} - new_dictionary = {} if method_name == "plex_search": - type_override = f"{self.collection_level}s" if self.collection_level in plex.collection_level_options else None - new_dictionary = self.build_filter("plex_search", dict_data, type_override=type_override) + self.builders.append((method_name, self.build_filter("plex_search", dict_data))) elif method_name == "plex_collectionless": prefix_list = util.parse(self.Type, "exclude_prefix", dict_data, datatype="list", methods=dict_methods) if "exclude_prefix" in dict_methods else [] exact_list = util.parse(self.Type, "exclude", dict_data, datatype="list", methods=dict_methods) if "exclude" in dict_methods else [] if len(prefix_list) == 0 and len(exact_list) == 0: raise Failed(f"{self.Type} Error: you must have at least one exclusion") exact_list.append(self.name) - new_dictionary["exclude_prefix"] = prefix_list - new_dictionary["exclude"] = exact_list - self.builders.append((method_name, new_dictionary)) + self.builders.append((method_name, {"exclude_prefix": prefix_list, "exclude": exact_list})) else: self.builders.append(("plex_search", self.build_filter("plex_search", {"any": {method_name: method_data}}))) @@ -1670,7 +1667,7 @@ class CollectionBuilder: if self.details["save_report"] is True and filtered_items: self.library.add_filtered(self.name, [(i.title, self.library.get_id_from_maps(i.ratingKey)) for i in filtered_items], self.library.is_movie) - def build_filter(self, method, plex_filter, display=False, default_sort=None, type_override=None): + def build_filter(self, method, plex_filter, display=False, default_sort=None): if display: logger.info("") logger.info(f"Validating Method: {method}") @@ -1686,24 +1683,18 @@ class CollectionBuilder: if "any" in filter_alias and "all" in filter_alias: raise Failed(f"{self.Type} Error: Cannot have more then one base") - if type_override: - sort_type = type_override - elif "type" in filter_alias and self.library.is_show: - if plex_filter[filter_alias["type"]] not in ["shows", "seasons", "episodes"]: - raise Failed(f"{self.Type} Error: type: {plex_filter[filter_alias['type']]} is invalid, must be either shows, season, or episodes") - sort_type = plex_filter[filter_alias["type"]] - elif "type" in filter_alias and self.library.is_music: - if plex_filter[filter_alias["type"]] not in ["artists", "albums", "tracks"]: - raise Failed(f"{self.Type} Error: type: {plex_filter[filter_alias['type']]} is invalid, must be either artists, albums, or tracks") - sort_type = plex_filter[filter_alias["type"]] - elif self.library.is_show: - sort_type = "shows" - elif self.library.is_music: - sort_type = "artists" + if self.collection_level == "item": + if self.library.is_show: + sort_type = "show" + elif self.library.is_music: + sort_type = "artist" + else: + sort_type = "movie" else: - sort_type = "movies" + sort_type = self.collection_level + ms = method.split("_") - filter_details = f"{ms[0].capitalize()} {sort_type.capitalize()[:-1]} {ms[1].capitalize()}\n" + filter_details = f"{ms[0].capitalize()} {sort_type.capitalize()} {ms[1].capitalize()}\n" type_default_sort, type_key, sorts = plex.sort_types[sort_type] sort = default_sort if default_sort else type_default_sort diff --git a/modules/plex.py b/modules/plex.py index 62aa03cb..c5721f3d 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -375,13 +375,13 @@ track_sorts = { "random": "random" } sort_types = { - "movies": ("title.asc", 1, movie_sorts), - "shows": ("title.asc", 2, show_sorts), - "seasons": ("season.asc", 3, season_sorts), - "episodes": ("title.asc", 4, episode_sorts), - "artists": ("title.asc", 8, artist_sorts), - "albums": ("title.asc", 9, album_sorts), - "tracks": ("title.asc", 10, track_sorts) + "movie": ("title.asc", 1, movie_sorts), + "show": ("title.asc", 2, show_sorts), + "season": ("season.asc", 3, season_sorts), + "episode": ("title.asc", 4, episode_sorts), + "artist": ("title.asc", 8, artist_sorts), + "album": ("title.asc", 9, album_sorts), + "track": ("title.asc", 10, track_sorts) } class Plex(Library): @@ -996,6 +996,8 @@ class Plex(Library): if not item_asset_directory: if isinstance(item, (Movie, Artist, Album, Show, Episode, Season)): starting = item.show() if isinstance(item, (Episode, Season)) else item + if not starting.locations: + raise Failed(f"Asset Warning: No video filepath found fo {item.title}") path_test = str(starting.locations[0]) if not os.path.dirname(path_test): path_test = path_test.replace("\\", "/") diff --git a/modules/util.py b/modules/util.py index 754ce5d9..f06a4d11 100644 --- a/modules/util.py +++ b/modules/util.py @@ -780,22 +780,26 @@ def check_time(message, end=False): logger.debug(f"{message}: {current_time - previous_time}") previous_time = None if end else current_time +system_fonts = [] def get_system_fonts(): - dirs = [] - if sys.platform == "win32": - windir = os.environ.get("WINDIR") - if windir: - dirs.append(os.path.join(windir, "fonts")) - elif sys.platform in ("linux", "linux2"): - lindirs = os.environ.get("XDG_DATA_DIRS", "") - if not lindirs: - lindirs = "/usr/share" - dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")] - elif sys.platform == "darwin": - dirs += ["/Library/Fonts", "/System/Library/Fonts", os.path.expanduser("~/Library/Fonts")] - else: - return dirs - return [n for d in dirs for _, _, ns in os.walk(d) for n in ns] + global system_fonts + if not system_fonts: + dirs = [] + if sys.platform == "win32": + windir = os.environ.get("WINDIR") + if windir: + dirs.append(os.path.join(windir, "fonts")) + elif sys.platform in ("linux", "linux2"): + lindirs = os.environ.get("XDG_DATA_DIRS", "") + if not lindirs: + lindirs = "/usr/share" + dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")] + elif sys.platform == "darwin": + dirs += ["/Library/Fonts", "/System/Library/Fonts", os.path.expanduser("~/Library/Fonts")] + else: + return dirs + system_fonts = [n for d in dirs for _, _, ns in os.walk(d) for n in ns] + return system_fonts class YAML: def __init__(self, path=None, input_data=None, check_empty=False, create=False): @@ -975,8 +979,7 @@ class Overlay: if not match: raise Failed(f"Overlay Error: failed to parse overlay text name: {self.name}") self.name = f"text({match.group(1)})" - if os.path.exists("fonts/Roboto-Medium.ttf"): - self.font_name = "fonts/Roboto-Medium.ttf" + self.font_name = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fonts", "Roboto-Medium.ttf") if "font_size" in self.data: self.font_size = parse("Overlay", "font_size", self.data["font_size"], datatype="int", parent="overlay", default=self.font_size) if "font" in self.data and self.data["font"]: