diff --git a/src/functions.py b/src/functions.py index 04173d8..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}" @@ -93,6 +99,20 @@ def search_mapping(dictionary: dict, key_value: str): 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..55c3609 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -13,13 +13,7 @@ log_marked, str_to_bool, ) -from src.library import ( - check_skip_logic, - generate_library_guids_dict, -) -from src.watched import ( - combine_watched_dicts, -) +from src.library import generate_library_guids_dict load_dotenv(override=True) @@ -112,7 +106,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 @@ -127,6 +120,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: @@ -178,13 +172,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,13 +219,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 +275,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 +311,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 +320,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( @@ -315,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()) @@ -352,26 +384,22 @@ 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][ - 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", - 1, + f"{self.server_type}: Added {episode} to {user_name} watched list", + 3, ) logger( f"{self.server_type}: Got watched for {user_name} in library {library_title}", 1, ) - if library_title in user_watched[user_name]: - logger( - f"{self.server_type}: {user_watched[user_name][library_title]}", 3 - ) + if library_title in user_watched: + logger(f"{self.server_type}: {user_watched[library_title]}", 3) return user_watched except Exception as e: @@ -383,130 +411,64 @@ def get_user_library_watched( logger(traceback.format_exc(), 2) return {} - def get_users_watched( - self, - user_name, - user_id, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, - ): + def get_watched(self, 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 library in libraries: - if len(library["Items"]) == 0: - continue + for user_name, user_id in users.items(): + libraries = [] - 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"] - ] - ) + all_libraries = self.query(f"/Users/{user_id}/Views", "get") + for library in all_libraries["Items"]: + library_id = library["Id"] + library_title = library["Name"] - skip_reason = check_skip_logic( - library_title, - types, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, - ) + if library_title not in sync_libraries: + continue - if skip_reason: - logger( - f"{self.server_type}: Skipping library {library_title}: {skip_reason}", - 1, + 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, + ) ) - 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, + 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"] + ] ) - 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: @@ -570,6 +532,8 @@ def update_user_watched( logger(msg, 6) log_marked( + self.server_type, + self.server_name, user_name, library, jellyfin_video.get("Name"), @@ -592,6 +556,8 @@ def update_user_watched( logger(msg, 6) log_marked( + self.server_type, + self.server_name, user_name, library, jellyfin_video.get("Name"), @@ -698,6 +664,8 @@ def update_user_watched( logger(msg, 6) log_marked( + self.server_type, + self.server_name, user_name, library, jellyfin_episode.get("SeriesName"), @@ -726,6 +694,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 3d5e458..dfcc005 100644 --- a/src/library.py +++ b/src/library.py @@ -1,5 +1,6 @@ from src.functions import ( logger, + match_list, search_mapping, ) @@ -129,6 +130,77 @@ 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: show_output_dict = {} diff --git a/src/main.py b/src/main.py index 5f84d53..07b56a1 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,24 @@ def main_loop(): server_1, server_2, blacklist_users, whitelist_users, user_mapping ) - logger("Creating watched lists", 1) - server_1_watched = server_1[1].get_watched( - server_1_users, + server_1_libraries, server_2_libraries = setup_libraries( + server_1[1], + server_2[1], blacklist_library, - whitelist_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 + ) 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_users, server_2_libraries ) logger("Finished creating watched list server 2", 1) diff --git a/src/plex.py b/src/plex.py index 4f6ca32..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) @@ -186,7 +183,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, ) @@ -317,7 +314,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 +332,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 +365,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 +381,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, @@ -466,15 +477,24 @@ def get_users(self): logger(f"Plex: Failed to get users, Error: {e}", 2) raise Exception(e) - def get_watched( - self, - users, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, - ): + 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, sync_libraries): try: # Get all libraries users_watched = {} @@ -500,23 +520,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) 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/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" 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 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")