youtube-tui

Shell scripts to query YouTube from a terminal.
git clone git://git.concealed.world/youtube-tui
Log | Files | Refs | README | LICENSE

ytsearch (20135B)


      1 #!/bin/bash
      2 # A messy bash TUI client for YT/Invidious.
      3 # DEPENDS: youtube-dl/yt-dlp, mpv, ffmpeg, xclip, coreutils
      4 #
      5 #    Copyright (C) <2020>  <nixx@firemail.cc>
      6 #
      7 #    This program is free software: you can redistribute it and/or modify
      8 #    it under the terms of the GNU General Public License as published by
      9 #    the Free Software Foundation, either version 3 of the License, or
     10 #    (at your option) any later version.
     11 #
     12 #    This program is distributed in the hope that it will be useful,
     13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 #    GNU General Public License for more details.
     16 #
     17 #    You should have received a copy of the GNU General Public License
     18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     19 
     20 message () {
     21 cat << EOF
     22 ytsearch
     23 ~~~~~~~~
     24 
     25 TUI client for querying YouTube content.
     26 
     27 Using Invidious to fetch results is faster, and 'friendlier' about
     28 number of server requests. When a result is chosen, it is downloaded/
     29 streamed directly from YouTube.
     30 
     31 Some code pertaining to fetching results directly from YouTube
     32 remains, but this is slow, and tends to get you banned - I would not
     33 recommend.
     34 
     35 Usage:
     36 
     37 	All parameters are optional to start the client.
     38 
     39 	search  - Initial search term.
     40 	results - Number of results to return - defaults to 15.
     41 	invid   - Alternate Invidious mirror to use - 
     42 		  invidious.snopyta.org works best at time of writing
     43 		  this, it is the default.
     44 
     45 	'ytsearch "<search>" <results> <invid>
     46 
     47 	An example: 
     48 	'ytsearch "linus torvalds" 5 https://yt.iswleuven.be/'
     49 
     50 
     51 Config File:
     52 
     53 	Located at either \$HOME/.config/ytsearch or at \$HOME/.ytsearch.
     54 
     55 	Number of results, alternate mirror, preferred download format
     56 	(default 'best'), and search timeout (secs per loaded page -
     57 	default 20) can be set via config file. An example:
     58 
     59 	's_results = 5'
     60 	's_website = https://yt.iswleuven.be/'
     61 	's_format = 18'
     62 	's_timeout = 10'
     63 
     64 	MORE DETAILS:
     65 
     66 	s_results:
     67 	----------
     68 	This is the number of individual results returned. Equivalent to
     69 	second parameter the in command line.
     70 
     71 	s_website:
     72 	----------
     73 	This is the alternative Invidious mirror to use. Some will work
     74 	more reliably than others. Equivalent to the third parameter in
     75 	the command line.
     76 
     77 	s_format:
     78 	---------
     79 	Can only be set in config file. Determines video/audio quality:
     80 	see 'FORMAT SELECTION' on youtube-dl's/yt-dlp's man page for more
     81 	information on options. Defaults to 'best' - combination of best
     82 	audio and video quality, youtube-dl's/yt-dlp's default.
     83 	Note #1: when you select 'audio' it will download bestaudio and no
     84 	video, no matter the value of s_format.
     85 	Note #2: I generally recommend you put a '/best' on the end of the
     86 	quality option, so that if all else fails, it chooses the best
     87 	default.
     88 
     89 	s_timeout:
     90 	----------
     91 	Time taken to load a page of results, before giving up. Note that 
     92 	timeout applies per page of results (20 results). 10 pages 
     93 	(200 results) with a timeout of 10 seconds, could take up to 100 
     94 	seconds (assuming they all take the maximum time of 10 seconds).
     95 
     96 Issues:
     97 
     98 	Invidious instances can be highly unreliable. That's why
     99 	switching instance via config file exists, often one mirror will
    100 	be having issues and another will be fine. Increasing your search
    101 	timeout beyond the default may also help, if queries tend to
    102 	timeout.
    103 
    104 Managing YouTube subscriptons:
    105 
    106 	See the companion program 'ytchannel'. Most of the requirements of
    107 	subscriptions are already met by an RSS feed reader, so ytchannel
    108 	fetches and formats channel IDs for such a purpose.
    109 EOF
    110 }
    111 
    112 copy () {
    113 if xset -q >/dev/null 2>&1; then
    114 	printf "${1}" | xclip -in -selection clipboard
    115 	printf "\e[0;94m# Copied URL to clipboard.\e[0m\n"
    116 fi
    117 echo -e "\e[0;94m# URL: ${1}\e[0m"
    118 }
    119 
    120 hexformat () {
    121 echo "${1}" | sed -e "s/%/%25/g" -e "s/!/%21/g" -e "s/\"/%22/g" -e "s/#/%23/g" -e 's/\$/%24/g' -e "s/&/%26/g" -e "s/'/%27/g" -e "s/(/%28/g" -e "s/)/%29/g" -e "s/=/%3D/g" -e "s/\^/%5E/g" -e "s/|/%7C/g" -e "s/?/%3F/g" -e "s|/|%2F|g" -e "s/,/%2C/g" -e "s/</%3C/g" -e "s/>/%3E/g" -e "s/+/%2B/g" -e "s/;/%3B/g" -e "s/:/%3A/g" -e "s/]/%5D/g" -e "s/\[/%5B/g" -e "s/}/%7D/g" -e "s/{/%7B/g" -e "s/\`/%60/g" -e "s/\"/%22/g" -e "s/ /+/g"
    122 }
    123 
    124 line () {
    125 printf "\e[1;34m=============================================================================\e[0m\n"
    126 }
    127 
    128 title () {
    129 printf "\e[1;97m#\tDURAT\t\tTITLE\e[0m\n"
    130 line
    131 }
    132 
    133 process () {
    134 	countto=$3
    135 	[[ -z $countto ]] && countto=9999
    136 	links=$(cat "${1}" | grep -n '<a style="width:100%" href="/watch?v=\|<a style="width:100%" href="/playlist?list=')
    137 	lengths=$(cat "${1}" | grep '<p class="length"')
    138 	rescount=0
    139 	reslinkscount=$(echo "${links}" | wc -l)
    140 	reslenscount=$(echo "${lengths}" | wc -l)
    141 	pageslen=$(($pagecount*20))
    142 	while [[ $rescount -le $(($reslinkscount-1)) && $rescount -lt $countto ]]; do ((rescount++))
    143 		time=$(echo "${lengths}" | head -n $rescount | tail -n 1)
    144 		[[ $time == *"LIVE"* ]] && time="LIVE" || time=$(echo $time | cut -d '>' -f 2 | cut -d '<' -f 1)
    145 		title=$(echo "${links}" | head -n $rescount | tail -n 1)
    146 		ln=$(echo $title | cut -d : -f 1)
    147 		if [[ $title == *"/playlist?list="* ]]; then
    148 			title=$(sed "$((ln+=7))q;d" "${1}" | cut -d '>' -f 2 | cut -d '<' -f 1)
    149 		else
    150 			title=$(sed "$((ln+=11))q;d" "${1}" | cut -d '>' -f 2 | cut -d '<' -f 1)
    151 		fi
    152 		link=$(echo "${links}" | head -n $rescount | tail -n 1 | cut -d '/' -f 2 | cut -d \" -f 1)
    153 		[[ -n $title && -n $link && -n $time ]] && echo "${title}" >> $2 && echo "${link}" >> $2 && echo "${time}" >> $2
    154 	done
    155 }
    156 
    157 playlistpages () {
    158 	playstop=$(($playcount+75))
    159 	while [[ ${playcount} -le ${lenplay} && ${playcount} -lt ${playstop} ]]; do
    160 		((halfcount++))
    161 		echo -en "\e[0;96m$halfcount\t"
    162 		timeline=$(sed "$(( $playcount + 2))q;d" $playtmpout)
    163 		echo -en "\e[0;92m${timeline}"
    164 		[[ $(echo $timeline | wc -L) -gt 7 ]] && printf '\t' || printf '\t\t'
    165 		titleline="$(sed "${playcount}q;d" $playtmpout)"
    166 		printf "\e[0;97m"
    167 		[[ $(echo ${titleline} | wc -L) -gt 50 ]] && printf "$(echo -n "${titleline}" | cut -c 1-50)" && echo -n ... || echo -n "${titleline}"
    168 		printf '\n'
    169 		((playcount+=3))
    170 	done
    171 }
    172 
    173 playlist () {
    174 read -p "(e)xit | (d)escend into playlist $2, or (u)se whole playlist?: " descend
    175 [[ $descend == 'e' || $descend == 'E' || $descend == 'exit' || $descend == 'Exit' || $descend == 'q' || $descend == 'Q' ]] && rm $tmp && exit 0
    176 if [[ $descend == 'd' || $descend == 'D' || $descend == 'descend' || $descend == 'Descend' ]]; then
    177 	playtmp=$(mktemp)
    178 	playtmpout=$(mktemp)
    179 	curlpage=1
    180 	curlcheck=$(curl --http2-prior-knowledge -m ${timeout} -s "${1}")
    181 	echo "${curlcheck}" >> $playtmp
    182 	while [[ -n $(echo "${curlcheck}" | grep "playlist?list=.*page=$((curlpage+1))") ]]; do ((curlpage++))
    183 		curlcheck=$(curl --http2-prior-knowledge -m ${timeout} -s "${1}&page=${curlpage}")
    184 		echo "${curlcheck}" >> $playtmp
    185 	done
    186 	[[ $? == 92 ]] && printf "\e[1;91m# ERROR: HTTP/2 stream 0 was not closed cleanly. This is a server problem, consider switching instance.\e[0m\n" && rm $tmp $playtmp $playtmpout && exit 1
    187 	process $playtmp $playtmpout
    188 	[[ $(wc -l $playtmpout | awk '{print $1}') == 0 && -n $(grep "Could not extract playlist info. Instance is likely blocked." $playtmp) ]] && printf "\e[1;91m# ERROR: Could not extract playlist info. Instance is likely blocked - Consider switching instance.\e[0m\n" rm $playtmp $playtmpout $tmp && exit 1
    189 	[[ $(wc -l $playtmpout | awk '{print $1}') == 0 && -n $(grep "Read timed out" $playtmp) ]] && printf "\e[1;91m# ERROR: Read timed out - Consider switching instance.\e[0m\n" && rm $playtmp $playtmpout $tmp && exit 1
    190 	[[ $(wc -l $playtmpout | awk '{print $1}') == 0 ]] && printf "\e[1;91m# ERROR: Could not fetch playlist. Consider increasing timeout in the config file, or switching Invidious instance.\e[0m\n" && rm $playtmp $playtmpout $tmp && exit 1
    191 	rm $playtmp
    192 
    193 	lenplay=$(cat $playtmpout | wc -l)
    194 	playcount=1
    195 	halfcount=0
    196 	playnum=''
    197 	while [[ -z $playnum || $playnum == *[a-z]* || $playnum == *[A-Z]* ]]; do
    198 		printf '\n'
    199 		title
    200 		playlistpages
    201 		line
    202 		printf '\n'
    203 		
    204 		if [[ $lenplay -gt 76 ]]; then
    205 			if [[ $halfcount -lt 26 ]]; then
    206 				echo "(n)ext page."
    207 			elif [[ $halfcount -ge 26 && $halfcount -lt $(($lenplay/3-25)) ]]; then
    208 				echo "(n)ext/(p)revious page."
    209 			else
    210 				echo "(p)revious page."
    211 			fi
    212 		fi
    213 		read -p '(e)xit | Sel. vid. (format: 1,2,3), or all (.): ' playnum
    214 		[[ $playnum == '.' || $playnum == 'all' || $playnum == 'All' ]] && return 2
    215 		if [[ $playnum == 'p' || $playnum == 'P' || $playnum == 'prev'* || $playnum == 'Prev'* ]]; then
    216 			if [[ $halfcount -gt 49 && $playcount -gt 151 ]]; then
    217 				halfcount=$(($halfcount-50)) && playcount=$(($playcount-150))
    218 			else
    219 				halfcount=0 && playcount=1
    220 			fi
    221 		elif [[ $playnum == 'e' || $playnum == 'E' || $playnum == 'exit' || $playnum == 'Exit' || $playnum == 'q' || $playnum == 'Q' ]]; then
    222 			rm $tmp $playtmpout && exit 0
    223 		fi
    224 	done
    225 	pn=$(echo -n $playnum | tr -d "[:blank:]")
    226 	IFS="," read -a playarray <<< $pn
    227 	for no in "${playarray[@]}"; do
    228 		[[ $no == *[a-z]* || $no == *[A-Z]* || $no -gt $halfcount || $no -lt 1 ]] && printf "\e[1;91mERROR: Invalid number.\e[0m\n" && rm $tmp && exit 1
    229 		((loc=no*3-1))
    230 		playnewurl=$(sed "${loc}q;d" $playtmpout)
    231 		playurl[$playurlcount]=$playnewurl
    232 		((playurlcount++))
    233 	done
    234 	rm $playtmpout
    235 elif [[ $descend == 'u' || $descend == 'U' || $descend == 'use' || $descend == 'Use' ]]; then
    236 	printf "\e[0;94m# Using whole playlist as input.\e[0m\n"
    237 	return 2
    238 else
    239 	printf "\e[1;91mERROR: Invalid option.\n" && exit 0
    240 fi
    241 }
    242 
    243 main () {
    244 tmp=$(mktemp)
    245 
    246 if [[ $website == "https://www.youtube.com/" ]]; then
    247 	${downloader} --get-id --get-duration -e ytsearch${num}:"${1}" 1> $tmp 2>/dev/null
    248 else
    249 	searchhex=$(hexformat "${1}")
    250 	resulttmp=$(mktemp)
    251 	pagecount=0
    252 	while [ $pagecount -lt $(($num/20+1)) ]; do ((pagecount++))
    253 		curl --http2-prior-knowledge -m ${timeout} -s "${website}search?q=${searchhex}&page=${pagecount}" >> $resulttmp
    254 	done
    255 	[[ $? == 92 ]] && printf "\e[1;91m# ERROR: HTTP/2 stream 0 was not closed cleanly. This is a server problem, consider switching instance.\e[0m\n" && rm $tmp $resulttmp && exit 1
    256 	[[ -n $(grep "<title>502 Bad Gateway</title>" $resulttmp) ]] && printf "\e[1;91m# ERROR: 502 Bad Gateway - Consider switching instance.\e[0m\n" && rm $tmp $resulttmp && exit 1
    257 	process $resulttmp $tmp $num
    258 	[[ -n $(grep "Read timed out" $resulttmp) && $(wc -l $tmp | awk '{print $1}') == 0 ]] && printf "\e[1;91m# ERROR: Read timed out - Consider switching instance.\e[0m\n" && rm $tmp $resulttmp && exit 1
    259 	rm $resulttmp
    260 fi
    261 
    262 len=$(wc -l $tmp | awk '{print $1}')
    263 [[ $len -eq 0 ]] && echo -e "\e[1;91m# ERROR: Could not fetch videos.\n1. Check the spelling of search instance ${website}.\n2. Increase search timeout - currently ${timeout} secs.\n3. Check: ${website}search?q=${searchhex}&page=${pagecount} - instance may be down.\n4. You may also be HTTP Error 429 blocked - have you been making a lot of requests?.\n5. Your search may just be too obscure.\e[0m" && rm $tmp && exit 1
    264 printf "\e[0;94m# Searched for results 1 to ${num}.\n\n"
    265 title
    266 
    267 count=1
    268 halfcount=0
    269 while [ ${count} -le ${len} ]; do
    270 	((halfcount++))
    271 	echo -en "\e[0;96m$halfcount\t"
    272 	timeline=$(sed "$(( $count + 2))q;d" $tmp)
    273 	echo -en "\e[0;92m${timeline}"
    274 	[[ $(echo $timeline | wc -L) -gt 7 ]] && printf '\t' || printf '\t\t'
    275 	titleline="$(sed "${count}q;d" $tmp)"
    276 	printf "\e[0;97m"
    277 	[[ $(echo "${titleline}" | wc -L) -gt 50 ]] && echo -n "$(echo -n "${titleline}" | cut -c 1-50)" && echo -n ... || echo -n "${titleline}"
    278 	printf '\n'
    279 	((count+=3))
    280 done
    281 line
    282 printf '\n'
    283 
    284 read -p 'Sel. vid. (format:1,2,3), (s)earch again, or (e)xit: ' vidnum
    285 [[ $vidnum == 'e' || $vidnum == 'E' || $vidnum == 'q' || $vidnum == 'Q' || $vidnum == 'exit' || $vidnum == 'Exit' ]] && rm $tmp && exit 0
    286 [[ $vidnum == 's' || $vidnum == 'S' || $vidnum == 'search' || $vidnum == 'Search' ]] && rm $tmp && again=1 && return 0
    287 count=0
    288 if [[ $vidnum == *","* ]]; then
    289 	nums=$(echo -n $vidnum | tr -d "[:blank:]")
    290 	declare -A url
    291 	IFS="," read -a array <<< $nums
    292 	for vnum in "${array[@]}"; do
    293 		[[ $vnum == *[a-z]* || $vnum == *[A-Z]* || $vnum -gt $halfcount || $vnum -lt 1 ]] && printf "\e[1;91mERROR: Invalid number.\e[0m\n" && rm $tmp && exit 1
    294 		((loc=vnum*3-1))
    295 		newurl=$(sed "${loc}q;d" $tmp)
    296 		if [[ $newurl == *"playlist?list="* ]]; then
    297 			playlist "${website}${newurl}" $vnum
    298 		fi
    299 		if [[ $? == 2 && $newurl == *"playlist?list="* || $newurl != *"playlist?list="* ]]; then
    300 			url[$count]=$newurl
    301 			((count++))
    302 		fi
    303 	done
    304 	rm $tmp
    305 else
    306 	[[ $vidnum == *[a-z]* || $vidnum == *[A-Z]* || $vidnum -gt $halfcount || $vidnum -lt 1 ]] && printf "\e[1;91mERROR: Invalid number.\e[0m\n" && rm $tmp && exit 1
    307 	((loc=vidnum*3-1))
    308 	url=$(sed "${loc}q;d" $tmp)
    309 	[[ $url == *"playlist?list="* ]] && playlist "${website}${url}" $vidnum
    310 	[[ $? != 2 && $url == *"playlist?list="* ]] && url=""
    311 	rm $tmp
    312 fi
    313 
    314 matchcount=0
    315 playmatchcount=0
    316 read -p 'Action? [(d)ownload/(s)tream/get(u)rl/(e)xit]: ' confirm
    317 [[ $confirm == 'E' || $confirm == 'e' || $confirm == 'q' || $confirm == 'Q' || $confirm == 'exit' || $confirm == 'Exit' ]] && exit 0
    318 if [[ $confirm == 'D' || $confirm == 'd' || $confirm == 'download' || $confirm == 'Download' ]]; then
    319 	read -p '(e)xit | (v)ideo or (a)udio only?: ' vid
    320 	[[ $vid == 'e' || $vid == 'E' || $vid == 'exit' || $vid == 'Exit' || $vid == 'q' || $vid == 'Q' ]] && exit 0
    321 	if [[ $vid == 'v' || $vid == 'V' || $vid == 'video' || $vid == 'Video' ]]; then
    322 		if [[ $count == 0 && -n $url ]]; then
    323 			printf '\n'
    324 			copy "${website}${url}"
    325 			[ $vformat ] && ${downloader} -i -f "${vformat}" "${streambase}${url}" || ${downloader} -i "${streambase}${url}"
    326 		else
    327 			printf '\n'
    328 			while [ $(( $matchcount + 1 )) -le $count ]; do
    329 				copy "${website}${url[$matchcount]}"
    330 				[ $vformat ] && ${downloader} -i -f "${vformat}" "${streambase}${url[$matchcount]}" || ${downloader} -i "${streambase}${url[$matchcount]}"
    331 				((matchcount++))
    332 			done
    333 		fi
    334 		while [[ $playmatchcount -lt ${#playurl[@]} ]]; do
    335 			fullurl="${playurl[$playmatchcount]}"
    336 			copy "${website}${fullurl}"
    337 			[ $vformat ] && ${downloader} -i -f "${vformat}" --no-playlist "${streambase}${fullurl}" || ${downloader} -i --no-playlist "${streambase}${fullurl}"
    338 			((playmatchcount++))
    339 		done
    340 	elif [[ $vid == 'a' || $vid == 'A' || $vid == 'audio' || $vid == 'Audio' ]]; then
    341 		if [[ $count == 0 && -n $url ]]; then
    342 			printf '\n'
    343 			copy "${website}${url}"
    344 			${downloader} -ix --audio-format "mp3" "${streambase}${url}"
    345 		else
    346 			printf '\n'
    347 			while [ $(( $matchcount + 1 )) -le $count ]; do
    348 				copy "${website}${url[$matchcount]}"
    349 				${downloader} -ix --audio-format "mp3" "${streambase}${url[$matchcount]}"
    350 				((matchcount++))
    351 			done
    352 		fi
    353 		while [[ $playmatchcount -lt ${#playurl[@]} ]]; do
    354 			fullurl="${playurl[$playmatchcount]}"
    355 			copy "${website}${fullurl}"
    356 			${downloader} -ix --audio-format "mp3" --no-playlist "${streambase}${fullurl}"
    357 			((playmatchcount++))
    358 		done
    359 	else 
    360 		printf "\e[1;91mERROR: Invalid action.\e[0m\n" && exit 1
    361 	fi
    362 elif [[ $confirm == 'S' || $confirm == 's' || $confirm == 'stream' || $confirm == 'Stream' ]]; then
    363 	read -p '(e)xit | (v)ideo or (a)udio only?: ' vid
    364 	[[ $vid == 'e' || $vid == 'E' || $vid == 'exit' || $vid == 'Exit' || $vid == 'q' || $vid == 'Q' ]] && exit 0
    365 	if [[ $vid == 'v' || $vid == 'V' || $vid == 'video' || $vid == 'Video' ]]; then
    366 		if [[ $count == 0 && -n $url ]]; then
    367 			printf '\n'
    368 			copy "${website}${url}"
    369 			[ $vformat ] && ${player} --ytdl-format="${vformat}" --ytdl "${streambase}${url}" || ${player} --ytdl "${streambase}${url}"
    370 		else
    371 			printf '\n'
    372 			while [ $(( $matchcount + 1 )) -le $count ]; do
    373 				copy "${website}${url[$matchcount]}"
    374 				[ $vformat ] && ${player} --ytdl-format="${vformat}" --ytdl "${streambase}${url[$matchcount]}" || ${player} --ytdl "${streambase}${url[$matchcount]}"
    375 				((matchcount++))
    376 			done
    377 		fi
    378 		while [[ $playmatchcount -lt ${#playurl[@]} ]]; do
    379 			fullurl="${playurl[$playmatchcount]}"
    380 			copy "${website}${fullurl}"
    381 			[ $vformat ] && ${player} --ytdl-format="${vformat}" --ytdl "${streambase}${fullurl}" || ${player} --ytdl "${streambase}${fullurl}"
    382 			((playmatchcount++))
    383 		done
    384 	elif [[ $vid == 'a' || $vid == 'A' || $vid == 'audio' || $vid == 'Audio' ]]; then
    385 		if [[ $count == 0 && -n $url ]]; then
    386 			printf '\n'
    387 			copy "${website}${url}"
    388 			${player} --ytdl-format=bestaudio --no-video --ytdl "${streambase}${url}"
    389 		else
    390 			printf '\n'
    391 			while [ $(( $matchcount + 1 )) -le $count ]; do
    392 				copy "${website}${url[$matchcount]}"
    393 				${player} --ytdl-format=bestaudio --no-video --ytdl "${streambase}${url[$matchcount]}"
    394 				((matchcount++))
    395 			done
    396 		fi
    397 		while [[ $playmatchcount -lt ${#playurl[@]} ]]; do
    398 			fullurl="${playurl[$playmatchcount]}"
    399 			copy "${website}${fullurl}"
    400 			${player} --ytdl-format=bestaudio --no-video --ytdl "${streambase}${fullurl}"
    401 			((playmatchcount++))
    402 		done
    403 	else
    404 		printf "\e[1;91mERROR: Invalid action.\e[0m\n" && exit 1
    405 	fi
    406 elif [[ $confirm == 'u' || $confirm == 'U' ]]; then
    407 	read -p '(e)xit | Print to std(o)ut, or to (f)ile?: ' stdf
    408 	[[ $stdf == 'e' || $stdf == 'E' || $stdf == 'exit' || $stdf == 'Exit' || $stdf == 'q' || $stdf == 'Q' ]] && exit 0
    409 	[[ $stdf == 'f' || $std == 'F' ]] && read -p 'Append to file: ' fileloc
    410 
    411 	if [[ $stdf == 'o' || $stdf == 'O' ]]; then
    412 		if [[ $count == 0 && -n $url ]]; then
    413 			printf '\n'
    414 			copy "${website}${url}"
    415 			echo "${website}${url}"
    416 		else 
    417 			printf '\n'
    418 			while [[ $(( $matchcount + 1 )) -le $count ]]; do
    419 				echo "${website}${url[$matchcount]}"
    420 				((matchcount++))
    421 			done
    422 		fi
    423 		while [[ $playmatchcount -lt ${#playurl[@]} ]]; do
    424 			fullurl="${playurl[$playmatchcount]}"
    425 			echo "${website}${fullurl}"
    426 			((playmatchcount++))
    427 		done
    428 	elif [[ -n $fileloc ]]; then
    429 		if [[ $count == 0 && -n $url ]]; then
    430 			touch "${fileloc}"
    431 			copy "${website}${url}"
    432 			echo "${website}${url}" >> "${fileloc}"
    433 		else 
    434 			while [[ $(( $matchcount + 1 )) -le $count ]]; do
    435 				touch "${fileloc}"
    436 				echo "${website}${url[$matchcount]}" >> "${fileloc}"
    437 				((matchcount++))
    438 			done
    439 		fi
    440 		while [[ $playmatchcount -lt ${#playurl[@]} ]]; do
    441 			fullurl="${playurl[$playmatchcount]}"
    442 			echo "${website}${fullurl}" >> "${fileloc}"
    443 			((playmatchcount++))
    444 		done
    445 	else
    446 		printf "\e[1;91mERROR: Invalid action.\e[0m\n" && exit 1
    447 	fi
    448 else
    449 	printf "\e[1;91mERROR: Invalid action.\e[0m\n" && exit 1
    450 fi
    451 }
    452 
    453 [[ $1 == '-h' || $1 == *'-help'* || $# -gt 3 ]] && message && exit 0
    454 website="${3}"
    455 streambase="https://www.youtube.com/"
    456 if [[ -z $website ]]; then
    457 	find ~/.config/ytsearch >/dev/null 2>&1
    458 	[[ $? == 0 ]] && configf=~/.config/ytsearch
    459 	[[ -z $configf ]] && find ~/.ytsearch >/dev/null 2>&1
    460 	[[ $? == 0 ]] && configf=~/.ytsearch
    461 	[[ -n $configf ]] && website=$(grep "s_website" ${configf} | awk '{print $3}')
    462 	[[ -z $website ]] && website="https://invidious.snopyta.org/"
    463 fi
    464 [[ $website != "https://"* ]] && website="https://${website}"
    465 [[ $(echo $website | tail -c 2 | head -c 1) != '/' ]] && website="${website}/"
    466 [[ $2 == *[a-z]* || $2 == *[A-Z]* ]] && printf "\e[1;91mERROR: Invalid search time parameter.\n\e[0m" && exit 1
    467 if [[ -z $2 ]]; then
    468 	[[ -n $configf ]] && num=$(grep "s_results" "${configf}" | awk '{print $3}')
    469 	[[ -z $num ]] && num=15
    470 else
    471 	num=$2
    472 fi
    473 [[ -n $configf && -n $(grep "s_timeout" "${configf}") ]] && timeout=$(grep "s_timeout" "${configf}" | awk '{print $3}') || timeout=20
    474 [[ -n $configf && -n $(grep "s_format" "${configf}") ]] && vformat=$(grep "s_format" "${configf}" | awk '{print $3}')
    475 
    476 player="mpv"
    477 if [[ $(which yt-dlp > /dev/null 2>&1) ]]; then
    478 	downloader="youtube-dl"
    479 else
    480 	downloader="yt-dlp"
    481 fi
    482 
    483 search="${1}"
    484 again=0
    485 clear
    486 [[ -z $search ]] && read -p 'Search: ' search || echo "Search: ${search}"
    487 printf '\n'
    488 while ( true ); do
    489 	playurlcount=0
    490 	declare -A playurl
    491 	clearcount=0
    492 	for u in "${url[@]}"; do
    493 		url[$clearcount]=''
    494 		((clearcount++))
    495 	done
    496 	clearcount=0
    497 	for p in "${playurl[@]}"; do
    498 		playurl[$clearcount]=''
    499 		((clearcount++))
    500 	done
    501 	main "${search}" "${num}" "${website}"
    502 	quit='n'
    503 	[[ $again == 0 ]] && read -p 'Quit? [Y/n]: ' quit
    504 	again=0
    505 	[[ $quit != 'n' && $quit != 'N' ]] && exit 0
    506 	clear
    507 	read -p 'Search: ' search
    508 	printf '\n'
    509 done