diff --git a/VERSION b/VERSION index 8cbd7d94..b4b27dd1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.16.5-develop71 +1.16.5-develop72 diff --git a/docs/config/settings.md b/docs/config/settings.md index ffac1c7f..a32b6485 100644 --- a/docs/config/settings.md +++ b/docs/config/settings.md @@ -51,6 +51,7 @@ The available setting attributes which can be set at each level are outlined bel | [`ignore_imdb_ids`](#ignore-imdb-ids) | ✅ | ✅ | ✅ | | [`item_refresh_delay`](#item-refresh-delay) | ✅ | ✅ | ✅ | | [`playlist_sync_to_users`](#playlist-sync-to-users) | ✅ | ❌ | ✅ | +| [`playlist_report`](#playlist-report) | ✅ | ❌ | ❌ | | [`custom_repo`](#custom-repo) | ✅ | ❌ | ❌ | | [`verify_ssl`](#verify-ssl) | ✅ | ❌ | ❌ | | [`check_nightly`](#check-nightly) | ✅ | ❌ | ❌ | @@ -513,9 +514,24 @@ Set the default playlist `sync_to_users`. To Sync a playlist to only yourself le +# Playlist Report +Set `playlist_report` to true to print out a playlist report at the end of the log. + + + + + + + + + +
Default Valuefalse
Allowed Valuestrue or false +
+ ## Custom Repo Specify where the `repo` attribute's base is when defining `metadata_paths` and `playlist_files`. * Ensure you are using the raw GitHub link (i.e. https://github.com/meisnate12/Plex-Meta-Manager-Configs/tree/master/meisnate12 ) + diff --git a/docs/metadata/playlist.md b/docs/metadata/playlist.md index a7f45083..732be9b2 100644 --- a/docs/metadata/playlist.md +++ b/docs/metadata/playlist.md @@ -34,9 +34,15 @@ playlists: # ... builder, details, and filters for this playlist ``` -Playlists require the `libraries` attribute, which instructs the operation to look in the specified libraries. This allows media to be combined from multiple libraries into one playlist. The mappings that you define in the `libraries` attribute must match the library names in your [Configuration File](../config/configuration). +### Special Playlist Attributes -The playlist can also use the `sync_to_users` attributes to control who has visibility of the playlist. This will override the global [`playlist_sync_to_users` Setting](../config/settings.md#playlist-sync-to-users). `sync_to_users` can be set to `all` to sync to all users who have access to the Plex Media Server, or a list/comma-separated string of users. The Plex Media Server owner will always have visibility of the Playlists, so does not need to be defined within the attribute. Leaving `sync_to_users` empty will make the playlist visible to the Plex Media Server owner only. +| Attribute | Description | Required | +|:------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:| +| `libraries` | Determine which libraries the playlist will be built from.
**Options:** Comma-separated string or list of library mapping names defined in the `libraries` attribute. | ✅ | +| `sync_to_users` | Determine which Users have the playlist synced.
This will override the global [`playlist_sync_to_users` Setting](../config/settings.md#playlist-sync-to-users).
**Options:** Comma-separated string or list of users, `all` for every user who has server access, or leave blank for just the server owner. | ❌ | +| `delete_playlist` | Will delete this playlist for the users defined by sync_to_users.
**Options:** `true` or `false` | ❌ | + +* Any defined playlist will be always be visible by The Plex Media Server owner, so it doesn't need to be defined within `sync_to_users`. There are three types of attributes that can be utilized within a playlist: diff --git a/modules/builder.py b/modules/builder.py index 8ccc1716..dc7edcb0 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -312,7 +312,41 @@ class CollectionBuilder: else: logger.error(f"{self.Type} Error: suppress_overlays attribute is blank") + self.sync_to_users = None + self.valid_users = [] if self.playlist: + self.sync_to_users = config.general["playlist_sync_to_users"] + if "sync_to_users" in methods or "sync_to_user" in methods: + s_attr = f"sync_to_user{'s' if 'sync_to_users' in methods else ''}" + logger.debug("") + logger.debug(f"Validating Method: {s_attr}") + logger.debug(f"Value: {self.data[methods[s_attr]]}") + if self.data[methods[s_attr]]: + self.sync_to_users = self.data[methods[s_attr]] + else: + logger.warning(f"Playlist Error: sync_to_users attribute is blank defaulting to playlist_sync_to_users: {self.sync_to_users}") + else: + logger.warning(f"Playlist Error: sync_to_users attribute not found defaulting to playlist_sync_to_users: {self.sync_to_users}") + + plex_users = self.library.users + if self.sync_to_users: + if str(self.sync_to_users) == "all": + self.valid_users = plex_users + else: + for user in util.get_list(self.sync_to_users): + if user in plex_users: + self.valid_users.append(user) + else: + raise Failed(f"Playlist Error: User: {user} not found in plex\nOptions: {plex_users}") + + if "delete_playlist" in methods: + logger.debug("") + logger.debug("Validating Method: delete_not_scheduled") + logger.debug(f"Value: {data[methods['delete_not_scheduled']]}") + if util.parse(self.Type, "delete_not_scheduled", self.data, datatype="bool", methods=methods, default=False): + self.obj = self.library.get_playlist(self.name) + logger.info(self.delete()) + if "libraries" in methods: logger.debug("") logger.debug("Validating Method: libraries") @@ -389,8 +423,6 @@ class CollectionBuilder: self.exists = False self.created = False self.deleted = False - self.sync_to_users = None - self.valid_users = [] if self.playlist: server_check = None @@ -401,30 +433,6 @@ class CollectionBuilder: else: server_check = pl_library.PlexServer.machineIdentifier - self.sync_to_users = config.general["playlist_sync_to_users"] - if "sync_to_users" in methods or "sync_to_user" in methods: - s_attr = f"sync_to_user{'s' if 'sync_to_users' in methods else ''}" - logger.debug("") - logger.debug(f"Validating Method: {s_attr}") - logger.debug(f"Value: {self.data[methods[s_attr]]}") - if self.data[methods[s_attr]]: - self.sync_to_users = self.data[methods[s_attr]] - else: - logger.warning(f"Playlist Error: sync_to_users attribute is blank defaulting to playlist_sync_to_users: {self.sync_to_users}") - else: - logger.warning(f"Playlist Error: sync_to_users attribute not found defaulting to playlist_sync_to_users: {self.sync_to_users}") - - plex_users = self.library.users - if self.sync_to_users: - if str(self.sync_to_users) == "all": - self.valid_users = plex_users - else: - for user in util.get_list(self.sync_to_users): - if user in plex_users: - self.valid_users.append(user) - else: - raise Failed(f"Playlist Error: User: {user} not found in plex\nOptions: {plex_users}") - if "delete_not_scheduled" in methods and not self.overlay: logger.debug("") logger.debug("Validating Method: delete_not_scheduled") @@ -2679,11 +2687,6 @@ class CollectionBuilder: self.library.moveItem(self.obj, item, previous) previous = item - def delete_user_playlist(self, title, user): - user_server = self.library.PlexServer.switchUser(user) - user_playlist = user_server.playlist(title) - user_playlist.delete() - def delete(self): output = "" if self.obj: @@ -2693,7 +2696,7 @@ class CollectionBuilder: if self.valid_users: for user in self.valid_users: try: - self.delete_user_playlist(self.obj.title, user) + self.library.delete_user_playlist(self.obj.title, user) output += f"\nPlaylist {self.obj.title} deleted on User {user}" except NotFound: output += f"\nPlaylist {self.obj.title} not found on User {user}" @@ -2706,7 +2709,7 @@ class CollectionBuilder: logger.info("") for user in self.valid_users: try: - self.delete_user_playlist(self.obj.title, user) + self.library.delete_user_playlist(self.obj.title, user) except NotFound: pass self.obj.copyToUser(user) diff --git a/modules/config.py b/modules/config.py index 61ca8b92..ae8f45e6 100644 --- a/modules/config.py +++ b/modules/config.py @@ -318,6 +318,7 @@ class ConfigFile: "ignore_ids": check_for_attribute(self.data, "ignore_ids", parent="settings", var_type="int_list", default_is_none=True), "ignore_imdb_ids": check_for_attribute(self.data, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True), "playlist_sync_to_users": check_for_attribute(self.data, "playlist_sync_to_users", parent="settings", default="all", default_is_none=True), + "playlist_report": check_for_attribute(self.data, "playlist_report", parent="settings", var_type="bool", default=True), "verify_ssl": check_for_attribute(self.data, "verify_ssl", parent="settings", var_type="bool", default=True), "custom_repo": check_for_attribute(self.data, "custom_repo", parent="settings", default_is_none=True), "check_nightly": check_for_attribute(self.data, "check_nightly", parent="settings", var_type="bool", default=False), diff --git a/modules/plex.py b/modules/plex.py index f01e51f7..5fdec8b0 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -450,10 +450,9 @@ class Plex(Library): @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex) def exact_search(self, title, libtype=None, year=None): + terms = {"title=": title} if year: - terms = {"title=": title, "year": year} - else: - terms = {"title=": title} + terms["year"] = year return self.Plex.search(libtype=libtype, **terms) @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_plex) @@ -622,6 +621,21 @@ class Plex(Library): self._users = users return self._users + def delete_user_playlist(self, title, user): + self.PlexServer.switchUser(user).playlist(title).delete() + + def playlist_report(self): + playlists = {} + def scan_user(server, username): + for playlist in server.playlists(): + if playlist.title not in playlists: + playlists[playlist.title] = [] + playlists[playlist.title].append(username) + scan_user(self.PlexServer, self.PlexServer.myPlexAccount().title) + for user in self.users: + scan_user(self.PlexServer.switchUser(user), user) + return playlists + def manage_recommendations(self): return [(r.title, r._data.attrib.get('identifier'), r._data.attrib.get('promotedToRecommended'), r._data.attrib.get('promotedToOwnHome'), r._data.attrib.get('promotedToSharedHome')) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 7cae07b4..0b51b77f 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -333,9 +333,28 @@ def update_libraries(config): playlist_status = {} playlist_stats = {} - if config.playlist_files: + if config.playlist_files or config.general["playlist_report"]: logger.add_playlists_handler() - playlist_status, playlist_stats = run_playlists(config) + if config.playlist_files: + playlist_status, playlist_stats = run_playlists(config) + if config.general["playlist_report"]: + ran = [] + for library in config.libraries: + if library.PlexServer.machineIdentifier in ran: + continue + ran.append(library.PlexServer.machineIdentifier) + logger.info("") + logger.separator(f"{library.PlexServer.friendlyName} Playlist Report") + logger.info("") + report = library.playlist_report() + max_length = 0 + for playlist_name in report: + if len(playlist_name) > max_length: + max_length = len(playlist_name) + logger.info(f"{'Playlist Title':<{max_length}} | Users") + logger.separator(f"{logger.separating_character * max_length}|", space=False, border=False, side_space=False, left=True) + for playlist_name, users in report.items(): + logger.info(f"{playlist_name:<{max_length}} | {'all' if len(users) == len(library.users) + 1 else ', '.join(users)}") logger.remove_playlists_handler() has_run_again = False @@ -411,13 +430,14 @@ def update_libraries(config): logger.info(error) logger.info("") + logger.info("") logger.separator("Summary") for library in config.libraries: logger.info("") logger.separator(f"{library.name} Summary", space=False, border=False) logger.info("") logger.info(f"{'Title':<27} | Run Time |") - logger.separator(f"{logger.separating_character * 27}|{logger.separating_character * 10}|", space=False, border=False, side_space=False, left=True) + logger.info(f"{logger.separating_character * 27} | {logger.separating_character * 8} |") for text, value in library_status[library.name].items(): logger.info(f"{text:<27} | {value:>8} |") logger.info("")
Default Value