aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--jellystack_lib.py85
2 files changed, 83 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index c18dd8d..5fb4e00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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}"
bgstack15