#!/bin/sh version_number="5.0.0" # UI # shellcheck disable=SC2086 # $1 = prompt, $2 = multi select flag (for fzf/rofi), $3 = extra flags by user menu() { case "$menu_program" in fzf) fzf --reverse --cycle --prompt "$1" $2 $3 ;; rofi) rofi -sort -dmenu -i -p "$1" $2 $3 ;; dmenu) dmenu -l 20 -p "$1" $3 ;; *) "$menu_program" $3 "$1" ;; esac } # provide menu. input format is either (ep_list) or (number \t anime_id \t anime_name) nth() { _stdin=$(cat -) [ -z "$_stdin" ] && return 1 _line_count="$(printf "%s\n" "$_stdin" | wc -l | tr -d "[:space:]")" [ "$_line_count" -eq 1 ] && printf "%s" "$_stdin" | cut -f 2,3 && return 0 _prompt="$1" [ $# -ne 1 ] && _multi_flag="$2" _line=$(printf "%s" "$_stdin" | cut -f 1,3 | tr '\t' ' ' | menu "$_prompt" "$_multi_flag" "$menu_extra_flags" | cut -d " " -f 1) _line_start=$(printf "%s" "$_line" | head -n 1) _line_end=$(printf "%s" "$_line" | tail -n 1) [ -n "$_line" ] || return 1 if [ "$_line_start" = "$_line_end" ]; then printf "%s" "$_stdin" | grep -E '^'"${_line}"'($|[[:space:]])' | cut -f 2,3 || return 1 else printf "%s" "$_stdin" | sed -n '/^'"${_line_start}"'$/,/^'"${_line_end}$"'/p' || return 1 fi } die() { printf "\33[2K\r\033[1;31m%s\033[0m\n" "$*" >&2 exit 1 } info() { printf "\33[2K\r\033[1;34m%s\033[0m\n" "$*" } help_info() { printf " Usage: %s [options] [query] %s [query] [options] %s [options] [query] [options] Options: -c, --continue Continue watching from history -d, --download Download the video instead of playing it -D, --delete Delete history -l, --logview Show logs -s, --syncplay Use Syncplay to watch with friends -S, --select-nth Select nth entry -q, --quality Specify the video quality -v, --vlc Use VLC to play the video -V, --version Show the version of the script -h, --help Show this help message and exit -e, --episode, -r, --range Specify the number of episodes to watch --dub Play dubbed version --rofi Use rofi instead of fzf for the interactive menu --dmenu Use dmenu instead of fzf for the interactive menu --skip Use ani-skip to skip the intro of the episode (mpv only) --no-detach Don't detach the player (useful for in-terminal playback, mpv only) --exit-after-play Exit the player, and return the player exit code (useful for non interactive scenarios) -N, --nextep-countdown Display a countdown to the next episode -U, --update Update the script Some example usages: %s -q 720p banana fish %s --skip -S 2 one piece %s -d -e 2 cyberpunk edgerunners %s --vlc cyberpunk edgerunners -q 1080p -e 4 %s blue lock -e 5-6 %s -e \"5 6\" blue lock \n" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" exit 0 } version_info() { printf "%s\n" "$version_number" exit 0 } # Bookkeeping update_script() { info "[branch:$branch] Checking for Update.." update="$($curl_exe --fail-with-body -sL -A "$agent" "https://raw.githubusercontent.com/pystardust/ani-cli/$branch/ani-cli")" || die "[branch:$branch] Connection error: $update" new_version="$(printf "%s" "$update" | sed -nE 's|^version_number="([^"]+)"$|\1|p')" [ -z "$new_version" ] && die "[branch:$branch] Invalid response." printf "%s" "$update" | grep -q "^version_number" || die "[branch:$branch] Invalid response." update="$(printf "%s\n" "$update" | diff -u "$0" -)" if [ -z "$update" ]; then info "[branch:$branch] Script is up to date :) ($version_number)" else if printf "%s\n" "$update" | patch "$0" -; then info "[branch:$branch] Updated: $version_number -> $new_version" else die "[branch:$branch] Failed to update. Read the above line!" fi fi exit 0 } # checks if dependencies are present dep_ch() { command -v "$1" >/dev/null || die "Program $1 not found. Please install it." } # checks for deps in order and returns the first available. format is "prog_a,prog_b,prog_c" dep_ch_failover() { [ -z "$1" ] && return 1 prog=$(printf "%s" "$1" | cut -d "," -f 1) rest=$(printf "%s" "$1" | cut -s -d "," -f 2-) command -v "$prog" >/dev/null 2>&1 && printf "%s" "$prog" && return 0 [ -e "$prog" ] && printf "%s" "$prog" && return 0 dep_ch_failover "$rest" } cleanup() { tput sgr0 # clears colors rm -f "${histfile}.new" # remove temp logfile } # SCRAPING #shellcheck disable=SC2086 anidb_curl() { $curl_exe -sL -A "$agent" --max-time 10 $cipher_flag "$@" } # search the query and give results. format is (id \t name) anidb_search() { #shellcheck disable=SC2059 _page="$(anidb_curl "$(printf "$search_api" "$1")" | tr '\n' ' ' | sed 's|SeasonsDetails]*title="([^"]+)".*|\1\t\2|p' | sed -e "s|'|'|g")" [ -n "$seasons" ] && seasons_option="\nchange_season" } # get the episodes list of the selected anime. format is (id \t ep_no) anidb_episodes() { #shellcheck disable=SC2059 _page="$(anidb_curl "$(printf "$episodes_api" "${1##*-}")")" printf "%s" "$_page" | sed 's|},{|}\n{|g' | sed -nE 's|.*"id":([0-9]+).*"number":([0-9]+).*|\1\t\2|p' } # extract the individual m3u8 links from response of embed urls anidb_m3u8() { _ep_id=$(printf "%s" "$episode_maps" | sed -nE "s|^([0-9]+)\t${1}$|\1|p") _lang="jpn" [ "$2" = "dub" ] && _lang="eng" #shellcheck disable=SC2059 _page="$(anidb_curl "$(printf "$m3u8_api" "${_ep_id}")")" _embed="$(printf "%s" "$_page" | sed 's|},{|}\n{|g' | sed -nE 's|.*'"${_lang}"'.*embed_url":"([^"]+)".*|\1|p' | sed 's|\\/|/|g' | head -n 1)" [ -z "$_embed" ] && return 1 _embed_page="$(anidb_curl "$_embed")" _m3u8_master="$(printf "%s" "$_embed_page" | sed -nE "s|.*file: '([^']*)'.*|\1|p" | head -n 1)" anidb_curl "${_m3u8_master}" | sed 's|^#EXT-X-STREAM.*x||g; s|,.*|p|g; /^#/d; $!N; s|\n| >|;/EXT-X-I-FRAME/d' } # select the quality according to the setting select_quality() { case "$1" in best) _result=$(printf "%s" "$links" | head -n 1) ;; worst) _result=$(printf "%s" "$links" | grep -E '^[0-9]{3,4}' | tail -n 1) ;; *) _result=$(printf "%s" "$links" | grep -m 1 "$1") ;; esac [ -z "$_result" ] && printf "\33[2K\r\033[1;33m%s\033[0m\n" "$1 not found, defaulting to best" >&2 && _result=$(printf "%s" "$links" | head -n 1) video_link=$(printf "%s" "$_result" | cut -d ">" -f 2) } # gets embed url, collects direct links and selects one with desired quality into $episode get_video_link() { links=$(anidb_m3u8 "$ep_no" "$mode" | sort -g -r -s) [ -z "$links" ] && die "No sources found for $mode!" info "anidb.app links fetched" select_quality "$quality" # should never trigger but just in case if printf "%s" "$ep_list" | grep -q "^$ep_no$"; then [ -z "$video_link" ] && die "Episode is released, but no valid sources!" else [ -z "$video_link" ] && die "Episode not released!" fi } # provide countdown for the next episode release both sub and dub time_until_next_ep() { animeschedule="https://animeschedule.net" _query="$(printf "%s" "$*" | tr ' ' '+')" $curl_exe -s -G "$animeschedule/api/v3/anime" --data "q=${_query}" | sed 's|},{"id"|\n|g' | while read -r _entry; do _route=$(printf "%s" "$_entry" | sed -nE 's|.*"route":"([^"]*)",.*|\1|p') _eng_title=$(printf '%s' "$_entry" | sed -nE 's|.*"english":"([^"]*)".*|\1|p') [ -z "$_eng_title" ] && _eng_title=$(printf '%s' "$_entry" | sed -nE 's|.*,"title":"([^"]*)".*|\1|p') [ -z "$_eng_title" ] && _eng_title="N/A" _jpn_title=$(printf '%s' "$_entry" | sed -nE 's|.*"romaji":"([^"]*)".*|\1|p') [ -z "$_jpn_title" ] && _jpn_title="N/A" _status=$(printf '%s' "$_entry" | sed -nE 's|.*"status":"([^"]*)".*|\1|p') [ -z "$_status" ] && _status="Unknown" case "$_status" in Ongoing) _color="33" ;; Finished) _color="32" ;; *) _color="36" ;; esac printf "Eng: %s\n" "$_eng_title" printf "Jpn: %s\n" "$_jpn_title" printf "Status: \033[1;%sm%s\033[0m\n" "$_color" "$_status" if [ "$_status" != "Finished" ]; then _page="$($curl_exe -s "$animeschedule/anime/$_route")" _sub_ep=$(printf "%s" "$_page" | sed -nE 's|.*release-time-type-subs"[^>]*>Episode ([0-9]+).*|\1|p' | head -n 1) _dub_ep=$(printf "%s" "$_page" | sed -nE 's|.*release-time-type-dub"[^>]*>Episode ([0-9]+).*|\1|p' | head -n 1) _sub_countdown=$(printf "%s" "$_page" | sed -nE 's|.*class="countdown-time" datetime="([^"]*)".*|\1|p' | head -n 1) _dub_countdown=$(printf "%s" "$_page" | sed -nE 's|.*class="countdown-time countdown-time-dub" datetime="([^"]*)".*|\1|p' | head -n 1) [ -n "$_sub_ep" ] && [ -n "$_sub_countdown" ] && printf "Next Sub: Episode %s in \033[1;%sm%s\033[0m\n" "$_sub_ep" "$_color" "$_sub_countdown" || printf "Next Sub: N/A\n" [ -n "$_dub_ep" ] && [ -n "$_dub_countdown" ] && printf "Next Dub: Episode %s in \033[1;%sm%s\033[0m\n" "$_dub_ep" "$_color" "$_dub_countdown" || printf "Next Dub: N/A\n" fi printf "\n" done exit 0 } # HISTORY backup_old_hist() { backupfile="${histfile}.v4" while IFS=" " read -r ep_no anime_id anime_title; do case "$anime_id" in *-*) printf "%s\t%s\t%s\n" "$ep_no" "$anime_id" "$anime_title" >>"${histfile}.new" ;; *) printf "%s\t%s\t%s\n" "$ep_no" "$anime_id" "$anime_title" >>"$backupfile" ;; esac done <"$histfile" mv "${histfile}.new" "$histfile" } process_hist_entry() { ep_list=$(anidb_episodes "$anime_id" | cut -f 2) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null [ -n "$ep_no" ] && printf "%s\t%s - episode %s\n" "$anime_id" "$anime_title" "$ep_no" } update_history() { if grep -q " ${anime_id} " "$histfile"; then # escape sed special chars in replacement: & and \ and delimiter | safe_title=$(printf "%s" "$anime_title" | sed 's|[&\|]|\\&|g') sed -E "s|^[^ ]+ ${anime_id} [^ ]+$|${ep_no} ${anime_id} ${safe_title}|" "$histfile" >"${histfile}.new" else cp "$histfile" "${histfile}.new" printf "%s\t%s\t%s\n" "$ep_no" "$anime_id" "$anime_title" >>"${histfile}.new" fi mv "${histfile}.new" "$histfile" } # PLAYING # shellcheck disable=SC2086 # $1 = video link, $2 = video name, $3 = extra flags download() { command -v "yt-dlp" >/dev/null && yt-dlp "$1" --no-skip-unavailable-fragments --fragment-retries infinite -N 16 -o "$download_dir/$2.mp4" $3 && return 0 ffmpeg -extension_picky 0 -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4" $3 } play_episode() { [ "$log_episode" = 1 ] && [ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && command -v logger >/dev/null && logger -t ani-cli "${anime_title} ${ep_no}" [ "$skip_intro" = 1 ] && skip_flag="$(ani-skip -i "$mal_id" -e "$canon_ep_no")" [ -z "$video_link" ] && get_video_link # wait for the previous player to close (in range selection) wait # shellcheck disable=SC2086 case "$player_function" in debug) printf "All links:\n%s\nSelected link:\n%s\n" "$links" "$video_link" ;; android_mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_link" -n is.xyz.mpv/.MPVActivity -e "title" "${anime_title} Episode ${ep_no}" $player_extra_flags >/dev/null 2>&1 & ;; android_vlc) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_link" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "${anime_title} Episode ${ep_no}" $player_extra_flags >/dev/null 2>&1 & ;; "$HOME"/.local/share/flatpak/app/io.mpv/Mpv/) flatpak run io.mpv.Mpv $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & ;; *mpv*) if [ "$no_detach" = 0 ]; then nohup $player_function $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & else $player_function $skip_flag $player_extra_flags --force-media-title="${anime_title} Episode ${ep_no}" "$video_link" mpv_exitcode=$? [ "$exit_after_play" = 1 ] && [ -z "$_range" ] && exit "$mpv_exitcode" fi ;; *iina*) if pgrep -f "IINA" >/dev/null 2>&1; then # omit --keep-running when an IINA instance exists to prevent hanging nohup $player_function $player_extra_flags --no-stdin --mpv-force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & else nohup $player_function $player_extra_flags --no-stdin --keep-running --mpv-force-media-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & fi ;; *vlc*) nohup $player_function $player_extra_flags --play-and-exit --meta-title="${anime_title} Episode ${ep_no}" "$video_link" >/dev/null 2>&1 & ;; *yncpla*) nohup $player_function $player_extra_flags "$video_link" -- --force-media-title="${anime_title} Episode ${ep_no}" >/dev/null 2>&1 & ;; download) "$player_function" "$video_link" "${anime_title} Episode ${ep_no}" $player_extra_flags ;; catt) nohup catt cast $player_extra_flags "$video_link" >/dev/null 2>&1 & ;; iSH) printf "\033]8;;vlc://%s\a~~~~~~~~~~~~~~~~~~~~\n~ Tap to open VLC ~\n~~~~~~~~~~~~~~~~~~~~\033]8;;\a\n" "$video_link" sleep 5 ;; *) nohup $player_function $player_extra_flags "$video_link" >/dev/null 2>&1 & ;; esac replay="$video_link" unset video_link update_history [ "$menu_program" != "fzf" ] && wait } play() { _start=$(printf "%s" "$ep_no" | grep -Eo '^(-1|[0-9]+(\.[0-9]+)?)') _end=$(printf "%s" "$ep_no" | grep -Eo '(-1|[0-9]+(\.[0-9]+)?)$') [ "$_start" = "-1" ] && ep_no=$(printf "%s" "$ep_list" | tail -n 1) && unset _start [ -z "$_end" ] || [ "$_end" = "$_start" ] && unset _start _end [ "$_end" = "-1" ] && _end=$(printf "%s" "$ep_list" | tail -n 1) _line_count=$(printf "%s\n" "$ep_no" | wc -l | tr -d "[:space:]") if [ "$_line_count" != 1 ] || [ -n "$_start" ]; then [ -z "$_start" ] && _start=$(printf "%s\n" "$ep_no" | head -n 1) [ -z "$_end" ] && _end=$(printf "%s\n" "$ep_no" | tail -n 1) _range=$(printf "%s\n" "$ep_list" | sed -nE "/^${_start}\$/,/^${_end}\$/p") [ -z "$_range" ] && die "Invalid range!" for i in $_range; do tput clear ep_no=$i canon_ep_no="$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/=")" info "Playing episode $ep_no..." [ "$i" = "$_end" ] && unset _range play_episode done else printf "%s" "$ep_list" | grep -q "^$ep_no$" || die "Invalid episode!" canon_ep_no="$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/=")" play_episode fi # moves up to stored position and deletes to end [ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && tput rc && tput ed } # MAIN # setup base_api="https://anidb.app" search_api="${base_api}/browse?q=%s" # search_api_alt="${base_api}/search/suggestions?q=%s" desc_api="${base_api}/anime/%s" episodes_api="${base_api}/api/frontend/anime/%s/episodes" m3u8_api="${base_api}/api/frontend/episode/%s/languages" ciphers='ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305' tls13_ciphers='TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256' agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" mode="${ANI_CLI_MODE:-sub}" download_dir="${ANI_CLI_DOWNLOAD_DIR:-.}" log_episode="${ANI_CLI_LOG:-1}" quality="${ANI_CLI_QUALITY:-best}" curl_exe=$(dep_ch_failover "curl_firefox135,curl_chrome136,curl_chrome116,curl_ff117,curl") || die "Program curl not found" case "$(uname -a | cut -d " " -f 1,3-)" in *Darwin*) cipher_flag="--ciphers $ciphers --tls13-ciphers $tls13_ciphers" player_function="${ANI_CLI_PLAYER:-$(dep_ch_failover "iina,/Applications/IINA.app/Contents/MacOS/iina-cli,mpv,vlc")}" || die 'No player found. Looked for iina, mpv and vlc' ;; # mac OS *ndroid*) player_function="${ANI_CLI_PLAYER:-android_mpv}" ;; # Android OS (termux) *MINGW* | *WSL2*) player_function="${ANI_CLI_PLAYER:-mpv.exe}" ;; # Windows OS *ish*) player_function="${ANI_CLI_PLAYER:-iSH}" curl_exe=$(dep_ch_failover "curl_safari260_ios,$curl_exe") ;; # iOS (iSH) *) player_function="${ANI_CLI_PLAYER:-$(dep_ch_failover "mpv,$HOME/.local/share/flatpak/app/io.mpv/Mpv/,vlc")}" || die 'No player found. Looked for mpv and vlc' ;; # Linux OS esac player_extra_flags="${ANI_CLI_PLAYER_FLAGS:-""}" no_detach="${ANI_CLI_NO_DETACH:-0}" exit_after_play="${ANI_CLI_EXIT_AFTER_PLAY:-0}" skip_intro="${ANI_CLI_SKIP_INTRO:-0}" menu_program="${ANI_CLI_MENU:-fzf}" menu_extra_flags="${ANI_CLI_MENU_FLAGS:-""}" # not opening from a terminal if [ ! -t 0 ]; then command -v dmenu >/dev/null && menu_program=dmenu command -v rofi >/dev/null && menu_program=rofi fi hist_dir="${ANI_CLI_HIST_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/ani-cli}" [ ! -d "$hist_dir" ] && mkdir -p "$hist_dir" histfile="$hist_dir/ani-hsts" [ ! -f "$histfile" ] && : >"$histfile" source="${ANI_CLI_DEFAULT_SOURCE:-search}" branch="${ANI_CLI_BRANCH:-master}" while [ $# -gt 0 ]; do case "$1" in -v | --vlc) case "$(uname -a | cut -d " " -f 1,3-)" in *ndroid*) player_function="android_vlc" ;; MINGW* | *WSL2*) player_function="vlc.exe" ;; *ish*) player_function="iSH" ;; *) player_function="vlc" ;; esac ;; -s | --syncplay) case "$(uname -s)" in Darwin*) player_function="/Applications/Syncplay.app/Contents/MacOS/syncplay" ;; MINGW* | *Msys) export PATH="$PATH":"/c/Program Files (x86)/Syncplay/" player_function="syncplay.exe" ;; *) player_function="syncplay" ;; esac ;; -q | --quality) [ $# -lt 2 ] && die "missing argument!" quality="$2" shift ;; -S | --select-nth) [ $# -lt 2 ] && die "missing argument!" index="$2" shift ;; -c | --continue) source=history ;; -d | --download) player_function=download ;; -D | --delete) : >"$histfile" exit 0 ;; -l | --logview) case "$(uname -s)" in Darwin*) log show --predicate 'process == "logger"' ;; Linux*) journalctl -t ani-cli ;; *) die "Logger not implemented for your platform" ;; esac exit 0 ;; -V | --version) version_info ;; -h | --help) help_info ;; -e | --episode | -r | --range) [ $# -lt 2 ] && die "missing argument!" ep_no="$2" shift ;; --dub) mode="dub" ;; --no-detach) no_detach=1 ;; --exit-after-play) exit_after_play=1 && no_detach=1 ;; --rofi) menu_program=rofi ;; --dmenu) menu_program=dmenu ;; --skip) skip_intro=1 ;; -N | --nextep-countdown) source=nextep ;; -U | --update) dep_ch "patch" branch="${2:-$branch}" update_script ;; *) query="$(printf "%s" "$query $1" | sed 's|^ ||;s| |+|g')" ;; esac shift done [ "$menu_program" = "fzf" ] && menu_multi_flag="-m" [ "$menu_program" = "rofi" ] && menu_multi_flag="-multi-select" info "Checking dependencies..." dep_ch "sed" dep_ch "grep" [ "$skip_intro" = 1 ] && dep_ch "ani-skip" dep_ch "$menu_program" # TODO: use dep_ch_failover to iterate through options case "$player_function" in debug) ;; download) dep_ch_failover "yt-dlp,ffmpeg" >/dev/null || die 'Neither yt-dlp nor ffmpeg found' ;; android*) printf "\33[2K\rChecking of players on Android is disabled.\n" ;; *iSH*) printf "\33[2K\rChecking of players on iOS is disabled\n" ;; *) dep_ch "$player_function" ;; esac # idk where to put this, so I put it here... trap 'cleanup; exit 1' INT HUP TERM # searching case "$source" in history) backup_old_hist anime_list=$(while read -r ep_no anime_id anime_title; do process_hist_entry & done <"$histfile") wait [ -z "$anime_list" ] && die "No unwatched series in history!" [ -z "${index##*[!0-9]*}" ] && anime_id=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: " | cut -f 1) [ -z "${index##*[!0-9]*}" ] || anime_id=$(printf "%s" "$anime_list" | sed -n "${index}p" | cut -f 1) [ -z "$anime_id" ] && exit 1 anime_title=$(printf "%s" "$anime_list" | grep "^$anime_id " | cut -f 2 | sed 's| - episode.*||') anidb_desc "$anime_id" || true episode_maps="$(anidb_episodes "$anime_id")" ep_list="$(printf "%s" "$episode_maps" | cut -f 2)" ep_no=$(printf "%s" "$anime_list" | grep "^$anime_id " | sed -nE 's/.*- episode (.+)$/\1/p') ;; *) if [ "$menu_program" = "fzf" ]; then while [ -z "$query" ]; do printf "\33[2K\r\033[1;36mSearch anime: \033[0m" && read -r query done else [ -z "$query" ] && query=$(: | menu "Search anime: " "" "$menu_extra_flags") [ -z "$query" ] && exit 1 fi # for checking new releases by specifying anime name [ "$source" = "nextep" ] && time_until_next_ep "$query" query=$(printf "%s" "$query" | sed 's| |+|g') anime_list=$(anidb_search "$query") [ -z "$anime_list" ] && die "No results found!" [ "$index" -eq "$index" ] 2>/dev/null && result=$(printf "%s" "$anime_list" | sed -n "${index}p") [ -z "$index" ] && result=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: ") [ -z "$result" ] && die "Invalid anime selection" anime_title="$(printf "%s" "$result" | cut -f 2)" anime_id="$(printf "%s" "$result" | cut -f 1)" anidb_desc "$anime_id" || true episode_maps="$(anidb_episodes "$anime_id")" ep_list="$(printf '%s' "$episode_maps" | cut -f 2)" [ -z "$ep_no" ] && ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$menu_multi_flag") [ -z "$ep_no" ] && die "Invalid episode selection" ;; esac # moves the cursor up one line and clears that line tput cuu1 && tput el # stores the position of cursor tput sc # playback & loop play [ "$player_function" = "download" ] || [ "$player_function" = "debug" ] && exit 0 #shellcheck disable=SC2059 while cmd=$(printf "next\nreplay\nprevious\nselect$seasons_option\nchange_quality\nquit" | nth "Playing episode $ep_no of $anime_title... "); do case "$cmd" in next) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null ;; replay) video_link="$replay" ;; previous) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{g;1!p;};h") 2>/dev/null ;; select) ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$menu_multi_flag") ;; change_season) new_anime="$(printf "%s" "$seasons" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select season: ")" [ -z "$new_anime" ] && die "Invalid season selection" anime_title="$(printf "%s" "$new_anime" | cut -f 2)" anime_id="$(printf "%s" "$new_anime" | cut -f 1)" anidb_desc "$anime_id" || true episode_maps="$(anidb_episodes "$anime_id")" ep_list="$(printf '%s' "$episode_maps" | cut -f 2)" ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$menu_multi_flag") [ -z "$ep_no" ] && die "Invalid episode selection" ;; change_quality) new_quality="$(printf "%s" "$links" | menu "Select Quality: " "" "$menu_extra_flags" | cut -d ">" -f 1)" [ -z "$new_quality" ] && die "No quality selected" select_quality "$new_quality" ;; *) cleanup exit 0 ;; esac [ -z "$ep_no" ] && die "Out of range" play done # ani-cli # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Project repository: https://github.com/pystardust/ani-cli