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

View File

@@ -42,8 +42,7 @@ auth_quick_connect () {
info "Your Quick Connect Code: " info "Your Quick Connect Code: "
printf '%s\n' "$CODE" printf '%s\n' "$CODE"
info "Waiting for you to authorized." info "Waiting for you to authorized."
while curl -s "$JF_URL/QuickConnect/Connect?Secret=$SECRET" -H "$custom_auth" | grep -q '"Authenticated":false'; while curl -s "$JF_URL/QuickConnect/Connect?Secret=$SECRET" -H "$custom_auth" | grep -q '"Authenticated":false'; do
do
printf '.' printf '.'
sleep 2 sleep 2
done done
@@ -91,12 +90,15 @@ configure() {
} }
mpv_jellyfin() { mpv_jellyfin() {
[ -z "$1" ] && return 0
[ -z "$2" ] && return 0
success "Playing $2 on mpv" success "Playing $2 on mpv"
url="$JF_URL/Items/$1/Download?api_key=$JF_TOKEN" 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" 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" ! curl -s "$sub" | grep -q "Error processing request" && sub_arg="--sub-file=$sub"
#shellcheck disable=SC2086 #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 "$(printf '%s' "$1" | sed -E 's/(.{8})(.{4})(.{4})(.{4})(.{12})/\1-\2-\3-\4-\5/')"
} }
@@ -105,6 +107,7 @@ ITEM_ID=$1
\cat <<EOF >"$progress_track_file" \cat <<EOF >"$progress_track_file"
#!/bin/sh #!/bin/sh
positionTicks=$playbackPositionTicks
while sleep 5;do 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') 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 [ -z "\$position" ] && break
@@ -120,11 +123,60 @@ setsid -f "$progress_track_file"
} }
get_data() { 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() { 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 check_config_auth || configure
@@ -136,26 +188,12 @@ info ""
socket="/tmp/${0##*/}-mpvsocket" socket="/tmp/${0##*/}-mpvsocket"
progress_track_file="/tmp/${0##*/}-progress" 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? >") case "$what_to_watch" in
[ -z "$what_to_watch" ] && exit 1 'My Media') collection ;;
what_to_watch_id=$(printf '%s' "$what_to_watch" | cut -f1) Resume) resume ;;
what_to_watch_title=$(printf '%s' "$what_to_watch" | cut -f3 | sed 's|.$||g') '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 >") mpv_jellyfin "$id" "$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"

View File

@@ -78,7 +78,8 @@ get_cookies() {
get_data() { get_data() {
lol=$(date +%s) lol=$(date +%s)
grep -q "SAPISID" "$logdir/cookies" 2>/dev/null && sapisid_hash=$(printf '%s_%s' "$lol" "$(printf '%s %s %s' "$lol" "$(sed -nE 's|.*SAPISID=([^;]*);.*|\1|p' "$logdir/cookies")" "$base_url" | sha1sum | cut -d' ' -f1)") && sapisid_header="Authorization: SAPISIDHASH $sapisid_hash" || sapisid_header="" grep -q "SAPISID" "$logdir/cookies" 2>/dev/null && sapisid_hash=$(printf '%s_%s' "$lol" "$(printf '%s %s %s' "$lol" "$(sed -nE 's|.*SAPISID=([^;]*);.*|\1|p' "$logdir/cookies")" "$base_url" | sha1sum | cut -d' ' -f1)") && sapisid_header="Authorization: SAPISIDHASH $sapisid_hash" || sapisid_header=""
curl -X POST -A "${3:-$agent}" -s "$base_url/youtubei/v1/$1?prettyPrint=false" -H "content-type:application/json" -d "$2" -e "$base_url" -b "$(cat "$cookie")" -H "$sapisid_header" output=$(curl -X POST -A "${3:-$agent}" -s "$base_url/youtubei/v1/$1?prettyPrint=false" -H "content-type:application/json" -d "$2" -e "$base_url" -b "$(cat "$cookie")" -H "$sapisid_header")
printf '%s\n' "$output" | tee -a "$logdir/logs"
} }
get_music_list() { get_music_list() {
@@ -181,7 +182,7 @@ play() {
\"racyCheckOk\": true \"racyCheckOk\": true
}" }"
audio_url=$(get_data "player" "$json" "$yt_agent" | sed -nE 's_.*itag":251,"url":"([^"]*)".*_\1_p') audio_url=$(get_data "player" "$json" "$yt_agent" | sed -nE 's_.*itag":240,"url":"([^"]*)".*_\1_p')
[ -z "$audio_url" ] && return 0 [ -z "$audio_url" ] && return 0
if [ -n "$2" ]; then if [ -n "$2" ]; then
printf "Name >> %s\n" "$title" printf "Name >> %s\n" "$title"
@@ -190,7 +191,7 @@ play() {
fi fi
curl -s "https://i.ytimg.com/vi/$id/hqdefault.jpg" -o - | magick convert - -crop 270x270+105+45 "$logdir/default.jpg" && notify-send -e -h "string:x-canonical-private-synchronous:${0##*/}" -i "$logdir/default.jpg" "Now Playing" "$title" -t 5000 curl -s "https://i.ytimg.com/vi/$id/hqdefault.jpg" -o - | magick convert - -crop 270x270+105+45 "$logdir/default.jpg" && notify-send -e -h "string:x-canonical-private-synchronous:${0##*/}" -i "$logdir/default.jpg" "Now Playing" "$title" -t 5000
pgrep -f "$socket" >/dev/null || (setsid -f mpv --really-quiet --input-ipc-server="$socket" --idle --quiet >/dev/null && sleep 1) pgrep -f "$socket" >/dev/null || (setsid -f mpv --really-quiet --input-ipc-server="$socket" --idle --quiet --user-agent="$yt_agent" >/dev/null && sleep 1)
printf '{"command":["loadfile","%s","replace"]}\n' "$audio_url" | socat - "$socket" printf '{"command":["loadfile","%s","replace"]}\n' "$audio_url" | socat - "$socket"
printf 'SONG="%s"\nARTIST="%s"\nID="%s"' "$(printf '%s' "$title" | sed 's|[^-]*$||g;s|-$||g;s| $||g;s|^ ||g;s/\\//g;s|"||g')" "$(printf '%s' "$title" | sed 's_.* - __;s| $||;s|"||g')" "$id" >"$logdir/current" printf 'SONG="%s"\nARTIST="%s"\nID="%s"' "$(printf '%s' "$title" | sed 's|[^-]*$||g;s|-$||g;s| $||g;s|^ ||g;s/\\//g;s|"||g')" "$(printf '%s' "$title" | sed 's_.* - __;s| $||;s|"||g')" "$id" >"$logdir/current"
@@ -210,9 +211,9 @@ search_play() {
[ -z "$query" ] && notify-send -e "Err.. Search query empty" -u critical -h "string:x-canonical-private-synchronous:${0##*/}" && exit 1 [ -z "$query" ] && notify-send -e "Err.. Search query empty" -u critical -h "string:x-canonical-private-synchronous:${0##*/}" && exit 1
#storing context #storing context
printf '{"client":{"clientName":"WEB_REMIX","clientVersion":"1.20250305.01.00"}}' >"$logdir/context" printf '{"client":{"clientName":"WEB_REMIX","clientVersion":"1.20250310.01.00"}}' >"$logdir/context"
#extracting your cookies so that the song list are according to your taste #extracting your cookies so that the song list are according to your taste
get_cookies #get_cookies
if ! (printf '%s' "$query" | grep -q 'https:' || printf '%s' "$query" | grep -q 'ID:'); then if ! (printf '%s' "$query" | grep -q 'https:' || printf '%s' "$query" | grep -q 'ID:'); then
#json for song search #json for song search
@@ -275,16 +276,16 @@ base_url="https://music.youtube.com"
cookie="$logdir/cookies" cookie="$logdir/cookies"
# user agents, used by script # user agents, used by script
random_no=$(head /dev/urandom | tr -dc '0-4' | cut -c1) random_no=$(head /dev/urandom | tr -dc '1-5' | cut -c1)
# web # web
agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/13$random_no.0.0.0 Safari/537.36" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/13$random_no.0.0.0 Safari/537.36"
# android # android
extra_up=$((random_no > 1)) extra_up=$((random_no > 1))
# youtube # youtube
yt_ver="19.42.41" yt_ver="20.10.40"
yt_agent="com.google.android.youtube/$yt_ver (Linux; U; Android 1$random_no) gzip" yt_agent="com.google.android.youtube/$yt_ver (Linux; U; Android 1$random_no) gzip"
# youtube music # youtube music
yt_music_ver="7.24.51" yt_music_ver="8.10.51"
yt_music_agent="com.google.android.apps.youtube.music/$yt_music_ver (Linux; U; Android 1$random_no) gzip" yt_music_agent="com.google.android.apps.youtube.music/$yt_music_ver (Linux; U; Android 1$random_no) gzip"