#!/bin/bash # DISCLAIMER: this script has been started solely by me (sayan01) # and has had contributions from others like Nicholas-Baron and # juanCortelezzi (refer to contributors on github for details) # this script is not based off of any other work by anyone else, # nor is it made in association with anyone else, regardless of # their claims. # Dependencies: mpv youtube-dl fzf rofi/dmenu gnu-grep # NOTE: if you don't have gnu grep, you can replace grep with rg # Explain usage function usage () { printf "usage: yt\n" printf " -h help\n" printf " -c channels/subscriptions\n" printf " -s query search\n" printf " -g / -r gui mode (rofi/dmenu)\n" printf " -m music mode (audio only) [dont use with -r]\n" printf " nothing use defaults (search from prompt)\n" printf "\n" printf "add channel names to the file %s to show them\n" "$sublistpath" printf "in yt -c option. First word should be channel url, optionally\n" printf "followed by tab and then anything else (channel name/description)\n" printf "channels not in sublist can be viewed by typing their url in the prompt\n" printf "\n" printf "example file format:\n" printf "markrober Mark Rober\n" printf "vsauce1 VSauce Michael Steven's Channel\n" printf "BlackGryph0n Black Gryph0n Gabriel Brown signs stuff\n" printf "TomScottGo Tom Scott\n" printf "danielthrasher Daniel Thrasher\n" exit 0 } # Use defaults if no arguments provided useDefaults="f" if [[ ${#} -eq 0 ]]; then useDefaults="t" fi # Available flags optstring=":s:cgrhm" defcmd="fzf" defaction="s" guicmd="rofi -dmenu -i" # uncomment next line for dmenu # guicmd="dmenu -i -l 15" # Defaults promptcmd="$defcmd" action="$defaction" isGui="f" query="" mpv_options="" max_resolution="480" max_fps="30" ignore_codec="" ytdlformats="bestvideo" if [ -n "$max_resolution" ] ; then ytdlformats+="[height<=?$max_resolution]" fi if [ -n "$max_fps" ] ; then ytdlformats+="[fps<=?$max_fps]" fi if [ -n "$ignore_codec" ] ; then ytdlformats+="[vcodec!=$ignore_codec]" fi ytdlformats+="+bestaudio/best" # Subscription list mkdir -p "${HOME:-}/.config/yt" sublistpath="${HOME:-}/.config/yt/sublist" sublist="" [ -f "$sublistpath" ] && sublist=$(cat "$sublistpath") # If not using defaults, search for flags if [[ $useDefaults = "f" ]]; then while getopts ${optstring} arg; do case "${arg}" in s) # Search in YouTube for a query action="s" query="${OPTARG}" ;; c) # Search in subscriptions for a specific channel action="c" query="${OPTARG}" ;; g|r) # Set gui mode to true and change the prompt to gui prompt isGui="t" promptcmd="$guicmd" ;; m) # Make the mpv headless mpv_options+="--no-video" ;; h) # Display help / usage usage ;; \?|:) # Wrong args -> exit with explanation of usage echo "invalid option: -${OPTARG}" echo usage exit 1 ;; esac done fi # If no query is set with flags, then ask for one if [ -z "$query" ]; then # Ask for a channel if [[ $action = "c" ]]; then # If in gui mode, use gui prompt if [[ $isGui = "t" ]]; then query=$($promptcmd -p "Channel: " <<< "$sublist") promptcmd="$promptcmd -p Video:" else query=$($promptcmd --print-query <<< "$sublist" | tail -n1) fi query=$(echo "$query" | awk '{print $1}') else # Ask for a query # If in gui mode, use gui prompt if [[ $isGui = "t" ]]; then query=$(echo | $promptcmd -p "Search: ") promptcmd="$promptcmd -p Video:" else echo -n "Search: " read -r query fi fi fi # Program cancelled -> exit if [ -z "$query" ]; then exit; fi # Clean query / channel query=$(sed \ -e 's|+|%2B|g'\ -e 's|#|%23|g'\ -e 's|&|%26|g'\ -e 's| |+|g' <<< "$query") # If channel, look for channel vids if [[ $action = "c" ]]; then response=$(curl -s "https://www.youtube.com/c/$query/videos" |\ sed "s/{\"gridVideoRenderer/\n\n&/g" |\ sed "s/}]}}}]}}/&\n\n/g" |\ awk -v ORS="\n\n" '/gridVideoRenderer/') # If unable to fetch the YouTube results page, inform and exit if ! grep -q "gridVideoRenderer" <<< "$response"; then echo "unable to fetch yt"; exit 1; fi # Regex expression to match video entries from YouTube channel page # Get the list of videos and their ids to ids ids=$(awk -F '[""]' '{print $6 "\t" $50;}' <<< "$response" | grep "^\S") # URL prefix for videos videolink="https://youtu.be/" # Prompt the results to user infinitely until they exit (escape) while true; do choice=$(echo -e "$ids" | cut -d' ' -f2 | $promptcmd) # Don't show id if [ -z "$choice" ]; then exit; fi # If esc-ed then exit id=$(echo -e "$ids" | grep -Fwm1 "$choice" | cut -d' ' -f1) # Get id of choice echo -e "$choice\t($id)" case $id in ???????????) mpv "$videolink$id" "$mpv_options" --ytdl-format="$ytdlformats";; *) exit ;; esac done else # If in search, show query result vids response="$(curl -s "https://www.youtube.com/results?search_query=$query" |\ sed 's|\\.||g')" # If unable to fetch the YouTube results page, inform and exit if ! grep -q "script" <<< "$response"; then echo "unable to fetch yt"; exit 1; fi # Regex expression to match video and playlist entries from YouTube result page vgrep='"videoRenderer":{"videoId":"\K.{11}".+?"text":".+?[^\\](?=")' pgrep='"playlistRenderer":{"playlistId":"\K.{34}?","title":{"simpleText":".+?[^\"](?=")' # Grep the id and title # Return them in format id (type) title getresults() { grep -oP "$1" <<< "$response" |\ awk -F\" -v p="$2" '{ print $1 "\t" p " " $NF}' } # Get the list of videos/playlists and their ids in videoids and playlistids videoids=$(getresults "$vgrep") playlistids=$(getresults "$pgrep" "(playlist)") # If there are playlists or videos, append them to list [ -n "$playlistids" ] && ids="$playlistids\n" [ -n "$videoids" ] && ids="$ids$videoids" # URL prefix for videos and playlists videolink="https://youtu.be/" playlink="https://youtube.com/playlist?list=" # Prompt the results to user infinitely until they exit (escape) while true; do choice=$(echo -e "$ids" | cut -d' ' -f2 | $promptcmd) # Don't show id if [ -z "$choice" ]; then exit; fi # If esc-ed then exit id=$(echo -e "$ids" | grep -Fwm1 "$choice" | cut -d' ' -f1) # Get id of choice echo -e "$choice\t($id)" case $id in # 11 digit id = video ???????????) mpv "$videolink$id" $mpv_options --ytdl-format="$ytdlformats";; # 34 digit id = playlist ??????????????????????????????????) mpv "$playlink$id" $mpv_options --ytdl-format="$ytdlformats";; *) exit ;; esac done fi