From ca84bbb19ddb4e27ea57183a6052269e175eaf5b Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Mon, 13 Jun 2022 14:49:37 -0600 Subject: [PATCH 1/4] Improve show/movie matching by using IDs only --- .vscode/launch.json | 16 +++++++ README.md | 2 +- main.py | 97 +++++++++++++++++++++----------------- src/functions.py | 38 +++++++++++++++ src/jellyfin.py | 66 ++++++++++++++++---------- src/plex.py | 112 ++++++++++++++++++++++++++------------------ 6 files changed, 217 insertions(+), 114 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ae8f15f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python", + "type": "python", + "request": "launch", + "program": "main.py", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 6b108b2..36556d5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Keep in sync all your users watched history between jellyfin and plex locally. T ## Installation -### Baremeta +### Baremetal - Setup virtualenv of your choice diff --git a/main.py b/main.py index fe32e40..b2146f5 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ from dotenv import load_dotenv from time import sleep -from src.functions import logger, str_to_bool, search_mapping +from src.functions import logger, str_to_bool, search_mapping, generate_library_guids_dict from src.plex import Plex from src.jellyfin import Jellyfin @@ -35,49 +35,59 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m elif library_other in watched_list_2[user_2]: library_2 = library_other else: - logger(f"User {library_1} and {library_other} not found in watched list 2", 1) + logger(f"library {library_1} and {library_other} not found in watched list 2", 1) continue - for item in watched_list_1[user_1][library_1]: - if item in modified_watched_list_1[user_1][library_1]: - # Movies - if isinstance(watched_list_1[user_1][library_1], list): - for watch_list_1_key, watch_list_1_value in item.items(): - for watch_list_2_item in watched_list_2[user_2][library_2]: - for watch_list_2_item_key, watch_list_2_item_value in watch_list_2_item.items(): - if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value: - if item in modified_watched_list_1[user_1][library_1]: - modified_watched_list_1[user_1][library_1].remove(item) - - # TV Shows - elif isinstance(watched_list_1[user_1][library_1], dict): - if item in watched_list_2[user_2][library_2]: - for season in watched_list_1[user_1][library_1][item]: - if season in watched_list_2[user_2][library_2][item]: - for episode in watched_list_1[user_1][library_1][item][season]: - for watch_list_1_episode_key, watch_list_1_episode_value in episode.items(): - for watch_list_2_episode in watched_list_2[user_2][library_2][item][season]: - for watch_list_2_episode_key, watch_list_2_episode_value in watch_list_2_episode.items(): - if watch_list_1_episode_key == watch_list_2_episode_key and watch_list_1_episode_value == watch_list_2_episode_value: - if episode in modified_watched_list_1[user_1][library_1][item][season]: - modified_watched_list_1[user_1][library_1][item][season].remove(episode) - - # If season is empty, remove season - if len(modified_watched_list_1[user_1][library_1][item][season]) == 0: - if season in modified_watched_list_1[user_1][library_1][item]: - del modified_watched_list_1[user_1][library_1][item][season] - - # If the show is empty, remove the show - if len(modified_watched_list_1[user_1][library_1][item]) == 0: - if item in modified_watched_list_1[user_1][library_1]: - del modified_watched_list_1[user_1][library_1][item] - - # If library is empty then remove it - if len(modified_watched_list_1[user_1][library_1]) == 0: - if library_1 in modified_watched_list_1[user_1]: - del modified_watched_list_1[user_1][library_1] + + # Movies + if isinstance(watched_list_1[user_1][library_1], list): + for item in watched_list_1[user_1][library_1]: + for watch_list_1_key, watch_list_1_value in item.items(): + for watch_list_2_item in watched_list_2[user_2][library_2]: + for watch_list_2_item_key, watch_list_2_item_value in watch_list_2_item.items(): + if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value: + if item in modified_watched_list_1[user_1][library_1]: + logger(f"Removing {item} from {library_1}", 1) + modified_watched_list_1[user_1][library_1].remove(item) + + + # TV Shows + elif isinstance(watched_list_1[user_1][library_1], dict): + # Generate full list of provider ids for episodes in watch_list_2 to easily compare if they exist in watch_list_1 + _, episode_watched_list_2_keys_dict, _ = generate_library_guids_dict(watched_list_2[user_2][library_2], 1) + + for show_key_1, show_item_1 in watched_list_1[user_1][library_1].items(): + show_key_dict = dict(show_key_1) + for season in watched_list_1[user_1][library_1][show_key_1]: + for episode in watched_list_1[user_1][library_1][show_key_1][season]: + for episode_key, episode_item in episode.items(): + # If episode_key and episode_item are in episode_watched_list_2_keys_dict exactly, then remove from watch_list_1 + if episode_key in episode_watched_list_2_keys_dict.keys(): + if episode_item in episode_watched_list_2_keys_dict[episode_key]: + if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]: + logger(f"Removing {show_key_dict['title']} {episode} from {library_1}", 1) + modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode) + + # Remove empty seasons + if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0: + if season in modified_watched_list_1[user_1][library_1][show_key_1]: + logger(f"Removing {season} from {library_1} because it is empty", 1) + del modified_watched_list_1[user_1][library_1][show_key_1][season] + + # If the show is empty, remove the show + if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0: + if show_key_1 in modified_watched_list_1[user_1][library_1]: + logger(f"Removing {show_key_dict['title']} from {library_1} because it is empty", 1) + del modified_watched_list_1[user_1][library_1][show_key_1] + + # If library is empty then remove it + if len(modified_watched_list_1[user_1][library_1]) == 0: + if library_1 in modified_watched_list_1[user_1]: + logger(f"Removing {library_1} from {user_1} because it is empty", 1) + del modified_watched_list_1[user_1][library_1] # If user is empty delete user if len(modified_watched_list_1[user_1]) == 0: + logger(f"Removing {user_1} from watched list 1 because it is empty", 1) del modified_watched_list_1[user_1] return modified_watched_list_1 @@ -253,15 +263,18 @@ def main(): plex_watched = plex.get_plex_watched(plex_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping) jellyfin_watched = jellyfin.get_jellyfin_watched(jellyfin_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping) - + # clone watched so it isnt modified in the cleanup function so all duplicates are actually removed plex_watched_filtered = copy.deepcopy(plex_watched) jellyfin_watched_filtered = copy.deepcopy(jellyfin_watched) + logger(f"Cleaning Plex Watched", 1) plex_watched = cleanup_watched(plex_watched_filtered, jellyfin_watched_filtered, user_mapping, library_mapping) - logger(f"plex_watched that needs to be synced to jellyfin:\n{plex_watched}", 1) + logger(f"Cleaning Jellyfin Watched", 1) jellyfin_watched = cleanup_watched(jellyfin_watched_filtered, plex_watched_filtered, user_mapping, library_mapping) + + logger(f"plex_watched that needs to be synced to jellyfin:\n{plex_watched}", 1) logger(f"jellyfin_watched that needs to be synced to plex:\n{jellyfin_watched}", 1) # Update watched status diff --git a/src/functions.py b/src/functions.py index b371212..d0363db 100644 --- a/src/functions.py +++ b/src/functions.py @@ -73,3 +73,41 @@ def check_skip_logic(library_title, library_type, blacklist_library, whitelist_l skip_reason = "is not whitelist_library" return skip_reason + + +def generate_library_guids_dict(user_list: dict, generate_output: int): + # if generate_output is 0 then only generate shows, if 1 then only generate episodes, if 2 then generate movies, if 3 then generate shows and episodes + show_output_dict = {} + episode_output_dict = {} + movies_output_dict = {} + + if generate_output in (0, 3): + show_output_keys = user_list.keys() + show_output_keys = ([ dict(x) for x in list(show_output_keys) ]) + for show_key in show_output_keys: + for provider_key, prvider_value in show_key.items(): + # Skip title + if provider_key.lower() == "title": + continue + if provider_key.lower() not in show_output_dict: + show_output_dict[provider_key.lower()] = [] + show_output_dict[provider_key.lower()].append(prvider_value.lower()) + + if generate_output in (1, 3): + for show in user_list: + for season in user_list[show]: + for episode in user_list[show][season]: + for episode_key, episode_value in episode.items(): + if episode_key.lower() not in episode_output_dict: + episode_output_dict[episode_key.lower()] = [] + episode_output_dict[episode_key.lower()].append(episode_value.lower()) + + if generate_output == 2: + for movie in user_list: + for movie_key, movie_value in movie.items(): + if movie_key.lower() not in movies_output_dict: + movies_output_dict[movie_key.lower()] = [] + movies_output_dict[movie_key.lower()].append(movie_value.lower()) + + return show_output_dict, episode_output_dict, movies_output_dict + diff --git a/src/jellyfin.py b/src/jellyfin.py index 5cd0a9c..a0b8bed 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -1,6 +1,6 @@ import requests, os from dotenv import load_dotenv -from src.functions import logger, search_mapping, str_to_bool, check_skip_logic +from src.functions import logger, search_mapping, str_to_bool, check_skip_logic, generate_library_guids_dict load_dotenv(override=True) @@ -99,11 +99,14 @@ def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blac # TV Shows if library_type == "Episode": - watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}", "get") + watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Fields=ItemCounts,ProviderIds", "get") watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"] for show in watched_shows: - seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts", "get") + show_guids = {k.lower(): v for k, v in show["ProviderIds"].items()} + show_guids["title"] = show["Name"] + show_guids = frozenset(show_guids.items()) + seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts,ProviderIds", "get") if len(seasons["Items"]) > 0: for season in seasons["Items"]: episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds", "get") @@ -115,14 +118,14 @@ def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blac users_watched[user_name] = {} if library_title not in users_watched[user_name]: users_watched[user_name][library_title] = {} - if show["Name"] not in users_watched[user_name][library_title]: - users_watched[user_name][library_title][show["Name"]] = {} - if season["Name"] not in users_watched[user_name][library_title][show["Name"]]: - users_watched[user_name][library_title][show["Name"]][season["Name"]] = [] + if show_guids not in users_watched[user_name][library_title]: + users_watched[user_name][library_title][show_guids] = {} + if season["Name"] not in users_watched[user_name][library_title][show_guids]: + users_watched[user_name][library_title][show_guids][season["Name"]] = [] # Lowercase episode["ProviderIds"] keys episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()} - users_watched[user_name][library_title][show["Name"]][season["Name"]].append(episode["ProviderIds"]) + users_watched[user_name][library_title][show_guids][season["Name"]].append(episode["ProviderIds"]) return users_watched @@ -182,13 +185,16 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, # Movies if library_type == "Movie": - jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get") + _, _, videos_movies_ids = generate_library_guids_dict(videos, 2) + + jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get") for jellyfin_video in jellyfin_search["Items"]: if str_to_bool(jellyfin_video["UserData"]["Played"]) == False: jellyfin_video_id = jellyfin_video["Id"] - for video in videos: - for key, value in jellyfin_video["ProviderIds"].items(): - if key.lower() in video.keys() and value.lower() == video[key.lower()].lower(): + + for movie_provider_source, movie_provider_id in jellyfin_video["ProviderIds"].items(): + if movie_provider_source.lower() in videos_movies_ids: + if movie_provider_id.lower() in videos_movies_ids[movie_provider_source.lower()]: msg = f"{jellyfin_video['Name']} as watched for {user} in {library} for Jellyfin" if not dryrun: logger(f"Marking {msg}", 0) @@ -199,25 +205,33 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, # TV Shows if library_type == "Episode": - jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&isPlayed=false", "get") - jellyfin_shows = [x for x in jellyfin_search["Items"] if x["Type"] == "Series"] + videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3) + + jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get") + jellyfin_shows = [x for x in jellyfin_search["Items"]] for jellyfin_show in jellyfin_shows: - if jellyfin_show["Name"] in videos.keys(): - jellyfin_show_id = jellyfin_show["Id"] - jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds", "get") - for jellyfin_episode in jellyfin_episodes["Items"]: - if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False: - jellyfin_episode_id = jellyfin_episode["Id"] - for show in videos: - for season in videos[show]: - for episode in videos[show][season]: - for key, value in jellyfin_episode["ProviderIds"].items(): - if key.lower() in episode.keys() and value.lower() == episode[key.lower()].lower(): - msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} {jellyfin_episode['Name']} as watched for {user} in {library} for Jellyfin" + show_found = False + for show_provider_source, show_provider_id in jellyfin_show["ProviderIds"].items(): + if show_provider_source.lower() in videos_shows_ids: + if show_provider_id.lower() in videos_shows_ids[show_provider_source.lower()]: + show_found = True + jellyfin_show_id = jellyfin_show["Id"] + jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds", "get") + for jellyfin_episode in jellyfin_episodes["Items"]: + if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False: + jellyfin_episode_id = jellyfin_episode["Id"] + + for episode_provider_source, episode_provider_id in jellyfin_episode["ProviderIds"].items(): + if episode_provider_source.lower() in videos_episode_ids: + if episode_provider_id.lower() in videos_episode_ids[episode_provider_source.lower()]: + msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['IndexNumber']} {jellyfin_episode['Name']} as watched for {user} in {library} for Jellyfin" if not dryrun: logger(f"Marked {msg}", 0) self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post") else: logger(f"Dryrun {msg}", 0) break + + if show_found: + break diff --git a/src/plex.py b/src/plex.py index e8b0350..b59a086 100644 --- a/src/plex.py +++ b/src/plex.py @@ -1,7 +1,7 @@ -import re, os +import re, os, time from dotenv import load_dotenv -from src.functions import logger, search_mapping, check_skip_logic +from src.functions import logger, search_mapping, check_skip_logic, generate_library_guids_dict from plexapi.server import PlexServer from plexapi.myplex import MyPlexAccount @@ -78,26 +78,35 @@ def get_plex_user_watched(self, user, library): watched = {} library_videos = user_plex.library.section(library.title) for show in library_videos.search(unmatched=False, unwatched=False): + show_guids = {} + for show_guid in show.guids: + show_guids["title"] = show.title + # Extract after :// from guid.id + show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower() + show_guid_id = re.search(r'://(.*)', show_guid.id).group(1) + show_guids[show_guid_source] = show_guid_id + show_guids = frozenset(show_guids.items()) + for season in show.seasons(): - guids = [] + episode_guids = [] for episode in season.episodes(): if episode.viewCount > 0: - guids_temp = {} + episode_guids_temp = {} for guid in episode.guids: # Extract after :// from guid.id guid_source = re.search(r'(.*)://', guid.id).group(1).lower() guid_id = re.search(r'://(.*)', guid.id).group(1) - guids_temp[guid_source] = guid_id + episode_guids_temp[guid_source] = guid_id - guids.append(guids_temp) + episode_guids.append(episode_guids_temp) - if guids: + if episode_guids: # append show, season, episode - if show.title not in watched: - watched[show.title] = {} - if season.title not in watched[show.title]: - watched[show.title][season.title] = {} - watched[show.title][season.title] = guids + if show_guids not in watched: + watched[show_guids] = {} + if season.title not in watched[show_guids]: + watched[show_guids][season.title] = {} + watched[show_guids][season.title] = episode_guids return watched @@ -177,40 +186,53 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, library_videos = user_plex.library.section(library) if library_videos.type == "movie": + _, _, videos_movies_ids = generate_library_guids_dict(videos, 2) for movies_search in library_videos.search(unmatched=False, unwatched=True): - for guid in movies_search.guids: - guid_source = re.search(r'(.*)://', guid.id).group(1).lower() - guid_id = re.search(r'://(.*)', guid.id).group(1) - for video in videos: - for video_keys, video_id in video.items(): - if video_keys == guid_source and video_id == guid_id: - if movies_search.viewCount == 0: - msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex" - if not dryrun: - logger(f"Marked {msg}", 0) - movies_search.markWatched() - else: - logger(f"Dryrun {msg}", 0) - break + for movie_guid in movies_search.guids: + movie_guid_source = re.search(r'(.*)://', movie_guid.id).group(1).lower() + movie_guid_id = re.search(r'://(.*)', movie_guid.id).group(1) + # If movie provider source and movie provider id are in videos_movie_ids exactly, then the movie is in the list + if movie_guid_source in videos_movies_ids.keys(): + if movie_guid_id in videos_movies_ids[movie_guid_source]: + if movies_search.viewCount == 0: + msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex" + if not dryrun: + logger(f"Marked {msg}", 0) + movies_search.markWatched() + else: + logger(f"Dryrun {msg}", 0) + break + elif library_videos.type == "show": + videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3) + for show_search in library_videos.search(unmatched=False, unwatched=True): - if show_search.title in videos: - for season_search in show_search.seasons(): - for episode_search in season_search.episodes(): - for guid in episode_search.guids: - guid_source = re.search(r'(.*)://', guid.id).group(1).lower() - guid_id = re.search(r'://(.*)', guid.id).group(1) - for show in videos: - for season in videos[show]: - for episode in videos[show][season]: - for episode_keys, episode_id in episode.items(): - if episode_keys == guid_source and episode_id == guid_id: - if episode_search.viewCount == 0: - msg = f"{show_search.title} {season_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex" - if not dryrun: - logger(f"Marked {msg}", 0) - episode_search.markWatched() - else: - logger(f"Dryrun {msg}", 0) - break + show_found = False + for show_guid in show_search.guids: + show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower() + show_guid_id = re.search(r'://(.*)', show_guid.id).group(1) + + # If show provider source and show provider id are in videos_shows_ids exactly, then the show is in the list + if show_guid_source in videos_shows_ids.keys(): + if show_guid_id in videos_shows_ids[show_guid_source]: + show_found = True + for episode_search in show_search.episodes(): + for episode_guid in episode_search.guids: + episode_guid_source = re.search(r'(.*)://', episode_guid.id).group(1).lower() + episode_guid_id = re.search(r'://(.*)', episode_guid.id).group(1) + + # If episode provider source and episode provider id are in videos_episode_ids exactly, then the episode is in the list + if episode_guid_source in videos_episode_ids.keys(): + if episode_guid_id in videos_episode_ids[episode_guid_source]: + if episode_search.viewCount == 0: + msg = f"{show_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex" + if not dryrun: + logger(f"Marked {msg}", 0) + episode_search.markWatched() + else: + logger(f"Dryrun {msg}", 0) + break + + if show_found: + break From 4657097f6dbbb56b64b1ab0fb079f5741182a0cc Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Mon, 13 Jun 2022 16:46:29 -0600 Subject: [PATCH 2/4] Cleanup --- main.py | 18 +++++++++--------- src/functions.py | 4 ++-- src/jellyfin.py | 10 +++++----- src/plex.py | 12 ++++++------ 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index b2146f5..4a54255 100644 --- a/main.py +++ b/main.py @@ -37,7 +37,7 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m else: logger(f"library {library_1} and {library_other} not found in watched list 2", 1) continue - + # Movies if isinstance(watched_list_1[user_1][library_1], list): for item in watched_list_1[user_1][library_1]: @@ -49,13 +49,13 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m logger(f"Removing {item} from {library_1}", 1) modified_watched_list_1[user_1][library_1].remove(item) - + # TV Shows elif isinstance(watched_list_1[user_1][library_1], dict): - # Generate full list of provider ids for episodes in watch_list_2 to easily compare if they exist in watch_list_1 + # Generate full list of provider ids for episodes in watch_list_2 to easily compare if they exist in watch_list_1 _, episode_watched_list_2_keys_dict, _ = generate_library_guids_dict(watched_list_2[user_2][library_2], 1) - - for show_key_1, show_item_1 in watched_list_1[user_1][library_1].items(): + + for show_key_1 in watched_list_1[user_1][library_1].keys(): show_key_dict = dict(show_key_1) for season in watched_list_1[user_1][library_1][show_key_1]: for episode in watched_list_1[user_1][library_1][show_key_1][season]: @@ -66,7 +66,7 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]: logger(f"Removing {show_key_dict['title']} {episode} from {library_1}", 1) modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode) - + # Remove empty seasons if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0: if season in modified_watched_list_1[user_1][library_1][show_key_1]: @@ -263,15 +263,15 @@ def main(): plex_watched = plex.get_plex_watched(plex_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping) jellyfin_watched = jellyfin.get_jellyfin_watched(jellyfin_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping) - + # clone watched so it isnt modified in the cleanup function so all duplicates are actually removed plex_watched_filtered = copy.deepcopy(plex_watched) jellyfin_watched_filtered = copy.deepcopy(jellyfin_watched) - logger(f"Cleaning Plex Watched", 1) + logger("Cleaning Plex Watched", 1) plex_watched = cleanup_watched(plex_watched_filtered, jellyfin_watched_filtered, user_mapping, library_mapping) - logger(f"Cleaning Jellyfin Watched", 1) + logger("Cleaning Jellyfin Watched", 1) jellyfin_watched = cleanup_watched(jellyfin_watched_filtered, plex_watched_filtered, user_mapping, library_mapping) logger(f"plex_watched that needs to be synced to jellyfin:\n{plex_watched}", 1) diff --git a/src/functions.py b/src/functions.py index d0363db..ae6a4bd 100644 --- a/src/functions.py +++ b/src/functions.py @@ -92,7 +92,7 @@ def generate_library_guids_dict(user_list: dict, generate_output: int): if provider_key.lower() not in show_output_dict: show_output_dict[provider_key.lower()] = [] show_output_dict[provider_key.lower()].append(prvider_value.lower()) - + if generate_output in (1, 3): for show in user_list: for season in user_list[show]: @@ -101,7 +101,7 @@ def generate_library_guids_dict(user_list: dict, generate_output: int): if episode_key.lower() not in episode_output_dict: episode_output_dict[episode_key.lower()] = [] episode_output_dict[episode_key.lower()].append(episode_value.lower()) - + if generate_output == 2: for movie in user_list: for movie_key, movie_value in movie.items(): diff --git a/src/jellyfin.py b/src/jellyfin.py index a0b8bed..ec4714c 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -186,12 +186,12 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, # Movies if library_type == "Movie": _, _, videos_movies_ids = generate_library_guids_dict(videos, 2) - + jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get") for jellyfin_video in jellyfin_search["Items"]: if str_to_bool(jellyfin_video["UserData"]["Played"]) == False: jellyfin_video_id = jellyfin_video["Id"] - + for movie_provider_source, movie_provider_id in jellyfin_video["ProviderIds"].items(): if movie_provider_source.lower() in videos_movies_ids: if movie_provider_id.lower() in videos_movies_ids[movie_provider_source.lower()]: @@ -206,7 +206,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, # TV Shows if library_type == "Episode": videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3) - + jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get") jellyfin_shows = [x for x in jellyfin_search["Items"]] @@ -221,7 +221,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, for jellyfin_episode in jellyfin_episodes["Items"]: if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False: jellyfin_episode_id = jellyfin_episode["Id"] - + for episode_provider_source, episode_provider_id in jellyfin_episode["ProviderIds"].items(): if episode_provider_source.lower() in videos_episode_ids: if episode_provider_id.lower() in videos_episode_ids[episode_provider_source.lower()]: @@ -232,6 +232,6 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, else: logger(f"Dryrun {msg}", 0) break - + if show_found: break diff --git a/src/plex.py b/src/plex.py index b59a086..5207214 100644 --- a/src/plex.py +++ b/src/plex.py @@ -1,4 +1,4 @@ -import re, os, time +import re, os from dotenv import load_dotenv from src.functions import logger, search_mapping, check_skip_logic, generate_library_guids_dict @@ -86,7 +86,7 @@ def get_plex_user_watched(self, user, library): show_guid_id = re.search(r'://(.*)', show_guid.id).group(1) show_guids[show_guid_source] = show_guid_id show_guids = frozenset(show_guids.items()) - + for season in show.seasons(): episode_guids = [] for episode in season.episodes(): @@ -202,7 +202,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, else: logger(f"Dryrun {msg}", 0) break - + elif library_videos.type == "show": videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3) @@ -221,7 +221,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, for episode_guid in episode_search.guids: episode_guid_source = re.search(r'(.*)://', episode_guid.id).group(1).lower() episode_guid_id = re.search(r'://(.*)', episode_guid.id).group(1) - + # If episode provider source and episode provider id are in videos_episode_ids exactly, then the episode is in the list if episode_guid_source in videos_episode_ids.keys(): if episode_guid_id in videos_episode_ids[episode_guid_source]: @@ -233,6 +233,6 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, else: logger(f"Dryrun {msg}", 0) break - + if show_found: - break + break From c18f0a2582d3f77042ed6162acb61f22a73e4ebc Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Mon, 13 Jun 2022 18:16:55 -0600 Subject: [PATCH 3/4] Add debug_level option --- .env.sample | 2 ++ main.py | 6 +++--- src/functions.py | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index c2e54fc..6464b80 100644 --- a/.env.sample +++ b/.env.sample @@ -2,6 +2,8 @@ DRYRUN = "True" ## Additional logging information DEBUG = "True" +## Debugging level, INFO is default, DEBUG is more verbose +DEBUG_LEVEL = "INFO" ## How often to run the script in seconds SLEEP_DURATION = "3600" ## Log file where all output will be written to diff --git a/main.py b/main.py index 4a54255..4b02093 100644 --- a/main.py +++ b/main.py @@ -46,7 +46,7 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m for watch_list_2_item_key, watch_list_2_item_value in watch_list_2_item.items(): if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value: if item in modified_watched_list_1[user_1][library_1]: - logger(f"Removing {item} from {library_1}", 1) + logger(f"Removing {item} from {library_1}", 3) modified_watched_list_1[user_1][library_1].remove(item) @@ -64,13 +64,13 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m if episode_key in episode_watched_list_2_keys_dict.keys(): if episode_item in episode_watched_list_2_keys_dict[episode_key]: if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]: - logger(f"Removing {show_key_dict['title']} {episode} from {library_1}", 1) + logger(f"Removing {show_key_dict['title']} {episode} from {library_1}", 3) modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode) # Remove empty seasons if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0: if season in modified_watched_list_1[user_1][library_1][show_key_1]: - logger(f"Removing {season} from {library_1} because it is empty", 1) + logger(f"Removing {season} from {library_1} because it is empty", 3) del modified_watched_list_1[user_1][library_1][show_key_1][season] # If the show is empty, remove the show diff --git a/src/functions.py b/src/functions.py index ae6a4bd..e246ead 100644 --- a/src/functions.py +++ b/src/functions.py @@ -6,14 +6,17 @@ def logger(message, log_type=0): debug = str_to_bool(os.getenv("DEBUG", "True")) + debug_level = os.getenv("DEBUG_LEVEL", "INFO") output = str(message) if log_type == 0: pass - elif log_type == 1 and debug: + elif log_type == 1 and (debug or debug_level == "INFO"): output = f"[INFO]: {output}" elif log_type == 2: output = f"[ERROR]: {output}" + elif log_type == 3 and (debug and debug_level == "DEBUG"): + output = f"[DEBUG]: {output}" else: output = None From 7ef2986bdeb8172f236c02661ab43a1a1d285442 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Mon, 13 Jun 2022 18:41:43 -0600 Subject: [PATCH 4/4] Remove unused variable --- src/jellyfin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jellyfin.py b/src/jellyfin.py index ec4714c..44332c6 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -144,7 +144,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, user = user_other user_id = None - for key, value in self.users.items(): + for key in self.users.keys(): if user.lower() == key.lower(): user_id = self.users[key] break