[15] add director, producer, writer dynamic collections

pull/790/head
meisnate12 3 years ago
parent 248ab974a5
commit 92fa57e060

@ -1 +1 @@
1.16.0-develop14 1.16.0-develop15

@ -36,7 +36,7 @@ The available setting attributes which can be set at each level are outlined bel
| [`default_collection_order`](#default-collection-order) | ✅ | ✅ | ❌ | | [`default_collection_order`](#default-collection-order) | ✅ | ✅ | ❌ |
| [`minimum_items`](#minimum-items) | ✅ | ✅ | ✅ | | [`minimum_items`](#minimum-items) | ✅ | ✅ | ✅ |
| [`delete_below_minimum`](#delete-below-minimum) | ✅ | ✅ | ✅ | | [`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) | ✅ | ❌ | ❌ | | [`run_again_delay`](#run-again-delay) | ✅ | ❌ | ❌ |
| [`missing_only_released`](#missing-only-released) | ✅ | ✅ | ✅ | | [`missing_only_released`](#missing-only-released) | ✅ | ✅ | ✅ |
| [`show_unmanaged`](#show-unmanaged-collections) | ✅ | ✅ | ❌ | | [`show_unmanaged`](#show-unmanaged-collections) | ✅ | ✅ | ❌ |

@ -104,6 +104,7 @@ This will display a full path:
<details> <details>
<summary>OS X</summary> <summary>OS X</summary>
<br /> <br />
``` ```
/Users/YOURUSERNAME/plex-meta-manager /Users/YOURUSERNAME/plex-meta-manager
``` ```
@ -112,6 +113,7 @@ This will display a full path:
<details> <details>
<summary>Linux</summary> <summary>Linux</summary>
<br /> <br />
``` ```
/home/YOURUSERNAME/plex-meta-manager /home/YOURUSERNAME/plex-meta-manager
``` ```
@ -120,6 +122,7 @@ This will display a full path:
<details> <details>
<summary>Windows</summary> <summary>Windows</summary>
<br /> <br />
``` ```
C:\Users\YOURUSERNAME\plex-meta-manager C:\Users\YOURUSERNAME\plex-meta-manager
``` ```

@ -67,12 +67,12 @@ dynamic_collections:
dynamic_collections: dynamic_collections:
networks: networks:
type: network type: network
addons: addons:
MTV Worldwide: MTV Worldwide:
- MTV - MTV
- MTV2 - MTV2
- MTV3 - MTV3
- MTV (UK) - MTV (UK)
``` ```
## Attributes ## 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 | &#10060; | &#9989; | &#9989; | &#10060; | &#10060; | | [`trakt_liked_lists`](#trakt-liked-lists) | Create a collection for each list the authenticated trakt user likes | &#10060; | &#9989; | &#9989; | &#10060; | &#10060; |
| [`trakt_people_list`](#trakt-people-lists) | Create a collection for each actor found in the trakt list | &#9989; | &#9989; | &#9989; | &#10060; | &#10060; | | [`trakt_people_list`](#trakt-people-lists) | Create a collection for each actor found in the trakt list | &#9989; | &#9989; | &#9989; | &#10060; | &#10060; |
| [`actor`](#actor) | Create a collection for each actor found in the library | &#9989; | &#9989; | &#9989; | &#10060; | &#10060; | | [`actor`](#actor) | Create a collection for each actor found in the library | &#9989; | &#9989; | &#9989; | &#10060; | &#10060; |
| [`director`](#director) | Create a collection for each director found in the library | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; |
| [`writer`](#writer) | Create a collection for each writer found in the library | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; |
| [`producer`](#producer) | Create a collection for each producer found in the library | &#9989; | &#9989; | &#10060; | &#10060; | &#10060; |
| [`genre`](#genre) | Create a collection for each genre found in the library | &#10060; | &#9989; | &#9989; | &#9989; | &#9989; | | [`genre`](#genre) | Create a collection for each genre found in the library | &#10060; | &#9989; | &#9989; | &#9989; | &#9989; |
| [`year`](#year) | Create a collection for each year found in the library | &#10060; | &#9989; | &#9989; | &#10060; | &#10060; | | [`year`](#year) | Create a collection for each year found in the library | &#10060; | &#9989; | &#9989; | &#10060; | &#10060; |
| [`decade`](#decade) | Create a collection for each decade found in the library | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; | | [`decade`](#decade) | Create a collection for each decade found in the library | &#10060; | &#9989; | &#10060; | &#10060; | &#10060; |
@ -434,17 +437,17 @@ Create a collection for each actor found in the library.
<th>Description & Values</th> <th>Description & Values</th>
</tr> </tr>
<tr> <tr>
<td><code>actor_depth</code></td> <td><code>depth</code></td>
<td><strong>Values:</strong> Number greater then 0</td> <td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td> <td><strong>Default:</strong> 3</td>
</tr> </tr>
<tr> <tr>
<td><code>actor_minimum</code></td> <td><code>minimum</code></td>
<td><strong>Values:</strong> Number greater then 0</td> <td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td> <td><strong>Default:</strong> 3</td>
</tr> </tr>
<tr> <tr>
<td><code>number_of_actors</code></td> <td><code>limit</code></td>
<td><strong>Values:</strong> Number greater then 0</td> <td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> None</td> <td><strong>Default:</strong> None</td>
</tr> </tr>
@ -479,9 +482,210 @@ default_template:
</tr> </tr>
</table> </table>
* `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) * `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. * `minimum` determines the minimum number of times the actor must appear within `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) * `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.
<table class="dualTable colwidths-auto align-default table">
<tr>
<th><code>type</code> Option</th>
<td><code>director</code></td>
</tr>
<tr>
<th><code>data</code> Values</th>
<td>
<table class="clearTable">
<tr>
<th>Attribute</th>
<th>Description & Values</th>
</tr>
<tr>
<td><code>depth</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td>
</tr>
<tr>
<td><code>minimum</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td>
</tr>
<tr>
<td><code>limit</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> None</td>
</tr>
</table>
</td>
</tr>
<tr>
<th>Keys</th>
<td>TMDb Person ID</td>
</tr>
<tr>
<th>Titles</th>
<td>TMDb Person Name</td>
</tr>
<tr>
<th>Default <code>title_format</code></th>
<td><code>&lt;&lt;title&gt;&gt;</code></td>
</tr>
<tr>
<th>Default Template</th>
<td>
```yaml
default_template:
tmdb_person: <<director>>
plex_search:
all:
director: tmdb
```
</td>
</tr>
</table>
* `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.
<table class="dualTable colwidths-auto align-default table">
<tr>
<th><code>type</code> Option</th>
<td><code>writer</code></td>
</tr>
<tr>
<th><code>data</code> Values</th>
<td>
<table class="clearTable">
<tr>
<th>Attribute</th>
<th>Description & Values</th>
</tr>
<tr>
<td><code>depth</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td>
</tr>
<tr>
<td><code>minimum</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td>
</tr>
<tr>
<td><code>limit</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> None</td>
</tr>
</table>
</td>
</tr>
<tr>
<th>Keys</th>
<td>TMDb Person ID</td>
</tr>
<tr>
<th>Titles</th>
<td>TMDb Person Name</td>
</tr>
<tr>
<th>Default <code>title_format</code></th>
<td><code>&lt;&lt;title&gt;&gt;</code></td>
</tr>
<tr>
<th>Default Template</th>
<td>
```yaml
default_template:
tmdb_person: <<writer>>
plex_search:
all:
writer: tmdb
```
</td>
</tr>
</table>
* `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.
<table class="dualTable colwidths-auto align-default table">
<tr>
<th><code>type</code> Option</th>
<td><code>producer</code></td>
</tr>
<tr>
<th><code>data</code> Values</th>
<td>
<table class="clearTable">
<tr>
<th>Attribute</th>
<th>Description & Values</th>
</tr>
<tr>
<td><code>depth</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td>
</tr>
<tr>
<td><code>minimum</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> 3</td>
</tr>
<tr>
<td><code>limit</code></td>
<td><strong>Values:</strong> Number greater then 0</td>
<td><strong>Default:</strong> None</td>
</tr>
</table>
</td>
</tr>
<tr>
<th>Keys</th>
<td>TMDb Person ID</td>
</tr>
<tr>
<th>Titles</th>
<td>TMDb Person Name</td>
</tr>
<tr>
<th>Default <code>title_format</code></th>
<td><code>&lt;&lt;title&gt;&gt;</code></td>
</tr>
<tr>
<th>Default Template</th>
<td>
```yaml
default_template:
tmdb_person: <<producer>>
plex_search:
all:
producer: tmdb
```
</td>
</tr>
</table>
* `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: #### Example:
@ -492,8 +696,8 @@ dynamic_collections:
Top Actors: # mapping name does not matter just needs to be unique Top Actors: # mapping name does not matter just needs to be unique
type: actor type: actor
data: data:
actor_depth: 5 depth: 5
number_of_actors: 25 limit: 25
``` ```
#### Example: #### Example:
@ -506,8 +710,8 @@ dynamic_collections:
Actors: # mapping name does not matter just needs to be unique Actors: # mapping name does not matter just needs to be unique
type: actor type: actor
data: data:
actor_depth: 5 depth: 5
actor_minimum: 20 minimum: 20
``` ```
### Genre ### Genre
@ -571,7 +775,7 @@ dynamic_collections:
Genres: # mapping name does not matter just needs to be unique Genres: # mapping name does not matter just needs to be unique
type: genre type: genre
exclude: exclude:
- Talk Show - Talk Show
title_format: Top <<title>> <<library_type>>s title_format: Top <<title>> <<library_type>>s
template: genre collection template: genre collection
``` ```
@ -963,12 +1167,12 @@ For example, the `addons` attribute can be used to combine multiple `keys`, i.e.
dynamic_collections: dynamic_collections:
networks: networks:
type: network type: network
addons: addons:
MTV Worldwide: MTV Worldwide:
- MTV - MTV
- MTV2 - MTV2
- MTV3 - MTV3
- MTV (UK) - MTV (UK)
``` ```
## Template ## Template

@ -558,10 +558,12 @@ class CollectionBuilder:
self.custom_sort = False self.custom_sort = False
for method_key, method_data in self.data.items(): 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) method_name, method_mod, method_final = self._split(method_key)
if method_name in ignored_details: if method_name in ignored_details:
continue continue
logger.debug("")
logger.debug(f"Validating Method: {method_key}") logger.debug(f"Validating Method: {method_key}")
logger.debug(f"Value: {method_data}") logger.debug(f"Value: {method_data}")
try: try:

@ -13,13 +13,16 @@ github_base = "https://raw.githubusercontent.com/meisnate12/Plex-Meta-Manager-Co
all_auto = ["genre"] all_auto = ["genre"]
ms_auto = ["actor", "year", "original_language", "tmdb_popular_people", "trakt_user_lists", "trakt_liked_lists", "trakt_people_list"] ms_auto = ["actor", "year", "original_language", "tmdb_popular_people", "trakt_user_lists", "trakt_liked_lists", "trakt_people_list"]
auto = { 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, "Show": ["network"] + all_auto + ms_auto,
"Artist": ["mood", "style", "country"] + all_auto, "Artist": ["mood", "style", "country"] + all_auto,
"Video": ["country"] + all_auto "Video": ["country"] + all_auto
} }
default_templates = { default_templates = {
"actor": {"tmdb_person": f"<<actor>>", "plex_search": {"all": {"actor": "tmdb"}}}, "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>>"}}, "original_language": {"plex_all": True, "filters": {"original_language": "<<original_language>>"}},
"tmdb_collection": {"tmdb_collection_details": "<<tmdb_collection>>"}, "tmdb_collection": {"tmdb_collection_details": "<<tmdb_collection>>"},
"trakt_user_lists": {"trakt_list_details": "<<trakt_user_lists>>"}, "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") 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 {} addons = util.parse("Config", "addons", dynamic, parent=map_name, methods=methods, datatype="dictlist") if "addons" in methods else {}
for k, v in addons.items(): 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_title_format = "<<title>>"
default_template = None default_template = None
auto_list = {} auto_list = {}
@ -303,40 +308,47 @@ class MetadataFile(DataFile):
auto_list[tmdb_item.original_language.iso_639_1] = tmdb_item.original_language.english_name auto_list[tmdb_item.original_language.iso_639_1] = tmdb_item.original_language.english_name
logger.exorcise() logger.exorcise()
default_title_format = "<<title>> <<library_type>>s" default_title_format = "<<title>> <<library_type>>s"
elif auto_type == "actor": elif auto_type in ["actor", "director", "writer", "producer"]:
people = {} people = {}
if "data" in methods: if "data" in methods:
dynamic_data = util.parse("Config", "data", dynamic, parent=map_name, methods=methods, datatype="dict") dynamic_data = util.parse("Config", "data", dynamic, parent=map_name, methods=methods, datatype="dict")
else: else:
raise Failed(f"Config Error: {map_name} data attribute not found") raise Failed(f"Config Error: {map_name} data attribute not found")
actor_methods = {am.lower(): am for am in dynamic_data} person_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) if "actor_depth" in person_methods:
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 person_methods["depth"] = person_methods.pop("actor_depth")
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 "actor_minimum" in person_methods:
if not actor_minimum and not number_of_actors: person_methods["minimum"] = person_methods.pop("actor_minimum")
actor_minimum = 3 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: if not all_items:
all_items = library.get_all() all_items = library.get_all()
for i, item in enumerate(all_items, 1): for i, item in enumerate(all_items, 1):
try: try:
self.library.reload(item) self.library.reload(item)
for actor in item.actors[:actor_depth]: for person in getattr(item, f"{auto_type}s")[:person_depth]:
if actor.id not in people: if person.id not in people:
people[actor.id] = {"name": actor.tag, "count": 0} people[person.id] = {"name": person.tag, "count": 0}
people[actor.id]["count"] += 1 people[person.id]["count"] += 1
except Failed as e: except Failed as e:
logger.error(f"Plex Error: {e}") logger.error(f"Plex Error: {e}")
roles = [data for _, data in people.items()] roles = [data for _, data in people.items()]
roles.sort(key=operator.itemgetter('count'), reverse=True) roles.sort(key=operator.itemgetter('count'), reverse=True)
actor_count = 0 person_count = 0
for role in roles: 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 break
if role["name"] not in exclude: if role["name"] not in exclude:
try: try:
results = self.config.TMDb.search_people(role["name"]) results = self.config.TMDb.search_people(role["name"])
auto_list[results[0].id] = results[0].name if results[0].id not in exclude:
actor_count += 1 auto_list[results[0].id] = results[0].name
person_count += 1
except TMDbNotFound: except TMDbNotFound:
logger.error(f"TMDb Error: Actor {role['name']} Not Found") logger.error(f"TMDb Error: Actor {role['name']} Not Found")
elif auto_type == "trakt_user_lists": elif auto_type == "trakt_user_lists":
@ -397,6 +409,9 @@ class MetadataFile(DataFile):
logger.debug(f"Sync: {sync}") logger.debug(f"Sync: {sync}")
logger.debug(f"Include: {include}") logger.debug(f"Include: {include}")
logger.debug(f"Other Name: {other_name}") 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(): for key, value in auto_list.items():
if include and key not in include: if include and key not in include:

@ -702,6 +702,7 @@ class Plex(Library):
for d in cols: for d in cols:
if d.title == data: if d.title == data:
return d return d
logger.debug("")
for d in cols: for d in cols:
logger.debug(f"Found: {d.title}") logger.debug(f"Found: {d.title}")
logger.debug(f"Looking for: {data}") logger.debug(f"Looking for: {data}")

@ -213,7 +213,7 @@ def update_libraries(config):
logger.debug(f"Create Asset Folders: {library.create_asset_folders}") logger.debug(f"Create Asset Folders: {library.create_asset_folders}")
logger.debug(f"Download URL Assets: {library.download_url_assets}") logger.debug(f"Download URL Assets: {library.download_url_assets}")
logger.debug(f"Sync Mode: {library.sync_mode}") 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 Below Minimum: {library.delete_below_minimum}")
logger.debug(f"Delete Not Scheduled: {library.delete_not_scheduled}") logger.debug(f"Delete Not Scheduled: {library.delete_not_scheduled}")
logger.debug(f"Default Collection Order: {library.default_collection_order}") logger.debug(f"Default Collection Order: {library.default_collection_order}")

Loading…
Cancel
Save