Do not mark shows/movies that do not exist

This commit is contained in:
Luigi311
2022-12-19 01:35:16 -07:00
parent 370e9bac63
commit e8faf52b2b
4 changed files with 217 additions and 109 deletions

View File

@@ -170,7 +170,9 @@ def combine_watched_dicts(dicts: list):
# If the subkey already exists in the combined dictionary, # If the subkey already exists in the combined dictionary,
# check if the values are different and raise an exception if they are # check if the values are different and raise an exception if they are
if combined_dict[key][subkey] != subvalue: if combined_dict[key][subkey] != subvalue:
raise ValueError(f"Conflicting values for subkey '{subkey}' under key '{key}'") raise ValueError(
f"Conflicting values for subkey '{subkey}' under key '{key}'"
)
else: else:
# If the subkey does not exist in the combined dictionary, add it # If the subkey does not exist in the combined dictionary, add it
combined_dict[key][subkey] = subvalue combined_dict[key][subkey] = subvalue
@@ -218,22 +220,56 @@ def cleanup_watched(
for show_key_1 in watched_list_1[user_1][library_1].keys(): for show_key_1 in watched_list_1[user_1][library_1].keys():
show_key_dict = dict(show_key_1) show_key_dict = dict(show_key_1)
for season in watched_list_1[user_1][library_1][show_key_1]: for season in watched_list_1[user_1][library_1][show_key_1]:
for episode in watched_list_1[user_1][library_1][show_key_1][season]: for episode in watched_list_1[user_1][library_1][show_key_1][
if is_episode_in_dict(episode, episode_watched_list_2_keys_dict): season
if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]: ]:
logger(f"Removing {episode} from {show_key_dict['title']}", 3) if is_episode_in_dict(
modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode) episode, episode_watched_list_2_keys_dict
):
if (
episode
in modified_watched_list_1[user_1][library_1][
show_key_1
][season]
):
logger(
f"Removing {episode} from {show_key_dict['title']}",
3,
)
modified_watched_list_1[user_1][library_1][
show_key_1
][season].remove(episode)
# Remove empty seasons # Remove empty seasons
if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0: if (
if season in modified_watched_list_1[user_1][library_1][show_key_1]: len(
logger(f"Removing {season} from {show_key_dict['title']} because it is empty", 3) modified_watched_list_1[user_1][library_1][show_key_1][
del modified_watched_list_1[user_1][library_1][show_key_1][season] season
]
)
== 0
):
if (
season
in modified_watched_list_1[user_1][library_1][
show_key_1
]
):
logger(
f"Removing {season} from {show_key_dict['title']} because it is empty",
3,
)
del modified_watched_list_1[user_1][library_1][
show_key_1
][season]
# Remove empty shows # Remove empty shows
if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0: if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0:
if show_key_1 in modified_watched_list_1[user_1][library_1]: if show_key_1 in modified_watched_list_1[user_1][library_1]:
logger(f"Removing {show_key_dict['title']} because it is empty", 3) logger(
f"Removing {show_key_dict['title']} because it is empty",
3,
)
del modified_watched_list_1[user_1][library_1][show_key_1] del modified_watched_list_1[user_1][library_1][show_key_1]
for user_1 in watched_list_1: for user_1 in watched_list_1:
@@ -280,7 +316,7 @@ def is_movie_in_dict(movie, movies_watched_list_2_keys_dict):
# If the movie_value is in the movies_watched_list_2_keys_dict dictionary, return True # If the movie_value is in the movies_watched_list_2_keys_dict dictionary, return True
if movie_value in movies_watched_list_2_keys_dict[movie_key]: if movie_value in movies_watched_list_2_keys_dict[movie_key]:
return True return True
# If the loop completes without finding a match, return False # If the loop completes without finding a match, return False
return False return False
@@ -306,6 +342,7 @@ def is_episode_in_dict(episode, episode_watched_list_2_keys_dict):
# If the loop completes without finding a match, return False # If the loop completes without finding a match, return False
return False return False
def future_thread_executor(args: list, workers: int = -1): def future_thread_executor(args: list, workers: int = -1):
futures_list = [] futures_list = []
results = [] results = []

View File

@@ -85,8 +85,9 @@ class Jellyfin:
f"Jellyfin: Generating watched for {user_name} in library {library_title}", f"Jellyfin: Generating watched for {user_name} in library {library_title}",
0, 0,
) )
# Movies
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
# Movies
if library_type == "Movie": if library_type == "Movie":
user_watched[user_name][library_title] = [] user_watched[user_name][library_title] = []
watched = await self.query( watched = await self.query(
@@ -95,16 +96,27 @@ class Jellyfin:
"get", "get",
session, session,
) )
for movie in watched["Items"]: for movie in watched["Items"]:
if movie["UserData"]["Played"] is True: # Check if the movie has been played
movie_guids = {} if (
movie_guids["title"] = movie["Name"] movie["UserData"]["Played"] is True
and "MediaSources" in movie
and movie["MediaSources"] is not {}
):
# Create a dictionary for the movie with its title
movie_guids = {"title": movie["Name"]}
# If the movie has provider IDs, add them to the dictionary
if "ProviderIds" in movie: if "ProviderIds" in movie:
# Lowercase movie["ProviderIds"] keys movie_guids.update(
movie_guids = { {
k.lower(): v k.lower(): v
for k, v in movie["ProviderIds"].items() for k, v in movie["ProviderIds"].items()
} }
)
# If the movie has media sources, add them to the dictionary
if "MediaSources" in movie: if "MediaSources" in movie:
movie_guids["locations"] = tuple( movie_guids["locations"] = tuple(
[ [
@@ -112,22 +124,31 @@ class Jellyfin:
for x in movie["MediaSources"] for x in movie["MediaSources"]
] ]
) )
# Append the movie dictionary to the list for the given user and library
user_watched[user_name][library_title].append(movie_guids) user_watched[user_name][library_title].append(movie_guids)
# TV Shows # TV Shows
if library_type == "Series": if library_type == "Series":
# Initialize an empty dictionary for the given user and library
user_watched[user_name][library_title] = {} user_watched[user_name][library_title] = {}
# Retrieve a list of watched TV shows
watched_shows = await self.query( watched_shows = await self.query(
f"/Users/{user_id}/Items" f"/Users/{user_id}/Items"
+ f"?ParentId={library_id}&isPlaceHolder=false&Fields=ProviderIds,Path,RecursiveItemCount", + f"?ParentId={library_id}&isPlaceHolder=false&Fields=ProviderIds,Path,RecursiveItemCount",
"get", "get",
session, session,
) )
# Filter the list of shows to only include those that have been partially or fully watched
watched_shows_filtered = [] watched_shows_filtered = []
for show in watched_shows["Items"]: for show in watched_shows["Items"]:
if "PlayedPercentage" in show["UserData"]: if "PlayedPercentage" in show["UserData"]:
if show["UserData"]["PlayedPercentage"] > 0: if show["UserData"]["PlayedPercentage"] > 0:
watched_shows_filtered.append(show) watched_shows_filtered.append(show)
# Create a list of tasks to retrieve the seasons of each watched show
seasons_tasks = [] seasons_tasks = []
for show in watched_shows_filtered: for show in watched_shows_filtered:
show_guids = { show_guids = {
@@ -136,21 +157,26 @@ class Jellyfin:
show_guids["title"] = show["Name"] show_guids["title"] = show["Name"]
show_guids["locations"] = tuple([show["Path"].split("/")[-1]]) show_guids["locations"] = tuple([show["Path"].split("/")[-1]])
show_guids = frozenset(show_guids.items()) show_guids = frozenset(show_guids.items())
identifiers = {"show_guids": show_guids, "show_id": show["Id"]} show_identifiers = {
task = asyncio.ensure_future( "show_guids": show_guids,
"show_id": show["Id"],
}
season_task = asyncio.ensure_future(
self.query( self.query(
f"/Shows/{show['Id']}/Seasons" f"/Shows/{show['Id']}/Seasons"
+ f"?userId={user_id}&isPlaceHolder=false&Fields=ProviderIds,RecursiveItemCount", + f"?userId={user_id}&isPlaceHolder=false&Fields=ProviderIds,RecursiveItemCount",
"get", "get",
session, session,
frozenset(identifiers.items()), frozenset(show_identifiers.items()),
) )
) )
seasons_tasks.append(task) seasons_tasks.append(season_task)
# Retrieve the seasons for each watched show
seasons_watched = await asyncio.gather(*seasons_tasks) seasons_watched = await asyncio.gather(*seasons_tasks)
seasons_watched_filtered = []
# Filter the list of seasons to only include those that have been partially or fully watched
seasons_watched_filtered = []
for seasons in seasons_watched: for seasons in seasons_watched:
seasons_watched_filtered_dict = {} seasons_watched_filtered_dict = {}
seasons_watched_filtered_dict["Identifiers"] = seasons[ seasons_watched_filtered_dict["Identifiers"] = seasons[
@@ -169,6 +195,7 @@ class Jellyfin:
seasons_watched_filtered_dict seasons_watched_filtered_dict
) )
# Create a list of tasks to retrieve the episodes of each watched season
episodes_tasks = [] episodes_tasks = []
for seasons in seasons_watched_filtered: for seasons in seasons_watched_filtered:
if len(seasons["Items"]) > 0: if len(seasons["Items"]) > 0:
@@ -176,7 +203,7 @@ class Jellyfin:
season_identifiers = dict(seasons["Identifiers"]) season_identifiers = dict(seasons["Identifiers"])
season_identifiers["season_id"] = season["Id"] season_identifiers["season_id"] = season["Id"]
season_identifiers["season_name"] = season["Name"] season_identifiers["season_name"] = season["Name"]
task = asyncio.ensure_future( episode_task = asyncio.ensure_future(
self.query( self.query(
f"/Shows/{season_identifiers['show_id']}/Episodes" f"/Shows/{season_identifiers['show_id']}/Episodes"
+ f"?seasonId={season['Id']}&userId={user_id}&isPlaceHolder=false&isPlayed=true&Fields=ProviderIds,MediaSources", + f"?seasonId={season['Id']}&userId={user_id}&isPlaceHolder=false&isPlayed=true&Fields=ProviderIds,MediaSources",
@@ -185,62 +212,67 @@ class Jellyfin:
frozenset(season_identifiers.items()), frozenset(season_identifiers.items()),
) )
) )
episodes_tasks.append(task) episodes_tasks.append(episode_task)
# Retrieve the episodes for each watched season
watched_episodes = await asyncio.gather(*episodes_tasks) watched_episodes = await asyncio.gather(*episodes_tasks)
for episodes in watched_episodes:
if len(episodes["Items"]) > 0:
for episode in episodes["Items"]:
if episode["UserData"]["Played"] is True:
if (
"ProviderIds" in episode
or "MediaSources" in episode
):
episode_identifiers = dict(
episodes["Identifiers"]
)
show_guids = episode_identifiers["show_guids"]
if (
show_guids
not in user_watched[user_name][
library_title
]
):
user_watched[user_name][library_title][
show_guids
] = {}
if (
episode_identifiers["season_name"]
not in user_watched[user_name][
library_title
][show_guids]
):
user_watched[user_name][library_title][
show_guids
][episode_identifiers["season_name"]] = []
episode_guids = {} # Iterate through the watched episodes
if "ProviderIds" in episode: for episodes in watched_episodes:
episode_guids = { # If the season has any watched episodes
k.lower(): v if len(episodes["Items"]) > 0:
for k, v in episode[ # Create a dictionary for the season with its identifier and episodes
"ProviderIds" season_dict = {}
].items() season_dict["Identifiers"] = dict(episodes["Identifiers"])
} season_dict["Episodes"] = []
if "MediaSources" in episode: for episode in episodes["Items"]:
episode_guids["locations"] = tuple( if (
[ episode["UserData"]["Played"] is True
x["Path"].split("/")[-1] and "MediaSources" in episode
for x in episode["MediaSources"] and episode["MediaSources"] is not {}
] ):
) # Create a dictionary for the episode with its provider IDs and media sources
user_watched[user_name][library_title][ episode_dict = {
show_guids k.lower(): v
][episode_identifiers["season_name"]].append( for k, v in episode["ProviderIds"].items()
episode_guids }
) episode_dict["title"] = episode["Name"]
episode_dict["locations"] = tuple(
logger(f"Jellyfin: Got watched for {user_name} in library {library_title}") [
x["Path"].split("/")[-1]
for x in episode["MediaSources"]
]
)
# Add the episode dictionary to the season's list of episodes
season_dict["Episodes"].append(episode_dict)
# 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"]
] = {}
if (
season_dict["Identifiers"]["season_name"]
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_name"]] = []
user_watched[user_name][library_title][
season_dict["Identifiers"]["show_guids"]
][season_dict["Identifiers"]["season_name"]] = season_dict[
"Episodes"
]
logger(
f"Jellyfin: Got watched for {user_name} in library {library_title}", 1
)
return user_watched return user_watched
except Exception as e: except Exception as e:
logger( logger(

View File

@@ -13,6 +13,7 @@ from src.jellyfin import Jellyfin
load_dotenv(override=True) load_dotenv(override=True)
def setup_black_white_lists( def setup_black_white_lists(
blacklist_library: str, blacklist_library: str,
whitelist_library: str, whitelist_library: str,
@@ -310,9 +311,28 @@ def generate_server_connections():
return servers return servers
def get_server_watched(server_connection: list, users: dict, blacklist_library: list, whitelist_library: list, blacklist_library_type: list, whitelist_library_type: list, library_mapping: dict):
def get_server_watched(
server_connection: list,
users: dict,
blacklist_library: list,
whitelist_library: list,
blacklist_library_type: list,
whitelist_library_type: list,
library_mapping: dict,
):
if server_connection[0] == "plex": if server_connection[0] == "plex":
return server_connection[1].get_watched( return server_connection[1].get_watched(
users,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
)
elif server_connection[0] == "jellyfin":
return asyncio.run(
server_connection[1].get_watched(
users, users,
blacklist_library, blacklist_library,
whitelist_library, whitelist_library,
@@ -320,22 +340,27 @@ def get_server_watched(server_connection: list, users: dict, blacklist_library:
whitelist_library_type, whitelist_library_type,
library_mapping, library_mapping,
) )
elif server_connection[0] == "jellyfin": )
return asyncio.run(server_connection[1].get_watched(
users,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
))
def update_server_watched(server_connection: list, server_watched_filtered: dict, user_mapping: dict, library_mapping: dict, dryrun: bool): def update_server_watched(
server_connection: list,
server_watched_filtered: dict,
user_mapping: dict,
library_mapping: dict,
dryrun: bool,
):
if server_connection[0] == "plex": if server_connection[0] == "plex":
server_connection[1].update_watched(server_watched_filtered, user_mapping, library_mapping, dryrun) server_connection[1].update_watched(
server_watched_filtered, user_mapping, library_mapping, dryrun
)
elif server_connection[0] == "jellyfin": elif server_connection[0] == "jellyfin":
asyncio.run(server_connection[1].update_watched(server_watched_filtered, user_mapping, library_mapping, dryrun)) asyncio.run(
server_connection[1].update_watched(
server_watched_filtered, user_mapping, library_mapping, dryrun
)
)
def main_loop(): def main_loop():
logfile = os.getenv("LOGFILE", "log.log") logfile = os.getenv("LOGFILE", "log.log")
@@ -448,11 +473,19 @@ def main_loop():
) )
update_server_watched( update_server_watched(
server_1, server_2_watched_filtered, user_mapping, library_mapping, dryrun server_1,
server_2_watched_filtered,
user_mapping,
library_mapping,
dryrun,
) )
update_server_watched( update_server_watched(
server_2, server_1_watched_filtered, user_mapping, library_mapping, dryrun server_2,
server_1_watched_filtered,
user_mapping,
library_mapping,
dryrun,
) )

View File

@@ -15,11 +15,14 @@ from src.functions import (
# Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186 # Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186
class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter): class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter):
def init_poolmanager(self, connections, maxsize, block=..., **pool_kwargs): def init_poolmanager(self, connections, maxsize, block=..., **pool_kwargs):
self.poolmanager = PoolManager(num_pools=connections, self.poolmanager = PoolManager(
maxsize=maxsize, num_pools=connections,
block=block, maxsize=maxsize,
assert_hostname=False, block=block,
**pool_kwargs) assert_hostname=False,
**pool_kwargs,
)
def get_user_library_watched(user, user_plex, library): def get_user_library_watched(user, user_plex, library):
try: try:
@@ -39,8 +42,9 @@ def get_user_library_watched(user, user_plex, library):
for video in library_videos.search(unwatched=False): for video in library_videos.search(unwatched=False):
movie_guids = {} movie_guids = {}
for guid in video.guids: for guid in video.guids:
guid_source = re.search(r"(.*)://", guid.id).group(1).lower() # Extract source and id from guid.id
guid_id = re.search(r"://(.*)", guid.id).group(1) m = re.match(r"(.*)://(.*)", guid.id)
guid_source, guid_id = m.group(1).lower(), m.group(2)
movie_guids[guid_source] = guid_id movie_guids[guid_source] = guid_id
movie_guids["title"] = video.title movie_guids["title"] = video.title
@@ -68,7 +72,7 @@ def get_user_library_watched(user, user_plex, library):
[x.split("/")[-1] for x in show.locations] [x.split("/")[-1] for x in show.locations]
) )
show_guids = frozenset(show_guids.items()) show_guids = frozenset(show_guids.items())
# Get all watched episodes for show # Get all watched episodes for show
episode_guids = {} episode_guids = {}
for episode in show.watched(): for episode in show.watched():
@@ -86,15 +90,15 @@ def get_user_library_watched(user, user_plex, library):
if episode.parentTitle not in episode_guids: if episode.parentTitle not in episode_guids:
episode_guids[episode.parentTitle] = [] episode_guids[episode.parentTitle] = []
episode_guids[episode.parentTitle].append(episode_guids_temp) episode_guids[episode.parentTitle].append(episode_guids_temp)
if episode_guids: if episode_guids:
# append show, season, episode # append show, season, episode
if show_guids not in user_watched[user_name][library.title]: 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] = {}
user_watched[user_name][library.title][show_guids] = episode_guids user_watched[user_name][library.title][show_guids] = episode_guids
logger(f"Plex: Got watched for {user_name} in library {library.title}", 0) logger(f"Plex: Got watched for {user_name} in library {library.title}", 1)
return user_watched return user_watched
except Exception as e: except Exception as e:
logger( logger(
@@ -316,7 +320,9 @@ class Plex:
user_plex = self.plex user_plex = self.plex
else: else:
user_plex = self.login( user_plex = self.login(
self.plex._baseurl, user.get_token(self.plex.machineIdentifier), self.ssl_bypass self.plex._baseurl,
user.get_token(self.plex.machineIdentifier),
self.ssl_bypass,
) )
libraries = user_plex.library.sections() libraries = user_plex.library.sections()
@@ -412,7 +418,7 @@ class Plex:
continue continue
else: else:
logger( logger(
f"Plex: Library {library} not found in library list", f"Plex: Library {library} not found in library list",
1, 1,
) )
continue continue