From bc09c873e92c323dcaa31d78104a954b90a87f7d Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 27 Oct 2024 13:43:09 -0600 Subject: [PATCH 1/6] Simplify get watched process. Only get watched for syncing libraries Signed-off-by: Luis Garcia --- src/functions.py | 12 +++ src/jellyfin_emby.py | 207 +++++++++++++++++++------------------------ src/library.py | 44 +++++++++ src/main.py | 17 ++-- src/plex.py | 42 +++++---- 5 files changed, 176 insertions(+), 146 deletions(-) diff --git a/src/functions.py b/src/functions.py index 04173d8..efdf8bf 100644 --- a/src/functions.py +++ b/src/functions.py @@ -92,6 +92,18 @@ def search_mapping(dictionary: dict, key_value: str): else: return None +# Return list of objects that exist in both lists including mappings +def match_list(list1, list2, list_mapping=None): + output = [] + for element in list1: + if element in list2: + output.append(element) + elif list_mapping: + element_other = search_mapping(list_mapping, element) + if element_other in list2: + output.append(element) + + return output def future_thread_executor( args: list, threads: int = None, override_threads: bool = False diff --git a/src/jellyfin_emby.py b/src/jellyfin_emby.py index 84a55fd..b084c3d 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -223,13 +223,54 @@ def get_users(self): logger(f"{self.server_type}: Get users failed {e}", 2) raise Exception(e) + def get_libraries(self): + try: + libraries = {} + + # Theres no way to get all libraries so individually get list of libraries from all users + users = self.get_users() + + for _, user_id in users.items(): + user_libraries = self.query(f"/Users/{user_id}/Views", "get") + for library in user_libraries["Items"]: + library_id = library["Id"] + library_title = library["Name"] + + # Get library items to check the type + media_info = self.query( + f"/Users/{user_id}/Items" + + f"?ParentId={library_id}&Filters=IsPlayed&Recursive=True&excludeItemTypes=Folder&limit=100", + "get", + ) + + types = set( + [ + x["Type"] + for x in media_info["Items"] + if x["Type"] in ["Movie", "Series", "Episode"] + ] + ) + all_types = set([x["Type"] for x in media_info["Items"]]) + + if not types: + logger( + f"{self.server_type}: Skipping Library {library_title} found wanted types: {all_types}", + 1, + ) + else: + libraries[library_title] = str(types) + + return libraries + except Exception as e: + logger(f"{self.server_type}: Get libraries failed {e}", 2) + raise Exception(e) + def get_user_library_watched( self, user_name, user_id, library_type, library_id, library_title ): try: user_name = user_name.lower() user_watched = {} - user_watched[user_name] = {} logger( f"{self.server_type}: Generating watched for {user_name} in library {library_title}", @@ -238,7 +279,7 @@ def get_user_library_watched( # Movies if library_type == "Movie": - user_watched[user_name][library_title] = [] + user_watched[library_title] = [] watched = self.query( f"/Users/{user_id}/Items" + f"?ParentId={library_id}&Filters=IsPlayed&IncludeItemTypes=Movie&Recursive=True&Fields=ItemCounts,ProviderIds,MediaSources", @@ -274,7 +315,7 @@ def get_user_library_watched( movie_guids = get_guids(self.server_type, movie) # Append the movie dictionary to the list for the given user and library - user_watched[user_name][library_title].append(movie_guids) + user_watched[library_title].append(movie_guids) logger( f"{self.server_type}: Added {movie_guids} to {user_name} watched list", 3, @@ -283,7 +324,7 @@ def get_user_library_watched( # TV Shows if library_type in ["Series", "Episode"]: # Initialize an empty dictionary for the given user and library - user_watched[user_name][library_title] = {} + user_watched[library_title] = {} # Retrieve a list of watched TV shows watched_shows = self.query( @@ -352,10 +393,10 @@ def get_user_library_watched( if mark_episodes_list: # Add the show dictionary to the user's watched list - if show_guids not in user_watched[user_name][library_title]: - user_watched[user_name][library_title][show_guids] = [] + if show_guids not in user_watched[library_title]: + user_watched[library_title][show_guids] = [] - user_watched[user_name][library_title][ + user_watched[library_title][ show_guids ] = mark_episodes_list for episode in mark_episodes_list: @@ -368,9 +409,9 @@ def get_user_library_watched( f"{self.server_type}: Got watched for {user_name} in library {library_title}", 1, ) - if library_title in user_watched[user_name]: + if library_title in user_watched: logger( - f"{self.server_type}: {user_watched[user_name][library_title]}", 3 + f"{self.server_type}: {user_watched[library_title]}", 3 ) return user_watched @@ -383,130 +424,68 @@ def get_user_library_watched( logger(traceback.format_exc(), 2) return {} - def get_users_watched( + def get_watched( self, - user_name, - user_id, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, + users, + sync_libraries ): try: - # Get all libraries - user_name = user_name.lower() + users_watched = {} watched = [] - libraries = [] - - all_libraries = self.query(f"/Users/{user_id}/Views", "get") - for library in all_libraries["Items"]: - library_id = library["Id"] - library_title = library["Name"] - identifiers = { - "library_id": library_id, - "library_title": library_title, - } - libraries.append( - self.query( - f"/Users/{user_id}/Items" - + f"?ParentId={library_id}&Filters=IsPlayed&Recursive=True&excludeItemTypes=Folder&limit=100", - "get", - identifiers=identifiers, + for user_name, user_id in users.items(): + libraries = [] + + all_libraries = self.query(f"/Users/{user_id}/Views", "get") + for library in all_libraries["Items"]: + library_id = library["Id"] + library_title = library["Name"] + identifiers = { + "library_id": library_id, + "library_title": library_title, + } + libraries.append( + self.query( + f"/Users/{user_id}/Items" + + f"?ParentId={library_id}&Filters=IsPlayed&Recursive=True&excludeItemTypes=Folder&limit=100", + "get", + identifiers=identifiers, + ) ) - ) - - for library in libraries: - if len(library["Items"]) == 0: - continue - library_id = library["Identifiers"]["library_id"] - library_title = library["Identifiers"]["library_title"] - # Get all library types excluding "Folder" - types = set( - [ - x["Type"] - for x in library["Items"] - if x["Type"] in ["Movie", "Series", "Episode"] - ] - ) + for library in libraries: + if len(library["Items"]) == 0: + continue - skip_reason = check_skip_logic( - library_title, - types, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, - ) + library_id = library["Identifiers"]["library_id"] + library_title = library["Identifiers"]["library_title"] - if skip_reason: - logger( - f"{self.server_type}: Skipping library {library_title}: {skip_reason}", - 1, - ) - continue + if library_title not in sync_libraries: + continue - # If there are multiple types in library raise error - if types is None or len(types) < 1: - all_types = set([x["Type"] for x in library["Items"]]) - logger( - f"{self.server_type}: Skipping Library {library_title} found types: {types}, all types: {all_types}", - 1, + # Get all library types excluding "Folder" + types = set( + [ + x["Type"] + for x in library["Items"] + if x["Type"] in ["Movie", "Series", "Episode"] + ] ) - continue - for library_type in types: - # Get watched for user - watched.append( - self.get_user_library_watched( + for library_type in types: + # Get watched for user + watched = self.get_user_library_watched( user_name, user_id, library_type, library_id, library_title, ) - ) - - return watched - except Exception as e: - logger(f"{self.server_type}: Failed to get users watched, Error: {e}", 2) - raise Exception(e) - - def get_watched( - self, - users, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping=None, - ): - try: - users_watched = {} - watched = [] - - for user_name, user_id in users.items(): - watched.append( - self.get_users_watched( - user_name, - user_id, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, - ) - ) - for user_watched in watched: - user_watched_combine = combine_watched_dicts(user_watched) - for user, user_watched_temp in user_watched_combine.items(): - if user not in users_watched: - users_watched[user] = {} - users_watched[user].update(user_watched_temp) + + if user_name.lower() not in users_watched: + users_watched[user_name.lower()] = {} + users_watched[user_name.lower()].update(watched) return users_watched except Exception as e: diff --git a/src/library.py b/src/library.py index 3d5e458..6e8ee9c 100644 --- a/src/library.py +++ b/src/library.py @@ -1,5 +1,6 @@ from src.functions import ( logger, + match_list, search_mapping, ) @@ -128,6 +129,49 @@ def check_whitelist_logic( return skip_reason +def filter_libaries( + server_libraries, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping=None +): + filtered_libaries = [] + for library in server_libraries: + skip_reason = check_skip_logic( + library, + server_libraries[library], + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + library_mapping, + ) + + if skip_reason: + logger( + f"Skipping library {library}: {skip_reason}", 1 + ) + continue + + filtered_libaries.append(library) + + return filtered_libaries + + +def setup_libraries( + server_1, server_2, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping=None +): + server_1_libraries = server_1.get_libraries() + server_2_libraries = server_2.get_libraries() + logger(f"Server 1 libraries: {server_1_libraries}", 1) + logger(f"Server 2 libraries: {server_2_libraries}", 1) + + # Filter out all blacklist, whitelist libaries + filtered_server_1_libraries = filter_libaries(server_1_libraries, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping) + filtered_server_2_libraries = filter_libaries(server_2_libraries, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping) + + output_server_1_libaries = match_list(filtered_server_1_libraries, filtered_server_2_libraries, library_mapping) + output_server_2_libaries = match_list(filtered_server_2_libraries, filtered_server_1_libraries, library_mapping) + + return output_server_1_libaries, output_server_2_libaries + def show_title_dict(user_list: dict): try: diff --git a/src/main.py b/src/main.py index 5f84d53..d6f72a1 100644 --- a/src/main.py +++ b/src/main.py @@ -2,6 +2,7 @@ from dotenv import load_dotenv from time import sleep, perf_counter +from src.library import setup_libraries from src.functions import ( logger, str_to_bool, @@ -153,24 +154,20 @@ def main_loop(): server_1, server_2, blacklist_users, whitelist_users, user_mapping ) + server_1_libraries, server_2_libraries = setup_libraries( + server_1[1], server_2[1], blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping + ) + logger("Creating watched lists", 1) server_1_watched = server_1[1].get_watched( server_1_users, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, + server_1_libraries ) logger("Finished creating watched list server 1", 1) server_2_watched = server_2[1].get_watched( server_2_users, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, + server_2_libraries ) logger("Finished creating watched list server 2", 1) diff --git a/src/plex.py b/src/plex.py index 4f6ca32..0019874 100644 --- a/src/plex.py +++ b/src/plex.py @@ -466,14 +466,28 @@ def get_users(self): logger(f"Plex: Failed to get users, Error: {e}", 2) raise Exception(e) + def get_libraries(self): + try: + output = {} + + libraries = self.plex.library.sections() + + for library in libraries: + library_title = library.title + library_type = library.type + + output[library_title] = library_type + + return output + except Exception as e: + logger(f"Plex: Failed to get libraries, Error: {e}", 2) + raise Exception(e) + + def get_watched( self, users, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, + sync_libraries ): try: # Get all libraries @@ -500,23 +514,7 @@ def get_watched( libraries = user_plex.library.sections() for library in libraries: - library_title = library.title - library_type = library.type - - skip_reason = check_skip_logic( - library_title, - library_type, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, - ) - - if skip_reason: - logger( - f"Plex: Skipping library {library_title}: {skip_reason}", 1 - ) + if library.title not in sync_libraries: continue user_watched = get_user_library_watched(user, user_plex, library) From 30f31b2f3fd2cfcfb9adcbd930d6ba56f28d034d Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 27 Oct 2024 14:07:13 -0600 Subject: [PATCH 2/6] Remove unused combine_watched_dicts Signed-off-by: Luis Garcia --- src/jellyfin_emby.py | 3 -- src/watched.py | 27 ---------- test/test_watched.py | 115 +------------------------------------------ 3 files changed, 1 insertion(+), 144 deletions(-) diff --git a/src/jellyfin_emby.py b/src/jellyfin_emby.py index b084c3d..62977d7 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -17,9 +17,6 @@ check_skip_logic, generate_library_guids_dict, ) -from src.watched import ( - combine_watched_dicts, -) load_dotenv(override=True) diff --git a/src/watched.py b/src/watched.py index 78f0264..4f898e2 100644 --- a/src/watched.py +++ b/src/watched.py @@ -5,33 +5,6 @@ from src.library import generate_library_guids_dict -def combine_watched_dicts(dicts: list): - # Ensure that the input is a list of dictionaries - if not all(isinstance(d, dict) for d in dicts): - raise ValueError("Input must be a list of dictionaries") - - combined_dict = {} - - for single_dict in dicts: - for key, value in single_dict.items(): - if key not in combined_dict: - combined_dict[key] = {} - - for subkey, subvalue in value.items(): - if subkey in combined_dict[key]: - # If the subkey already exists in the combined dictionary, - # check if the values are different and raise an exception if they are - if combined_dict[key][subkey] != subvalue: - raise ValueError( - f"Conflicting values for subkey '{subkey}' under key '{key}'" - ) - else: - # If the subkey does not exist in the combined dictionary, add it - combined_dict[key][subkey] = subvalue - - return combined_dict - - def check_remove_entry(video, library, video_index, library_watched_list_2): if video_index is not None: if ( diff --git a/test/test_watched.py b/test/test_watched.py index ada7e77..10cb5f9 100644 --- a/test/test_watched.py +++ b/test/test_watched.py @@ -13,7 +13,7 @@ # the sys.path. sys.path.append(parent) -from src.watched import cleanup_watched, combine_watched_dicts +from src.watched import cleanup_watched tv_shows_watched_list_1 = { frozenset( @@ -541,116 +541,3 @@ def test_mapping_cleanup_watched(): assert return_watched_list_1 == expected_watched_list_1 assert return_watched_list_2 == expected_watched_list_2 - - -def test_combine_watched_dicts(): - input_watched = [ - { - "test3": { - "Anime Movies": [ - { - "title": "Ponyo", - "tmdb": "12429", - "imdb": "tt0876563", - "locations": ("Ponyo (2008) Bluray-1080p.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "title": "Spirited Away", - "tmdb": "129", - "imdb": "tt0245429", - "locations": ("Spirited Away (2001) Bluray-1080p.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "title": "Castle in the Sky", - "tmdb": "10515", - "imdb": "tt0092067", - "locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",), - "status": {"completed": True, "time": 0}, - }, - ] - } - }, - {"test3": {"Anime Shows": {}}}, - {"test3": {"Cartoon Shows": {}}}, - { - "test3": { - "Shows": { - frozenset( - { - ("tmdb", "64464"), - ("tvdb", "301824"), - ("tvrage", "45210"), - ("title", "11.22.63"), - ("locations", ("11.22.63",)), - ("imdb", "tt2879552"), - } - ): [ - { - "imdb": "tt4460418", - "title": "The Rabbit Hole", - "locations": ( - "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", - ), - "status": {"completed": True, "time": 0}, - } - ] - } - } - }, - {"test3": {"Subbed Anime": {}}}, - ] - expected = { - "test3": { - "Anime Movies": [ - { - "title": "Ponyo", - "tmdb": "12429", - "imdb": "tt0876563", - "locations": ("Ponyo (2008) Bluray-1080p.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "title": "Spirited Away", - "tmdb": "129", - "imdb": "tt0245429", - "locations": ("Spirited Away (2001) Bluray-1080p.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "title": "Castle in the Sky", - "tmdb": "10515", - "imdb": "tt0092067", - "locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",), - "status": {"completed": True, "time": 0}, - }, - ], - "Anime Shows": {}, - "Cartoon Shows": {}, - "Shows": { - frozenset( - { - ("tmdb", "64464"), - ("tvdb", "301824"), - ("tvrage", "45210"), - ("title", "11.22.63"), - ("locations", ("11.22.63",)), - ("imdb", "tt2879552"), - } - ): [ - { - "imdb": "tt4460418", - "title": "The Rabbit Hole", - "locations": ( - "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", - ), - "status": {"completed": True, "time": 0}, - } - ] - }, - "Subbed Anime": {}, - } - } - - assert combine_watched_dicts(input_watched) == expected From a5995d3999ac9cd735be6bddb72510be15143e15 Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 27 Oct 2024 16:08:41 -0600 Subject: [PATCH 3/6] CI: More verbose Signed-off-by: Luis Garcia --- test/ci_emby.env | 2 +- test/ci_guids.env | 2 +- test/ci_jellyfin.env | 2 +- test/ci_locations.env | 2 +- test/ci_plex.env | 2 +- test/ci_write.env | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/ci_emby.env b/test/ci_emby.env index cd91107..77eacee 100644 --- a/test/ci_emby.env +++ b/test/ci_emby.env @@ -7,7 +7,7 @@ DRYRUN = "True" DEBUG = "True" ## Debugging level, "info" is default, "debug" is more verbose -DEBUG_LEVEL = "info" +DEBUG_LEVEL = "debug" ## If set to true then the script will only run once and then exit RUN_ONLY_ONCE = "True" diff --git a/test/ci_guids.env b/test/ci_guids.env index 0a6a519..0aa1943 100644 --- a/test/ci_guids.env +++ b/test/ci_guids.env @@ -7,7 +7,7 @@ DRYRUN = "True" DEBUG = "True" ## Debugging level, "info" is default, "debug" is more verbose -DEBUG_LEVEL = "info" +DEBUG_LEVEL = "debug" ## If set to true then the script will only run once and then exit RUN_ONLY_ONCE = "True" diff --git a/test/ci_jellyfin.env b/test/ci_jellyfin.env index 06818b4..9320c06 100644 --- a/test/ci_jellyfin.env +++ b/test/ci_jellyfin.env @@ -7,7 +7,7 @@ DRYRUN = "True" DEBUG = "True" ## Debugging level, "info" is default, "debug" is more verbose -DEBUG_LEVEL = "info" +DEBUG_LEVEL = "debug" ## If set to true then the script will only run once and then exit RUN_ONLY_ONCE = "True" diff --git a/test/ci_locations.env b/test/ci_locations.env index 88b6992..158d7c3 100644 --- a/test/ci_locations.env +++ b/test/ci_locations.env @@ -7,7 +7,7 @@ DRYRUN = "True" DEBUG = "True" ## Debugging level, "info" is default, "debug" is more verbose -DEBUG_LEVEL = "info" +DEBUG_LEVEL = "debug" ## If set to true then the script will only run once and then exit RUN_ONLY_ONCE = "True" diff --git a/test/ci_plex.env b/test/ci_plex.env index 54b24e5..f81a3fc 100644 --- a/test/ci_plex.env +++ b/test/ci_plex.env @@ -7,7 +7,7 @@ DRYRUN = "True" DEBUG = "True" ## Debugging level, "info" is default, "debug" is more verbose -DEBUG_LEVEL = "info" +DEBUG_LEVEL = "debug" ## If set to true then the script will only run once and then exit RUN_ONLY_ONCE = "True" diff --git a/test/ci_write.env b/test/ci_write.env index 8d364d0..03361cb 100644 --- a/test/ci_write.env +++ b/test/ci_write.env @@ -7,7 +7,7 @@ DRYRUN = "False" DEBUG = "True" ## Debugging level, "info" is default, "debug" is more verbose -DEBUG_LEVEL = "info" +DEBUG_LEVEL = "debug" ## If set to true then the script will only run once and then exit RUN_ONLY_ONCE = "True" From 7294241feda05baefd4919a3958d9bdbc3831ed5 Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 27 Oct 2024 16:31:30 -0600 Subject: [PATCH 4/6] Add server type and name to marklog Signed-off-by: Luis Garcia --- src/functions.py | 12 ++++++++++-- src/jellyfin_emby.py | 35 +++++++++++++++++---------------- src/library.py | 46 +++++++++++++++++++++++++++++++++++--------- src/main.py | 14 +++++++++----- src/plex.py | 25 ++++++++++++++++-------- 5 files changed, 91 insertions(+), 41 deletions(-) diff --git a/src/functions.py b/src/functions.py index efdf8bf..8abb3cd 100644 --- a/src/functions.py +++ b/src/functions.py @@ -37,12 +37,18 @@ def logger(message: str, log_type=0): def log_marked( - username: str, library: str, movie_show: str, episode: str = None, duration=None + server_type: str, + server_name: str, + username: str, + library: str, + movie_show: str, + episode: str = None, + duration=None, ): if mark_file is None: return - output = f"{username}/{library}/{movie_show}" + output = f"{server_type}/{server_name}/{username}/{library}/{movie_show}" if episode: output += f"/{episode}" @@ -92,6 +98,7 @@ def search_mapping(dictionary: dict, key_value: str): else: return None + # Return list of objects that exist in both lists including mappings def match_list(list1, list2, list_mapping=None): output = [] @@ -105,6 +112,7 @@ def match_list(list1, list2, list_mapping=None): return output + def future_thread_executor( args: list, threads: int = None, override_threads: bool = False ): diff --git a/src/jellyfin_emby.py b/src/jellyfin_emby.py index 62977d7..4a778c8 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -109,7 +109,6 @@ class JellyfinEmby: def __init__(self, server_type, baseurl, token, headers): if server_type not in ["Jellyfin", "Emby"]: raise Exception(f"Server type {server_type} not supported") - self.server_type = server_type self.baseurl = baseurl self.token = token @@ -124,6 +123,7 @@ def __init__(self, server_type, baseurl, token, headers): self.session = requests.Session() self.users = self.get_users() + self.server_name = self.info(name_only=True) def query(self, query, query_type, identifiers=None, json=None): try: @@ -175,13 +175,15 @@ def query(self, query, query_type, identifiers=None, json=None): ) raise Exception(e) - def info(self) -> str: + def info(self, name_only: bool = False) -> str: try: query_string = "/System/Info/Public" response = self.query(query_string, "get") if response: + if name_only: + return f"{response['ServerName']}" return f"{self.server_type} {response['ServerName']}: {response['Version']}" else: return None @@ -223,7 +225,7 @@ def get_users(self): def get_libraries(self): try: libraries = {} - + # Theres no way to get all libraries so individually get list of libraries from all users users = self.get_users() @@ -256,12 +258,12 @@ def get_libraries(self): ) else: libraries[library_title] = str(types) - + return libraries except Exception as e: logger(f"{self.server_type}: Get libraries failed {e}", 2) raise Exception(e) - + def get_user_library_watched( self, user_name, user_id, library_type, library_id, library_title ): @@ -393,9 +395,7 @@ def get_user_library_watched( if show_guids not in user_watched[library_title]: user_watched[library_title][show_guids] = [] - user_watched[library_title][ - show_guids - ] = mark_episodes_list + user_watched[library_title][show_guids] = mark_episodes_list for episode in mark_episodes_list: logger( f"{self.server_type}: Added {episode} to {user_name} {show_display_name} watched list", @@ -407,9 +407,7 @@ def get_user_library_watched( 1, ) if library_title in user_watched: - logger( - f"{self.server_type}: {user_watched[library_title]}", 3 - ) + logger(f"{self.server_type}: {user_watched[library_title]}", 3) return user_watched except Exception as e: @@ -421,11 +419,7 @@ def get_user_library_watched( logger(traceback.format_exc(), 2) return {} - def get_watched( - self, - users, - sync_libraries - ): + def get_watched(self, users, sync_libraries): try: users_watched = {} watched = [] @@ -479,7 +473,6 @@ def get_watched( library_title, ) - if user_name.lower() not in users_watched: users_watched[user_name.lower()] = {} users_watched[user_name.lower()].update(watched) @@ -546,6 +539,8 @@ def update_user_watched( logger(msg, 6) log_marked( + self.server_type, + self.server_name, user_name, library, jellyfin_video.get("Name"), @@ -568,6 +563,8 @@ def update_user_watched( logger(msg, 6) log_marked( + self.server_type, + self.server_name, user_name, library, jellyfin_video.get("Name"), @@ -674,6 +671,8 @@ def update_user_watched( logger(msg, 6) log_marked( + self.server_type, + self.server_name, user_name, library, jellyfin_episode.get("SeriesName"), @@ -702,6 +701,8 @@ def update_user_watched( logger(msg, 6) log_marked( + self.server_type, + self.server_name, user_name, library, jellyfin_episode.get("SeriesName"), diff --git a/src/library.py b/src/library.py index 6e8ee9c..dfcc005 100644 --- a/src/library.py +++ b/src/library.py @@ -129,8 +129,14 @@ def check_whitelist_logic( return skip_reason + def filter_libaries( - server_libraries, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping=None + server_libraries, + blacklist_library, + blacklist_library_type, + whitelist_library, + whitelist_library_type, + library_mapping=None, ): filtered_libaries = [] for library in server_libraries: @@ -145,9 +151,7 @@ def filter_libaries( ) if skip_reason: - logger( - f"Skipping library {library}: {skip_reason}", 1 - ) + logger(f"Skipping library {library}: {skip_reason}", 1) continue filtered_libaries.append(library) @@ -156,7 +160,13 @@ def filter_libaries( def setup_libraries( - server_1, server_2, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping=None + server_1, + server_2, + blacklist_library, + blacklist_library_type, + whitelist_library, + whitelist_library_type, + library_mapping=None, ): server_1_libraries = server_1.get_libraries() server_2_libraries = server_2.get_libraries() @@ -164,11 +174,29 @@ def setup_libraries( logger(f"Server 2 libraries: {server_2_libraries}", 1) # Filter out all blacklist, whitelist libaries - filtered_server_1_libraries = filter_libaries(server_1_libraries, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping) - filtered_server_2_libraries = filter_libaries(server_2_libraries, blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping) + filtered_server_1_libraries = filter_libaries( + server_1_libraries, + blacklist_library, + blacklist_library_type, + whitelist_library, + whitelist_library_type, + library_mapping, + ) + filtered_server_2_libraries = filter_libaries( + server_2_libraries, + blacklist_library, + blacklist_library_type, + whitelist_library, + whitelist_library_type, + library_mapping, + ) - output_server_1_libaries = match_list(filtered_server_1_libraries, filtered_server_2_libraries, library_mapping) - output_server_2_libaries = match_list(filtered_server_2_libraries, filtered_server_1_libraries, library_mapping) + output_server_1_libaries = match_list( + filtered_server_1_libraries, filtered_server_2_libraries, library_mapping + ) + output_server_2_libaries = match_list( + filtered_server_2_libraries, filtered_server_1_libraries, library_mapping + ) return output_server_1_libaries, output_server_2_libaries diff --git a/src/main.py b/src/main.py index d6f72a1..07b56a1 100644 --- a/src/main.py +++ b/src/main.py @@ -155,19 +155,23 @@ def main_loop(): ) server_1_libraries, server_2_libraries = setup_libraries( - server_1[1], server_2[1], blacklist_library, blacklist_library_type, whitelist_library, whitelist_library_type, library_mapping + server_1[1], + server_2[1], + blacklist_library, + blacklist_library_type, + whitelist_library, + whitelist_library_type, + library_mapping, ) logger("Creating watched lists", 1) server_1_watched = server_1[1].get_watched( - server_1_users, - server_1_libraries + server_1_users, server_1_libraries ) logger("Finished creating watched list server 1", 1) server_2_watched = server_2[1].get_watched( - server_2_users, - server_2_libraries + server_2_users, server_2_libraries ) logger("Finished creating watched list server 2", 1) diff --git a/src/plex.py b/src/plex.py index 0019874..490a4e4 100644 --- a/src/plex.py +++ b/src/plex.py @@ -317,7 +317,15 @@ def update_user_watched(user, user_plex, library, videos, dryrun): else: logger(msg, 6) - log_marked(user.title, library, movies_search.title, None, None) + log_marked( + "Plex", + user_plex.friendlyName, + user.title, + library, + movies_search.title, + None, + None, + ) elif video_status["time"] > 60_000: msg = f"Plex: {movies_search.title} as partially watched for {floor(video_status['time'] / 60_000)} minutes for {user.title} in {library}" if not dryrun: @@ -327,6 +335,8 @@ def update_user_watched(user, user_plex, library, videos, dryrun): logger(msg, 6) log_marked( + "Plex", + user_plex.friendlyName, user.title, library, movies_search.title, @@ -358,6 +368,8 @@ def update_user_watched(user, user_plex, library, videos, dryrun): logger(msg, 6) log_marked( + "Plex", + user_plex.friendlyName, user.title, library, show_search.title, @@ -372,6 +384,8 @@ def update_user_watched(user, user_plex, library, videos, dryrun): logger(msg, 6) log_marked( + "Plex", + user_plex.friendlyName, user.title, library, show_search.title, @@ -477,18 +491,13 @@ def get_libraries(self): library_type = library.type output[library_title] = library_type - + return output except Exception as e: logger(f"Plex: Failed to get libraries, Error: {e}", 2) raise Exception(e) - - def get_watched( - self, - users, - sync_libraries - ): + def get_watched(self, users, sync_libraries): try: # Get all libraries users_watched = {} From a096a09eb78724757263159342a05ea0bbabcb37 Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 27 Oct 2024 16:37:08 -0600 Subject: [PATCH 5/6] CI: Fix Validation. Print marklog on failed validation Signed-off-by: Luis Garcia --- src/jellyfin_emby.py | 11 ++-- src/plex.py | 2 +- test/validate_ci_marklog.py | 124 +++++++++++++++++++----------------- 3 files changed, 72 insertions(+), 65 deletions(-) diff --git a/src/jellyfin_emby.py b/src/jellyfin_emby.py index 4a778c8..11b76f3 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -398,8 +398,8 @@ def get_user_library_watched( user_watched[library_title][show_guids] = mark_episodes_list for episode in mark_episodes_list: logger( - f"{self.server_type}: Added {episode} to {user_name} {show_display_name} watched list", - 1, + f"{self.server_type}: Added {episode} to {user_name} watched list", + 3, ) logger( @@ -431,6 +431,10 @@ def get_watched(self, users, sync_libraries): for library in all_libraries["Items"]: library_id = library["Id"] library_title = library["Name"] + + if library_title not in sync_libraries: + continue + identifiers = { "library_id": library_id, "library_title": library_title, @@ -451,9 +455,6 @@ def get_watched(self, users, sync_libraries): library_id = library["Identifiers"]["library_id"] library_title = library["Identifiers"]["library_title"] - if library_title not in sync_libraries: - continue - # Get all library types excluding "Folder" types = set( [ diff --git a/src/plex.py b/src/plex.py index 490a4e4..7e95f7c 100644 --- a/src/plex.py +++ b/src/plex.py @@ -186,7 +186,7 @@ def get_user_library_watched(user, user_plex, library): if show_guids and episode_guids: watched[show_guids] = episode_guids logger( - f"Plex: Added {episode_guids} to {user_name} {show_guids} watched list", + f"Plex: Added {episode_guids} to {user_name} watched list", 3, ) diff --git a/test/validate_ci_marklog.py b/test/validate_ci_marklog.py index 906dfa9..e369713 100644 --- a/test/validate_ci_marklog.py +++ b/test/validate_ci_marklog.py @@ -74,74 +74,74 @@ def check_marklog(lines, expected_values): def main(): args = parse_args() expected_jellyfin = [ - "jellyplex_watched/Movies/Five Nights at Freddy's", - "jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215", - "jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", - "jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741", - "jellyplex_watched/Movies/The Family Plan", - "jellyplex_watched/Movies/Five Nights at Freddy's", - "jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5", - "jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", - "jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out", + "Plex/JellyPlex-CI/jellyplex_watched/Movies/Five Nights at Freddy's", + "Plex/JellyPlex-CI/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741", + "Emby/Emby-Server/jellyplex_watched/Movies/The Family Plan", + "Emby/Emby-Server/jellyplex_watched/Movies/Five Nights at Freddy's", + "Emby/Emby-Server/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out", ] expected_emby = [ - "jellyplex_watched/Movies/Tears of Steel", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429", - "JellyUser/Movies/Tears of Steel", - "JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", + "Plex/JellyPlex-CI/jellyplex_watched/Movies/Tears of Steel", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429", + "Jellyfin/Jellyfin-Server/JellyUser/Movies/Tears of Steel", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", ] expected_plex = [ - "JellyUser/Movies/Big Buck Bunny", - "JellyUser/Movies/Killers of the Flower Moon/4", - "JellyUser/Shows/Doctor Who/The Unquiet Dead", - "JellyUser/Shows/Doctor Who/Aliens of London (1)/4", - "JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies", - "JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", - "jellyplex_watched/Movies/Big Buck Bunny", - "jellyplex_watched/Movies/The Family Plan", - "jellyplex_watched/Movies/Killers of the Flower Moon/4", - "jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead", - "jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out", + "Jellyfin/Jellyfin-Server/JellyUser/Movies/Big Buck Bunny", + "Jellyfin/Jellyfin-Server/JellyUser/Movies/Killers of the Flower Moon/4", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/The Unquiet Dead", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/Aliens of London (1)/4", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", + "Emby/Emby-Server/jellyplex_watched/Movies/Big Buck Bunny", + "Emby/Emby-Server/jellyplex_watched/Movies/The Family Plan", + "Emby/Emby-Server/jellyplex_watched/Movies/Killers of the Flower Moon/4", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out", ] expected_dry = expected_emby + expected_plex + expected_jellyfin expected_write = [ - "jellyplex_watched/Movies/Five Nights at Freddy's", - "jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215", - "jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", - "jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741", - "JellyUser/Movies/Big Buck Bunny", - "JellyUser/Movies/Killers of the Flower Moon/4", - "JellyUser/Shows/Doctor Who/The Unquiet Dead", - "JellyUser/Shows/Doctor Who/Aliens of London (1)/4", - "JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies", - "JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", - "jellyplex_watched/Movies/Tears of Steel", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429", - "jellyplex_watched/Movies/Big Buck Bunny", - "jellyplex_watched/Movies/The Family Plan", - "jellyplex_watched/Movies/Five Nights at Freddy's", - "jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5", - "jellyplex_watched/Movies/Killers of the Flower Moon/4", - "jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", - "jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5", - "jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead", - "jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies", - "jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out", - "JellyUser/Movies/Tears of Steel", - "JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", + "Plex/JellyPlex-CI/jellyplex_watched/Movies/Five Nights at Freddy's", + "Plex/JellyPlex-CI/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741", + "Jellyfin/Jellyfin-Server/JellyUser/Movies/Big Buck Bunny", + "Jellyfin/Jellyfin-Server/JellyUser/Movies/Killers of the Flower Moon/4", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/The Unquiet Dead", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/Aliens of London (1)/4", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", + "Plex/JellyPlex-CI/jellyplex_watched/Movies/Tears of Steel", + "Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429", + "Emby/Emby-Server/jellyplex_watched/Movies/Big Buck Bunny", + "Emby/Emby-Server/jellyplex_watched/Movies/The Family Plan", + "Emby/Emby-Server/jellyplex_watched/Movies/Five Nights at Freddy's", + "Emby/Emby-Server/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5", + "Emby/Emby-Server/jellyplex_watched/Movies/Killers of the Flower Moon/4", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies", + "Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out", + "Jellyfin/Jellyfin-Server/JellyUser/Movies/Tears of Steel", + "Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4" ] # Expected values for the mark.log file, dry-run is slightly different than write-run @@ -164,6 +164,12 @@ def main(): lines = read_marklog() if not check_marklog(lines, expected_values): print("Failed to validate marklog") + for line in lines: + # Remove the newline character + line = line.strip() + + print(line) + exit(1) print("Successfully validated marklog") From 62d0319aad6ada1d92cceae1072334e8523f9c08 Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 27 Oct 2024 17:59:07 -0600 Subject: [PATCH 6/6] Remove unused Signed-off-by: Luis Garcia --- src/jellyfin_emby.py | 10 +--------- src/plex.py | 5 +---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/jellyfin_emby.py b/src/jellyfin_emby.py index 11b76f3..55c3609 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -13,10 +13,7 @@ log_marked, str_to_bool, ) -from src.library import ( - check_skip_logic, - generate_library_guids_dict, -) +from src.library import generate_library_guids_dict load_dotenv(override=True) @@ -355,11 +352,6 @@ def get_user_library_watched( if "Path" in show else tuple() ) - show_display_name = ( - show_guids["title"] - if show_guids["title"] - else show_guids["locations"] - ) show_guids = frozenset(show_guids.items()) diff --git a/src/plex.py b/src/plex.py index 7e95f7c..77b06da 100644 --- a/src/plex.py +++ b/src/plex.py @@ -19,10 +19,7 @@ log_marked, str_to_bool, ) -from src.library import ( - check_skip_logic, - generate_library_guids_dict, -) +from src.library import generate_library_guids_dict load_dotenv(override=True)