[191] added delete_collections operation

pull/1165/head
meisnate12 2 years ago
parent d14e9f461a
commit becd838eae

@ -1 +1 @@
1.17.3-develop190 1.17.3-develop191

@ -50,7 +50,7 @@
{% elif subsublink|length == 5 %} {% elif subsublink|length == 5 %}
<a tabindex="-1" href="{{ pathto(subsublink[2]) }}{{ subsublink[3] }}">{{ subsublink[1] }}</a> <a tabindex="-1" href="{{ pathto(subsublink[2]) }}{{ subsublink[3] }}">{{ subsublink[1] }}</a>
{% else %} {% else %}
<a tabindex="-1" href="#">{{ subsubsublink[1] }}</a> <a tabindex="-1" href="#">{{ subsublink[1] }}</a>
{% endif %} {% endif %}
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{%- for subsubsublink in subsublink[-1] %} {%- for subsubsublink in subsublink[-1] %}

@ -19,8 +19,7 @@ The available attributes for the operations attribute are as follows
| Attribute | Description | | Attribute | Description |
|:----------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| |:----------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Assets For All](#assets-for-all) | Search in assets for images for every item in your library. | | [Assets For All](#assets-for-all) | Search in assets for images for every item in your library. |
| [Delete Collections With Less](#delete-collections-with-less) | Deletes every collection with less than the given number of items. | | [Delete Collections](#delete-collections) | Deletes collections based on a set of given attributes. |
| [Delete Unmanaged Collections](#delete-unmanaged-collections) | Deletes every unmanaged collection. |
| [Mass Genre Update](#mass-genre-update) | Updates every item's genres in the library to the chosen site's genres. | | [Mass Genre Update](#mass-genre-update) | Updates every item's genres in the library to the chosen site's genres. |
| [Mass Content Rating Update](#mass-content-rating-update) | Updates every item's content rating in the library to the chosen site's content rating. | | [Mass Content Rating Update](#mass-content-rating-update) | Updates every item's content rating in the library to the chosen site's content rating. |
| [Mass Original Title Update](#mass-original-title-update) | Updates every item's original title in the library to the chosen site's original title. | | [Mass Original Title Update](#mass-original-title-update) | Updates every item's original title in the library to the chosen site's original title. |
@ -50,21 +49,34 @@ Search in assets for images for every item in your library.
**Values:** `true` or `false` **Values:** `true` or `false`
## Delete Collections With Less ## Delete Collections
Deletes every collection with less than the given number of items. Deletes collections based on a set of given attributes. The Collection must match all set attributes to be deleted.
**Attribute:** `delete_collections_with_less` **Attribute:** `delete_collections`
**Values:** number greater than 0 **Values:** There are a few different options to determine how the `delete_collections` works.
## Delete Unmanaged Collections | Attribute | Description |
|:---------------|:----------------------------------------------------------------------------------------------------------------------------------------------------|
| `managed` | Matches with a Collection Managed by PMM (the collection has the `PMM` label).<br>**Default:** `false`<br>**Values:** `true` or `false` |
| `unmanaged` | Matches with a Collection Unmanaged by PMM (the collection does not have the `PMM` label).<br>**Default:** `false`<br>**Values:** `true` or `false` |
| `configured` | Matches with a Collection Configured in the specific PMM run.<br>**Default:** `false`<br>**Values:** `true` or `false` |
| `unconfigured` | Matches with a Collection Not Configured in the specific PMM run.<br>**Default:** `false`<br>**Values:** `true` or `false` |
| `less` | Matches with a Collection that contains less then the given number of items.<br>**Default:** ` `<br>**Values:** Number Greater then 0 |
Deletes every collection that doesn't have the PMM label. **Example:**
**Attribute:** `delete_unmanaged_collections` Removes all Managed Collections (Collections with the `PMM` Label) that are not configured in the Current Run.
**Values:** `true` or `false` ```yaml
library:
Movies:
operations:
delete_collections:
unconfigured: true
managed: true
```
## Mass Genre Update ## Mass Genre Update

@ -61,7 +61,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -58,7 +58,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -32,7 +32,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -23,7 +23,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -37,7 +37,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) except `horizontal_offset`, `horizontal_align`, `vertical_offset`, and `vertical_align` are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) except `horizontal_offset`, `horizontal_align`, `vertical_offset`, and `vertical_align` are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------------:| |:--------------------|:-----------------:|

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 553 KiB

@ -41,7 +41,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -6,7 +6,7 @@ The `languages` Default Overlay File is used to create an overlay of a flag and
**Designed for [TRaSH Guides](https://trash-guides.info/) filename naming scheme.** **Designed for [TRaSH Guides](https://trash-guides.info/) filename naming scheme.**
![](images/languages.png) ![](images/language.png)
## Supported Audio/Subtitle Language Flags ## Supported Audio/Subtitle Language Flags
@ -78,13 +78,13 @@ The `languages` Default Overlay File is used to create an overlay of a flag and
Below is a screenshot of the alternative Square (`square`) style which can be set via the `style` template variable. Below is a screenshot of the alternative Square (`square`) style which can be set via the `style` template variable.
![](../images/languages2.png) ![](images/language2.png)
#### Half Style #### Half Style
Below is a screenshot of the alternative Half (`half`) style which can be set via the `style` template variable. Below is a screenshot of the alternative Half (`half`) style which can be set via the `style` template variable.
![](../images/languages3.png) ![](images/language3.png)
## Config ## Config
@ -112,7 +112,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:----------------------------:| |:--------------------|:----------------------------:|

@ -23,7 +23,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -60,9 +60,9 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
All [Shared Overlay Variables](../overlay_variables.md) can be appended by `rating1_`, `rating2_`, or `rating3_` to change that attribute on each rating individually. All [Shared Overlay Variables](../overlay_variables) can be appended by `rating1_`, `rating2_`, or `rating3_` to change that attribute on each rating individually.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -66,7 +66,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -36,7 +36,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:--------:| |:--------------------|:--------:|

@ -26,7 +26,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -35,7 +35,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -48,7 +48,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -32,7 +32,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:----------------:| |:--------------------|:----------------:|

@ -45,7 +45,7 @@ Template Variables can be used to manipulate the file in various ways to slightl
Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored. Note that the `templates_variables:` section only needs to be used if you do want to actually change how the defaults work. Any value not specified is its default value if it has one if not it's just ignored.
All [Shared Overlay Variables](../overlay_variables.md) are available with the default values below as well as the additional Variables below which can be used to customize the file. All [Shared Overlay Variables](../overlay_variables) are available with the default values below as well as the additional Variables below which can be used to customize the file.
| Variable | Default | | Variable | Default |
|:--------------------|:-----------:| |:--------------------|:-----------:|

@ -380,4 +380,4 @@ docker run -d \
That will create a container that will run in the background until you explicitly stop it, surviving reboots, and waking up every morning at 3AM to process collections. That will create a container that will run in the background until you explicitly stop it, surviving reboots, and waking up every morning at 3AM to process collections.
There are of course [other flags you can add](../environmental.md), but this is the minimal command to create this container. There are of course [other flags you can add](../environmental), but this is the minimal command to create this container.

@ -34,7 +34,7 @@ Steps.
For example: `/tini -s python3 plex_meta_manager.py -- --run` For example: `/tini -s python3 plex_meta_manager.py -- --run`
Information on available command line argument can be found [here](../environmental.md) Information on available command line argument can be found [here](../environmental)
Click "Advanced Settings >>" Click "Advanced Settings >>"
@ -44,7 +44,7 @@ Steps.
3. Environment Variables can be added here: 3. Environment Variables can be added here:
Information on available Environment Variables can be found [here](../environmental.md) Information on available Environment Variables can be found [here](../environmental)
![](qnap/qnap5.png) ![](qnap/qnap5.png)

@ -61,7 +61,7 @@ This is a quick walkthrough of setting up the Plex-Meta-Manager Docker container
- To add an Environment Variable Click "Add". - To add an Environment Variable Click "Add".
- To use Command line arguments put the arguments in the "Command" text field. - To use Command line arguments put the arguments in the "Command" text field.
Information on available command line argument and Environment Variables can be found [here](../environmental.md) Information on available command line argument and Environment Variables can be found [here](../environmental)
![](synology/synology-10.png) ![](synology/synology-10.png)

@ -5,7 +5,7 @@ To install a container from docker hub, you will need community applications - a
## Basic Installation ## Basic Installation
1. Head to the `Apps` tab of unRAID (Community Applications), and search `plex-meta-manager` in the upper right search box. There will be a couple of results shown, but you should ignore them ([Why?](images.md)) and use the official image, which is on DockerHub. Click `Click Here To Get More Results From DockerHub`. 1. Head to the `Apps` tab of unRAID (Community Applications), and search `plex-meta-manager` in the upper right search box. There will be a couple of results shown, but you should ignore them ([Why?](images)) and use the official image, which is on DockerHub. Click `Click Here To Get More Results From DockerHub`.
2. Click the download icon on the `plex meta manager` container by `meisnate12`. 2. Click the download icon on the `plex meta manager` container by `meisnate12`.

@ -466,7 +466,7 @@ class CollectionBuilder:
suffix = "" suffix = ""
if self.details["delete_not_scheduled"]: if self.details["delete_not_scheduled"]:
try: try:
self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name) self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name, force_search=True)
logger.info(self.delete()) logger.info(self.delete())
self.deleted = True self.deleted = True
suffix = f" and was deleted" suffix = f" and was deleted"
@ -790,7 +790,7 @@ class CollectionBuilder:
or (self.library.Sonarr and self.sonarr_details["add_missing"])) or (self.library.Sonarr and self.sonarr_details["add_missing"]))
if self.build_collection: if self.build_collection:
try: try:
self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name) self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name, force_search=True)
if (self.smart and not self.obj.smart) or (not self.smart and self.obj.smart): if (self.smart and not self.obj.smart) or (not self.smart and self.obj.smart):
logger.info("") logger.info("")
logger.error(f"{self.Type} Error: Converting {self.obj.title} to a {'smart' if self.smart else 'normal'} collection") logger.error(f"{self.Type} Error: Converting {self.obj.title} to a {'smart' if self.smart else 'normal'} collection")
@ -2518,7 +2518,7 @@ class CollectionBuilder:
self.library.create_smart_collection(self.name, smart_type, self.smart_url, self.ignore_blank_results) self.library.create_smart_collection(self.name, smart_type, self.smart_url, self.ignore_blank_results)
except Failed: except Failed:
raise Failed(f"{self.Type} Error: Label: {self.name} was not added to any items in the Library") raise Failed(f"{self.Type} Error: Label: {self.name} was not added to any items in the Library")
self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name) self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name, force_search=True)
if not self.exists: if not self.exists:
self.created = True self.created = True
@ -2791,7 +2791,7 @@ class CollectionBuilder:
logger.error(f"Webhooks Error: {e}") logger.error(f"Webhooks Error: {e}")
def run_collections_again(self): def run_collections_again(self):
self.obj = self.library.get_collection(self.name) self.obj = self.library.get_collection(self.name, force_search=True)
name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection) name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection)
self.created = False self.created = False
rating_keys = [] rating_keys = []

@ -86,6 +86,18 @@ mass_rating_options = {
"mal": "Use MyAnimeList Rating" "mal": "Use MyAnimeList Rating"
} }
reset_overlay_options = {"tmdb": "Reset to TMDb poster", "plex": "Reset to Plex Poster"} reset_overlay_options = {"tmdb": "Reset to TMDb poster", "plex": "Reset to Plex Poster"}
library_operations = {
"assets_for_all": "bool", "split_duplicates": "bool", "update_blank_track_titles": "bool", "remove_title_parentheses": "bool",
"radarr_add_all_existing": "bool", "radarr_remove_by_tag": "bool", "sonarr_add_all_existing": "bool", "sonarr_remove_by_tag": "bool",
"mass_genre_update": mass_genre_options, "mass_content_rating_update": mass_content_options,
"mass_audience_rating_update": mass_rating_options, "mass_episode_audience_rating_update": mass_episode_rating_options,
"mass_critic_rating_update": mass_rating_options, "mass_episode_critic_rating_update": mass_episode_rating_options,
"mass_user_rating_update": mass_rating_options, "mass_episode_user_rating_update": mass_episode_rating_options,
"mass_original_title_update": mass_original_title_options, "mass_originally_available_update": mass_available_options,
"mass_imdb_parental_labels": imdb_label_options, "mass_poster_update": mass_image_options, "mass_background_update": mass_image_options,
"mass_collection_mode": "mass_collection_mode", "metadata_backup": "metadata_backup", "delete_collections": "delete_collections",
"genre_mapper": "mapper", "content_rating_mapper": "mapper",
}
class ConfigFile: class ConfigFile:
def __init__(self, default_dir, attrs): def __init__(self, default_dir, attrs):
@ -342,6 +354,7 @@ class ConfigFile:
"missing_only_released": check_for_attribute(self.data, "missing_only_released", parent="settings", var_type="bool", default=False), "missing_only_released": check_for_attribute(self.data, "missing_only_released", parent="settings", var_type="bool", default=False),
"only_filter_missing": check_for_attribute(self.data, "only_filter_missing", parent="settings", var_type="bool", default=False), "only_filter_missing": check_for_attribute(self.data, "only_filter_missing", parent="settings", var_type="bool", default=False),
"show_unmanaged": check_for_attribute(self.data, "show_unmanaged", parent="settings", var_type="bool", default=True), "show_unmanaged": check_for_attribute(self.data, "show_unmanaged", parent="settings", var_type="bool", default=True),
"show_unconfigured": check_for_attribute(self.data, "show_unconfigured", parent="settings", var_type="bool", default=True),
"show_filtered": check_for_attribute(self.data, "show_filtered", parent="settings", var_type="bool", default=False), "show_filtered": check_for_attribute(self.data, "show_filtered", parent="settings", var_type="bool", default=False),
"show_options": check_for_attribute(self.data, "show_options", parent="settings", var_type="bool", default=False), "show_options": check_for_attribute(self.data, "show_options", parent="settings", var_type="bool", default=False),
"show_missing": check_for_attribute(self.data, "show_missing", parent="settings", var_type="bool", default=True), "show_missing": check_for_attribute(self.data, "show_missing", parent="settings", var_type="bool", default=True),
@ -634,28 +647,9 @@ class ConfigFile:
for library_name, lib in libs.items(): for library_name, lib in libs.items():
if self.requested_libraries and library_name not in self.requested_libraries: if self.requested_libraries and library_name not in self.requested_libraries:
continue continue
params = { params = {o: None for o in library_operations}
"mapping_name": str(library_name), params["mapping_name"] = str(library_name)
"name": str(lib["library_name"]) if lib and "library_name" in lib and lib["library_name"] else str(library_name), params["name"] = str(lib["library_name"]) if lib and "library_name" in lib and lib["library_name"] else str(library_name)
"genre_mapper": None,
"content_rating_mapper": None,
"radarr_remove_by_tag": None,
"sonarr_remove_by_tag": None,
"mass_collection_mode": None,
"metadata_backup": None,
"update_blank_track_titles": None,
"mass_content_rating_update": None,
"mass_original_title_update": None,
"mass_originally_available_update": None,
"mass_imdb_parental_labels": None,
"remove_title_parentheses": None,
"mass_user_rating_update": None,
"mass_episode_audience_rating_update": None,
"mass_episode_critic_rating_update": None,
"mass_episode_user_rating_update": None,
"mass_poster_update": None,
"mass_background_update": None,
}
display_name = f"{params['name']} ({params['mapping_name']})" if lib and "library_name" in lib and lib["library_name"] else params["mapping_name"] display_name = f"{params['name']} ({params['mapping_name']})" if lib and "library_name" in lib and lib["library_name"] else params["mapping_name"]
logger.separator(f"{display_name} Configuration") logger.separator(f"{display_name} Configuration")
@ -668,6 +662,7 @@ class ConfigFile:
params["sync_mode"] = check_for_attribute(lib, "sync_mode", parent="settings", test_list=sync_modes, default=self.general["sync_mode"], do_print=False, save=False) params["sync_mode"] = check_for_attribute(lib, "sync_mode", parent="settings", test_list=sync_modes, default=self.general["sync_mode"], do_print=False, save=False)
params["default_collection_order"] = check_for_attribute(lib, "default_collection_order", parent="settings", default=self.general["default_collection_order"], default_is_none=True, do_print=False, save=False) params["default_collection_order"] = check_for_attribute(lib, "default_collection_order", parent="settings", default=self.general["default_collection_order"], default_is_none=True, do_print=False, save=False)
params["show_unmanaged"] = check_for_attribute(lib, "show_unmanaged", parent="settings", var_type="bool", default=self.general["show_unmanaged"], do_print=False, save=False) params["show_unmanaged"] = check_for_attribute(lib, "show_unmanaged", parent="settings", var_type="bool", default=self.general["show_unmanaged"], do_print=False, save=False)
params["show_unconfigured"] = check_for_attribute(lib, "show_unconfigured", parent="settings", var_type="bool", default=self.general["show_unconfigured"], do_print=False, save=False)
params["show_filtered"] = check_for_attribute(lib, "show_filtered", parent="settings", var_type="bool", default=self.general["show_filtered"], do_print=False, save=False) params["show_filtered"] = check_for_attribute(lib, "show_filtered", parent="settings", var_type="bool", default=self.general["show_filtered"], do_print=False, save=False)
params["show_options"] = check_for_attribute(lib, "show_options", parent="settings", var_type="bool", default=self.general["show_options"], do_print=False, save=False) params["show_options"] = check_for_attribute(lib, "show_options", parent="settings", var_type="bool", default=self.general["show_options"], do_print=False, save=False)
params["show_missing"] = check_for_attribute(lib, "show_missing", parent="settings", var_type="bool", default=self.general["show_missing"], do_print=False, save=False) params["show_missing"] = check_for_attribute(lib, "show_missing", parent="settings", var_type="bool", default=self.general["show_missing"], do_print=False, save=False)
@ -686,131 +681,68 @@ class ConfigFile:
params["item_refresh_delay"] = check_for_attribute(lib, "item_refresh_delay", parent="settings", var_type="int", default=self.general["item_refresh_delay"], do_print=False, save=False) params["item_refresh_delay"] = check_for_attribute(lib, "item_refresh_delay", parent="settings", var_type="int", default=self.general["item_refresh_delay"], do_print=False, save=False)
params["delete_below_minimum"] = check_for_attribute(lib, "delete_below_minimum", parent="settings", var_type="bool", default=self.general["delete_below_minimum"], do_print=False, save=False) params["delete_below_minimum"] = check_for_attribute(lib, "delete_below_minimum", parent="settings", var_type="bool", default=self.general["delete_below_minimum"], do_print=False, save=False)
params["delete_not_scheduled"] = check_for_attribute(lib, "delete_not_scheduled", parent="settings", var_type="bool", default=self.general["delete_not_scheduled"], do_print=False, save=False) params["delete_not_scheduled"] = check_for_attribute(lib, "delete_not_scheduled", parent="settings", var_type="bool", default=self.general["delete_not_scheduled"], do_print=False, save=False)
params["delete_unmanaged_collections"] = check_for_attribute(lib, "delete_unmanaged_collections", parent="settings", var_type="bool", default=False, do_print=False, save=False)
params["delete_collections_with_less"] = check_for_attribute(lib, "delete_collections_with_less", parent="settings", var_type="int", default_is_none=True, do_print=False, save=False)
params["ignore_ids"] = check_for_attribute(lib, "ignore_ids", parent="settings", var_type="int_list", default_is_none=True, do_print=False, save=False) params["ignore_ids"] = check_for_attribute(lib, "ignore_ids", parent="settings", var_type="int_list", default_is_none=True, do_print=False, save=False)
params["ignore_ids"].extend([i for i in self.general["ignore_ids"] if i not in params["ignore_ids"]]) params["ignore_ids"].extend([i for i in self.general["ignore_ids"] if i not in params["ignore_ids"]])
params["ignore_imdb_ids"] = check_for_attribute(lib, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True, do_print=False, save=False) params["ignore_imdb_ids"] = check_for_attribute(lib, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True, do_print=False, save=False)
params["ignore_imdb_ids"].extend([i for i in self.general["ignore_imdb_ids"] if i not in params["ignore_imdb_ids"]]) params["ignore_imdb_ids"].extend([i for i in self.general["ignore_imdb_ids"] if i not in params["ignore_imdb_ids"]])
params["error_webhooks"] = check_for_attribute(lib, "error", parent="webhooks", var_type="list", default=self.webhooks["error"], do_print=False, save=False, default_is_none=True) params["error_webhooks"] = check_for_attribute(lib, "error", parent="webhooks", var_type="list", default=self.webhooks["error"], do_print=False, save=False, default_is_none=True)
params["changes_webhooks"] = check_for_attribute(lib, "changes", parent="webhooks", var_type="list", default=self.webhooks["changes"], do_print=False, save=False, default_is_none=True) params["changes_webhooks"] = check_for_attribute(lib, "changes", parent="webhooks", var_type="list", default=self.webhooks["changes"], do_print=False, save=False, default_is_none=True)
params["assets_for_all"] = check_for_attribute(lib, "assets_for_all", parent="settings", var_type="bool", default=self.general["assets_for_all"], do_print=False, save=False)
params["mass_genre_update"] = check_for_attribute(lib, "mass_genre_update", test_list=mass_genre_options, default_is_none=True, save=False, do_print=False)
params["mass_audience_rating_update"] = check_for_attribute(lib, "mass_audience_rating_update", test_list=mass_rating_options, default_is_none=True, save=False, do_print=False)
params["mass_critic_rating_update"] = check_for_attribute(lib, "mass_critic_rating_update", test_list=mass_rating_options, default_is_none=True, save=False, do_print=False)
params["mass_trakt_rating_update"] = check_for_attribute(lib, "mass_trakt_rating_update", var_type="bool", default=False, save=False, do_print=False)
params["split_duplicates"] = check_for_attribute(lib, "split_duplicates", var_type="bool", default=False, save=False, do_print=False)
params["radarr_add_all_existing"] = check_for_attribute(lib, "radarr_add_all_existing", var_type="bool", default=False, save=False, do_print=False)
params["sonarr_add_all_existing"] = check_for_attribute(lib, "sonarr_add_all_existing", var_type="bool", default=False, save=False, do_print=False)
params["report_path"] = None params["report_path"] = None
if lib and "report_path" in lib and lib["report_path"]: if lib and "report_path" in lib and lib["report_path"]:
if os.path.exists(os.path.dirname(os.path.abspath(lib["report_path"]))): if os.path.exists(os.path.dirname(os.path.abspath(lib["report_path"]))):
params["report_path"] = lib["report_path"] params["report_path"] = lib["report_path"]
else: else:
logger.error(f"Config Error: Folder {os.path.dirname(os.path.abspath(lib['report_path']))} does not exist") logger.error(f"Config Error: Folder {os.path.dirname(os.path.abspath(lib['report_path']))} does not exist")
if lib and "operations" in lib and lib["operations"]: if lib and "operations" in lib and lib["operations"]:
if isinstance(lib["operations"], dict): if isinstance(lib["operations"], dict):
if "assets_for_all" in lib["operations"]: if "delete_collections" not in lib["operations"] and ("delete_unmanaged_collections" in lib["operations"] or "delete_collections_with_less" in lib["operations"]):
params["assets_for_all"] = check_for_attribute(lib["operations"], "assets_for_all", var_type="bool", default=False, save=False) lib["operations"]["delete_collections"] = {}
if "delete_unmanaged_collections" in lib["operations"]: if "delete_unmanaged_collections" in lib["operations"]:
params["delete_unmanaged_collections"] = check_for_attribute(lib["operations"], "delete_unmanaged_collections", var_type="bool", default=False, save=False) lib["operations"]["delete_collections"]["unmanaged"] = check_for_attribute(lib["operations"], "delete_unmanaged_collections", var_type="bool", default=False, save=False)
if "delete_collections_with_less" in lib["operations"]: if "delete_collections_with_less" in lib["operations"]:
params["delete_collections_with_less"] = check_for_attribute(lib["operations"], "delete_collections_with_less", var_type="int", default_is_none=True, save=False) lib["operations"]["delete_collections"]["less"] = check_for_attribute(lib["operations"], "delete_collections_with_less", var_type="int", default_is_none=True, save=False)
if "mass_genre_update" in lib["operations"]: for op, data_type in library_operations.items():
params["mass_genre_update"] = check_for_attribute(lib["operations"], "mass_genre_update", test_list=mass_genre_options, default_is_none=True, save=False) if op not in lib["operations"]:
if "mass_audience_rating_update" in lib["operations"]: continue
params["mass_audience_rating_update"] = check_for_attribute(lib["operations"], "mass_audience_rating_update", test_list=mass_rating_options, default_is_none=True, save=False) elif isinstance(data_type, list):
if "mass_critic_rating_update" in lib["operations"]: params[op] = check_for_attribute(lib["operations"], op, test_list=data_type, default_is_none=True, save=False)
params["mass_critic_rating_update"] = check_for_attribute(lib["operations"], "mass_critic_rating_update", test_list=mass_rating_options, default_is_none=True, save=False) elif data_type == "mass_collection_mode":
if "mass_user_rating_update" in lib["operations"]: params[op] = util.check_collection_mode(lib["operations"][op])
params["mass_user_rating_update"] = check_for_attribute(lib["operations"], "mass_user_rating_update", test_list=mass_rating_options, default_is_none=True, save=False) elif data_type in ["metadata_backup", "mapper", "delete_collections"]:
if "mass_episode_audience_rating_update" in lib["operations"]: if not lib["operations"][op] or not isinstance(lib["operations"][op], dict):
params["mass_episode_audience_rating_update"] = check_for_attribute(lib["operations"], "mass_episode_audience_rating_update", test_list=mass_episode_rating_options, default_is_none=True, save=False) raise Failed(f"Config Error: {op} must be a dictionary")
if "mass_episode_critic_rating_update" in lib["operations"]: if data_type == "metadata_backup":
params["mass_episode_critic_rating_update"] = check_for_attribute(lib["operations"], "mass_episode_critic_rating_update", test_list=mass_episode_rating_options, default_is_none=True, save=False) default_path = os.path.join(default_dir, f"{str(library_name)}_Metadata_Backup.yml")
if "mass_episode_user_rating_update" in lib["operations"]:
params["mass_episode_user_rating_update"] = check_for_attribute(lib["operations"], "mass_episode_user_rating_update", test_list=mass_episode_rating_options, default_is_none=True, save=False)
if "mass_content_rating_update" in lib["operations"]:
params["mass_content_rating_update"] = check_for_attribute(lib["operations"], "mass_content_rating_update", test_list=mass_content_options, default_is_none=True, save=False)
if "mass_original_title_update" in lib["operations"]:
params["mass_original_title_update"] = check_for_attribute(lib["operations"], "mass_original_title_update", test_list=mass_original_title_options, default_is_none=True, save=False)
if "mass_originally_available_update" in lib["operations"]:
params["mass_originally_available_update"] = check_for_attribute(lib["operations"], "mass_originally_available_update", test_list=mass_available_options, default_is_none=True, save=False)
if "mass_imdb_parental_labels" in lib["operations"]:
params["mass_imdb_parental_labels"] = check_for_attribute(lib["operations"], "mass_imdb_parental_labels", test_list=imdb_label_options, default_is_none=True, save=False)
if "mass_poster_update" in lib["operations"]:
params["mass_poster_update"] = check_for_attribute(lib["operations"], "mass_poster_update", test_list=mass_image_options, default_is_none=True, save=False)
if "mass_background_update" in lib["operations"]:
params["mass_background_update"] = check_for_attribute(lib["operations"], "mass_background_update", test_list=mass_image_options, default_is_none=True, save=False)
if "mass_trakt_rating_update" in lib["operations"]:
params["mass_trakt_rating_update"] = check_for_attribute(lib["operations"], "mass_trakt_rating_update", var_type="bool", default=False, save=False, do_print=False)
if "split_duplicates" in lib["operations"]:
params["split_duplicates"] = check_for_attribute(lib["operations"], "split_duplicates", var_type="bool", default=False, save=False)
if "radarr_add_all_existing" in lib["operations"]:
params["radarr_add_all_existing"] = check_for_attribute(lib["operations"], "radarr_add_all_existing", var_type="bool", default=False, save=False)
if "radarr_remove_by_tag" in lib["operations"]:
params["radarr_remove_by_tag"] = check_for_attribute(lib["operations"], "radarr_remove_by_tag", var_type="comma_list", default=False, save=False)
if "sonarr_add_all_existing" in lib["operations"]:
params["sonarr_add_all_existing"] = check_for_attribute(lib["operations"], "sonarr_add_all_existing", var_type="bool", default=False, save=False)
if "sonarr_remove_by_tag" in lib["operations"]:
params["sonarr_remove_by_tag"] = check_for_attribute(lib["operations"], "sonarr_remove_by_tag", var_type="comma_list", default=False, save=False)
if "update_blank_track_titles" in lib["operations"]:
params["update_blank_track_titles"] = check_for_attribute(lib["operations"], "update_blank_track_titles", var_type="bool", default=False, save=False)
if "remove_title_parentheses" in lib["operations"]:
params["remove_title_parentheses"] = check_for_attribute(lib["operations"], "remove_title_parentheses", var_type="bool", default=False, save=False)
if "mass_collection_mode" in lib["operations"]:
try: try:
params["mass_collection_mode"] = util.check_collection_mode(lib["operations"]["mass_collection_mode"]) default_path = check_for_attribute(lib["operations"][op], "path", var_type="path", save=False)
except Failed as e: except Failed as e:
logger.error(e) logger.debug(f"{e} using default {default_path}")
if "metadata_backup" in lib["operations"]: params[op] = {
params["metadata_backup"] = { "path": default_path,
"path": os.path.join(default_dir, f"{str(library_name)}_Metadata_Backup.yml"), "exclude": check_for_attribute(lib["operations"][op], "exclude", var_type="comma_list", default_is_none=True, save=False),
"exclude": [], "sync_tags": check_for_attribute(lib["operations"][op], "sync_tags", var_type="bool", default=False, save=False),
"sync_tags": False, "add_blank_entries": check_for_attribute(lib["operations"][op], "add_blank_entries", var_type="bool", default=True, save=False)
"add_blank_entries": True
} }
if lib["operations"]["metadata_backup"] and isinstance(lib["operations"]["metadata_backup"], dict): if data_type == "mapper":
try: params[op] = lib["operations"][op]
params["metadata_backup"]["path"] = check_for_attribute(lib["operations"]["metadata_backup"], "path", var_type="path", save=False) for old_value, new_value in lib["operations"][op].items():
except Failed as e: if old_value == new_value:
logger.debug(f"{e} using default {params['metadata_backup']['path']}") logger.warning(f"Config Warning: {op} value '{new_value}' ignored as it cannot be mapped to itself")
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 "genre_mapper" in lib["operations"]:
if lib["operations"]["genre_mapper"] and isinstance(lib["operations"]["genre_mapper"], dict):
params["genre_mapper"] = lib["operations"]["genre_mapper"]
for old_genre, new_genre in lib["operations"]["genre_mapper"].items():
if old_genre == new_genre:
logger.error("Config Error: genres cannot be mapped to themselves")
else:
params["genre_mapper"][old_genre] = new_genre if new_genre else None
else:
logger.error("Config Error: genre_mapper is blank")
if "content_rating_mapper" in lib["operations"]:
if lib["operations"]["content_rating_mapper"] and isinstance(lib["operations"]["content_rating_mapper"], dict):
params["content_rating_mapper"] = lib["operations"]["content_rating_mapper"]
for old_content, new_content in lib["operations"]["content_rating_mapper"].items():
if old_content == new_content:
logger.error("Config Error: content rating cannot be mapped to themselves")
else: else:
params["content_rating_mapper"][old_content] = new_content if new_content else None params[op][old_value] = new_value if new_value else None
if data_type == "delete_collections":
params[op] = {
"managed": check_for_attribute(lib["operations"][op], "managed", var_type="bool", default=False, save=False),
"unmanaged": check_for_attribute(lib["operations"][op], "unmanaged", var_type="bool", default=False, save=False),
"configured": check_for_attribute(lib["operations"][op], "configured", var_type="bool", default=False, save=False),
"unconfigured": check_for_attribute(lib["operations"][op], "unconfigured", var_type="bool", default=False, save=False),
"less": check_for_attribute(lib["operations"][op], "less", var_type="int", default_is_none=True, save=False, int_min=1),
}
else: else:
logger.error("Config Error: content_rating_mapper is blank") params[op] = check_for_attribute(lib["operations"], op, var_type=data_type, default=False, save=False)
for atr in ["tmdb_collections", "genre_collections"]:
if atr in lib["operations"]:
logger.error(f"Deprecated Error: {atr} has been replaced with dynamic collections")
else: else:
logger.error("Config Error: operations must be a dictionary") logger.error("Config Error: operations must be a dictionary")
if params["mass_trakt_rating_update"]:
if params["mass_user_rating_update"]:
logger.error("Config Error: User Rating is already being set by mass_user_rating_update")
else:
params["mass_user_rating_update"] = "trakt_user"
def error_check(attr, service): def error_check(attr, service):
logger.error(f"Config Error: Operation {attr} cannot be {params[attr]} without a successful {service} Connection") logger.error(f"Config Error: Operation {attr} cannot be {params[attr]} without a successful {service} Connection")
params[attr] = None params[attr] = None

@ -65,6 +65,7 @@ class Library(ABC):
self.delete_not_scheduled = params["delete_not_scheduled"] self.delete_not_scheduled = params["delete_not_scheduled"]
self.missing_only_released = params["missing_only_released"] self.missing_only_released = params["missing_only_released"]
self.show_unmanaged = params["show_unmanaged"] self.show_unmanaged = params["show_unmanaged"]
self.show_unconfigured = params["show_unconfigured"]
self.show_filtered = params["show_filtered"] self.show_filtered = params["show_filtered"]
self.show_options = params["show_options"] self.show_options = params["show_options"]
self.show_missing = params["show_missing"] self.show_missing = params["show_missing"]
@ -75,7 +76,10 @@ class Library(ABC):
self.ignore_imdb_ids = params["ignore_imdb_ids"] self.ignore_imdb_ids = params["ignore_imdb_ids"]
self.assets_for_all = params["assets_for_all"] self.assets_for_all = params["assets_for_all"]
self.delete_unmanaged_collections = params["delete_unmanaged_collections"] self.delete_unmanaged_collections = params["delete_unmanaged_collections"]
self.delete_unconfigured_collections = params["delete_unconfigured_collections"]
self.delete_managed_unconfigured_collections = params["delete_managed_unconfigured_collections"]
self.delete_collections_with_less = params["delete_collections_with_less"] self.delete_collections_with_less = params["delete_collections_with_less"]
self.delete_collections = params["delete_collections"]
self.mass_genre_update = params["mass_genre_update"] self.mass_genre_update = params["mass_genre_update"]
self.mass_audience_rating_update = params["mass_audience_rating_update"] self.mass_audience_rating_update = params["mass_audience_rating_update"]
self.mass_critic_rating_update = params["mass_critic_rating_update"] self.mass_critic_rating_update = params["mass_critic_rating_update"]
@ -117,9 +121,9 @@ class Library(ABC):
or self.mass_content_rating_update or self.mass_originally_available_update or self.mass_original_title_update\ or self.mass_content_rating_update or self.mass_originally_available_update or self.mass_original_title_update\
or self.mass_imdb_parental_labels or self.genre_mapper or self.content_rating_mapper \ or self.mass_imdb_parental_labels or self.genre_mapper or self.content_rating_mapper \
or self.radarr_add_all_existing or self.sonarr_add_all_existing or self.mass_poster_update or self.mass_background_update else False or self.radarr_add_all_existing or self.sonarr_add_all_existing or self.mass_poster_update or self.mass_background_update else False
self.library_operation = True if self.items_library_operation or self.delete_unmanaged_collections or self.delete_collections_with_less \ self.library_operation = True if self.items_library_operation or self.delete_collections or self.mass_collection_mode \
or self.radarr_remove_by_tag or self.sonarr_remove_by_tag or self.mass_collection_mode \ or self.radarr_remove_by_tag or self.sonarr_remove_by_tag or self.show_unmanaged or self.show_unconfigured \
or self.show_unmanaged or self.metadata_backup or self.update_blank_track_titles else False or self.metadata_backup or self.update_blank_track_titles else False
self.meta_operations = [i for i in [getattr(self, o) for o in operations.meta_operations] if i] self.meta_operations = [i for i in [getattr(self, o) for o in operations.meta_operations] if i]
if self.asset_directory: if self.asset_directory:

@ -23,8 +23,7 @@ class Operations:
logger.separator(f"{self.library.name} Library Operations") logger.separator(f"{self.library.name} Library Operations")
logger.info("") logger.info("")
logger.debug(f"Assets For All: {self.library.assets_for_all}") logger.debug(f"Assets For All: {self.library.assets_for_all}")
logger.debug(f"Delete Collections With Less: {self.library.delete_collections_with_less}") logger.debug(f"Delete Collections: {self.library.delete_collections}")
logger.debug(f"Delete Unmanaged Collections: {self.library.delete_unmanaged_collections}")
logger.debug(f"Show Unmanaged Collections: {self.library.show_unmanaged}") logger.debug(f"Show Unmanaged Collections: {self.library.show_unmanaged}")
logger.debug(f"Mass Genre Update: {self.library.mass_genre_update}") logger.debug(f"Mass Genre Update: {self.library.mass_genre_update}")
logger.debug(f"Mass Audience Rating Update: {self.library.mass_audience_rating_update}") logger.debug(f"Mass Audience Rating Update: {self.library.mass_audience_rating_update}")
@ -577,30 +576,41 @@ class Operations:
if self.library.sonarr_remove_by_tag: if self.library.sonarr_remove_by_tag:
self.library.Sonarr.remove_all_with_tags(self.library.sonarr_remove_by_tag) self.library.Sonarr.remove_all_with_tags(self.library.sonarr_remove_by_tag)
if self.library.delete_collections_with_less is not None or self.library.delete_unmanaged_collections: less = None
managed = False
unmanaged = False
configured = False
unconfigured = False
if self.library.delete_collections:
logger.info("") logger.info("")
print_suffix = "" logger.separator(f"Deleting All Collections", space=False, border=False)
unmanaged = ""
if self.library.delete_collections_with_less is not None and self.library.delete_collections_with_less > 0:
print_suffix = f" with less then {self.library.delete_collections_with_less} item{'s' if self.library.delete_collections_with_less > 1 else ''}"
if self.library.delete_unmanaged_collections:
if self.library.delete_collections_with_less is None:
unmanaged = "Unmanaged Collections "
elif self.library.delete_collections_with_less > 0:
unmanaged = "Unmanaged Collections and "
logger.separator(f"Deleting All {unmanaged}Collections{print_suffix}", space=False, border=False)
logger.info("") logger.info("")
if self.library.delete_collections["less"] is not None:
less = self.library.delete_collections["less"]
managed = self.library.delete_collections["managed"]
unmanaged = self.library.delete_collections["unmanaged"]
configured = self.library.delete_collections["configured"]
unconfigured = self.library.delete_collections["unconfigured"]
unmanaged_collections = [] unmanaged_collections = []
unconfigured_collections = []
all_collections = self.library.get_all_collections() all_collections = self.library.get_all_collections()
for i, col in enumerate(all_collections, 1): for i, col in enumerate(all_collections, 1):
logger.ghost(f"Reading Collection: {i}/{len(all_collections)} {col.title}") logger.ghost(f"Reading Collection: {i}/{len(all_collections)} {col.title}")
labels = [la.tag for la in self.library.item_labels(col)] labels = [la.tag for la in self.library.item_labels(col)]
if (self.library.delete_collections_with_less and col.childCount < self.library.delete_collections_with_less) \ if (less is not None or unmanaged or managed or unconfigured or configured) \
or (self.library.delete_unmanaged_collections and "PMM" not in labels): and (less is None or col.childCount < less) \
and (unmanaged is False or "PMM" not in labels) \
and (managed is False or "PMM" in labels) \
and (unconfigured is False or col.title not in self.library.collections) \
and (configured is False or col.title in self.library.collections):
self.library.query(col.delete) self.library.query(col.delete)
logger.info(f"{col.title} Deleted") logger.info(f"{col.title} Deleted")
elif "PMM" not in labels: else:
if "PMM" not in labels:
unmanaged_collections.append(col) unmanaged_collections.append(col)
if col.title not in self.library.collections:
unconfigured_collections.append(col)
if self.library.show_unmanaged and len(unmanaged_collections) > 0: if self.library.show_unmanaged and len(unmanaged_collections) > 0:
logger.info("") logger.info("")
@ -615,11 +625,24 @@ class Operations:
logger.separator(f"No Unmanaged Collections in {self.library.name} Library", space=False, border=False) logger.separator(f"No Unmanaged Collections in {self.library.name} Library", space=False, border=False)
logger.info("") logger.info("")
if self.library.assets_for_all and len(unmanaged_collections) > 0: if self.library.show_unconfigured and len(unconfigured_collections) > 0:
logger.info("") logger.info("")
logger.separator(f"Unmanaged Collection Assets Check for {self.library.name} Library", space=False, border=False) logger.separator(f"Unconfigured Collections in {self.library.name} Library", space=False, border=False)
logger.info("") logger.info("")
for col in unmanaged_collections: for col in unconfigured_collections:
logger.info(col.title)
logger.info("")
logger.info(f"{len(unconfigured_collections)} Unconfigured Collection{'s' if len(unconfigured_collections) > 1 else ''}")
elif self.library.show_unconfigured:
logger.info("")
logger.separator(f"No Unconfigured Collections in {self.library.name} Library", space=False, border=False)
logger.info("")
if self.library.assets_for_all and len(unconfigured_collections) > 0:
logger.info("")
logger.separator(f"Unconfigured Collection Assets Check for {self.library.name} Library", space=False, border=False)
logger.info("")
for col in unconfigured_collections:
try: try:
poster, background, item_dir, name = self.library.find_item_assets(col) poster, background, item_dir, name = self.library.find_item_assets(col)
if poster or background: if poster or background:
@ -631,9 +654,9 @@ class Operations:
if self.library.mass_collection_mode: if self.library.mass_collection_mode:
logger.info("") logger.info("")
logger.separator(f"Unmanaged Mass Collection Mode to {self.library.mass_collection_mode} for {self.library.name} Library", space=False, border=False) logger.separator(f"Unconfigured Mass Collection Mode to {self.library.mass_collection_mode} for {self.library.name} Library", space=False, border=False)
logger.info("") logger.info("")
for col in unmanaged_collections: for col in unconfigured_collections:
if int(col.collectionMode) not in plex.collection_mode_keys \ if int(col.collectionMode) not in plex.collection_mode_keys \
or plex.collection_mode_keys[int(col.collectionMode)] != self.library.mass_collection_mode: or plex.collection_mode_keys[int(col.collectionMode)] != self.library.mass_collection_mode:
self.library.collection_mode_query(col, self.library.mass_collection_mode) self.library.collection_mode_query(col, self.library.mass_collection_mode)

@ -822,11 +822,11 @@ class Plex(Library):
except NotFound: except NotFound:
raise Failed(f"Plex Error: Playlist {title} not found") raise Failed(f"Plex Error: Playlist {title} not found")
def get_collection(self, data): def get_collection(self, data, force_search=False):
if isinstance(data, int): if isinstance(data, Collection):
return self.fetchItem(data)
elif isinstance(data, Collection):
return data return data
elif isinstance(data, int) and not force_search:
return self.fetchItem(data)
else: else:
cols = self.search(title=str(data), libtype="collection") cols = self.search(title=str(data), libtype="collection")
for d in cols: for d in cols:
@ -841,8 +841,10 @@ class Plex(Library):
def validate_collections(self, collections): def validate_collections(self, collections):
valid_collections = [] valid_collections = []
for collection in collections: for collection in collections:
try: valid_collections.append(self.get_collection(collection)) try:
except Failed as e: logger.error(e) valid_collections.append(self.get_collection(collection))
except Failed as e:
logger.error(e)
if len(valid_collections) == 0: if len(valid_collections) == 0:
raise Failed(f"Collection Error: No valid Plex Collections in {collections}") raise Failed(f"Collection Error: No valid Plex Collections in {collections}")
return valid_collections return valid_collections

Loading…
Cancel
Save