diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | jellystack_lib.py | 85 |
2 files changed, 83 insertions, 3 deletions
@@ -1 +1,2 @@ __pycache__/ +play.py diff --git a/jellystack_lib.py b/jellystack_lib.py index 194cfe2..5a96e26 100644 --- a/jellystack_lib.py +++ b/jellystack_lib.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: GPL-3.0-only # Title: jellyfin stackrpms library # Project: jellystack -# Purpose: Useful functions that use python3 jellyfin apiclient +# Purpose: Useful functions that use python3 jellyfin apiclient # History: # 2024-01 started for listing/rescanning libraries # Usage: @@ -14,13 +14,15 @@ # client.auth.login("vm4:8096","admin","0EXAMPLE0f93EXAMPLEXAMPLE534e0f6") # Reference: # from firefox devtools: -# curl 'https://albion320.no-ip.biz:500/Items/14895ee3d991844aee62d94dd46dfb7a/Refresh?Recursive=true&ImageRefreshMode=Default&MetadataRefreshMode=Default&ReplaceAllImages=false&ReplaceAllMetadata=false' -X POST -H 'X-Emby-Authorization: MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFgxMTsgTGlEXAMPLEg2XzY0OyBydjo5MS4wKSBHZWNEXAMPLEEwMDEwMSBGaXJlZm94LzkxLjB8MTYzODk3NjExMzkwMA11", Version="10.8.10", Token="0f6f671c4eEXAMPLEc219aEXAMPLE7b3" -H 'Content-Length: 0' +# curl 'https://jellyfin.example.com:500/Items/14895ee3d991844aee62d94dd46dfb7a/Refresh?Recursive=true&ImageRefreshMode=Default&MetadataRefreshMode=Default&ReplaceAllImages=false&ReplaceAllMetadata=false' -X POST -H 'X-Emby-Authorization: MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFgxMTsgTGlEXAMPLEg2XzY0OyBydjo5MS4wKSBHZWNEXAMPLEEwMDEwMSBGaXJlZm94LzkxLjB8MTYzODk3NjExMzkwMA11", Version="10.8.10", Token="0f6f671c4eEXAMPLEc219aEXAMPLE7b3" -H 'Content-Length: 0' +# https://jmshrv.com/posts/jellyfin-api/ +# swagger interface at https://jellyfin.example.com:500/api-docs/swagger/ # Improve: # Dependencies: # req-devuan: jellyfin-apiclient-python from jellyfin_apiclient_python import JellyfinClient -import json, os +import json, os, sys def get_authenticated_client(url = "http://vm4:8096", username = "admin", password = "None"): client = JellyfinClient() @@ -69,3 +71,80 @@ def refresh_library(library, libraries, client): "url": f"{client.config.data['auth.server']}/Items/{_use_id}/Refresh?Recursive=true&ImageRefreshMode=Default&MetadataRefreshMode=Default&ReplaceAllImages=false&ReplaceAllMetadata=false" } ) + +def is_like_id(input_id): + """ Given input_id, if it is a 32-char hexadecimal value, return true, else false. """ + try: + int(input_id, 16) + except ValueError as e: + return False + return len(input_id) == 32 + +def watched_episodes_for_show(client, library_id_or_name, show_id_or_name, season_id = -1, verbose = False): + """ + Given the show_id_or_name (show_id from the user's view in jellyfin, or exact name), return a list of the episode count watched and total episode count. If season_id is not defined (-1), then do it for all seasons. The episode-watched number is dependent on who logged in to jellyfin. + If you cannot find the exact name to use, pass verbose=True to see what it is comparing against. + WARNING: Seasons are zero-indexed, but specials (if present in your library) are number zero! + Improve: research if admin user can look at other users' views. + Examples: + + a = jellystack_lib.get_authenticated_client(url="http://vm4:8096",username="jellyfin",password="KEEPASS") + jellystack_lib.watched_episodes_for_show(a,"TV","Star Trek: Strange New Worlds",-1) + ['0/17', '2/10', '0/10'] + + """ + view_library_id = None + view_series_id = None + # validate library_id_or_name + if library_id_or_name.isdigit(): + view_library_id = library_id_or_name + else: + views = client.jellyfin.get_views() + for i in views["Items"]: + if i["Name"] == library_id_or_name: + view_library_id = i["Id"] + if verbose: + print(f"verbose: {library_id_or_name} matches {i['Id']}", file=sys.stderr) + break + elif verbose: + print(f"verbose: {library_id_or_name} does not match {i['Name']}", file=sys.stderr) + if view_library_id is None: + return "Failed to find library matching {library_id_or_name}." + # validate show_id_or_name + if is_like_id(show_id_or_name): + view_series_id = show_id_or_name + else: + library_items = client.http.request({"type":"GET","url":f"{client.config.data['auth.server']}/Users/{client.jellyfin.users()['Id']}/Items?ParentId={view_library_id}"}) + for i in library_items["Items"]: + if i["Name"] == show_id_or_name: + view_series_id = i["Id"] + if verbose: + print(f"verbose: {show_id_or_name} matches {i['Id']}", file=sys.stderr) + break + elif verbose: + print(f"verbose: {show_id_or_name} does not match {i['Name']}", file=sys.stderr) + if view_series_id is None: + return f"Failed to find show matching {show_id_or_name}." + # get season count + seasons = client.jellyfin.get_seasons(view_series_id) + season_count = seasons["TotalRecordCount"] + if season_id != -1: + # a specific season + return _watched_episodes_for_season(client, seasons["Items"][season_id]["Id"], season_id) + else: + x = 0 + # Improvement: are lists ordered in python? Is .append() good enough for order of seasons? + response = [] + while x < season_count: + response.append(_watched_episodes_for_season(client, seasons["Items"][x]["Id"], x)) + x += 1 + return response + +def _watched_episodes_for_season(client, view_season_id, season_index): + y = 0 + watched = 0 + season_info = client.http.request({"type":"GET","url":f"{client.config.data['auth.server']}/Users/{client.jellyfin.users()['Id']}/Items?ParentId={view_season_id}"}) + while y < season_info["TotalRecordCount"]: + watched += season_info["Items"][y]["UserData"]["Played"] + y += 1 + return f"{watched}/{y}" |