diff --git a/VERSION b/VERSION index ff4f1a93..31d6e28e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.16.0-develop14 +1.16.0-develop15 diff --git a/docs/config/settings.md b/docs/config/settings.md index 6f12c983..3f0b0b67 100644 --- a/docs/config/settings.md +++ b/docs/config/settings.md @@ -36,7 +36,7 @@ The available setting attributes which can be set at each level are outlined bel | [`default_collection_order`](#default-collection-order) | ✅ | ✅ | ❌ | | [`minimum_items`](#minimum-items) | ✅ | ✅ | ✅ | | [`delete_below_minimum`](#delete-below-minimum) | ✅ | ✅ | ✅ | -| [`delete_not_scheduled`d](#delete-not-scheduled) | ✅ | ✅ | ✅ | +| [`delete_not_scheduled`](#delete-not-scheduled) | ✅ | ✅ | ✅ | | [`run_again_delay`](#run-again-delay) | ✅ | ❌ | ❌ | | [`missing_only_released`](#missing-only-released) | ✅ | ✅ | ✅ | | [`show_unmanaged`](#show-unmanaged-collections) | ✅ | ✅ | ❌ | diff --git a/docs/home/guides/docker.md b/docs/home/guides/docker.md index 68bd3fd5..ba2773b7 100644 --- a/docs/home/guides/docker.md +++ b/docs/home/guides/docker.md @@ -104,6 +104,7 @@ This will display a full path:
OS X
+ ``` /Users/YOURUSERNAME/plex-meta-manager ``` @@ -112,6 +113,7 @@ This will display a full path:
Linux
+ ``` /home/YOURUSERNAME/plex-meta-manager ``` @@ -120,6 +122,7 @@ This will display a full path:
Windows
+ ``` C:\Users\YOURUSERNAME\plex-meta-manager ``` diff --git a/docs/metadata/dynamic.md b/docs/metadata/dynamic.md index f7bdc78b..05ad7721 100644 --- a/docs/metadata/dynamic.md +++ b/docs/metadata/dynamic.md @@ -67,12 +67,12 @@ dynamic_collections: dynamic_collections: networks: type: network - addons: - MTV Worldwide: - - MTV - - MTV2 - - MTV3 - - MTV (UK) + addons: + MTV Worldwide: + - MTV + - MTV2 + - MTV3 + - MTV (UK) ``` ## Attributes @@ -110,6 +110,9 @@ Depending on the `type` of dynamic collection, `data` is used to specify the opt | [`trakt_liked_lists`](#trakt-liked-lists) | Create a collection for each list the authenticated trakt user likes | ❌ | ✅ | ✅ | ❌ | ❌ | | [`trakt_people_list`](#trakt-people-lists) | Create a collection for each actor found in the trakt list | ✅ | ✅ | ✅ | ❌ | ❌ | | [`actor`](#actor) | Create a collection for each actor found in the library | ✅ | ✅ | ✅ | ❌ | ❌ | +| [`director`](#director) | Create a collection for each director found in the library | ✅ | ✅ | ❌ | ❌ | ❌ | +| [`writer`](#writer) | Create a collection for each writer found in the library | ✅ | ✅ | ❌ | ❌ | ❌ | +| [`producer`](#producer) | Create a collection for each producer found in the library | ✅ | ✅ | ❌ | ❌ | ❌ | | [`genre`](#genre) | Create a collection for each genre found in the library | ❌ | ✅ | ✅ | ✅ | ✅ | | [`year`](#year) | Create a collection for each year found in the library | ❌ | ✅ | ✅ | ❌ | ❌ | | [`decade`](#decade) | Create a collection for each decade found in the library | ❌ | ✅ | ❌ | ❌ | ❌ | @@ -434,17 +437,17 @@ Create a collection for each actor found in the library. Description & Values - actor_depth + depth Values: Number greater then 0 Default: 3 - actor_minimum + minimum Values: Number greater then 0 Default: 3 - number_of_actors + limit Values: Number greater then 0 Default: None @@ -479,9 +482,210 @@ default_template: -* `actor_depth` determines how many top billed actor per item they are in. (i.e. if they play a cameo role, this is unlikely to be counted) -* `actor_minimum` determines the minimum number of times the actor must appear within `actor_depth` for the collection to be created. -* `number_of_actors` determines the number of actor collection to max out at. (i.e. if to make collections for the top 25 actors) +* `depth` determines how many top billed actor per item they are in. (i.e. if they play a cameo role, this is unlikely to be counted) +* `minimum` determines the minimum number of times the actor must appear within `depth` for the collection to be created. +* `limit` determines the number of actor collection to max out at. (i.e. if to make collections for the top 25 actors) + +### Director + +Create a collection for each director found in the library. + + + + + + + + + + + + + + + + + + + + + + + + + + +
type Optiondirector
data Values + + + + + + + + + + + + + + + + + + + + +
AttributeDescription & Values
depthValues: Number greater then 0Default: 3
minimumValues: Number greater then 0Default: 3
limitValues: Number greater then 0Default: None
+
KeysTMDb Person ID
TitlesTMDb Person Name
Default title_format<<title>>
Default Template + +```yaml +default_template: + tmdb_person: <> + plex_search: + all: + director: tmdb +``` + +
+ +* `depth` determines how many directors are looked at per item. +* `minimum` determines the minimum number of times the director must appear within `depth` for the collection to be created. +* `limit` determines the number of director collection to max out at. (i.e. if to make collections for the top 25 directors) + +### Writer + +Create a collection for each writer found in the library. + + + + + + + + + + + + + + + + + + + + + + + + + + +
type Optionwriter
data Values + + + + + + + + + + + + + + + + + + + + +
AttributeDescription & Values
depthValues: Number greater then 0Default: 3
minimumValues: Number greater then 0Default: 3
limitValues: Number greater then 0Default: None
+
KeysTMDb Person ID
TitlesTMDb Person Name
Default title_format<<title>>
Default Template + +```yaml +default_template: + tmdb_person: <> + plex_search: + all: + writer: tmdb +``` + +
+ +* `depth` determines how many writers are looked at per item. +* `minimum` determines the minimum number of times the writer must appear within `depth` for the collection to be created. +* `limit` determines the number of writer collection to max out at. (i.e. if to make collections for the top 25 writers) + +### Producer + +Create a collection for each producer found in the library. + + + + + + + + + + + + + + + + + + + + + + + + + + +
type Optionproducer
data Values + + + + + + + + + + + + + + + + + + + + +
AttributeDescription & Values
depthValues: Number greater then 0Default: 3
minimumValues: Number greater then 0Default: 3
limitValues: Number greater then 0Default: None
+
KeysTMDb Person ID
TitlesTMDb Person Name
Default title_format<<title>>
Default Template + +```yaml +default_template: + tmdb_person: <> + plex_search: + all: + producer: tmdb +``` + +
+ +* `depth` determines how many producers are looked at per item. +* `minimum` determines the minimum number of times the producer must appear within `depth` for the collection to be created. +* `limit` determines the number of producer collection to max out at. (i.e. if to make collections for the top 25 producers) #### Example: @@ -492,8 +696,8 @@ dynamic_collections: Top Actors: # mapping name does not matter just needs to be unique type: actor data: - actor_depth: 5 - number_of_actors: 25 + depth: 5 + limit: 25 ``` #### Example: @@ -506,8 +710,8 @@ dynamic_collections: Actors: # mapping name does not matter just needs to be unique type: actor data: - actor_depth: 5 - actor_minimum: 20 + depth: 5 + minimum: 20 ``` ### Genre @@ -571,7 +775,7 @@ dynamic_collections: Genres: # mapping name does not matter just needs to be unique type: genre exclude: - - Talk Show + - Talk Show title_format: Top <> <<library_type>>s template: genre collection ``` @@ -963,12 +1167,12 @@ For example, the `addons` attribute can be used to combine multiple `keys`, i.e. dynamic_collections: networks: type: network - addons: - MTV Worldwide: - - MTV - - MTV2 - - MTV3 - - MTV (UK) + addons: + MTV Worldwide: + - MTV + - MTV2 + - MTV3 + - MTV (UK) ``` ## Template diff --git a/modules/builder.py b/modules/builder.py index 73191a6c..f540d330 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -558,10 +558,12 @@ class CollectionBuilder: self.custom_sort = False for method_key, method_data in self.data.items(): + if method_key.lower() in ignored_details: + continue + logger.debug("") method_name, method_mod, method_final = self._split(method_key) if method_name in ignored_details: continue - logger.debug("") logger.debug(f"Validating Method: {method_key}") logger.debug(f"Value: {method_data}") try: diff --git a/modules/meta.py b/modules/meta.py index 4aa91816..06ed58e5 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -13,13 +13,16 @@ github_base = "https://raw.githubusercontent.com/meisnate12/Plex-Meta-Manager-Co all_auto = ["genre"] ms_auto = ["actor", "year", "original_language", "tmdb_popular_people", "trakt_user_lists", "trakt_liked_lists", "trakt_people_list"] auto = { - "Movie": ["tmdb_collection", "decade", "country"] + all_auto + ms_auto, + "Movie": ["tmdb_collection", "decade", "country", "director", "producer", "writer"] + all_auto + ms_auto, "Show": ["network"] + all_auto + ms_auto, "Artist": ["mood", "style", "country"] + all_auto, "Video": ["country"] + all_auto } default_templates = { "actor": {"tmdb_person": f"<<actor>>", "plex_search": {"all": {"actor": "tmdb"}}}, + "director": {"tmdb_person": f"<<director>>", "plex_search": {"all": {"director": "tmdb"}}}, + "producer": {"tmdb_person": f"<<producer>>", "plex_search": {"all": {"producer": "tmdb"}}}, + "writer": {"tmdb_person": f"<<writer>>", "plex_search": {"all": {"writer": "tmdb"}}}, "original_language": {"plex_all": True, "filters": {"original_language": "<<original_language>>"}}, "tmdb_collection": {"tmdb_collection_details": "<<tmdb_collection>>"}, "trakt_user_lists": {"trakt_list_details": "<<trakt_user_lists>>"}, @@ -265,7 +268,9 @@ class MetadataFile(DataFile): raise Failed(f"Config Error: {map_name} cannot have both include and exclude attributes") addons = util.parse("Config", "addons", dynamic, parent=map_name, methods=methods, datatype="dictlist") if "addons" in methods else {} for k, v in addons.items(): - exclude.extend(v) + if k in v: + logger.warning(f"Config Warning: {k} cannot be an addon for itself") + exclude.extend([vv for vv in v if vv != k]) default_title_format = "<<title>>" default_template = None auto_list = {} @@ -303,40 +308,47 @@ class MetadataFile(DataFile): auto_list[tmdb_item.original_language.iso_639_1] = tmdb_item.original_language.english_name logger.exorcise() default_title_format = "<<title>> <<library_type>>s" - elif auto_type == "actor": + elif auto_type in ["actor", "director", "writer", "producer"]: people = {} if "data" in methods: dynamic_data = util.parse("Config", "data", dynamic, parent=map_name, methods=methods, datatype="dict") else: raise Failed(f"Config Error: {map_name} data attribute not found") - actor_methods = {am.lower(): am for am in dynamic_data} - actor_depth = util.parse("Config", "actor_depth", dynamic_data, parent=f"{map_name} data", methods=actor_methods, datatype="int", default=3, minimum=1) - actor_minimum = util.parse("Config", "actor_minimum", dynamic_data, parent=f"{map_name} data", methods=actor_methods, datatype="int", default=3, minimum=1) if "actor_minimum" in actor_methods else None - number_of_actors = util.parse("Config", "number_of_actors", dynamic_data, parent=f"{map_name} data", methods=actor_methods, datatype="int", default=25, minimum=1) if "number_of_actors" in actor_methods else None - if not actor_minimum and not number_of_actors: - actor_minimum = 3 + person_methods = {am.lower(): am for am in dynamic_data} + if "actor_depth" in person_methods: + person_methods["depth"] = person_methods.pop("actor_depth") + if "actor_minimum" in person_methods: + person_methods["minimum"] = person_methods.pop("actor_minimum") + if "number_of_actors" in person_methods: + person_methods["limit"] = person_methods.pop("number_of_actors") + person_depth = util.parse("Config", "depth", dynamic_data, parent=f"{map_name} data", methods=person_methods, datatype="int", default=3, minimum=1) + person_minimum = util.parse("Config", "minimum", dynamic_data, parent=f"{map_name} data", methods=person_methods, datatype="int", default=3, minimum=1) if "minimum" in person_methods else None + person_limit = util.parse("Config", "limit", dynamic_data, parent=f"{map_name} data", methods=person_methods, datatype="int", default=25, minimum=1) if "limit" in person_methods else None + if not person_minimum and not person_limit: + person_minimum = 3 if not all_items: all_items = library.get_all() for i, item in enumerate(all_items, 1): try: self.library.reload(item) - for actor in item.actors[:actor_depth]: - if actor.id not in people: - people[actor.id] = {"name": actor.tag, "count": 0} - people[actor.id]["count"] += 1 + for person in getattr(item, f"{auto_type}s")[:person_depth]: + if person.id not in people: + people[person.id] = {"name": person.tag, "count": 0} + people[person.id]["count"] += 1 except Failed as e: logger.error(f"Plex Error: {e}") roles = [data for _, data in people.items()] roles.sort(key=operator.itemgetter('count'), reverse=True) - actor_count = 0 + person_count = 0 for role in roles: - if (number_of_actors and actor_count >= number_of_actors) or (actor_minimum and role["count"] < actor_minimum): + if (person_limit and person_count >= person_limit) or (person_minimum and role["count"] < person_minimum): break if role["name"] not in exclude: try: results = self.config.TMDb.search_people(role["name"]) - auto_list[results[0].id] = results[0].name - actor_count += 1 + if results[0].id not in exclude: + auto_list[results[0].id] = results[0].name + person_count += 1 except TMDbNotFound: logger.error(f"TMDb Error: Actor {role['name']} Not Found") elif auto_type == "trakt_user_lists": @@ -397,6 +409,9 @@ class MetadataFile(DataFile): logger.debug(f"Sync: {sync}") logger.debug(f"Include: {include}") logger.debug(f"Other Name: {other_name}") + logger.debug(f"Keys (Title)") + for key, value in auto_list.items(): + logger.debug(f" - {key}{'' if key == value else f' ({value})'}") for key, value in auto_list.items(): if include and key not in include: diff --git a/modules/plex.py b/modules/plex.py index f1e2f9e6..8d10349e 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -702,6 +702,7 @@ class Plex(Library): for d in cols: if d.title == data: return d + logger.debug("") for d in cols: logger.debug(f"Found: {d.title}") logger.debug(f"Looking for: {data}") diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 1f4459a7..c12c953f 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -213,7 +213,7 @@ def update_libraries(config): logger.debug(f"Create Asset Folders: {library.create_asset_folders}") logger.debug(f"Download URL Assets: {library.download_url_assets}") logger.debug(f"Sync Mode: {library.sync_mode}") - logger.debug(f"Collection Minimum: {library.minimum_items}") + logger.debug(f"Minimum Items: {library.minimum_items}") logger.debug(f"Delete Below Minimum: {library.delete_below_minimum}") logger.debug(f"Delete Not Scheduled: {library.delete_not_scheduled}") logger.debug(f"Default Collection Order: {library.default_collection_order}")