#!/usr/bin/env python3 # File: jellystack_lib.py # Location: /mnt/public/Support/Programs/jellyfin/scripts/ # Author: bgstack15 # Startdate: 2024-01-20-7 13:59 # SPDX-License-Identifier: GPL-3.0-only # Title: jellyfin stackrpms library # Project: jellystack # Purpose: Useful functions that use python3 jellyfin apiclient # History: # 2024-01 started for listing/rescanning libraries # Usage: # in jellystack.py # client.auth.login("vm4:8096","admin","0EXAMPLE0f93EXAMPLEXAMPLE534e0f6") # Reference: # from firefox devtools: # 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, sys def get_authenticated_client(url = "http://vm4:8096", username = "admin", password = "None"): client = JellyfinClient() # this one works basically, but somehow my previous attempt did not. client.config.app('cli','0.0.1','any-ssh-node','a56decad32c0aefd696a7d3565ac1d0') client.config.data["auth.ssl"] = False client.auth.connect_to_address(url) client.auth.login(url,username,password) credentials = client.auth.credentials.get_credentials() server = credentials["Servers"][0] server["username"] = 'username' json.dumps(server) #json.loads(credentials) #client.authenticate({"Servers": [credentials]}, discover=False) return client def get_media_folders(client): results = client.jellyfin.get_media_folders()["Items"] libraries = [{"Name":i["Name"],"Id":i["Id"]} for i in results] return libraries def libraries_to_csv(libraries): for i in libraries: print(f"\"{i['Name']}\" {i['Id']}") def get_library_names_only(libraries): return [i["Name"] for i in libraries] def refresh_library(library, libraries, client): # find id of given library _use_id = "" for i in libraries: if i["Id"] == library: _use_id = library break elif i["Name"] == library: _use_id = i["Id"] break # if not found, fail out if _use_id == "": raise f"Unable to find which library, given {library}" print(f"Will try to refresh library {_use_id}") client.http.request( { "type": "POST", "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. Season_id may also be string "sum" which will just produce a single 100/110 fraction in the output string. 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 and season_id.isdigit(): # 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 if season_id == "sum": sumx = 0 sumy = 0 for i in response: x, y = i.split("/") sumx += int(x) sumy += int(y) return f"{sumx}/{sumy}" else: 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}"