aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2024-01-31 08:43:29 -0500
committerB. Stack <bgstack15@gmail.com>2024-01-31 08:43:29 -0500
commitb1ed46c64e689e0f22f1e94018a17f5c129d82a3 (patch)
tree8cfd3844ce825f93c0b00e0a2084343b0b2a3a52
downloadjellystack-b1ed46c64e689e0f22f1e94018a17f5c129d82a3.tar.gz
jellystack-b1ed46c64e689e0f22f1e94018a17f5c129d82a3.tar.bz2
jellystack-b1ed46c64e689e0f22f1e94018a17f5c129d82a3.zip
initial commit
-rw-r--r--.gitignore1
-rw-r--r--autocomplete-rescan.bash74
-rw-r--r--example-config-jellystack4
-rwxr-xr-xjellystack.py116
-rw-r--r--jellystack_lib.py71
-rwxr-xr-xrescan-library.sh28
6 files changed, 294 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c18dd8d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__/
diff --git a/autocomplete-rescan.bash b/autocomplete-rescan.bash
new file mode 100644
index 0000000..28787b2
--- /dev/null
+++ b/autocomplete-rescan.bash
@@ -0,0 +1,74 @@
+#!/bin/bash
+# File: autocomplete-rescan.bash
+# Location: /mnt/public/Support/Programs/jellyfin/scripts/
+# Author: bgstack15, pawamoy
+# Startdate: 2024-01-28-1 21:40
+# SPDX-License-Identifier: CC-BY-SA-4.0
+# Title: Bash autocompletion for rescan-library
+# Project: jellystack
+# Purpose: Make it easy to choose a jellyfin library to rescan
+# History:
+# Usage:
+# dot-source it and then alias the script:
+# . /mnt/public/Support/Programs/jellyfin/autocmplete-rescan.bash
+# alias rescan-library=/mnt/public/Support/Programs/jellyfin/rescan-library.sh
+# Reference:
+# 1. https://stackoverflow.com/questions/44453510/how-to-autocomplete-a-bash-commandline-with-file-paths-from-a-specific-directory/47799826#47799826
+# 2. https://stackoverflow.com/questions/1146098/properly-handling-spaces-and-quotes-in-bash-completion
+# Improve:
+# Dependencies:
+# password stored in ~/.jellyfin.password
+# jellystack.py frontend and jellystack_lib.py
+
+_complete_specific_path() {
+ # ripped from https://stackoverflow.com/questions/44453510/how-to-autocomplete-a-bash-commandline-with-file-paths-from-a-specific-directory/47799826#47799826 and modified for jellystack
+ # alt which did not work: https://stackoverflow.com/questions/1146098/properly-handling-spaces-and-quotes-in-bash-completion
+ /mnt/public/Support/Programs/jellyfin/scripts/jellystack.py --autocomplete 1>/dev/null # populates ~/.cache/jellystack
+ # declare variables
+ local _item _COMPREPLY _old_pwd
+ thisdir=~/.cache/jellystack
+
+ # if we already are in the completed directory, skip this part
+ _old_pwd="${PWD}"
+ # magic here: go the specific directory!
+ pushd "${thisdir}" &>/dev/null || return
+
+ # init completion and run _filedir inside specific directory
+ _init_completion -s || return
+ _filedir
+
+ # iterate on original replies
+ for _item in "${COMPREPLY[@]}"; do
+ # this check seems complicated, but it handles the case
+ # where you have files/dirs of the same name
+ # in the current directory and in the completed one:
+ # we want only one "/" appended
+ #if [ -d "${_item}" ] && [[ "${_item}" != */ ]] && [ ! -d "${_old_pwd}/${_item}" ]; then
+ # # append a slash if directory
+ # _COMPREPLY+=("${_item}/")
+ #else
+ _COMPREPLY+=("${_item}")
+ #fi
+ done
+
+ # popd as early as possible
+ popd &>/dev/null
+
+ # if only one reply and it is a directory, don't append a space
+ # (don't know why we must check for length == 2 though)
+ if [ ${#_COMPREPLY[@]} -eq 2 ]; then
+ if [[ "${_COMPREPLY}" == */ ]]; then
+ compopt -o nospace
+ fi
+ fi
+
+ # set the values in the right COMPREPLY variable
+ COMPREPLY=( "${_COMPREPLY[@]}" )
+
+ # clean up
+ unset _COMPREPLY
+ unset _item
+}
+
+complete -F _complete_specific_path rescan-library
+alias rescan-library="$( dirname "$( readlink -f "${0}" )" )/rescan-library.sh"
diff --git a/example-config-jellystack b/example-config-jellystack
new file mode 100644
index 0000000..0e03d98
--- /dev/null
+++ b/example-config-jellystack
@@ -0,0 +1,4 @@
+# file: ~/.config/jellystack
+# Also put password in ~/.jellyfin.password for the autocomplete to work
+# Omit the trailing slash on the server name.
+export password="eXamplePw$" username="admin" server="http://example.com:8096"
diff --git a/jellystack.py b/jellystack.py
new file mode 100755
index 0000000..bb9e2ac
--- /dev/null
+++ b/jellystack.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+# File: jellystack.py
+# Location: /mnt/public/Support/Programs/jellyfin/scripts/
+# Author: bgstack15
+# Startdate: 2024-01-29-2 09:25
+# SPDX-License-Identifier: GPL-3.0-only
+# Title: jellyfin stackrpms cli frontend
+# Project: jellystack
+# Purpose: List and rescan individual libraries in jellyfin easier than navigating web ui
+# History:
+# Usage:
+# from rescan-library.sh
+# Reference:
+# https://stackoverflow.com/questions/8258145/in-python-check-if-file-modification-time-is-older-than-a-specific-datetime
+# Improve:
+# Dependencies:
+# jellystack_lib.py, python>=3.4
+# Documentation:
+import jellystack_lib as js, os, argparse, shutil, sys
+from datetime import datetime, timedelta
+from pathlib import Path # python>=3.4
+js_debug = False
+
+# Ref: https://stackoverflow.com/questions/8258145/in-python-check-if-file-modification-time-is-older-than-a-specific-datetime
+def is_file_older_than (file, delta):
+ cutoff = datetime.utcnow() - delta
+ mtime = datetime.utcfromtimestamp(os.path.getmtime(file))
+ if mtime < cutoff:
+ return True
+ return False
+
+def check_cached_library_names(clear_cache = False):
+ """
+ If cache is absent or older than 23 hours, generate new cache of library names
+ """
+ cache_dir = os.path.join(os.path.expanduser("~"),".cache","jellystack")
+ _clear_cache = clear_cache
+ _cache_is_empty = True
+ try:
+ os.mkdir(cache_dir)
+ except:
+ pass
+ for thisdir, subdir, files in os.walk(cache_dir):
+ for tf in files:
+ if is_file_older_than(os.path.join(thisdir,tf), timedelta(hours=23)):
+ _clear_cache = True
+ if js_debug:
+ print(f"found too-old file {tf}, will clear the cache.",file=sys.stderr)
+ break
+ if _clear_cache:
+ shutil.rmtree(cache_dir)
+ if js_debug:
+ print(f"Clearing cache...",file=sys.stderr)
+ try:
+ os.mkdir(cache_dir)
+ except:
+ pass
+ # populate cache
+ try:
+ for thisdir, subdir, files in os.walk(cache_dir):
+ if len(files) > 0:
+ _cache_is_empty = False
+ except:
+ pass
+ if _cache_is_empty:
+ if js_debug:
+ print(f"Generating cache...",file=sys.stderr)
+ client = get_client()
+ lib_names = js.get_library_names_only(js.get_media_folders(client))
+ #print(lib_names)
+ for name in lib_names:
+ Path(os.path.join(cache_dir,name)).touch()
+ # and now, list all the files in that path
+ for thisdir, subdir, files in os.walk(cache_dir):
+ for tf in files:
+ print(tf)
+
+def get_client():
+ try:
+ client = js.get_authenticated_client(server,user,pw)
+ except:
+ raise Exception("Check password or url?")
+ return client
+
+if "__main__" == __name__:
+ server = os.environ.get("server","http://vm4:8096")
+ user = os.environ.get("username","admin")
+ pw = os.environ.get("password","none")
+ pwfile = os.path.join(os.path.expanduser("~"),".jellyfin.password")
+ if pw == "none":
+ #raise Exception(f"Set env var \"password\" and try again.")
+ if os.path.exists(pwfile):
+ try:
+ with open(pwfile,"r") as o:
+ pw = o.read().strip()
+ # the strip helps remove the newline that most people stick at the end of passwords in a passwordfile, and I seriously doubt the newline is part of the password.
+ except:
+ pass
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--autocomplete",action="store_true",help="print library names, using the cache.")
+ parser.add_argument("--clear-cache",action="store_true",help="print library names, using the cache.")
+ parser.add_argument("--library",help="Rescan this library by name or uuid.")
+ args = parser.parse_args()
+ if type(args.library) == str:
+ args.library = args.library.rstrip("/") # to help with the bash autocomplete which always wants to add a trailing slash, if PWD has a directory named exactly the same as the library. I cannot find a way around it.
+ #print(args,file=sys.stderr)
+ if args.autocomplete:
+ check_cached_library_names(args.clear_cache)
+ else:
+ client = get_client()
+ folders = js.get_media_folders(client)
+ if args.library is not None:
+ js.refresh_library(args.library, folders, client)
+ else:
+ print("Use --autocomplete, or one of these for --library:")
+ js.libraries_to_csv(folders)
diff --git a/jellystack_lib.py b/jellystack_lib.py
new file mode 100644
index 0000000..194cfe2
--- /dev/null
+++ b/jellystack_lib.py
@@ -0,0 +1,71 @@
+#!/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://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'
+# Improve:
+# Dependencies:
+# req-devuan: jellyfin-apiclient-python
+
+from jellyfin_apiclient_python import JellyfinClient
+import json, os
+
+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"
+ }
+ )
diff --git a/rescan-library.sh b/rescan-library.sh
new file mode 100755
index 0000000..230c78b
--- /dev/null
+++ b/rescan-library.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# File: rescan-library.sh
+# Location: /mnt/public/Support/Programs/jellyfin/scripts/
+# Author: bgstack15
+# Startdate: 2024-01-20-7 13:09
+# SPDX-License-Identifier: GPL-3.0-only
+# Title: rescan-library utility for jellyfin
+# Project: jellystack
+# Purpose: oneliner to rescan a specific library so I don't have to navigate web ui
+# History:
+# Usage:
+# . /mnt/public/Support/Programs/jellyfin/scripts/autocmplete-rescan.bash
+# alias rescan-library=/mnt/public/Support/Programs/jellyfin/scripts/rescan-library.sh
+# rescan-library <TAB> for autocomplete
+# Reference:
+# Improve:
+# Dependencies:
+# jellystack.py, jellystack_lib.py, python3
+# Documentation:
+. ~/.config/jellystack
+test -z "${password}" && export password="example"
+test -z "${username}" && export username="admin"
+test -z "${server}" && export server="http://example.com:8096" # omit the slash
+executable="$( dirname "$( readlink -f "${0}" )" )/jellystack.py"
+for word in "${@}" ;
+do
+ python3 "${executable}" --library "${word}"
+done
bgstack15