From 158983b29ad4a2027845c24070791dc2c66dc249 Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Mon, 19 Feb 2024 13:47:56 -0500 Subject: add show-manager and its autocomplete --- .gitignore | 2 + autocomplete-rescan.bash | 74 -------------------------- input/strangenewworlds.conf.example | 12 +++++ jellystack-autocomplete.bash | 89 +++++++++++++++++++++++++++++++ jellystack_lib.py | 12 ++++- show-manager.sh | 103 ++++++++++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 75 deletions(-) delete mode 100644 autocomplete-rescan.bash create mode 100644 input/strangenewworlds.conf.example create mode 100644 jellystack-autocomplete.bash create mode 100755 show-manager.sh diff --git a/.gitignore b/.gitignore index 5fb4e00..f390e0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ __pycache__/ play.py +.*.swp +*.conf diff --git a/autocomplete-rescan.bash b/autocomplete-rescan.bash deleted file mode 100644 index 06af729..0000000 --- a/autocomplete-rescan.bash +++ /dev/null @@ -1,74 +0,0 @@ -#!/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="/mnt/public/Support/Programs/jellyfin/scripts/rescan-library.sh" diff --git a/input/strangenewworlds.conf.example b/input/strangenewworlds.conf.example new file mode 100644 index 0000000..289e7a5 --- /dev/null +++ b/input/strangenewworlds.conf.example @@ -0,0 +1,12 @@ +# Where all the episode mkv files are +SOURCE_DIR="/mnt/public/Video/off.movies/Star Trek - Strange New Worlds (2021)" +# Where they should be placed for Jellyfin to pick them up +LINK_DIR="/mnt/public/Video/TV/Star Trek - Strange New Worlds (2021)" +# Symlink prefix, so probably the relative path from LINK_DIR to SOURCE_DIR +LINK_PREFIX="../../../off.movies/Star Trek - Strange New Worlds (2021)" +# Jellyfin library name +LIBRARY="TV" +# Jellyfin show name +SHOW="Star Trek: Strange New Worlds" +# Show directory name. Not necessarily the same as SHOW. +DIR="Star Trek - Strange New Worlds (2021)" diff --git a/jellystack-autocomplete.bash b/jellystack-autocomplete.bash new file mode 100644 index 0000000..55ffad4 --- /dev/null +++ b/jellystack-autocomplete.bash @@ -0,0 +1,89 @@ +#!/bin/bash +# File: jellystack-autocomplete.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="/mnt/public/Support/Programs/jellyfin/scripts/rescan-library.sh" + +_show_manager_confs() { + # reference: vm4:/etc/bash_completion.d/docker-nfs-check + local cur prev words cword; + _init_completion || return + _tmpfile1="$( mktemp )" + # populate list + find /mnt/public/Support/Programs/jellyfin/scripts/input -mindepth 1 -maxdepth 1 ! -type d -name '*.conf' -printf '%P\n' | sed -r -e 's/\.conf$//;' > "${_tmpfile1}" + COMPREPLY=($( compgen -W "$( cat ${_tmpfile1} )" -- "$cur" | sed -r -e "/^${prev}/d;" )) + command rm -rf "${_tmpfile1:-NOTHINGTODEL}" 1>/dev/null 2>&1 + return 0 +} && +complete -F _show_manager_confs manage-show +. /mnt/public/Support/Programs/jellyfin/scripts/show-manager.sh +alias manage-show="manage_show" diff --git a/jellystack_lib.py b/jellystack_lib.py index 5a96e26..5c5c132 100644 --- a/jellystack_lib.py +++ b/jellystack_lib.py @@ -83,6 +83,7 @@ def is_like_id(input_id): 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. @@ -128,7 +129,7 @@ def watched_episodes_for_show(client, library_id_or_name, show_id_or_name, seaso # get season count seasons = client.jellyfin.get_seasons(view_series_id) season_count = seasons["TotalRecordCount"] - if season_id != -1: + 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: @@ -138,6 +139,15 @@ def watched_episodes_for_show(client, library_id_or_name, show_id_or_name, seaso 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): diff --git a/show-manager.sh b/show-manager.sh new file mode 100755 index 0000000..4c8c4bf --- /dev/null +++ b/show-manager.sh @@ -0,0 +1,103 @@ +#!/bin/sh +# File: show-manager.sh +# Location: /mnt/public/Support/Programs/jellyfin/scripts/ +# Author: bgstack15 +# Startdate: 2024-02-18-1 17:24 +# SPDX-License-Identifier: GPL-3.0-only +# Title: Slowly add new episodes of a completed show to jellyfin +# Purpose: Check jellyfin for if the last season of a given show is greater than (x-2)/x episodes, and if so, then add that many new episodes. +# History: +# Usage: +# . ./jellystack-autocomplete.bash ; manage_show stsnw +# Improve: +# hook in to jellyfin deeper, by checking file paths of the episodes where watched=True, to know which episodes to skip trying to add +# learn how to completely clear the "watched" information about the show in jellyfin. +# Documentation: +# Assumptions: +# 1. media files are sorted in "Season 01/" type directories. +# 2. Media files are named "*s01e07 *" style which will be sorted alphabetically in episode number order. +# 3. watched episodes are in exact order! +# 4. input files are all mkv (this is to simplify filtering out non-episode files like images; it could be made more complex as needed) +# Dependencies: +# jellystack_lib with watched_episodes_for_show() + +# flow: +# get episodes-watched for last season of show (value is, e.g., "8/10" + + +# depends on content being in "Season 01/" directories. +get_episodes_watched_count() { + # input env vars: library, show, DEBUG + # output: prints watched= and total= numbers + # usage: $( library="TV" show="Star Trek: Strange New Worlds" get_episodes_watched_count ) + _pyverbose=False + test -n "${DEBUG}" && _pyverbose=True + cd /mnt/public/Support/Programs/jellyfin/scripts + . ~/.config/jellystack.viewing.user + { + python3 <<-EOF +import jellystack_lib +a = jellystack_lib.get_authenticated_client(url="${server}",username="${username}",password="${password}") +b = jellystack_lib.watched_episodes_for_show(a,"${library}","${show}","sum",${_pyverbose}) +c = b.split("/") +print(f"watched={c[0]}") +print(f"total={c[1]}") +EOF + } +} + +symlink_file() { + # input vars: infile SOURCE_DIR LINK_DIR LINK_PREFIX VERBOSE APPLY + # reference: printf '%s\n' *s01e0* | while read line ; do ln -sf "../../../off.movies/Star Trek - Strange New Worlds (2021/Season 01/${line}" "/mnt/public/Video/TV/Star Trek - Strange New Worlds (2021/Season 01/" ; done + # We now calculate season number based on file name + #season_num_str="$( \printf '%02d' "${season_num}" )" + base="$( basename "${infile}" )" + season_num_str="$( \printf '%02d' "$( echo "${base}" | grep -oE '\.*' -printf '%P\n' | sort -n +} + +# Logic flow: +# diff=total-watched +# if diff < 2, +# find the smallest-number episode files and add them in. +manage_show() { + # input vars: MAX_NEW_EPISODES SOURCE_DIR VERBOSE LIBRARY SHOW + # You may also run: manage_show "TV/Star Trek: Strange New Worlds" + conf="${1}" + test -f "/mnt/public/Support/Programs/jellyfin/scripts/input/${conf}.conf" && . "/mnt/public/Support/Programs/jellyfin/scripts/input/${conf}.conf" + # Not necessary with the conf file method + #test -z "${SHOW}" && test -z "${LIBRARY}" && { + # LIBRARY="$( echo "${1}" | awk -F'/' '{print $1}' )" + # SHOW="$( echo "${1}" | awk -F'/' '{print $2}' )" + #} + test -z "${MAX_NEW_EPISODES}" && MAX_NEW_EPISODES=2 + # this next function sets vars: watched, total + eval $( library="${LIBRARY}" show="${SHOW}" get_episodes_watched_count ) + # hardcode these to test max_new_episodes + #watched=6 total=8 + diff=$((total-watched)) + test -n "${VERBOSE}" && echo "Got watched=${watched} total=${total} diff=${diff}" 1>&2 + if test ${diff:-0} -lt ${MAX_NEW_EPISODES} ; + then + need=$((MAX_NEW_EPISODES-diff)) + echo "Must prepare next ${need} episodes." 1>&2 + w1=$((watched+1)) + end=$((watched+need)) + list_sorted_episode_files | sed -n -r -e "${w1},${end}p" | while read infile ; + do + # VERBOSE and APPLY are used in this function: + infile="${infile}" symlink_file + done + fi +} + +# Before the config file redesign, use one of these: +# These will still get "/Season 01" prepended to the file basename: +# SOURCE_DIR="/mnt/public/Video/off.movies/Star Trek - Strange New Worlds (2021)" LINK_DIR="/mnt/public/Video/TV/Star Trek - Strange New Worlds (2021)" LINK_PREFIX="../../../off.movies/Star Trek - Strange New Worlds (2021)" LIBRARY="TV" SHOW="Star Trek - Strange New Worlds (2021)" DIR="${SHOW}" manage_show -- cgit