From 7119956ec71d9e7a8000b871973715a02d21513a Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 2 Jun 2024 19:38:51 -0600 Subject: [PATCH 1/4] Remove seasons from watch list --- src/jellyfin_emby.py | 170 +++++--------- src/library.py | 98 ++++---- src/plex.py | 20 +- src/watched.py | 85 +++---- test/test_library.py | 29 ++- test/test_watched.py | 518 ++++++++++++++++++++----------------------- 6 files changed, 392 insertions(+), 528 deletions(-) diff --git a/src/jellyfin_emby.py b/src/jellyfin_emby.py index c33c49c..72d37d0 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -284,8 +284,10 @@ def get_user_library_watched( if show["UserData"]["PlayedPercentage"] > 0: watched_shows_filtered.append(show) - # Retrieve the seasons of each watched show - seasons_watched = [] + # Create a list of tasks to retrieve the episodes + watched_episodes = [] + + # Retrieve the watched/partially watched list of episodes of each watched show for show in watched_shows_filtered: logger( f"{self.server_type}: Adding {show.get('Name')} to {user_name} watched list", @@ -298,118 +300,62 @@ def get_user_library_watched( if "Path" in show else tuple() ) - show_guids = frozenset(show_guids.items()) - show_identifiers = { - "show_guids": show_guids, - "show_id": show["Id"], - } - - seasons_watched.append( - self.query( - f"/Shows/{show['Id']}/Seasons" - + f"?userId={user_id}&isPlaceHolder=false&Fields=ProviderIds,RecursiveItemCount", - "get", - identifiers=frozenset(show_identifiers.items()), - ) + show_display_name = ( + show_guids["title"] + if show_guids["title"] + else show_guids["locations"] ) - # Filter the list of seasons to only include those that have been partially or fully watched - seasons_watched_filtered = [] - for seasons in seasons_watched: - seasons_watched_filtered_dict = {} - seasons_watched_filtered_dict["Identifiers"] = seasons[ - "Identifiers" - ] - seasons_watched_filtered_dict["Items"] = [] - for season in seasons["Items"]: - if "PlayedPercentage" in season["UserData"]: - if season["UserData"]["PlayedPercentage"] > 0: - seasons_watched_filtered_dict["Items"].append(season) - - if seasons_watched_filtered_dict["Items"]: - seasons_watched_filtered.append(seasons_watched_filtered_dict) - - # Create a list of tasks to retrieve the episodes of each watched season - watched_episodes = [] - for seasons in seasons_watched_filtered: - if len(seasons["Items"]) > 0: - for season in seasons["Items"]: - if "IndexNumber" not in season: - logger( - f"Jellyfin: Skipping show {season.get('SeriesName')} season {season.get('Name')} as it has no index number", - 3, - ) + show_guids = frozenset(show_guids.items()) - continue - season_identifiers = dict(seasons["Identifiers"]) - season_identifiers["season_index"] = season["IndexNumber"] - watched_task = self.query( - f"/Shows/{season_identifiers['show_id']}/Episodes" - + f"?seasonId={season['Id']}&userId={user_id}&isPlaceHolder=false&Filters=IsPlayed&Fields=ProviderIds,MediaSources", - "get", - identifiers=frozenset(season_identifiers.items()), - ) + watched_task = self.query( + f"/Shows/{show['Id']}/Episodes" + + f"?userId={user_id}&isPlaceHolder=false&Filters=IsPlayed&Fields=ProviderIds,MediaSources", + "get", + ) - in_progress_task = self.query( - f"/Shows/{season_identifiers['show_id']}/Episodes" - + f"?seasonId={season['Id']}&userId={user_id}&isPlaceHolder=false&Filters=IsResumable&Fields=ProviderIds,MediaSources", - "get", - identifiers=frozenset(season_identifiers.items()), - ) - watched_episodes.append(watched_task) - watched_episodes.append(in_progress_task) - - # Iterate through the watched episodes - for episodes in watched_episodes: - # If the season has any watched episodes - if len(episodes["Items"]) > 0: - # Create a dictionary for the season with its identifier and episodes - season_dict = {} - season_dict["Identifiers"] = dict(episodes["Identifiers"]) - season_dict["Episodes"] = [] - for episode in episodes["Items"]: - if ( - "MediaSources" in episode - and episode["MediaSources"] != {} - ): - # If watched or watched more than a minute + in_progress_task = self.query( + f"/Shows/{show['Id']}/Episodes" + + f"?userId={user_id}&isPlaceHolder=false&Filters=IsResumable&Fields=ProviderIds,MediaSources", + "get", + ) + watched_episodes.append(watched_task) + watched_episodes.append(in_progress_task) + + # Iterate through the watched episodes + for episodes in watched_episodes: + # If has any watched episodes + if len(episodes["Items"]) > 0: + # Create a list to store the episodes + episodes_list = [] + for episode in episodes["Items"]: if ( - episode["UserData"]["Played"] == True - or episode["UserData"]["PlaybackPositionTicks"] - > 600000000 + "MediaSources" in episode + and episode["MediaSources"] != {} ): - episode_dict = get_guids(self.server_type, episode) - # Add the episode dictionary to the season's list of episodes - season_dict["Episodes"].append(episode_dict) + # If watched or watched more than a minute + if ( + episode["UserData"]["Played"] == True + or episode["UserData"]["PlaybackPositionTicks"] + > 600000000 + ): + episode_guids = get_guids( + self.server_type, episode + ) + episodes_list.append(episode_guids) - # Add the season dictionary to the show's list of seasons - if ( - season_dict["Identifiers"]["show_guids"] - not in user_watched[user_name][library_title] - ): - user_watched[user_name][library_title][ - season_dict["Identifiers"]["show_guids"] - ] = {} + # 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 ( - season_dict["Identifiers"]["season_index"] - not in user_watched[user_name][library_title][ - season_dict["Identifiers"]["show_guids"] - ] - ): user_watched[user_name][library_title][ - season_dict["Identifiers"]["show_guids"] - ][season_dict["Identifiers"]["season_index"]] = [] - - user_watched[user_name][library_title][ - season_dict["Identifiers"]["show_guids"] - ][season_dict["Identifiers"]["season_index"]] = season_dict[ - "Episodes" - ] - logger( - f"{self.server_type}: Added {season_dict['Episodes']} to {user_name} {season_dict['Identifiers']['show_guids']} watched list", - 1, - ) + show_guids + ] = episodes_list + for episode in episodes_list: + logger( + f"{self.server_type}: Added {episode} to {user_name} {show_display_name} watched list", + 1, + ) logger( f"{self.server_type}: Got watched for {user_name} in library {library_title}", @@ -674,7 +620,7 @@ def update_user_watched( is not None ): show_found = True - for shows, seasons in videos.items(): + for shows, episodes in videos.items(): show = {k: v for k, v in shows} if ( contains_nested( @@ -683,9 +629,8 @@ def update_user_watched( ) is not None ): - for season in seasons.values(): - for episode in season: - episode_videos.append(episode) + for episode in episodes: + episode_videos.append(episode) break @@ -702,14 +647,13 @@ def update_user_watched( ] ): show_found = True - for show, seasons in videos.items(): + for show, episodes in videos.items(): show = {k: v for k, v in show} if show_provider_id.lower() in show.get( show_provider_source.lower(), [] ): - for season in seasons.values(): - for episode in season: - episode_videos.append(episode) + for episode in episodes: + episode_videos.append(episode) break diff --git a/src/library.py b/src/library.py index c33c962..3d5e458 100644 --- a/src/library.py +++ b/src/library.py @@ -169,63 +169,53 @@ def episode_title_dict(user_list: dict): episode_output_dict["time"] = [] episode_output_dict["locations"] = [] episode_output_dict["show"] = [] - episode_output_dict["season"] = [] episode_counter = 0 # Initialize a counter for the current episode position - # Iterate through the shows, seasons, and episodes in user_list + # Iterate through the shows and episodes in user_list for show in user_list: - for season in user_list[show]: - for episode in user_list[show][season]: - # Add the show title to the episode_output_dict if it doesn't exist - if "show" not in episode_output_dict: - episode_output_dict["show"] = [None] * episode_counter - - # Add the season number to the episode_output_dict if it doesn't exist - if "season" not in episode_output_dict: - episode_output_dict["season"] = [None] * episode_counter - - # Add the show title to the episode_output_dict - episode_output_dict["show"].append(dict(show)) - - # Add the season number to the episode_output_dict - episode_output_dict["season"].append(season) - - # Iterate through the keys and values in each episode - for episode_key, episode_value in episode.items(): - # If the key is not "status", add the key to episode_output_dict if it doesn't exist - if episode_key != "status": - if episode_key.lower() not in episode_output_dict: - # Initialize the list with None values up to the current episode position - episode_output_dict[episode_key.lower()] = [ - None - ] * episode_counter - - # If the key is "locations", append each location to the list - if episode_key == "locations": - episode_output_dict[episode_key.lower()].append( - episode_value - ) - - # If the key is "status", append the "completed" and "time" values - elif episode_key == "status": - episode_output_dict["completed"].append( - episode_value["completed"] - ) - episode_output_dict["time"].append(episode_value["time"]) - - # For other keys, append the value to the list - else: - episode_output_dict[episode_key.lower()].append( - episode_value.lower() - ) - - # Increment the episode_counter - episode_counter += 1 - - # Extend the lists in episode_output_dict with None values to match the current episode_counter - for key in episode_output_dict: - if len(episode_output_dict[key]) < episode_counter: - episode_output_dict[key].append(None) + + for episode in user_list[show]: + # Add the show title to the episode_output_dict if it doesn't exist + if "show" not in episode_output_dict: + episode_output_dict["show"] = [None] * episode_counter + + # Add the show title to the episode_output_dict + episode_output_dict["show"].append(dict(show)) + + # Iterate through the keys and values in each episode + for episode_key, episode_value in episode.items(): + # If the key is not "status", add the key to episode_output_dict if it doesn't exist + if episode_key != "status": + if episode_key.lower() not in episode_output_dict: + # Initialize the list with None values up to the current episode position + episode_output_dict[episode_key.lower()] = [ + None + ] * episode_counter + + # If the key is "locations", append each location to the list + if episode_key == "locations": + episode_output_dict[episode_key.lower()].append(episode_value) + + # If the key is "status", append the "completed" and "time" values + elif episode_key == "status": + episode_output_dict["completed"].append( + episode_value["completed"] + ) + episode_output_dict["time"].append(episode_value["time"]) + + # For other keys, append the value to the list + else: + episode_output_dict[episode_key.lower()].append( + episode_value.lower() + ) + + # Increment the episode_counter + episode_counter += 1 + + # Extend the lists in episode_output_dict with None values to match the current episode_counter + for key in episode_output_dict: + if len(episode_output_dict[key]) < episode_counter: + episode_output_dict[key].append(None) return episode_output_dict except Exception: diff --git a/src/plex.py b/src/plex.py index e891809..fdec854 100644 --- a/src/plex.py +++ b/src/plex.py @@ -117,11 +117,9 @@ def get_user_library_watched_show(show, process_episodes, threads=None): episode_guids_args, threads=threads ) - episode_guids = {} + episode_guids = [] for index, episode in enumerate(process_episodes): - if episode.parentIndex not in episode_guids: - episode_guids[episode.parentIndex] = [] - episode_guids[episode.parentIndex].append(episode_guids_results[index]) + episode_guids.append(episode_guids_results[index]) return show_guids, episode_guids except Exception: @@ -220,7 +218,7 @@ def find_video(plex_search, video_ids, videos=None): ): episode_videos = [] if videos: - for show, seasons in videos.items(): + for show, episodes in videos.items(): show = {k: v for k, v in show} if ( contains_nested( @@ -228,9 +226,8 @@ def find_video(plex_search, video_ids, videos=None): ) is not None ): - for season in seasons.values(): - for episode in season: - episode_videos.append(episode) + for episode in episodes: + episode_videos.append(episode) return True, episode_videos @@ -243,13 +240,12 @@ def find_video(plex_search, video_ids, videos=None): if guid_id in video_ids[guid_source]: episode_videos = [] if videos: - for show, seasons in videos.items(): + for show, episodes in videos.items(): show = {k: v for k, v in show} if guid_source in show.keys(): if guid_id == show[guid_source]: - for season in seasons.values(): - for episode in season: - episode_videos.append(episode) + for episode in episodes: + episode_videos.append(episode) return True, episode_videos diff --git a/src/watched.py b/src/watched.py index 4d4f7cc..78f0264 100644 --- a/src/watched.py +++ b/src/watched.py @@ -122,53 +122,27 @@ def cleanup_watched( 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]: - # Filter the episode_watched_list_2_keys_dict dictionary to handle cases - # where episode location names are not unique such as S01E01.mkv - filtered_episode_watched_list_2_keys_dict = ( - filter_episode_watched_list_2_keys_dict( - episode_watched_list_2_keys_dict, show_key_dict, season - ) + # Filter the episode_watched_list_2_keys_dict dictionary to handle cases + # where episode location names are not unique such as S01E01.mkv + filtered_episode_watched_list_2_keys_dict = ( + filter_episode_watched_list_2_keys_dict( + episode_watched_list_2_keys_dict, show_key_dict ) - for episode in watched_list_1[user_1][library_1][show_key_1][ - season - ]: - episode_index = get_episode_index_in_dict( - episode, filtered_episode_watched_list_2_keys_dict - ) - if episode_index is not None: - if check_remove_entry( - episode, - library_1, - episode_index, - episode_watched_list_2_keys_dict, - ): - 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 - ] + ) + for episode in watched_list_1[user_1][library_1][show_key_1]: + episode_index = get_episode_index_in_dict( + episode, filtered_episode_watched_list_2_keys_dict + ) + if episode_index is not None: + if check_remove_entry( + episode, + library_1, + episode_index, + episode_watched_list_2_keys_dict, ): - logger( - f"Removing {season} from {show_key_dict['title']} because it is empty", - 3, - ) - del modified_watched_list_1[user_1][library_1][ + modified_watched_list_1[user_1][library_1][ show_key_1 - ][season] + ].remove(episode) # Remove empty shows if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0: @@ -231,27 +205,18 @@ def get_movie_index_in_dict(movie, movies_watched_list_2_keys_dict): def filter_episode_watched_list_2_keys_dict( - episode_watched_list_2_keys_dict, show_key_dict, season + episode_watched_list_2_keys_dict, show_key_dict ): - # If the episode_watched_list_2_keys_dict dictionary is empty, missing season or show then return an empty dictionary + # If the episode_watched_list_2_keys_dict dictionary is empty, missing show then return an empty dictionary if ( len(episode_watched_list_2_keys_dict) == 0 - or "season" not in episode_watched_list_2_keys_dict.keys() or "show" not in episode_watched_list_2_keys_dict.keys() ): return {} - # Filter the episode_watched_list_2_keys_dict dictionary to only include values for the correct show and season + # Filter the episode_watched_list_2_keys_dict dictionary to only include values for the correct show filtered_episode_watched_list_2_keys_dict = {} show_indecies = [] - season_indecies = [] - - # Iterate through episode_watched_list_2_keys_dict["season"] and find the indecies that match season - for season_index, season_value in enumerate( - episode_watched_list_2_keys_dict.get("season") - ): - if season_value == season: - season_indecies.append(season_index) # Iterate through episode_watched_list_2_keys_dict["show"] and find the indecies that match show_key_dict for show_index, show_value in enumerate(episode_watched_list_2_keys_dict["show"]): @@ -273,14 +238,14 @@ def filter_episode_watched_list_2_keys_dict( show_indecies.append(show_index) break - # Find the intersection of the show_indecies and season_indecies lists - indecies = list(set(show_indecies) & set(season_indecies)) + # lists + indecies = list(set(show_indecies)) - # If there are no indecies that match the show and season, return an empty dictionary + # If there are no indecies that match the show, return an empty dictionary if len(indecies) == 0: return {} - # Create a copy of the dictionary with indecies that match the show and season and none that don't + # Create a copy of the dictionary with indecies that match the show and none that don't for key, value in episode_watched_list_2_keys_dict.items(): if key not in filtered_episode_watched_list_2_keys_dict: filtered_episode_watched_list_2_keys_dict[key] = [] diff --git a/test/test_library.py b/test/test_library.py index e506ebb..585f449 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -42,21 +42,19 @@ ("tvdb", "392256"), ("title", "The Last of Us"), } - ): { - "Season 1": [ - { - "imdb": "tt11957006", - "tmdb": "2181581", - "tvdb": "8444132", - "locations": ( - ( - "The Last of Us - S01E01 - When You're Lost in the Darkness WEBDL-1080p.mkv", - ) - ), - "status": {"completed": True, "time": 0}, - } - ] - } + ): [ + { + "imdb": "tt11957006", + "tmdb": "2181581", + "tvdb": "8444132", + "locations": ( + ( + "The Last of Us - S01E01 - When You're Lost in the Darkness WEBDL-1080p.mkv", + ) + ), + "status": {"completed": True, "time": 0}, + } + ] } movie_list = [ { @@ -83,7 +81,6 @@ "tvdb": ["8444132"], "completed": [True], "time": [0], - "season": ["Season 1"], "show": [ { "imdb": "tt3581920", diff --git a/test/test_watched.py b/test/test_watched.py index e66be17..ada7e77 100644 --- a/test/test_watched.py +++ b/test/test_watched.py @@ -24,34 +24,32 @@ ("tvdb", "78804"), ("title", "Doctor Who (2005)"), } - ): { - 1: [ - { - "imdb": "tt0563001", - "tmdb": "968589", - "tvdb": "295296", - "title": "The Unquiet Dead", - "locations": ("S01E03.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "imdb": "tt0562985", - "tmdb": "968590", - "tvdb": "295297", - "title": "Aliens of London (1)", - "locations": ("S01E04.mkv",), - "status": {"completed": False, "time": 240000}, - }, - { - "imdb": "tt0563003", - "tmdb": "968592", - "tvdb": "295298", - "title": "World War Three (2)", - "locations": ("S01E05.mkv",), - "status": {"completed": True, "time": 0}, - }, - ] - }, + ): [ + { + "imdb": "tt0563001", + "tmdb": "968589", + "tvdb": "295296", + "title": "The Unquiet Dead", + "locations": ("S01E03.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "imdb": "tt0562985", + "tmdb": "968590", + "tvdb": "295297", + "title": "Aliens of London (1)", + "locations": ("S01E04.mkv",), + "status": {"completed": False, "time": 240000}, + }, + { + "imdb": "tt0563003", + "tmdb": "968592", + "tvdb": "295298", + "title": "World War Three (2)", + "locations": ("S01E05.mkv",), + "status": {"completed": True, "time": 0}, + }, + ], frozenset( { ("title", "Monarch: Legacy of Monsters"), @@ -63,34 +61,32 @@ ("Monarch - Legacy of Monsters {tvdb-422598} {imdb-tt17220216}",), ), } - ): { - 1: [ - { - "imdb": "tt21255044", - "tmdb": "4661246", - "tvdb": "10009418", - "title": "Secrets and Lies", - "locations": ("S01E03.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "imdb": "tt21255050", - "tmdb": "4712059", - "tvdb": "10009419", - "title": "Parallels and Interiors", - "locations": ("S01E04.mkv",), - "status": {"completed": False, "time": 240000}, - }, - { - "imdb": "tt23787572", - "tmdb": "4712061", - "tvdb": "10009420", - "title": "The Way Out", - "locations": ("S01E05.mkv",), - "status": {"completed": True, "time": 0}, - }, - ] - }, + ): [ + { + "imdb": "tt21255044", + "tmdb": "4661246", + "tvdb": "10009418", + "title": "Secrets and Lies", + "locations": ("S01E03.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "imdb": "tt21255050", + "tmdb": "4712059", + "tvdb": "10009419", + "title": "Parallels and Interiors", + "locations": ("S01E04.mkv",), + "status": {"completed": False, "time": 240000}, + }, + { + "imdb": "tt23787572", + "tmdb": "4712061", + "tvdb": "10009420", + "title": "The Way Out", + "locations": ("S01E05.mkv",), + "status": {"completed": True, "time": 0}, + }, + ], frozenset( { ("tmdb", "125928"), @@ -102,34 +98,32 @@ ), ("title", "My Adventures with Superman"), } - ): { - 1: [ - { - "imdb": "tt15699926", - "tmdb": "3070048", - "tvdb": "8438181", - "title": "Adventures of a Normal Man (1)", - "locations": ("S01E01.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "imdb": "tt20413322", - "tmdb": "4568681", - "tvdb": "9829910", - "title": "Adventures of a Normal Man (2)", - "locations": ("S01E02.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "imdb": "tt20413328", - "tmdb": "4497012", - "tvdb": "9870382", - "title": "My Interview with Superman", - "locations": ("S01E03.mkv",), - "status": {"completed": True, "time": 0}, - }, - ] - }, + ): [ + { + "imdb": "tt15699926", + "tmdb": "3070048", + "tvdb": "8438181", + "title": "Adventures of a Normal Man (1)", + "locations": ("S01E01.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "imdb": "tt20413322", + "tmdb": "4568681", + "tvdb": "9829910", + "title": "Adventures of a Normal Man (2)", + "locations": ("S01E02.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "imdb": "tt20413328", + "tmdb": "4497012", + "tvdb": "9870382", + "title": "My Interview with Superman", + "locations": ("S01E03.mkv",), + "status": {"completed": True, "time": 0}, + }, + ], } @@ -143,31 +137,29 @@ ("tvdb", "78804"), ("tvrage", "3332"), } - ): { - 1: [ - { - "tvdb": "295294", - "imdb": "tt0562992", - "title": "Rose", - "locations": ("S01E01.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "tvdb": "295295", - "imdb": "tt0562997", - "title": "The End of the World", - "locations": ("S01E02.mkv",), - "status": {"completed": False, "time": 300670}, - }, - { - "tvdb": "295298", - "imdb": "tt0563003", - "title": "World War Three (2)", - "locations": ("S01E05.mkv",), - "status": {"completed": True, "time": 0}, - }, - ] - }, + ): [ + { + "tvdb": "295294", + "imdb": "tt0562992", + "title": "Rose", + "locations": ("S01E01.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "tvdb": "295295", + "imdb": "tt0562997", + "title": "The End of the World", + "locations": ("S01E02.mkv",), + "status": {"completed": False, "time": 300670}, + }, + { + "tvdb": "295298", + "imdb": "tt0563003", + "title": "World War Three (2)", + "locations": ("S01E05.mkv",), + "status": {"completed": True, "time": 0}, + }, + ], frozenset( { ("title", "Monarch: Legacy of Monsters"), @@ -179,31 +171,29 @@ ("Monarch - Legacy of Monsters {tvdb-422598} {imdb-tt17220216}",), ), } - ): { - 1: [ - { - "tvdb": "9959300", - "imdb": "tt20412166", - "title": "Aftermath", - "locations": ("S01E01.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "tvdb": "10009417", - "imdb": "tt22866594", - "title": "Departure", - "locations": ("S01E02.mkv",), - "status": {"completed": False, "time": 300741}, - }, - { - "tvdb": "10009420", - "imdb": "tt23787572", - "title": "The Way Out", - "locations": ("S01E05.mkv",), - "status": {"completed": True, "time": 0}, - }, - ] - }, + ): [ + { + "tvdb": "9959300", + "imdb": "tt20412166", + "title": "Aftermath", + "locations": ("S01E01.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "tvdb": "10009417", + "imdb": "tt22866594", + "title": "Departure", + "locations": ("S01E02.mkv",), + "status": {"completed": False, "time": 300741}, + }, + { + "tvdb": "10009420", + "imdb": "tt23787572", + "title": "The Way Out", + "locations": ("S01E05.mkv",), + "status": {"completed": True, "time": 0}, + }, + ], frozenset( { ("tmdb", "125928"), @@ -215,31 +205,29 @@ ), ("title", "My Adventures with Superman"), } - ): { - 1: [ - { - "tvdb": "8438181", - "imdb": "tt15699926", - "title": "Adventures of a Normal Man (1)", - "locations": ("S01E01.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "tvdb": "9829910", - "imdb": "tt20413322", - "title": "Adventures of a Normal Man (2)", - "locations": ("S01E02.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "tvdb": "9870382", - "imdb": "tt20413328", - "title": "My Interview with Superman", - "locations": ("S01E03.mkv",), - "status": {"completed": True, "time": 0}, - }, - ] - }, + ): [ + { + "tvdb": "8438181", + "imdb": "tt15699926", + "title": "Adventures of a Normal Man (1)", + "locations": ("S01E01.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "tvdb": "9829910", + "imdb": "tt20413322", + "title": "Adventures of a Normal Man (2)", + "locations": ("S01E02.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "tvdb": "9870382", + "imdb": "tt20413328", + "title": "My Interview with Superman", + "locations": ("S01E03.mkv",), + "status": {"completed": True, "time": 0}, + }, + ], } expected_tv_show_watched_list_1 = { @@ -251,26 +239,24 @@ ("tvdb", "78804"), ("title", "Doctor Who (2005)"), } - ): { - 1: [ - { - "imdb": "tt0563001", - "tmdb": "968589", - "tvdb": "295296", - "title": "The Unquiet Dead", - "locations": ("S01E03.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "imdb": "tt0562985", - "tmdb": "968590", - "tvdb": "295297", - "title": "Aliens of London (1)", - "locations": ("S01E04.mkv",), - "status": {"completed": False, "time": 240000}, - }, - ] - }, + ): [ + { + "imdb": "tt0563001", + "tmdb": "968589", + "tvdb": "295296", + "title": "The Unquiet Dead", + "locations": ("S01E03.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "imdb": "tt0562985", + "tmdb": "968590", + "tvdb": "295297", + "title": "Aliens of London (1)", + "locations": ("S01E04.mkv",), + "status": {"completed": False, "time": 240000}, + }, + ], frozenset( { ("title", "Monarch: Legacy of Monsters"), @@ -282,26 +268,24 @@ ("Monarch - Legacy of Monsters {tvdb-422598} {imdb-tt17220216}",), ), } - ): { - 1: [ - { - "imdb": "tt21255044", - "tmdb": "4661246", - "tvdb": "10009418", - "title": "Secrets and Lies", - "locations": ("S01E03.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "imdb": "tt21255050", - "tmdb": "4712059", - "tvdb": "10009419", - "title": "Parallels and Interiors", - "locations": ("S01E04.mkv",), - "status": {"completed": False, "time": 240000}, - }, - ] - }, + ): [ + { + "imdb": "tt21255044", + "tmdb": "4661246", + "tvdb": "10009418", + "title": "Secrets and Lies", + "locations": ("S01E03.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "imdb": "tt21255050", + "tmdb": "4712059", + "tvdb": "10009419", + "title": "Parallels and Interiors", + "locations": ("S01E04.mkv",), + "status": {"completed": False, "time": 240000}, + }, + ], } expected_tv_show_watched_list_2 = { @@ -314,24 +298,22 @@ ("tvdb", "78804"), ("tvrage", "3332"), } - ): { - 1: [ - { - "tvdb": "295294", - "imdb": "tt0562992", - "title": "Rose", - "locations": ("S01E01.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "tvdb": "295295", - "imdb": "tt0562997", - "title": "The End of the World", - "locations": ("S01E02.mkv",), - "status": {"completed": False, "time": 300670}, - }, - ] - }, + ): [ + { + "tvdb": "295294", + "imdb": "tt0562992", + "title": "Rose", + "locations": ("S01E01.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "tvdb": "295295", + "imdb": "tt0562997", + "title": "The End of the World", + "locations": ("S01E02.mkv",), + "status": {"completed": False, "time": 300670}, + }, + ], frozenset( { ("title", "Monarch: Legacy of Monsters"), @@ -343,24 +325,22 @@ ("Monarch - Legacy of Monsters {tvdb-422598} {imdb-tt17220216}",), ), } - ): { - 1: [ - { - "tvdb": "9959300", - "imdb": "tt20412166", - "title": "Aftermath", - "locations": ("S01E01.mkv",), - "status": {"completed": True, "time": 0}, - }, - { - "tvdb": "10009417", - "imdb": "tt22866594", - "title": "Departure", - "locations": ("S01E02.mkv",), - "status": {"completed": False, "time": 300741}, - }, - ] - }, + ): [ + { + "tvdb": "9959300", + "imdb": "tt20412166", + "title": "Aftermath", + "locations": ("S01E01.mkv",), + "status": {"completed": True, "time": 0}, + }, + { + "tvdb": "10009417", + "imdb": "tt22866594", + "title": "Departure", + "locations": ("S01E02.mkv",), + "status": {"completed": False, "time": 300741}, + }, + ], } movies_watched_list_1 = [ @@ -463,20 +443,16 @@ ("locations", ("Criminal Minds",)), ("tmdb", "4057"), } - ): { - "Season 1": [ - { - "imdb": "tt0550489", - "tmdb": "282843", - "tvdb": "176357", - "title": "Extreme Aggressor", - "locations": ( - "Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv", - ), - "status": {"completed": True, "time": 0}, - }, - ] - } + ): [ + { + "imdb": "tt0550489", + "tmdb": "282843", + "tvdb": "176357", + "title": "Extreme Aggressor", + "locations": ("Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",), + "status": {"completed": True, "time": 0}, + }, + ] } @@ -610,18 +586,16 @@ def test_combine_watched_dicts(): ("locations", ("11.22.63",)), ("imdb", "tt2879552"), } - ): { - "Season 1": [ - { - "imdb": "tt4460418", - "title": "The Rabbit Hole", - "locations": ( - "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", - ), - "status": {"completed": True, "time": 0}, - } - ] - } + ): [ + { + "imdb": "tt4460418", + "title": "The Rabbit Hole", + "locations": ( + "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", + ), + "status": {"completed": True, "time": 0}, + } + ] } } }, @@ -664,18 +638,16 @@ def test_combine_watched_dicts(): ("locations", ("11.22.63",)), ("imdb", "tt2879552"), } - ): { - "Season 1": [ - { - "imdb": "tt4460418", - "title": "The Rabbit Hole", - "locations": ( - "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", - ), - "status": {"completed": True, "time": 0}, - } - ] - } + ): [ + { + "imdb": "tt4460418", + "title": "The Rabbit Hole", + "locations": ( + "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", + ), + "status": {"completed": True, "time": 0}, + } + ] }, "Subbed Anime": {}, } From 1a7178e32d659d899d38dd864d46a3150984357e Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 2 Jun 2024 22:07:58 -0600 Subject: [PATCH 2/4] Jellyfin: Remove episode filter as it doesnt exist in jellyfin --- src/jellyfin_emby.py | 139 ++++++++++++++++++++----------------------- src/plex.py | 2 +- 2 files changed, 67 insertions(+), 74 deletions(-) diff --git a/src/jellyfin_emby.py b/src/jellyfin_emby.py index 72d37d0..cd839cc 100644 --- a/src/jellyfin_emby.py +++ b/src/jellyfin_emby.py @@ -184,7 +184,7 @@ def info(self) -> str: response = self.query(query_string, "get") if response: - return f"{response['ServerName']}: {response['Version']}" + return f"{self.server_type} {response['ServerName']}: {response['Version']}" else: return None @@ -229,38 +229,42 @@ def get_user_library_watched( f"/Users/{user_id}/Items" + f"?ParentId={library_id}&Filters=IsPlayed&IncludeItemTypes=Movie&Recursive=True&Fields=ItemCounts,ProviderIds,MediaSources", "get", - ) + ).get("Items", []) in_progress = self.query( f"/Users/{user_id}/Items" + f"?ParentId={library_id}&Filters=IsResumable&IncludeItemTypes=Movie&Recursive=True&Fields=ItemCounts,ProviderIds,MediaSources", "get", - ) + ).get("Items", []) - for movie in watched["Items"] + in_progress["Items"]: - if "MediaSources" in movie and movie["MediaSources"] != {}: - if "UserData" not in movie: - continue + for movie in watched + in_progress: + # Skip if theres no user data which means the movie has not been watched + if "UserData" not in movie: + continue - # Skip if not watched or watched less than a minute - if ( - movie["UserData"]["Played"] == True - or movie["UserData"]["PlaybackPositionTicks"] > 600000000 - ): - logger( - f"{self.server_type}: Adding {movie.get('Name')} to {user_name} watched list", - 3, - ) + # Skip if theres no media tied to the movie + if "MediaSources" not in movie or movie["MediaSources"] == {}: + continue - # Get the movie's GUIDs - movie_guids = get_guids(self.server_type, movie) + # Skip if not watched or watched less than a minute + if ( + movie["UserData"]["Played"] == True + or movie["UserData"]["PlaybackPositionTicks"] > 600000000 + ): + logger( + f"{self.server_type}: Adding {movie.get('Name')} to {user_name} watched list", + 3, + ) - # Append the movie dictionary to the list for the given user and library - user_watched[user_name][library_title].append(movie_guids) - logger( - f"{self.server_type}: Added {movie_guids} to {user_name} watched list", - 3, - ) + # Get the movie's GUIDs + 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) + logger( + f"{self.server_type}: Added {movie_guids} to {user_name} watched list", + 3, + ) # TV Shows if library_type in ["Series", "Episode"]: @@ -272,21 +276,18 @@ def get_user_library_watched( f"/Users/{user_id}/Items" + f"?ParentId={library_id}&isPlaceHolder=false&IncludeItemTypes=Series&Recursive=True&Fields=ProviderIds,Path,RecursiveItemCount", "get", - ) + ).get("Items", []) # Filter the list of shows to only include those that have been partially or fully watched watched_shows_filtered = [] - for show in watched_shows["Items"]: - if not "UserData" in show: + for show in watched_shows: + if "UserData" not in show: continue if "PlayedPercentage" in show["UserData"]: if show["UserData"]["PlayedPercentage"] > 0: watched_shows_filtered.append(show) - # Create a list of tasks to retrieve the episodes - watched_episodes = [] - # Retrieve the watched/partially watched list of episodes of each watched show for show in watched_shows_filtered: logger( @@ -308,54 +309,46 @@ def get_user_library_watched( show_guids = frozenset(show_guids.items()) - watched_task = self.query( + show_episodes = self.query( f"/Shows/{show['Id']}/Episodes" - + f"?userId={user_id}&isPlaceHolder=false&Filters=IsPlayed&Fields=ProviderIds,MediaSources", + + f"?userId={user_id}&isPlaceHolder=false&Fields=ProviderIds,MediaSources", "get", - ) + ).get("Items", []) - in_progress_task = self.query( - f"/Shows/{show['Id']}/Episodes" - + f"?userId={user_id}&isPlaceHolder=false&Filters=IsResumable&Fields=ProviderIds,MediaSources", - "get", - ) - watched_episodes.append(watched_task) - watched_episodes.append(in_progress_task) - - # Iterate through the watched episodes - for episodes in watched_episodes: - # If has any watched episodes - if len(episodes["Items"]) > 0: - # Create a list to store the episodes - episodes_list = [] - for episode in episodes["Items"]: - if ( - "MediaSources" in episode - and episode["MediaSources"] != {} - ): - # If watched or watched more than a minute - if ( - episode["UserData"]["Played"] == True - or episode["UserData"]["PlaybackPositionTicks"] - > 600000000 - ): - episode_guids = get_guids( - self.server_type, episode - ) - episodes_list.append(episode_guids) + # Iterate through the episodes + # Create a list to store the episodes + mark_episodes_list = [] + for episode in show_episodes: + if "UserData" not in episode: + continue - # 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 ( + "MediaSources" not in episode + or episode["MediaSources"] == {} + ): + continue - user_watched[user_name][library_title][ - show_guids - ] = episodes_list - for episode in episodes_list: - logger( - f"{self.server_type}: Added {episode} to {user_name} {show_display_name} watched list", - 1, - ) + # If watched or watched more than a minute + if ( + episode["UserData"]["Played"] == True + or episode["UserData"]["PlaybackPositionTicks"] > 600000000 + ): + episode_guids = get_guids(self.server_type, episode) + mark_episodes_list.append(episode_guids) + + 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] = [] + + user_watched[user_name][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, + ) logger( f"{self.server_type}: Got watched for {user_name} in library {library_title}", diff --git a/src/plex.py b/src/plex.py index fdec854..4f6ca32 100644 --- a/src/plex.py +++ b/src/plex.py @@ -452,7 +452,7 @@ def login(self, baseurl, token): raise Exception(e) def info(self) -> str: - return f"{self.plex.friendlyName}: {self.plex.version}" + return f"Plex {self.plex.friendlyName}: {self.plex.version}" def get_users(self): try: From 502b3616dfd16eef6d9133c6bc47a779db685aa9 Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 2 Jun 2024 23:03:17 -0600 Subject: [PATCH 3/4] Fix ci validation marklog --- test/validate_ci_marklog.py | 144 ++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 80 deletions(-) diff --git a/test/validate_ci_marklog.py b/test/validate_ci_marklog.py index 801b134..be4e535 100644 --- a/test/validate_ci_marklog.py +++ b/test/validate_ci_marklog.py @@ -73,65 +73,30 @@ def check_marklog(lines, expected_values): def main(): args = parse_args() - expected = { - "dry": [ - # Jellyfin -> Plex - "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", + 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", + ] + 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/Departure/300741", - # Plex -> Jellyfin - "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", - # Emby -> Plex - "jellyplex_watched/Movies/Tears of Steel", - "jellyplex_watched/TV shows/Doctor Who (2005)/World War Three (2)", - "jellyplex_watched/TV shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429", - # Plex -> Emby - "jellyplex_watched/Movies/Big Buck Bunny", - "jellyplex_watched/Movies/The Family Plan", - "jellyplex_watched/Movies/Killers of the Flower Moon/4", - # Emby -> Jellyfin - "JellyUser/Movies/Tears of Steel", - # Jellyfin -> Emby - "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", - ], - "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/Doctor Who (2005)/World War Three (2)", - "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/Monarch: Legacy of Monsters/Parallels and Interiors/240429", "JellyUser/Movies/Tears of Steel", "JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4", - "jellyplex_watched/TV shows/Doctor Who (2005)/World War Three (2)", - "jellyplex_watched/TV shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429", - ], - "plex": [ - "JellyUser/Movies/Big Buck Bunny", + ] + 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", @@ -140,39 +105,58 @@ def main(): "jellyplex_watched/Movies/Big Buck Bunny", "jellyplex_watched/Movies/The Family Plan", "jellyplex_watched/Movies/Killers of the Flower Moon/4", - ], - "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", - ], - "emby": [ - "jellyplex_watched/Movies/Tears of Steel", - "jellyplex_watched/TV shows/Doctor Who (2005)/World War Three (2)", - "jellyplex_watched/TV shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429", - "JellyUser/Movies/Tears of Steel", - ], - } + "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", + ] + + 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", + ] # Expected values for the mark.log file, dry-run is slightly different than write-run # due to some of the items being copied over from one server to another and now being there # for the next server run. if args.dry: - expected_values = expected["dry"] + expected_values = expected_dry elif args.write: - expected_values = expected["write"] + expected_values = expected_write elif args.plex: - expected_values = expected["plex"] + expected_values = expected_plex elif args.jellyfin: - expected_values = expected["jellyfin"] + expected_values = expected_jellyfin elif args.emby: - expected_values = expected["emby"] + expected_values = expected_emby else: print("No server specified") exit(1) From 74f29d44b33e880019915cf26a4f3a841612bd4c Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Sun, 2 Jun 2024 23:22:06 -0600 Subject: [PATCH 4/4] README: Formatting --- README.md | 106 +++++++++++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 37880ed..c73c28e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # JellyPlex-Watched -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/26b47c5db63942f28f02f207f692dc85)](https://www.codacy.com/gh/luigi311/JellyPlex-Watched/dashboard?utm_source=github.com\&utm_medium=referral\&utm_content=luigi311/JellyPlex-Watched\&utm_campaign=Badge_Grade) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/26b47c5db63942f28f02f207f692dc85)](https://www.codacy.com/gh/luigi311/JellyPlex-Watched/dashboard?utm_source=github.com&utm_medium=referral&utm_content=luigi311/JellyPlex-Watched&utm_campaign=Badge_Grade) Sync watched between jellyfin, plex and emby locally @@ -12,33 +12,33 @@ Keep in sync all your users watched history between jellyfin, plex and emby serv ### Plex -* \[x] Match via filenames -* \[x] Match via provider ids -* \[x] Map usernames -* \[x] Use single login -* \[x] One way/multi way sync -* \[x] Sync watched -* \[x] Sync in progress +- \[x] Match via filenames +- \[x] Match via provider ids +- \[x] Map usernames +- \[x] Use single login +- \[x] One way/multi way sync +- \[x] Sync watched +- \[x] Sync in progress ### Jellyfin -* \[x] Match via filenames -* \[x] Match via provider ids -* \[x] Map usernames -* \[x] Use single login -* \[x] One way/multi way sync -* \[x] Sync watched -* \[x] Sync in progress +- \[x] Match via filenames +- \[x] Match via provider ids +- \[x] Map usernames +- \[x] Use single login +- \[x] One way/multi way sync +- \[x] Sync watched +- \[x] Sync in progress ### Emby -* \[x] Match via filenames -* \[x] Match via provider ids -* \[x] Map usernames -* \[x] Use single login -* \[x] One way/multi way sync -* \[x] Sync watched -* \[x] Sync in progress +- \[x] Match via filenames +- \[x] Match via provider ids +- \[x] Map usernames +- \[x] Use single login +- \[x] One way/multi way sync +- \[x] Sync watched +- \[x] Sync in progress ## Configuration @@ -48,62 +48,62 @@ Full list of configuration options can be found in the [.env.sample](.env.sample ### Baremetal -* Setup virtualenv of your choice +- Setup virtualenv of your choice -* Install dependencies +- Install dependencies - ```bash - pip install -r requirements.txt - ``` + ```bash + pip install -r requirements.txt + ``` -* Create a .env file similar to .env.sample, uncomment whitelist and blacklist if needed, fill in baseurls and tokens +- Create a .env file similar to .env.sample, uncomment whitelist and blacklist if needed, fill in baseurls and tokens -* Run +- Run - ```bash - python main.py - ``` + ```bash + python main.py + ``` ### Docker -* Build docker image +- Build docker image - ```bash - docker build -t jellyplex-watched . - ``` + ```bash + docker build -t jellyplex-watched . + ``` -* or use pre-built image +- or use pre-built image - ```bash - docker pull luigi311/jellyplex-watched:latest - ``` + ```bash + docker pull luigi311/jellyplex-watched:latest + ``` #### With variables -* Run +- Run - ```bash - docker run --rm -it -e PLEX_TOKEN='SuperSecretToken' luigi311/jellyplex-watched:latest - ``` + ```bash + docker run --rm -it -e PLEX_TOKEN='SuperSecretToken' luigi311/jellyplex-watched:latest + ``` #### With .env -* Create a .env file similar to .env.sample and set the variables to match your setup +- Create a .env file similar to .env.sample and set the variables to match your setup -* Run +- Run - ```bash - docker run --rm -it -v "$(pwd)/.env:/app/.env" luigi311/jellyplex-watched:latest - ``` + ```bash + docker run --rm -it -v "$(pwd)/.env:/app/.env" luigi311/jellyplex-watched:latest + ``` ## Troubleshooting/Issues -* Jellyfin - * Attempt to decode JSON with unexpected mimetype, make sure you enable remote access or add your docker subnet to lan networks in jellyfin settings +- Jellyfin -* Configuration - * Do not use quotes around variables in docker compose + - Attempt to decode JSON with unexpected mimetype, make sure you enable remote access or add your docker subnet to lan networks in jellyfin settings +- Configuration + - Do not use quotes around variables in docker compose ## Contributing