Feat: continue watching feature in jellyfin

This commit is contained in:
coolnsx
2025-04-12 17:19:34 +05:30
parent 1a2d5afdb8
commit cafaa47c9a
2 changed files with 85 additions and 46 deletions

114
jellyfin
View File

@@ -24,17 +24,17 @@ err() {
}
save_config() {
if [ -f "$config_file" ] && grep -q "$1" "$config_file";then
if [ -f "$config_file" ] && grep -q "$1" "$config_file"; then
sed -i "s|$1=.*|$1=$2|g" "$config_file" >/dev/null 2>&1
else
#shellcheck disable=SC2059
printf "$1=$2\n" >> "$config_file"
printf "$1=$2\n" >>"$config_file"
sed '/^$/d' "$config_file" >/dev/null 2>&1
fi
[ -z "$3" ] && success "$1 Saved in config, to override config values use it as envs\n"
}
auth_quick_connect () {
auth_quick_connect() {
info "Generating Quick Connect Code.."
DEVICE_ID="$(uuidgen)"
custom_auth='Authorization: MediaBrowser Client="jellyfin-cli", Device="jellyfin-cli", DeviceId="'"$DEVICE_ID"'", Version="'"$VERSION"'"'
@@ -42,8 +42,7 @@ auth_quick_connect () {
info "Your Quick Connect Code: "
printf '%s\n' "$CODE"
info "Waiting for you to authorized."
while curl -s "$JF_URL/QuickConnect/Connect?Secret=$SECRET" -H "$custom_auth" | grep -q '"Authenticated":false';
do
while curl -s "$JF_URL/QuickConnect/Connect?Secret=$SECRET" -H "$custom_auth" | grep -q '"Authenticated":false'; do
printf '.'
sleep 2
done
@@ -59,8 +58,8 @@ auth_quick_connect () {
check_config_auth() {
info "Checking Config.."
[ -f "$config_file" ] || return 1
for i in JF_USER_ID JF_TOKEN JF_URL;do
if ! grep -qE "$i=(.+)" "$config_file";then
for i in JF_USER_ID JF_TOKEN JF_URL; do
if ! grep -qE "$i=(.+)" "$config_file"; then
return 1
fi
done
@@ -74,15 +73,15 @@ check_config_auth() {
configure() {
info "Welcome to Jellyfin CLI script, we will go through some configuration.\n"
[ -f "$config_file" ] && . "$config_file"
if ! [ -f "$config_file" ] || ! grep -qE "JF_URL=(.+)" "$config_file";then
if ! [ -f "$config_file" ] || ! grep -qE "JF_URL=(.+)" "$config_file"; then
ask "First, Where is Jellyfin hosted? : "
read -r JF_URL
save_config "JF_URL" "$JF_URL"
fi
if ! [ -f "$config_file" ] || ! grep -qE "JF_TOKEN=(.+)" "$config_file";then
if ! [ -f "$config_file" ] || ! grep -qE "JF_TOKEN=(.+)" "$config_file"; then
auth_quick_connect
fi
if ! [ -f "$config_file" ] || ! grep -qE "JF_USER_ID=(.+)" "$config_file";then
if ! [ -f "$config_file" ] || ! grep -qE "JF_USER_ID=(.+)" "$config_file"; then
eval "$(curl -s "$JF_URL/Users/Me" -H 'Authorization: MediaBrowser Token="'"$JF_TOKEN"'"' | sed -nE 's|.*"Id":"([^"]*)".*|JF_USER_ID=\1|p')"
save_config "JF_USER_ID" "$JF_USER_ID"
fi
@@ -91,20 +90,24 @@ configure() {
}
mpv_jellyfin() {
[ -z "$1" ] && return 0
[ -z "$2" ] && return 0
success "Playing $2 on mpv"
url="$JF_URL/Items/$1/Download?api_key=$JF_TOKEN"
sub="$JF_URL/Videos/$(printf '%s' "$1" | sed -E 's/(.{8})(.{4})(.{4})(.{4})(.{12})/\1-\2-\3-\4-\5/')/$1/Subtitles/0/0/Stream.ass?api_key=$JF_TOKEN"
! curl -s "$sub" | grep -q "Error processing request" && sub_arg="--sub-file=$sub"
#shellcheck disable=SC2086
nohup mpv --input-ipc-server="$socket" --force-media-title="$2" "$url" $sub_arg >/dev/null 2>&1 &
nohup mpv --input-ipc-server="$socket" --start="$((playbackPositionTicks / 10000000))" --force-media-title="$2" "$url" $sub_arg >/dev/null 2>&1 &
track_progress "$(printf '%s' "$1" | sed -E 's/(.{8})(.{4})(.{4})(.{4})(.{12})/\1-\2-\3-\4-\5/')"
}
track_progress() {
ITEM_ID=$1
\cat <<EOF >"$progress_track_file"
ITEM_ID=$1
\cat <<EOF >"$progress_track_file"
#!/bin/sh
positionTicks=$playbackPositionTicks
while sleep 5;do
position=\$(echo '{"command" :["get_property","playback-time"]}' | socat - "$socket" 2>/dev/null | sed -nE 's_.*data":([^,]*).*_\1_p' | tr -d '.' | sed 's|$|0|g')
[ -z "\$position" ] && break
@@ -115,16 +118,65 @@ rm "$socket"
rm "\$0"
EOF
chmod +x "$progress_track_file"
setsid -f "$progress_track_file"
chmod +x "$progress_track_file"
setsid -f "$progress_track_file"
}
get_data() {
curl -s "${JF_URL}/$1" -H 'Authorization: MediaBrowser Token="'"$JF_TOKEN"'"' -H "Accept: application/json" | sed 's|\[{|\n|g;s|},{|\n|g' | sed -nE 's|^"Name":"([^"]*)",.*,"Id":"([^"]*)".*Primary":\{?"([^"]*)".*|\2\t\3\t\1|p' | menu "$2"
curl -s "${JF_URL}/$1" -H 'Authorization: MediaBrowser Token="'"$JF_TOKEN"'"' -H "Accept: application/json" | sed 's|\[{|\n|g;s|},{|\n|g' | sed -nE 's|^"Name":"([^"]*)",.*,"Id":"([^"]*)".*"PlaybackPositionTicks":([^,]*),.*Primary":\{?"([^"]*)".*|\2\t\4\t\1\t\3|p' | menu "$2"
}
shows() {
season=$(get_data "Shows/$id/Seasons?userId=$JF_USER_ID" "Select Season >")
[ -z "$season" ] && exit 1
season_title=$(printf "%s" "$season" | cut -f3)
season_id=$(printf "%s" "$season" | cut -f1)
episode=$(get_data "Shows/$id/Episodes?seasonId=$season_id&userId=$JF_USER_ID" "Select Episode >")
[ -z "$episode" ] && exit 1
episode_title=$(printf "%s" "$episode" | cut -f3)
episode_id=$(printf "%s" "$episode" | cut -f1)
title="$title $season_title ep: $episode_title"
id=$episode_id
playbackPositionTicks=$(printf '%s' "$episode" | cut -f4)
}
collection() {
collection=$(get_data "UserViews?userId=$JF_USER_ID" "Select Collection >")
[ -z "$collection" ] && exit 1
collection_id=$(printf '%s' "$collection" | cut -f1)
collection_title=$(printf '%s' "$collection" | cut -f3 | sed 's|.$||g')
data=$(get_data "Items?IncludeItemTypes=$collection_title&Recursive=false&ParentId=$collection_id" "Select $collection_title >")
[ -z "$data" ] && exit 1
id=$(printf "%s" "$data" | cut -f1)
title=$(printf "%s" "$data" | cut -f3)
playbackPositionTicks=$(printf '%s' "$data" | cut -f4)
case "$collection_title" in
Show) shows ;;
Movie) ;; # the id and title already belongs to Movie.
esac
}
resume() {
resume=$(get_data "UserItems/Resume?enableImages=true" "Resume >")
[ -z "$resume" ] && exit 1
id=$(printf "%s" "$resume" | cut -f1)
title=$(printf "%s" "$resume" | cut -f3)
playbackPositionTicks=$(printf '%s' "$resume" | cut -f4)
}
nextUp() {
nextup=$(get_data "Shows/NextUp?enableTotalRecordCount=true&disableFirstEpisode=false&enableResumable=true" "Next UP >")
[ -z "$nextup" ] && exit 1
id=$(printf "%s" "$nextup" | cut -f1)
title=$(printf "%s" "$nextup" | cut -f3)
playbackPositionTicks=$(printf '%s' "$nextup" | cut -f4)
}
menu() {
fzf --prompt="$1" --layout=reverse --border --with-nth=3.. --preview="img2sixel '$JF_URL/items/{1}/Images/Primary?fillHeight=450&quality=96'" --preview-window=right,70%
fzf --prompt="$1" --layout=reverse --border -d'\t' --with-nth=3 --preview="img2sixel '$JF_URL/items/{1}/Images/Primary?fillHeight=450&quality=96'" --preview-window=right,70%
}
check_config_auth || configure
@@ -136,26 +188,12 @@ info ""
socket="/tmp/${0##*/}-mpvsocket"
progress_track_file="/tmp/${0##*/}-progress"
what_to_watch=$(printf "My Media\nResume\nNext Up" | fzf --prompt="Select >" --layout=reverse --border)
what_to_watch=$(get_data "UserViews?userId=$JF_USER_ID" "What To Watch? >")
[ -z "$what_to_watch" ] && exit 1
what_to_watch_id=$(printf '%s' "$what_to_watch" | cut -f1)
what_to_watch_title=$(printf '%s' "$what_to_watch" | cut -f3 | sed 's|.$||g')
case "$what_to_watch" in
'My Media') collection ;;
Resume) resume ;;
'Next Up') nextUp ;;
esac
data=$(get_data "Items?IncludeItemTypes=$what_to_watch_title&Recursive=false&ParentId=$what_to_watch_id" "Select $what_to_watch_title >")
[ -z "$data" ] && exit 1
id=$(printf "%s" "$data" | cut -f1)
title=$(printf "%s" "$data" | cut -f3)
[ "$what_to_watch_title" = "Movie" ] && mpv_jellyfin "$id" "$title" && exit 0
season=$(get_data "Shows/$id/Seasons?userId=$JF_USER_ID" "Select Season >")
[ -z "$season" ] && exit 1
season_title=$(printf "%s" "$season" | cut -f3)
season_id=$(printf "%s" "$season" | cut -f1)
episode=$(get_data "Shows/$id/Episodes?seasonId=$season_id&userId=$JF_USER_ID" "Select Episode >")
[ -z "$episode" ] && exit 1
episode_title=$(printf "%s" "$episode" | cut -f3)
episode_id=$(printf "%s" "$episode" | cut -f1)
mpv_jellyfin "$episode_id" "$title $season_title ep: $episode_title"
mpv_jellyfin "$id" "$title"