diff --git a/CHANGELOG b/CHANGELOG
index 042ff9d4..81fa9c4b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
# New Features
Checks requirement versions to print a message if one needs to be updated
+Added the `mass_added_at_update` operation to mass set the Added At field of Movies and Shows.
# Updates
Changed the `overlay_artwork_filetype` Setting to accept `webp_lossy` and `webp_lossless` while the old attribute `webp` will be treated as `webp_lossy`.
diff --git a/VERSION b/VERSION
index ee2103b2..9ac22ce5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.0.1-develop2
+2.0.1-develop3
diff --git a/docs/config/operations.md b/docs/config/operations.md
index 52a8470d..c5917022 100644
--- a/docs/config/operations.md
+++ b/docs/config/operations.md
@@ -302,6 +302,45 @@ You can create individual blocks of operations by using a list under `operations
- 1900-01-01
```
+###### Mass Added At Update
+
+??? blank "`mass_added_at_update` - Updates the added at date of every item in the library."
+
+
Updates every item's added at date in the library to the chosen site's date.
+
+
+
+ **Attribute:** `mass_added_at_update`
+
+ **Accepted Values:** Source or List of sources to use in that order
+
+
+ `tmdb` | Use TMDb Release Date |
+ `tvdb` | Use TVDb Release Date |
+ `omdb` | Use IMDb Release Date through OMDb |
+ `mdb` | Use MDBList Release Date |
+ `mdb_digital` | Use MDBList Digital Release Date |
+ `anidb` | Use AniDB Release Date |
+ `mal` | Use MyAnimeList Release Date |
+ `lock` | Lock Added At Field |
+ `unlock` | Unlock Added At Field |
+ `remove` | Remove Added At and Lock Field |
+ `reset` | Remove Added At and Unlock Field |
+ Any String in the Format: YYYY-MM-DD for Added At (2022-05-28 ) |
+
+
+ ???+ example "Example"
+
+ ```yaml
+ libraries:
+ TV Shows:
+ operations:
+ mass_added_at_update:
+ - mdb_digital
+ - mdb
+ - 1900-01-01
+ ```
+
###### Mass Rating Update
??? blank "`mass_***_rating_update` - Updates the audience/critic/user rating of every item in the library."
diff --git a/json-schema/config-schema.json b/json-schema/config-schema.json
index af900945..a86b9b7e 100644
--- a/json-schema/config-schema.json
+++ b/json-schema/config-schema.json
@@ -1429,6 +1429,10 @@
"type": "string",
"enum": ["tmdb","tvdb","omdb","mdb","anidb","mal","lock","unlock","remove","reset"]
},
+ "mass_added_at_update": {
+ "type": "string",
+ "enum": ["tmdb","tvdb","omdb","mdb","anidb","mal","lock","unlock","remove","reset"]
+ },
"mass_audience_rating_update": {
"anyOf": [
{
diff --git a/modules/config.py b/modules/config.py
index 7d464110..17122730 100644
--- a/modules/config.py
+++ b/modules/config.py
@@ -135,8 +135,8 @@ library_operations = {
"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_original_title_update": mass_original_title_options, "mass_imdb_parental_labels": imdb_label_options,
+ "mass_originally_available_update": mass_available_options, "mass_added_at_update": mass_available_options,
"mass_collection_mode": "mass_collection_mode", "mass_poster_update": "dict", "mass_background_update": "dict",
"metadata_backup": "dict", "delete_collections": "dict", "genre_mapper": "dict", "content_rating_mapper": "dict",
}
@@ -917,7 +917,7 @@ class ConfigFile:
final_list.append(str(list_attr))
elif op == "mass_genre_update":
final_list.append(list_attr if isinstance(list_attr, list) else [list_attr])
- elif op == "mass_originally_available_update":
+ elif op in ["mass_originally_available_update", "mass_added_at_update"]:
final_list.append(util.validate_date(list_attr))
elif op.endswith("rating_update"):
final_list.append(util.check_int(list_attr, datatype="float", minimum=0, maximum=10, throw=True))
diff --git a/modules/library.py b/modules/library.py
index 1030849d..a11818fc 100644
--- a/modules/library.py
+++ b/modules/library.py
@@ -97,6 +97,7 @@ class Library(ABC):
self.mass_content_rating_update = params["mass_content_rating_update"]
self.mass_original_title_update = params["mass_original_title_update"]
self.mass_originally_available_update = params["mass_originally_available_update"]
+ self.mass_added_at_update = params["mass_added_at_update"]
self.mass_imdb_parental_labels = params["mass_imdb_parental_labels"]
self.mass_poster_update = params["mass_poster_update"]
self.mass_background_update = params["mass_background_update"]
@@ -124,7 +125,7 @@ class Library(ABC):
self.items_library_operation = True if self.assets_for_all or self.mass_genre_update or self.remove_title_parentheses \
or self.mass_audience_rating_update or self.mass_critic_rating_update or self.mass_user_rating_update \
or self.mass_episode_audience_rating_update or self.mass_episode_critic_rating_update or self.mass_episode_user_rating_update \
- 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_added_at_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_studio_update\
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_collections or self.mass_collection_mode \
diff --git a/modules/operations.py b/modules/operations.py
index 978bfada..62ff3956 100644
--- a/modules/operations.py
+++ b/modules/operations.py
@@ -1,5 +1,5 @@
import os, re
-from datetime import datetime
+from datetime import datetime, timedelta, timezone
from modules import plex, util, anidb
from modules.util import Failed, LimitReached, YAML
from plexapi.exceptions import NotFound
@@ -10,14 +10,15 @@ logger = util.logger
meta_operations = [
"mass_audience_rating_update", "mass_user_rating_update", "mass_critic_rating_update",
"mass_episode_audience_rating_update", "mass_episode_user_rating_update", "mass_episode_critic_rating_update",
- "mass_genre_update", "mass_content_rating_update", "mass_originally_available_update", "mass_original_title_update",
- "mass_poster_update", "mass_background_update", "mass_studio_update"
+ "mass_genre_update", "mass_content_rating_update", "mass_originally_available_update", "mass_added_at_update",
+ "mass_original_title_update", "mass_poster_update", "mass_background_update", "mass_studio_update"
]
name_display = {
"audienceRating": "Audience Rating",
"rating": "Critic Rating",
"userRating": "User Rating",
"originallyAvailableAt": "Originally Available Date",
+ "addedAt": "Added At Date",
"contentRating": "Content Rating"
}
@@ -45,6 +46,7 @@ class Operations:
logger.debug(f"Mass Content Rating Update: {self.library.mass_content_rating_update}")
logger.debug(f"Mass Original Title Update: {self.library.mass_original_title_update}")
logger.debug(f"Mass Originally Available Update: {self.library.mass_originally_available_update}")
+ logger.debug(f"Mass Added At Update: {self.library.mass_added_at_update}")
logger.debug(f"Mass IMDb Parental Labels: {self.library.mass_imdb_parental_labels}")
logger.debug(f"Mass Poster Update: {self.library.mass_poster_update}")
logger.debug(f"Mass Background Update: {self.library.mass_background_update}")
@@ -88,7 +90,7 @@ class Operations:
genre_edits = {"add": {}, "remove": {}}
content_edits = {}
studio_edits = {}
- available_edits = {}
+ date_edits = {"originallyAvailableAt": {}, "addedAt": {}}
remove_edits = {}
reset_edits = {}
lock_edits = {}
@@ -664,65 +666,69 @@ class Operations:
except Failed:
continue
- if self.library.mass_originally_available_update:
- current_available = item.originallyAvailableAt
- if current_available:
- current_available = current_available.strftime("%Y-%m-%d")
- for option in self.library.mass_originally_available_update:
- if option in ["lock", "remove"]:
- if option == "remove" and current_available:
- if "originallyAvailableAt" not in remove_edits:
- remove_edits["originallyAvailableAt"] = []
- remove_edits["originallyAvailableAt"].append(item.ratingKey)
- item_edits += "\nRemove Originally Available Date (Batched)"
- elif "originallyAvailableAt" not in locked_fields:
- if "originallyAvailableAt" not in lock_edits:
- lock_edits["originallyAvailableAt"] = []
- lock_edits["originallyAvailableAt"].append(item.ratingKey)
- item_edits += "\nLock Originally Available Date (Batched)"
- break
- elif option in ["unlock", "reset"]:
- if option == "reset" and current_available:
- if "originallyAvailableAt" not in reset_edits:
- reset_edits["originallyAvailableAt"] = []
- reset_edits["originallyAvailableAt"].append(item.ratingKey)
- item_edits += "\nReset Originally Available Date (Batched)"
- elif "originallyAvailableAt" in locked_fields:
- if "originallyAvailableAt" not in unlock_edits:
- unlock_edits["originallyAvailableAt"] = []
- unlock_edits["originallyAvailableAt"].append(item.ratingKey)
- item_edits += "\nUnlock Originally Available Date (Batched)"
- break
- else:
- try:
- if option == "tmdb":
- new_available = tmdb_obj().release_date if self.library.is_movie else tmdb_obj().first_air_date # noqa
- elif option == "omdb":
- new_available = omdb_obj().released # noqa
- elif option == "tvdb":
- new_available = tvdb_obj().release_date # noqa
- elif option == "mdb":
- new_available = mdb_obj().released # noqa
- elif option == "mdb_digital":
- new_available = mdb_obj().released_digital # noqa
- elif option == "anidb":
- new_available = anidb_obj().released # noqa
- elif option == "mal":
- new_available = mal_obj().aired # noqa
- else:
- new_available = option
- if not new_available:
- logger.info(f"No {option} Originally Available Date Found")
- raise Failed
- new_available = new_available.strftime("%Y-%m-%d")
- if current_available != new_available:
- if new_available not in available_edits:
- available_edits[new_available] = []
- available_edits[new_available].append(item.ratingKey)
- item_edits += f"\nUpdate Originally Available Date (Batched) | {new_available}"
+ for attribute, item_attr in [
+ (self.library.mass_originally_available_update, "originallyAvailableAt"),
+ (self.library.mass_added_at_update, "addedAt")
+ ]:
+ if attribute:
+ current = getattr(item, item_attr)
+ if current:
+ current = current.strftime("%Y-%m-%d")
+ for option in attribute:
+ if option in ["lock", "remove"]:
+ if option == "remove" and current:
+ if item_attr not in remove_edits:
+ remove_edits[item_attr] = []
+ remove_edits[item_attr].append(item.ratingKey)
+ item_edits += f"\nRemove {name_display[item_attr]} (Batched)"
+ elif item_attr not in locked_fields:
+ if item_attr not in lock_edits:
+ lock_edits[item_attr] = []
+ lock_edits[item_attr].append(item.ratingKey)
+ item_edits += f"\nLock {name_display[item_attr]} (Batched)"
break
- except Failed:
- continue
+ elif option in ["unlock", "reset"]:
+ if option == "reset" and current:
+ if item_attr not in reset_edits:
+ reset_edits[item_attr] = []
+ reset_edits[item_attr].append(item.ratingKey)
+ item_edits += f"\nReset {name_display[item_attr]} (Batched)"
+ elif item_attr in locked_fields:
+ if item_attr not in unlock_edits:
+ unlock_edits[item_attr] = []
+ unlock_edits[item_attr].append(item.ratingKey)
+ item_edits += f"\nUnlock {name_display[item_attr]} (Batched)"
+ break
+ else:
+ try:
+ if option == "tmdb":
+ new_date = tmdb_obj().release_date if self.library.is_movie else tmdb_obj().first_air_date # noqa
+ elif option == "omdb":
+ new_date = omdb_obj().released # noqa
+ elif option == "tvdb":
+ new_date = tvdb_obj().release_date # noqa
+ elif option == "mdb":
+ new_date = mdb_obj().released # noqa
+ elif option == "mdb_digital":
+ new_date = mdb_obj().released_digital # noqa
+ elif option == "anidb":
+ new_date = anidb_obj().released # noqa
+ elif option == "mal":
+ new_date = mal_obj().aired # noqa
+ else:
+ new_date = option
+ if not new_date:
+ logger.info(f"No {option} {name_display[item_attr]} Found")
+ raise Failed
+ new_date = new_date.strftime("%Y-%m-%d")
+ if current != new_date:
+ if new_date not in date_edits[item_attr]:
+ date_edits[item_attr][new_date] = []
+ date_edits[item_attr][new_date].append(item.ratingKey)
+ item_edits += f"\nUpdate {name_display[item_attr]} (Batched) | {new_date}"
+ break
+ except Failed:
+ continue
if len(item_edits) > 0:
logger.info(f"Item Edits{item_edits}")
@@ -920,11 +926,27 @@ class Operations:
self.library.Plex.editStudio(new_studio)
self.library.Plex.saveMultiEdits()
- _size = len(available_edits.items())
- for i, (new_available, rating_keys) in enumerate(sorted(available_edits.items()), 1):
- logger.info(get_batch_info(i, _size, "originallyAvailableAt", len(rating_keys), display_value=new_available))
+ _size = len(date_edits["originallyAvailableAt"].items())
+ for i, (new_date, rating_keys) in enumerate(sorted(date_edits["originallyAvailableAt"].items()), 1):
+ logger.info(get_batch_info(i, _size, "originallyAvailableAt", len(rating_keys), display_value=new_date))
self.library.Plex.batchMultiEdits(self.library.load_list_from_cache(rating_keys))
- self.library.Plex.editOriginallyAvailable(new_available)
+ self.library.Plex.editOriginallyAvailable(new_date)
+ self.library.Plex.saveMultiEdits()
+
+ epoch = datetime(1970, 1, 1)
+ _size = len(date_edits["addedAt"].items())
+ for i, (new_date, rating_keys) in enumerate(sorted(date_edits["addedAt"].items()), 1):
+ logger.info(get_batch_info(i, _size, "addedAt", len(rating_keys), display_value=new_date))
+ self.library.Plex.batchMultiEdits(self.library.load_list_from_cache(rating_keys))
+ new_date = datetime.strptime(new_date, "%Y-%m-%d")
+ logger.trace(new_date)
+ try:
+ ts = int(round(new_date.timestamp()))
+ except (TypeError, OSError):
+ offset = int(datetime(2000, 1, 1, tzinfo=timezone.utc).timestamp() - datetime(2000, 1, 1).timestamp())
+ ts = int((new_date - epoch).total_seconds()) - offset
+ logger.trace(epoch + timedelta(seconds=ts))
+ self.library.Plex.editAddedAt(ts)
self.library.Plex.saveMultiEdits()
_size = len(remove_edits.items())