#!/usr/bin/env bash #set -e # Begin Checking for Dependencies declare -a dependencies=(yt-dlp mpv jq curl xclip) for dependency in ${dependencies[@]}; do if ! command -v ${dependency} &> /dev/null; then die "${c_red}\"$dependency\" is not installed.${c_reset}\n" fi done # End Checking for Dependencies c_red="\033[1;31m" c_green="\033[1;32m" c_blue="\033[1;34m" c_reset="\033[0m" api_file="${XDG_CONFIG_HOME:-$HOME/.config}/.yt-api" tmp_file="/tmp/yt.json" search_file="/tmp/yt-script-search" queries_file="${XDG_CONFIG_HOME:-$HOME}/.yt-queries" history_file="${XDG_CONFIG_HOME:-$HOME}/.yt-history" # Arguments headless=false loop=false ## Begin Removing History Functions clearSearchHistory() { if [[ ! -f "$queries_file" ]]; then die "${c_red}Your search history is already cleared.${c_reset}\n" fi rm -rf "$queries_file" && die "${c_green}Search History cleared. You're safe now. ;)${c_reset}" } clearWatchHistory() { if [[ ! -f "$history_file" ]]; then die "${c_red}Your watch history is already cleared.${c_reset}\n" fi rm -rf "$history_file" && die "${c_green}Watch History cleared. You're safe now. ;)${c_reset}" } ## End Removing History Functions ## BeginSearch & Watch History listSearchHistory() { if [[ ! -f "$queries_file" ]]; then die "${c_red}You don't have a search history.${c_reset}\n" fi tput reset printf "${c_red}Your search history:${c_reset}\n\n" formatPrint "$queries_file" if [ -n "$1" ]; then printf "\n${c_red}Enter Choice:${c_reset} " read -r choice [ "$choice" -eq "$choice" ] 2>/dev/null || die "${c_red}Invalid Number Entered.${c_reset}\n" [[ $(wc -l ${queries_file} | awk '{print $1}') -lt "$choice" ]] || [[ "$choice" -lt 0 ]] || [[ "$choice" -eq 0 ]] && die "${c_red}Invalid choice range.${c_reset}\n" selected=$(awk -v choice="$choice" '{if(NR==choice) print $0}' "$queries_file") echo "$selected" | xclip -selection clipboard printf "\n${c_green}\"$selected\"${c_reset} has been copied to your clipboard.\n" fi } listWatchHistory() { if [[ ! -f "$history_file" ]]; then die "${c_red}You don't have a watch history.${c_reset}\n" fi tput reset printf "${c_red}Your watch history:${c_reset}\n\n" formatPrint "$history_file" if [ -n "$1" ]; then printf "\n${c_red}Enter Choice:${c_reset} " read -r choice [ "$choice" -eq "$choice" ] 2>/dev/null || die "${c_red}Invalid Number Entered.${c_reset}\n" [[ $(wc -l ${history_file} | awk '{print $1}') -lt "$choice" ]] || [[ "$choice" -lt 0 ]] || [[ "$choice" -eq 0 ]] && die "${c_red}Invalid choice range.${c_reset}\n" video=$(awk -v choice="$choice" '{if(NR==choice) print $0}' "$history_file") videourl=$(printf "%s" "$video" | awk '{ print $NF }') videotitle=$(printf "%s" "$video" | sed 's/^[^-]*--> //g' | sed 's/ http.*//g') # append to watch history file echo "$video" >> "$history_file" notify "Playing Video" "$videotitle" && playVideo "$videourl" tput reset fi } ## End Search & Watch History formatPrint(){ count=1 while IFS="" read -r line || [ -n "$line" ];do if [ $((count % 2)) -eq 0 ];then printf "${c_blue}[$count]: $line\n" else printf "${c_green}[$count]: $line${c_reset}\n" fi count=$((count+1)) done < "$1" } die() { printf "$*" 1>&2 ; exit 1; } notify() { local action="$1" local title="$2" notify-send -u normal \ -t 5000 "$action:" "$title" } arguments() { args="" # argument setting if [[ "$headless" == "true" && "$loop" != "true" ]]; then args="--no-video" fi if [[ "$loop" == "true" && "$headless" != "true" ]]; then args="--loop" fi if [[ "$headless" == "true" && "$loop" == "true" ]]; then args="--no-video --loop" fi } playVideo() { local link="$1" # kill mpv if it's running if pgrep mpv; then pkill mpv fi arguments setsid -f mpv ${args} "$link" 2>/dev/null 1>&2 } recentSearchHistory() { if [[ ! -f "$search_file" ]]; then die "${c_red}You don't have recent results history.${c_reset}\n" fi if [[ ! -f "$api_file" ]]; then searchVideo else api_key=$(cat "$api_file") searchVideoWithAPI fi } ## Begin With API Functions searchQueryWithAPI() { tput reset printf "${c_red}Search YouTube:${c_reset} " read -r query if [[ "$query" == "" ]]; then die "No query term specified\n" fi # append to search history file echo "$query" >> "$queries_file" query_fmt="${query// /+}" tput reset printf "${c_red}Searching ${c_blue}$query${c_red} with YouTube API.${c_reset} " urlstring="https://www.googleapis.com/youtube/v3/search?part=snippet&q=${query_fmt}&type=video&maxResults=20&key=${api_key}" curl -s "${urlstring}" | jq -r '.items[] | "\(.snippet.channelTitle) --> \(.snippet.title) https://youtu.be/\(.id.videoId)"' > "$search_file" searchVideoWithAPI } searchVideoWithAPI() { tput reset formatPrint "$search_file" printf "\n${c_red}Enter Choice:${c_reset} " read -r choice [ "$choice" -eq "$choice" ] 2>/dev/null || die "${c_red}Invalid Number Entered.${c_reset}\n" [[ $(wc -l ${search_file} | cut -d' ' -f1) -lt "$choice" ]] || [[ "$choice" -lt 0 ]] || [[ "$choice" -eq 0 ]] && die "${c_red}Invalid choice range.${c_reset}\n" video=$(awk -v choice="$choice" '{if(NR==choice) print $0}' ${search_file}) videourl=$(printf "%s" "$video" | awk '{ print $NF }') videotitle=$(printf "%s" "$video" | sed 's/^[^-]*--> //g' | sed 's/ http.*//g') # append to watch history file echo "$video" >> "$history_file" notify "Playing Video" "$videotitle" && playVideo "$videourl" tput reset } ## End With API Functions ## Begin Without API Functions searchQuery() { tput reset rm -rf "$search_file" printf "${c_red}Search YouTube:${c_reset} " read -r query if [[ "$query" == "" ]]; then die "No query term specified.\n" fi echo "$query" >> "$queries_file" query_fmt="${query// /+}" tput reset if [ -n "$1" ]; then search_length="$1" else search_length="5" fi printf "${c_red}Searching ${c_blue}$query${c_red} with yt-dlp.${c_reset} " yt-dlp --verbose -j "ytsearch$search_length:$query_fmt" > "$tmp_file" tput reset printf "${c_red}Formatting output.${c_reset} " while IFS="" read -r line;do uploader=$( echo $line | sed 's/.*"uploader": "\(.*\)", "uploader_id.*/\1/g') title=$( echo $line | sed 's/.*"fulltitle": "\(.*\)", "epoch.*/\1/g') url=$( echo $line | sed 's/.*"webpage_url": "\(.*\)", "categories.*/\1/g') echo "${uploader} --> ${title} ${url}" >> "$search_file" done < "$tmp_file" searchVideo } searchVideo() { tput reset formatPrint "$search_file" printf "\n${c_red}Enter Choice:${c_reset} " read -r choice [ "$choice" -eq "$choice" ] 2>/dev/null || die "${c_red}Invalid Number Entered.${c_reset}\n" [[ $(wc -l ${search_file} | awk '{print $1}') -lt "$choice" ]] || [[ "$choice" -lt 0 ]] || [[ "$choice" -eq 0 ]] && die "${c_red}Invalid choice range.${c_reset}\n" video=$(awk -v choice="$choice" '{if(NR==choice) print $0}' "$search_file") videourl=$(echo "$video"|awk '{print $NF}') format_title=$(echo "$video"|sed 's/^.*--> \(.*\) https:\/\/.*/\1/') # append to watch history file echo "$video" >> "$history_file" notify "Playing Youtube Video" "$format_title" && playVideo "$videourl" tput reset } ## End Without API Functions ## Begin Download Functions downloadVideo() { local id="$1" notify "Downloading Video" "$id" yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" "$id" } downloadMp3() { local id="$1" notify "Converting To Mp3" "$id" yt-dlp --extract-audio --audio-format=mp3 --audio-quality=0 "$id" } ## End Download Functions _sigterm(){ tput reset die } trap _sigterm SIGTERM trap _sigterm SIGINT helpScreen() { printf " ${c_blue}Usage:${c_reset} ${c_green}yt-watch${c_reset} [${c_red}OPTION${c_reset}] ${c_green}OPTIONS:${c_reset} ${c_red}-h, --help${c_reset} ${c_blue}This help screen${c_reset} ${c_red}None${c_reset} ${c_blue}Uses the YouTube link from your clipboard.${c_reset} ${c_red}-l, --link${c_reset} ${c_blue}Play YouTube video from link. (--link \"https://www.youtube.com/watch?v=dQw4w9WgXcQ\")${c_reset} ${c_red}-s, --search${c_reset} ${c_blue}Lets you enter a search query and uses youtube API/yt-dlp.${c_reset} ${c_red}-sl, --search-length${c_reset} ${c_blue}Customize the result length of your search query. (--search-length 20)${c_reset} ${c_red}-k, --kill${c_reset} ${c_blue}Kills mpv${c_reset} ${c_green}DOWNLOADING MEDIA:${c_reset} ${c_red}-dv, --download-mp4${c_reset} ${c_blue}Download YouTube video & converts it to '.mp4' format. (--download-mp4 \"dQw4w9WgXcQ\")${c_reset} ${c_red}-dm, --download-mp3${c_reset} ${c_blue}Downloads & converts Video to '.mp3' format. (--download-mp3 \"dQw4w9WgXcQ\")${c_reset} ${c_green}ADDITIONAL ARGUMENTS:${c_reset} ${c_red}--headless${c_reset} ${c_blue}Plays YouTube video content, but with no video.${c_reset} ${c_red}--loop${c_reset} ${c_blue}Loops the YouTube content.${c_reset} ${c_green}HISTORIES:${c_reset} ${c_red}--recent-search-history${c_reset} ${c_blue}Lists out the query results of your latest search.${c_reset} ${c_red}--clear-search-history${c_reset} ${c_blue}Deletes all your search history.${c_reset} ${c_red}--clear-watch-history${c_reset} ${c_blue}Deletes all your watch history.${c_reset} ${c_red}-q, --queries${c_reset} ${c_blue}Lists out all of your search history.${c_reset} ${c_red}-qc, --query-copy${c_reset} ${c_blue}Lists out search history, select & it copies it.${c_reset} ${c_red}-v, --videos${c_reset} ${c_blue}Lists out all of your watch history.${c_reset} ${c_red}-vc, --video-play${c_reset} ${c_blue}Lists out watch history, select & it plays it.${c_reset} ${c_green}EXAMPLE:${c_reset} ${c_red}-sl 25 --loop --headless${c_reset} ${c_blue}Lists out 25 results of your query. Selected Video will loop but not have video playback.${c_reset} " } # headless setting arg_h="--headless" if [[ "$2" == "$arg_h" || "$3" == "$arg_h" || "$4" == "$arg_h" ]]; then headless=true elif [[ "$1" == "$arg_h" ]]; then headless=true notify "Playing" "$(xclip -o)" && playVideo "$(xclip -o)" fi # loop setting arg_l="--loop" if [[ "$2" == "$arg_l" || "$3" == "$arg_l" || "$4" == "$arg_l" ]]; then loop=true elif [[ "$1" == "$arg_l" ]]; then loop=true notify "Playing" "$(xclip -o)" && playVideo "$(xclip -o)" fi # Play youtube video link from clipboard if [[ "$1" == "" ]]; then notify "Playing Video" "$(xclip -o)" && playVideo "$(xclip -o)" # input youtube link elif [[ "$1" == "-l" || "$1" == "--link" ]]; then notify "Playing Video" "$2" && playVideo "$2" # Download youtube video on .mp4 format with the highest quality elif [[ "$1" == "-dv" || "$1" == "--download-mp4" ]]; then downloadVideo "$2" # Convert & Download youtube video on .mp3 format with the highest quality elif [[ "$1" == "-dm" || "$1" == "--download-mp3" ]]; then downloadMp3 "$2" # Search youtube videos elif [[ "$1" == "-s" || "$1" == "--search" ]]; then if [[ ! -f "$api_file" ]]; then searchQuery else api_key=$(cat "$api_file") searchQueryWithAPI fi # Search youtube videos with specifying the result length. # The larger the value, the longer it takes. elif [[ "$1" == "-sl" || "$1" == "--search-length" ]]; then searchQuery "$2" # Uses recent results from recent search query. elif [[ "$1" == "--recent-search-history" ]]; then recentSearchHistory # Stops the video from playing elif [[ "$1" == "-k" || "$1" == "--kill" ]]; then pkill mpv # Lists your search. elif [[ "$1" == "-q" || "$1" == "--queries" ]]; then listSearchHistory # Lists out your search history, select & it copies it for you. elif [[ "$1" == "-qc" || "$1" == "--query-copy" ]]; then listSearchHistory " " # Lists your watch history. elif [[ "$1" == "-v" || "$1" == "--videos" ]]; then listWatchHistory # Lists out your watch history, select & it plays it for you. elif [[ "$1" == "-vc" || "$1" == "--video-play" ]]; then listWatchHistory " " # Clear search history :) elif [[ "$1" == "--clear-search-history" ]]; then clearSearchHistory # Cleanr watch history :) elif [[ "$1" == "--clear-watch-history" ]]; then clearWatchHistory # Help screen elif [[ "$1" == "-h" || "$1" == "--help" ]]; then helpScreen fi