summaryrefslogtreecommitdiff
path: root/ext/ytfzf
diff options
context:
space:
mode:
Diffstat (limited to 'ext/ytfzf')
-rwxr-xr-xext/ytfzf3764
1 files changed, 1914 insertions, 1850 deletions
diff --git a/ext/ytfzf b/ext/ytfzf
index 4973eb7..ae9fc30 100755
--- a/ext/ytfzf
+++ b/ext/ytfzf
@@ -7,7 +7,7 @@ source ./util/notify # MODIFIED
# versioning system:
# major.minor.bugs
-YTFZF_VERSION="2.5.5"
+YTFZF_VERSION="2.6.0"
#ENVIRONMENT VARIABLES {{{
: "${YTFZF_CONFIG_DIR:=${XDG_CONFIG_HOME:-$HOME/.config}/ytfzf}"
@@ -27,9 +27,11 @@ YTFZF_VERSION="2.5.5"
: "${YTFZF_LOGFILE:=}"
-[ "${YTFZF_LOGFILE}" ] && printf "[%s]\n==============\nSubmenu: %d\nFzf Preview: %d\n==============\n" "$(date)" "$__is_submenu" "$__is_fzf_preview" >> "${YTFZF_LOGFILE}"
+[ "${YTFZF_LOGFILE}" ] && printf "[%s]\n==============\nSubmenu: %d\nFzf Preview: %d\n==============\n" "$(date)" "$__is_submenu" "$__is_fzf_preview" >>"${YTFZF_LOGFILE}"
! [ -d "$YTFZF_TEMP_DIR" ] && mkdir -p "${YTFZF_TEMP_DIR}"
+
+export YTFZF_PID=$$
#}}}
############################
@@ -49,18 +51,18 @@ YTFZF_VERSION="2.5.5"
##################
# Starts with __ if it is a state variable thatt is allowed to be accessed globally.
- # for example: __is_submenu is a state variable that keeps track of whether or not itt is a submenu
- # another example: __scrape_count is the current scrape number
-
+# for example: __is_submenu is a state variable that keeps track of whether or not itt is a submenu
+# another example: __scrape_count is the current scrape number
+
# Environment variables should be all caps, do not use full caps for constansts
# Const variable should start with c_ or const_
# Configuration variables should not start with any prefix, and should have a --long-opt equivelent with as close of a name as posisble to the variable
- # example: the search_source variable has the long opt equivelent of --search-source
+# example: the search_source variable has the long opt equivelent of --search-source
# Private variables should start with an _
- # A major exception to this is the _search variable, which is global, and should not be used as a local variable.
+# A major exception to this is the _search variable, which is global, and should not be used as a local variable.
##################
# FUNCTION NAMES #
@@ -69,7 +71,7 @@ YTFZF_VERSION="2.5.5"
# Private functions should start with an _
# All other functions that should be accessed globally should not start with an _
- # A major exception to this is _get_request which is a global function
+# A major exception to this is _get_request which is a global function
# interface functions MUST start with interface_ in order to work properly
# scraper functions MUST start with scrape_ in order to work properly
@@ -81,7 +83,7 @@ YTFZF_VERSION="2.5.5"
# 0: success
# 1: general error
# 2: invalid -opt or command argument, invalid argument for opt, configuration error
- # eg: ytfzf -c terminal (invalid scrape)
+# eg: ytfzf -c terminal (invalid scrape)
# 3: missing dependency
# 4: scraping error
# 5: empty search
@@ -106,10 +108,10 @@ c_bold="\033[1m"
# __ytfzf__ extension {{{
-print_help___ytfzf__ () {
- #the [2A will clear the \n---__ytfzf__--- garbage (in supported terminals)
+print_help___ytfzf__() {
+ #the [2A will clear the \n---__ytfzf__--- garbage (in supported terminals)
printf "\033[2A%s" \
-"Usage: ytfzf [OPTIONS...] <search-query>
+ "Usage: ytfzf [OPTIONS...] <search-query>
The search-query can also be read from stdin
GENERAL OPTIONS:
-h Show this help text
@@ -348,226 +350,231 @@ print_help___ytfzf__ () {
"
}
-handle_playing_notifications (){
+handle_playing_notifications() {
# if no notify-send push error to /dev/null
if [ "$#" -le 1 ]; then
- unset IFS
- while read -r id title; do
- notify-send -c ytfzf -i "$thumb_dir/${id}.jpg" "Ytfzf Info" "Opening: $title" 2>/dev/null
- done <<-EOF
- $(jq -r '.[]|select(.url=="'"$*"'")|"\(.ID)\t\(.title)"' < "$ytfzf_video_json_file")
-EOF
+ unset IFS
+ while read -r id title; do
+ notify-send -c ytfzf -i "$thumb_dir/${id}.jpg" "Ytfzf Info" "Opening: $title" 2>/dev/null
+ done <<-EOF
+ $(jq -r '.[]|select(.url=="'"$*"'")|"\(.ID)\t\(.title)"' <"$ytfzf_video_json_file")
+ EOF
else
- notify-send -c ytfzf "ytfzf info" "Opening: $# videos" 2>/dev/null
+ notify-send -c ytfzf "ytfzf info" "Opening: $# videos" 2>/dev/null
fi
}
-on_open_url_handler___ytfzf__ () {
- [ "$notify_playing" -eq 1 ] && handle_playing_notifications "$@"
+on_open_url_handler___ytfzf__() {
+ [ "$notify_playing" -eq 1 ] && handle_playing_notifications "$@"
}
-on_clean_up___ytfzf__ () {
+on_clean_up___ytfzf__() {
# print_info "cleaning up\n"
# clean up only as parent process
# kill ytfzf sub process{{{
# I think this needs to be written to a file because of sub-shells
- jobs_file="${YTFZF_TEMP_DIR}/the-jobs-need-to-be-written-to-a-file-$$.list"
- jobs -p > "$jobs_file"
+ jobs_file="${YTFZF_TEMP_DIR}/the-jobs-need-to-be-written-to-a-file-$$.list"
+ jobs -p >"$jobs_file"
while read -r line; do
- [ "$line" ] && kill "$line" 2> /dev/null
- done < "$jobs_file"
- rm -f "$jobs_file"
+ [ "$line" ] && kill "$line" 2>/dev/null
+ done <"$jobs_file"
+ rm -f "$jobs_file"
#}}}
- if [ $__is_fzf_preview -eq 0 ]; then
- [ -d "$session_cache_dir" ] && [ "$keep_cache" -eq 0 ] && rm -rf "$session_cache_dir"
- fi
+ if [ "$__is_fzf_preview" -eq 0 ]; then
+ [ "$keep_cache" -eq 1 ] && print_debug "[CLEAN UP]: copying cache dir\n" cp -r "${session_cache_dir}" "${cache_dir}"
+ [ -d "$session_cache_dir" ] && rm -rf "$session_cache_dir"
+ fi
}
-on_load_fake_extension___ytfzf__ () {
- #these need to be here, because it modifies stuff for _getopts
- #also no harm done because enable_hist cannot be set to true with an --option
+on_load_fake_extension___ytfzf__() {
+ #these need to be here, because it modifies stuff for _getopts
+ #also no harm done because enable_hist cannot be set to true with an --option
- #do not check if hist is enabled, because on_load_fake_extension___ytfzf_history__ does that
- load_fake_extension "__ytfzf_history__" "1"
- load_fake_extension "__ytfzf_search_history__" "1"
+ #do not check if hist is enabled, because on_load_fake_extension___ytfzf_history__ does that
+ load_fake_extension "__ytfzf_history__" "1"
+ load_fake_extension "__ytfzf_search_history__" "1"
}
-on_post_set_vars___ytfzf__ () {
- [ -z "$ytdl_path" ] && { command_exists "yt-dlp" && ytdl_path="yt-dlp" || ytdl_path="youtube-dl"; }
- export YTDL_EXEC_NAME="${ytdl_path##*/}"
-
- : "${ytdl_pref:=$video_pref+$audio_pref/best/$video_pref/$audio_pref}"
+on_post_set_vars___ytfzf__() {
+ [ -z "$ytdl_path" ] && { command_exists "yt-dlp" && ytdl_path="yt-dlp" || ytdl_path="youtube-dl"; }
+ export YTDL_EXEC_NAME="${ytdl_path##*/}"
- : "${shortcut_binds="Enter,double-click,${download_shortcut},${video_shortcut},${audio_shortcut},${detach_shortcut},${print_link_shortcut},${show_formats_shortcut},${info_shortcut},${search_again_shortcut},${custom_shortcut_binds},${custom_shortcut_binds}"}"
+ : "${ytdl_pref:=$video_pref+$audio_pref/best/$video_pref/$audio_pref}"
- [ ! -d "$cache_dir" ] && mkdir -p "$cache_dir"
+ : "${shortcut_binds="Enter,double-click,${download_shortcut},${video_shortcut},${audio_shortcut},${detach_shortcut},${print_link_shortcut},${show_formats_shortcut},${info_shortcut},${search_again_shortcut},${custom_shortcut_binds},${custom_shortcut_binds}"}"
- # If file doesn't already exist (or if force-refresh is requested), cURL and cache it.
- # CHECK: implement check for force-request
- # CHECK: added --refresh-inv-instances to optargs
- [ ! -f "$instances_file" ] && refresh_inv_instances
+ [ ! -d "$cache_dir" ] && mkdir -p "$cache_dir"
- : "${invidious_instance:=$(get_random_invidious_instance)}"
+ # If file doesn't already exist (or if force-refresh is requested), cURL and cache it.
+ # CHECK: implement check for force-request
+ # CHECK: added --refresh-inv-instances to optargs
+ [ ! -f "$instances_file" ] && refresh_inv_instances
- FZF_DEFAULT_OPTS="--margin=0,3,0,0 $FZF_DEFAULT_OPTS"
+ : "${invidious_instance:=$(get_random_invidious_instance)}"
+ FZF_DEFAULT_OPTS="--margin=0,3,0,0 $FZF_DEFAULT_OPTS"
- [ "$multi_search" -eq 1 ] && load_fake_extension "__ytfzf_multisearch__"
+ [ "$multi_search" -eq 1 ] && load_fake_extension "__ytfzf_multisearch__"
- scrape_search_exclude="${scrape_search_exclude}${custom_scrape_search_exclude} "
+ scrape_search_exclude="${scrape_search_exclude}${custom_scrape_search_exclude} "
- source_scrapers
+ source_scrapers
- print_debug "\n=============\nVARIABLE DUMP\n=============\n"
- print_debug "$(set)\n"
- print_debug "\n============\nEND VAR DUMP\n============\n"
+ print_debug "\n=============\nVARIABLE DUMP\n=============\n"
+ print_debug "$(set)\n"
+ print_debug "\n============\nEND VAR DUMP\n============\n"
}
#}}}
# __ytfzf_multisearch__ extension {{{
-on_init_search___ytfzf_multisearch__ () {
- prepare_for_set_args ","
- # shellcheck disable=SC2086
- set -- $1
- end_of_set_args
- __total_search_count="$#"
- printf "%s\n" "$@" > "${session_cache_dir}/searches.list"
- # if we get rid of everything up to the first comma, and it's empty or equal to the original, there is 1 scrape
- if [ "$__total_scrape_count" -lt "$__total_search_count" ]; then
- scrape=$(mul_str "${scrape}," "$(($(wc -l < "${session_cache_dir}/searches.list")/__total_scrape_count))")
- set_scrape_count
+on_init_search___ytfzf_multisearch__() {
+ prepare_for_set_args ","
+ # shellcheck disable=SC2086
+ set -- $1
+ end_of_set_args
+ __total_search_count="$#"
+ printf "%s\n" "$@" >"${session_cache_dir}/searches.list"
+ # if we get rid of everything up to the first comma, and it's empty or equal to the original, there is 1 scrape
+ if [ "$__total_scrape_count" -lt "$__total_search_count" ]; then
+ scrape=$(mul_str "${scrape}," "$(($(wc -l <"${session_cache_dir}/searches.list") / __total_scrape_count))")
+ set_scrape_count
fi
}
-ext_on_search___ytfzf_multisearch__ () {
- get_search_from_source "next"
+ext_on_search___ytfzf_multisearch__() {
+ get_search_from_source "next"
}
-get_search_from_next (){
- _search=$(head -n "$__scrape_count" "${session_cache_dir}/searches.list" | tail -n 1)
+get_search_from_next() {
+ _search=$(head -n "$__scrape_count" "${session_cache_dir}/searches.list" | tail -n 1)
}
# }}}
# __ytfzf_history_management__ {{{
-on_load_fake_extension___ytfzf_history_management__ ( ){
- on_opt_parse_x () {
- clear_hist "${1:-all}"; exit 0
- }
- on_opt_parse_hist_clear () { on_opt_parse_x "$@"; }
-
- clear_hist () {
- case "$1" in
- search) : > "$search_hist_file"; print_info "Search history cleared\n" ;;
- watch) : > "$hist_file"; print_info "Watch history cleared\n" ;;
- *)
- : > "$search_hist_file"
- : > "$hist_file"
- print_info "History cleared\n" ;;
- esac
- }
+on_load_fake_extension___ytfzf_history_management__() {
+ on_opt_parse_x() {
+ clear_hist "${1:-all}"
+ exit 0
+ }
+ on_opt_parse_hist_clear() { on_opt_parse_x "$@"; }
+
+ clear_hist() {
+ case "$1" in
+ search)
+ : >"$search_hist_file"
+ print_info "Search history cleared\n"
+ ;;
+ watch)
+ : >"$hist_file"
+ print_info "Watch history cleared\n"
+ ;;
+ *)
+ : >"$search_hist_file"
+ : >"$hist_file"
+ print_info "History cleared\n"
+ ;;
+ esac
+ }
}
# }}}
# __ytfzf_history__ extension {{{
-on_load_fake_extension___ytfzf_history__ () {
+on_load_fake_extension___ytfzf_history__() {
- ! extension_is_loaded "__ytfzf_history_management__" && load_fake_extension "__ytfzf_history_management__"
+ ! extension_is_loaded "__ytfzf_history_management__" && load_fake_extension "__ytfzf_history_management__"
- : "${hist_file:="$cache_dir/watch_hist"}"
- on_opt_parse_history () {
- if [ "$enable_hist" -eq 0 ]; then
- die 1 "enable_hist must be set to 1 for -H/--history\n"
- fi
- scrape=history
- }
- on_opt_parse_H () {
- on_opt_parse_history "$@"
- }
+ : "${hist_file:="$cache_dir/watch_hist"}"
+ on_opt_parse_history() {
+ if [ "$enable_hist" -eq 0 ]; then
+ die 1 "enable_hist must be set to 1 for -H/--history\n"
+ fi
+ scrape=history
+ }
+ on_opt_parse_H() {
+ on_opt_parse_history "$@"
+ }
}
-on_open_url_handler___ytfzf_history__ () {
- add_to_hist "$ytfzf_video_json_file" < "$ytfzf_selected_urls"
+on_open_url_handler___ytfzf_history__() {
+ add_to_hist "$ytfzf_video_json_file" <"$ytfzf_selected_urls"
}
-add_to_hist () {
- print_debug "[WATCH HIST]: adding to file $hist_file\n"
+add_to_hist() {
+ [ "$enable_hist" -eq 1 ] || return
+ print_debug "[WATCH HIST]: adding to file $hist_file\n"
# id of the video to add to hist will be passed through stdin
# if multiple videos are selected, multiple ids will be present on multiple lines
json_file="$1"
- urls="["
- while read -r url; do
- urls="${urls}\"${url}\","
- done
- urls="${urls%,}]"
- jq -r '[ .[]|select(.url as $url | '"$urls"' | index($url) >= 0)]' < "$json_file" | sed "/\[\]/d" | sed "2s/$/\n \"viewed\": \"$(date +'%m\/%d\/%y\ %H\:%M\:%S\ %z')\",/" >> "$hist_file"
+ urls="$(printf '"%s",' $(cat))"
+ urls="[${urls%,}]"
+ jq -r '[ .[]|select(.url as $url | '"$urls"' | index($url) >= 0)]' <"$json_file" | sed "/\[\]/d" | sed "2s/$/\n \"viewed\": \"$(date +'%m\/%d\/%y\ %H\:%M\:%S\ %z')\",/" >>"$hist_file"
unset url urls json_file
}
-scrape_history () {
+scrape_history() {
enable_hist=0 # enabling history while scrape is history causes issues
scrape_json_file "$hist_file" "$2"
- cp "$2" "$2.tmp"
- jq -s '[.[]|.[]+{scraper: "watch_history"}]' < "$2.tmp" > "$2"
- rm "$2.tmp"
+ cp "$2" "$2.tmp"
+ jq -s '[.[]|.[]+{scraper: "watch_history"}]' <"$2.tmp" >"$2"
+ rm "$2.tmp"
}
-scrape_H (){ scrape_history "$@"; }
+scrape_H() { scrape_history "$@"; }
-video_info_text_watch_history () {
+video_info_text_watch_history() {
viewed_len=19
- [ "${views#"|"}" -eq "${views#"|"}" ] 2>/dev/null && views="|$(printf "%s" "${views#"|"}" | add_commas)"
- printf "%-${title_len}.${title_len}s\t" "$title"
- printf "%-${channel_len}.${channel_len}s\t" "$channel"
- printf "%-${dur_len}.${dur_len}s\t" "$duration"
- printf "%-${view_len}.${view_len}s\t" "$views"
- printf "%-${date_len}.${date_len}s\t" "$date"
- printf "%-${viewed_len}.${viewed_len}s\t" "$viewed"
- printf "%s" "$url"
- printf "\n"
+ [ "${views#"|"}" -eq "${views#"|"}" ] 2>/dev/null && views="|$(printf "%s" "${views#"|"}" | add_commas)"
+ printf "%-${title_len}.${title_len}s\t" "$title"
+ printf "%-${channel_len}.${channel_len}s\t" "$channel"
+ printf "%-${dur_len}.${dur_len}s\t" "$duration"
+ printf "%-${view_len}.${view_len}s\t" "$views"
+ printf "%-${date_len}.${date_len}s\t" "$date"
+ printf "%-${viewed_len}.${viewed_len}s\t" "$viewed"
+ printf "%s" "$url"
+ printf "\n"
}
# }}}
# __ytfzf_search_history__ extension {{{
-on_load_fake_extension___ytfzf_search_history__ (){
+on_load_fake_extension___ytfzf_search_history__() {
- ! extension_is_loaded "__ytfzf_history_management__" && load_fake_extension "__ytfzf_history_management__"
+ ! extension_is_loaded "__ytfzf_history_management__" && load_fake_extension "__ytfzf_history_management__"
- : "${search_hist_file:="/dev/null"}" # MODIFIED
- on_opt_parse_q () {
- if [ "$enable_search_hist" -eq 0 ]; then
- die 1 'In order to use this search history must be enabled\n'
- fi
- [ ! -s "$search_hist_file" ] && die 1 "You have no search history\n"
- search_source="hist"
- }
- on_opt_parse_search_hist () {
- on_opt_parse_q "$@"
- }
+ : "${search_hist_file:="/dev/null"}" # MODIFIED
+ on_opt_parse_q() {
+ if [ "$enable_search_hist" -eq 0 ]; then
+ die 1 'In order to use this search history must be enabled\n'
+ fi
+ [ ! -s "$search_hist_file" ] && die 1 "You have no search history\n"
+ search_source="hist"
+ }
+ on_opt_parse_search_hist() {
+ on_opt_parse_q "$@"
+ }
}
-on_post_set_vars___ytfzf_search_history__ () {
- [ "${use_search_hist:-0}" -eq 1 ] && print_warning "use_search_hist is deprecated, please use search_source=hist instead\n" && search_source=hist
+on_post_set_vars___ytfzf_search_history__() {
+ [ "${use_search_hist:-0}" -eq 1 ] && print_warning "use_search_hist is deprecated, please use search_source=hist instead\n" && search_source=hist
}
-on_init_search___ytfzf_history__ () {
- #these options should only exist if history is enabled
- [ -n "$_search" ] && [ "$__is_submenu" -eq 0 ] && [ "$__is_fzf_preview" -eq 0 ] && handle_search_history "$_search" "$search_hist_file"
+on_init_search___ytfzf_history__() {
+ [ "$enable_search_hist" -eq 1 ] && [ -n "$_search" ] && [ "$__is_submenu" -eq 0 ] && [ "$__is_fzf_preview" -eq 0 ] && handle_search_history "$_search" "$search_hist_file"
}
-get_search_from_hist (){
- _search="$(parse_search_hist_file < "$search_hist_file" | quick_menu_wrapper)"
+get_search_from_hist() {
+ _search="$(parse_search_hist_file <"$search_hist_file" | quick_menu_wrapper)"
}
-parse_search_hist_file () {
+parse_search_hist_file() {
awk -F"${tab_space}" '{ if ($2 == "") {print $1} else {print $2} }'
}
-handle_search_history () {
- printf "%s${tab_space}%s\n" "$(date +'%D %H:%M:%S %z')" "${1}" >> "$2"
+handle_search_history() {
+ printf "%s${tab_space}%s\n" "$(date +'%D %H:%M:%S %z')" "${1}" >>"$2"
}
# }}}
@@ -579,8 +586,8 @@ handle_search_history () {
############################
# In order to be a utility function it must meet the following requirements:
- # Does not have side effects
- # Can be redefined by the user in an extension or config file
+# Does not have side effects
+# Can be redefined by the user in an extension or config file
## Jq util{{{
jq_pad_left='
@@ -591,26 +598,26 @@ def pad_left(n; num):
# }}}
# Invidious{{{
-refresh_inv_instances () {
- print_info "Fetching list of healthy invidious instances ...\n" &&
- # The pipeline does the following:
- # - Fetches the avaiable invidious instances
- # - Gets the one where the api is public
- # - Puts them in a list
- curl -X GET -sSf "$instances_url" | jq -r '[.[]|select(.[1].api==true)|.[1].uri]|join("\n")' > "$instances_file"
+refresh_inv_instances() {
+ print_info "Fetching list of healthy invidious instances ...\n" &&
+ # The pipeline does the following:
+ # - Fetches the avaiable invidious instances
+ # - Gets the one where the api is public
+ # - Puts them in a list
+ curl -X GET -sSf "$instances_url" | jq -r '[.[]|select(.[1].api==true)|.[1].uri]|join("\n")' >"$instances_file"
}
-get_invidious_instances () {
- cat "$instances_file"
+get_invidious_instances() {
+ cat "$instances_file"
}
-get_random_invidious_instance () {
- shuf "$instances_file" | head -n 1
+get_random_invidious_instance() {
+ shuf "$instances_file" | head -n 1
}
# }}}
# General Scraping{{{
-_get_request () {
+_get_request() {
_base_url=$1
shift 1
# Get search query from youtube
@@ -621,140 +628,147 @@ _get_request () {
--compressed
}
-create_sorted_video_data () {
- jq -c -r 'select(.!=[])|.[]' < "$ytfzf_video_json_file" | sort_video_data_fn
+create_sorted_video_data() {
+ jq -c -r 'select(.!=[])|.[]' <"$ytfzf_video_json_file" | sort_video_data_fn
}
-download_thumbnails () {
- [ "$skip_thumb_download" -eq 1 ] && { print_info "Skipping thumbnail download\n"; return 0; }
+download_thumbnails() {
+ [ "$skip_thumb_download" -eq 1 ] && {
+ print_info "Skipping thumbnail download\n"
+ return 0
+ }
[ "$async_thumbnails" -eq 0 ] && print_info 'Fetching thumbnails...\n'
curl_config_file="${session_temp_dir}/curl_config"
[ -z "$*" ] && return 0
- : > "$curl_config_file"
+ : >"$curl_config_file"
for line in "$@"; do
printf "url=\"%s\"\noutput=\"$thumb_dir/%s.jpg\"\n" "${line%%';'*}" "${line##*';'}"
- done >> "$curl_config_file"
+ done >>"$curl_config_file"
curl -fLZ -K "$curl_config_file"
[ $? -eq 2 ] && curl -fL -K "$curl_config_file"
}
-get_missing_thumbnails () {
+get_missing_thumbnails() {
# this function could be done in a more pure-shell way, however it is extremely slow
_tmp_id_list_file="${session_temp_dir}/all-ids.list"
_downloaded_ids_file="${session_temp_dir}/downloaded-ids.list"
- # gets all ids and writes them to file
- jq -r '.[]|select(.thumbs!=null)|.ID' < "$ytfzf_video_json_file" | sort |uniq > "$_tmp_id_list_file"
- # gets thumb urls, and ids, and concatinates them such as: <thumbnail>;<id>
- # essencially gets all downloaded thumbnail ids, by checking $thumb_dir and substituting out the \.jpg at the end
- find "$thumb_dir" -type f | sed -n 's/^.*\///; s/\.jpg$//; /^[^\/]*$/p' | sort > "$_downloaded_ids_file"
+ # gets all ids and writes them to file
+ jq -r '.[]|select(.thumbs!=null)|.ID' <"$ytfzf_video_json_file" | sort | uniq >"$_tmp_id_list_file"
+ # gets thumb urls, and ids, and concatinates them such as: <thumbnail>;<id>
+ # essencially gets all downloaded thumbnail ids, by checking $thumb_dir and substituting out the \.jpg at the end
+ find "$thumb_dir" -type f | sed -n 's/^.*\///; s/\.jpg$//; /^[^\/]*$/p' | sort >"$_downloaded_ids_file"
- # Finds ids that appear in _tmp_id_list_file only
- # shellcheck disable=SC2089
- missing_ids="\"$(diff "$_downloaded_ids_file" "$_tmp_id_list_file" | sed -n 's/^[>+] *\(.*\)$/\1/p')\""
+ # Finds ids that appear in _tmp_id_list_file only
+ # shellcheck disable=SC2089
+ missing_ids="\"$(diff "$_downloaded_ids_file" "$_tmp_id_list_file" | sed -n 's/^[>+] *\(.*\)$/\1/p')\""
- # formats missing ids into the format: <thumb-url>;<id>
- jq --arg ids "$missing_ids" -r '.[]|select(.thumbs!=null)|select(.ID as $id | $ids | contains($id))|.thumbs + ";" + .ID' < "$ytfzf_video_json_file"
+ # formats missing ids into the format: <thumb-url>;<id>
+ jq --arg ids "$missing_ids" -r '.[]|select(.thumbs!=null)|select(.ID as $id | $ids | contains($id))|.thumbs + ";" + .ID' <"$ytfzf_video_json_file"
unset _tmp_id_list_file _downloaded_ids_file missing_ids
}
# }}}
#arg/ifs manipulation{{{
-prepare_for_set_args () {
- OLD_IFS=$IFS
- [ "$1" = "" ] && unset IFS || IFS=$1
- set -f
+prepare_for_set_args() {
+ OLD_IFS=$IFS
+ [ "$1" = "" ] && unset IFS || IFS=$1
+ set -f
}
-end_of_set_args (){
- IFS=$OLD_IFS
+end_of_set_args() {
+ IFS=$OLD_IFS
}
-modify_ifs () {
- OLD_IFS=$IFS
- IFS=${1:-" ${tab_space}${new_line}"}
+modify_ifs() {
+ OLD_IFS=$IFS
+ IFS=${1:-" ${tab_space}${new_line}"}
}
-end_modify_ifs (){
- IFS=$OLD_IFS
+end_modify_ifs() {
+ IFS=$OLD_IFS
}
# }}}
#general util{{{
-remove_quotes_on_var_value () {
- read -r val
- val="${val#[\"\']}"
- val="${val%[\"\']}"
- echo "$val"
- unset val
+remove_quotes_on_var_value() {
+ read -r val
+ val="${val#[\"\']}"
+ val="${val%[\"\']}"
+ echo "$val"
+ unset val
}
-_get_real_channel_link () {
- #trim whitespace
- read -r _input_link <<EOF
+_get_real_channel_link() {
+ #trim whitespace
+ read -r _input_link <<EOF
$1
EOF
- case "$1" in
- http?://*/@*)
- domain=${_input_link#https://}
- domain=${domain%%/*}
- printf "https://www.youtube.com/channel/%s\n" "$(_get_request "$_input_link" | sed -n 's/.*itemprop="channelId" content="\([^"]*\)".*/\1/p')"
- ;;
- http?://*/c/*|http?://*/user/*|*\.*)
- domain=${_input_link#https://}
- domain=${domain%%/*}
- url=$(printf "%s" "$_input_link" | sed 's_\(https://\)*\(www\.\)*youtube\.com_'"${invidious_instance}"'_')
- _get_real_channel_link_handle_empty_real_path () {
- printf "https://%s\n" "${1#https://}"
- } ;;
- [Uu][Cc]??????????????????????/videos|[Uu][Cc]??????????????????????|*channel/[Uu][Cc]??????????????????????|*channel/[Uu][Cc]??????????????????????/videos)
- id="${_input_link%/videos}"
- id="${id%/playlists}"
- id="${id%/streams}"
- id="${id##*channel/}"
- print_warning "$_input_link appears to be a youtube id, which is hard to detect, please use a full channel url next time\n"
- domain="youtube.com"
- url=$(printf "https://youtube.com/channel/%s/videos" "$id" | sed 's_\(https://\)*\(www\.\)*youtube\.com_'"${invidious_instance}"'_')
- _get_real_channel_link_handle_empty_real_path () {
- printf "%s\n" "https://${domain}/channel/${id}/videos"
- } ;;
- "@"*)
- for link in "https://www.youtube.com/user/${1#"@"}" "https://www.youtube.com/c/${1#"@"}"; do
- _real_link="$(_get_real_channel_link "$link")"
- if [ "$_real_link" != "$link" ] ; then
- printf "%s\n" "$_real_link"
- return 0
- fi
- done
- return 1 ;;
- *)
- _get_real_channel_link_handle_empty_real_path(){
- printf "$1\n"
- } ;;
- esac
+ case "$1" in
+ http?://*/@*)
+ domain=${_input_link#https://}
+ domain=${domain%%/*}
+ printf "https://www.youtube.com/channel/%s\n" "$(_get_request "$_input_link" | sed -n 's/.*itemprop="channelId" content="\([^"]*\)".*/\1/p')"
+ ;;
+ http?://*/c/* | http?://*/user/* | *\.*)
+ domain=${_input_link#https://}
+ domain=${domain%%/*}
+ url=$(printf "%s" "$_input_link" | sed 's_\(https://\)*\(www\.\)*youtube\.com_'"${invidious_instance}"'_')
+ _get_real_channel_link_handle_empty_real_path() {
+ printf "https://%s\n" "${1#https://}"
+ }
+ ;;
+ [Uu][Cc]??????????????????????/videos | [Uu][Cc]?????????????????????? | *channel/[Uu][Cc]?????????????????????? | *channel/[Uu][Cc]??????????????????????/videos)
+ id="${_input_link%/videos}"
+ id="${id%/playlists}"
+ id="${id%/streams}"
+ id="${id##*channel/}"
+ print_warning "$_input_link appears to be a youtube id, which is hard to detect, please use a full channel url next time\n"
+ domain="youtube.com"
+ url=$(printf "https://youtube.com/channel/%s/videos" "$id" | sed 's_\(https://\)*\(www\.\)*youtube\.com_'"${invidious_instance}"'_')
+ _get_real_channel_link_handle_empty_real_path() {
+ printf "%s\n" "https://${domain}/channel/${id}/videos"
+ }
+ ;;
+ "@"*)
+ for link in "https://www.youtube.com/user/${1#"@"}" "https://www.youtube.com/c/${1#"@"}"; do
+ _real_link="$(_get_real_channel_link "$link")"
+ if [ "$_real_link" != "$link" ]; then
+ printf "%s\n" "$_real_link"
+ return 0
+ fi
+ done
+ return 1
+ ;;
+ *)
+ _get_real_channel_link_handle_empty_real_path() {
+ printf "$1\n"
+ }
+ ;;
+ esac
real_path="$(curl -is "$url" | sed -n 's/^[Ll]ocation: //p' | sed 's/[\n\r]$//g')"
# prints the origional url because it was correct
- if [ -z "$real_path" ]; then
- _get_real_channel_link_handle_empty_real_path "$_input_link"
- return 0
- fi
- printf "%s\n" "https://${domain}${real_path}"
+ if [ -z "$real_path" ]; then
+ _get_real_channel_link_handle_empty_real_path "$_input_link"
+ return 0
+ fi
+ printf "%s\n" "https://${domain}${real_path}"
}
-trim_url () {
- while IFS= read -r _line;do
+trim_url() {
+ while IFS= read -r _line; do
printf '%s\n' "${_line##*"|"}"
done
}
-command_exists () {
- command -v "$1" > /dev/null 2>&1
+command_exists() {
+ command -v "$1" >/dev/null 2>&1
}
-is_relative_dir () {
+is_relative_dir() {
case "$1" in
- ../*|./*|~/*|/*) return 0 ;;
+ ../* | ./* | ~/* | /*) return 0 ;;
esac
return 1
}
@@ -771,21 +785,21 @@ get_key_value() {
}
# capitalizes the first letter of a string
-title_str () {
- awk '{printf "%s%s\n", toupper(substr($1,0,1)), substr($1,2)}' <<-EOF
- $1
-EOF
+title_str() {
+ awk '{printf "%s%s\n", toupper(substr($1,0,1)), substr($1,2)}' <<-EOF
+ $1
+ EOF
}
# backup shuf function, as shuf is not posix
-command_exists "shuf" || shuf () {
- #make awk read from fd 3, fd 3 will read $1 if exists, or stdin
- [ "$1" ] && exec 3< "$1" || exec 3<&0
+command_exists "shuf" || shuf() {
+ #make awk read from fd 3, fd 3 will read $1 if exists, or stdin
+ [ "$1" ] && exec 3<"$1" || exec 3<&0
awk -F'\n' 'BEGIN{srand()} {print rand() " " $0}' <&3 | sort -n | sed 's/[^ ]* //'
- exec 3<&-
+ exec 3<&-
}
-add_commas () {
+add_commas() {
awk '
{
for(i=0; i<length($1); i++){
@@ -799,7 +813,7 @@ add_commas () {
END{
print ""
}' |
- awk '
+ awk '
{
for (i=length($1); i>0; i--){
printf "%s", substr($1, i, 1)
@@ -808,92 +822,83 @@ add_commas () {
'
}
-mul_str () {
- str=$1
- by=$2
- new_str="$str"
- mul_str_i=1
- while [ "$mul_str_i" -lt "$by" ]; do
- new_str="${new_str}${str}"
- mul_str_i=$((mul_str_i+1))
- done
- printf "%s" "$new_str"
- unset mul_str_i new_str by str
+mul_str() {
+ str=$1
+ by=$2
+ new_str="$str"
+ mul_str_i=1
+ while [ "$mul_str_i" -lt "$by" ]; do
+ new_str="${new_str}${str}"
+ mul_str_i=$((mul_str_i + 1))
+ done
+ printf "%s" "$new_str"
+ unset mul_str_i new_str by str
}
-detach_cmd () {
- nohup "$@" > "/dev/null" 2>&1 &
+detach_cmd() {
+ nohup "$@" >"/dev/null" 2>&1 &
}
-remove_ansi_escapes () {
- sed -e 's/[[:cntrl:]]\[\([[:digit:]][[:digit:]]*\(;\|m\)\)*//g'
+remove_ansi_escapes() {
+ sed -e 's/[[:cntrl:]]\[\([[:digit:]][[:digit:]]*\(;\|m\)\)*//g'
}
# }}}
#Menu stuff{{{
-quick_menu () {
- fzf --ansi --reverse --prompt="$1"
+quick_menu() {
+ fzf --ansi --reverse --prompt="$1"
}
-quick_menu_ext (){
+quick_menu_ext() {
external_menu "$1"
}
-quick_menu_scripting () {
- quick_menu "$1"
-}
-info_wait_prompt () {
- printf "%s\n" "quit [q]" "quit (override -l) [Q]" "open menu [c]" "play [enter]"
- read -r info_wait_action
-}
-info_wait_prompt_ext () {
- info_wait_action=$(printf "%s\n" "quit: q" "quit (override -l): Q" "open menu: c" "play: enter" | quick_menu_wrapper "Choose action" | sed -e 's/enter//' -e 's/^.*: \(.*\)$/\1/p' | tr -d '[:space:]')
+info_wait_prompt() {
+ printf "%s\n" "quit [q]" "quit (override -l) [Q]" "open menu [c]" "play [enter]"
+ read -r info_wait_action
}
-info_wait_prompt_scripting () {
- info_wait_prompt "$@"
+info_wait_prompt_ext() {
+ info_wait_action=$(printf "%s\n" "quit: q" "quit (override -l): Q" "open menu: c" "play: enter" | quick_menu_wrapper "Choose action" | sed -e 's/enter//' -e 's/^.*: \(.*\)$/\1/p' | tr -d '[:space:]')
}
-display_text () {
- printf "%s\n" "$@"
-}
-display_text_ext () {
- display_text "$@"
+display_text() {
+ printf "%s\n" "$@"
}
-display_text_scripting () {
- display_text "$@"
+display_text_ext() {
+ display_text "$@"
}
-display_text_wrapper () {
- generic_wrapper "display_text" "$@"
+display_text_wrapper() {
+ generic_wrapper "display_text" "$@"
}
-info_wait_prompt_wrapper (){
- generic_wrapper "info_wait_prompt" "$@"
+info_wait_prompt_wrapper() {
+ generic_wrapper "info_wait_prompt" "$@"
}
-search_prompt_menu_wrapper () {
- generic_wrapper "search_prompt_menu" "$@"
+search_prompt_menu_wrapper() {
+ generic_wrapper "search_prompt_menu" "$@"
}
-quick_menu_wrapper () {
- generic_wrapper "quick_menu" "$1"
+quick_menu_wrapper() {
+ generic_wrapper "quick_menu" "$1"
}
-generic_wrapper () {
- base_name=$1
- shift
- fn_name="$base_name""$(printf "%s" "${interface:+_$interface}" | sed 's/-/_/g')"
- if command_exists "$fn_name"; then
- print_debug "[INTERFACE]: Running menu function: $fn_name\n"
- $fn_name "$@"
- else
- print_debug "[INTERFACE]: Menu function $fn_name did not exist, falling back to ${base_name}_ext\n"
- "$base_name"_ext "$@"
- fi
- unset fn_name
+generic_wrapper() {
+ base_name=$1
+ shift
+ fn_name="$base_name""$(printf "%s" "${interface:+_$interface}" | sed 's/-/_/g')"
+ if command_exists "$fn_name"; then
+ print_debug "[INTERFACE]: Running menu function: $fn_name\n"
+ $fn_name "$@"
+ else
+ print_debug "[INTERFACE]: Menu function $fn_name did not exist, falling back to ${base_name}_ext\n"
+ "$base_name"_ext "$@"
+ fi
+ unset fn_name
}
# The menu to use instead of fzf when -D is specified
-external_menu () {
+external_menu() {
# dmenu extremely laggy when showing tabs
tr -d '\t' | remove_ansi_escapes | dmenu -i \
-l 30 \
@@ -904,65 +909,63 @@ external_menu () {
-sf '#ffffff' # MODIFIED
}
-search_prompt_menu () {
- printf "Search\n> " > /dev/stderr
+search_prompt_menu() {
+ printf "Search\n> " >/dev/stderr
read -r _search
- printf "\033[1A\033[K\r%s\n" "> $_search" > /dev/stderr
+ printf "\033[1A\033[K\r%s\n" "> $_search" >/dev/stderr
}
-search_prompt_menu_ext () {
+search_prompt_menu_ext() {
_search="$(printf '' | external_menu "Search: ")"
}
-search_prompt_menu_scripting () {
- search_prompt_menu
-}
-run_interface () {
- if [ "$show_thumbnails" -eq 1 ]; then
- prepare_for_set_args
- case "$async_thumbnails" in
- 0) download_thumbnails $(get_missing_thumbnails) ;;
- 1) download_thumbnails $(get_missing_thumbnails) > /dev/null 2>&1 & ;;
- esac
- end_of_set_args
- fi
- _interface="interface_${interface:-text}"
+run_interface() {
+
+ if [ "$show_thumbnails" -eq 1 ]; then
+ prepare_for_set_args
+ case "$async_thumbnails" in
+ 0) download_thumbnails $(get_missing_thumbnails) ;;
+ 1) download_thumbnails $(get_missing_thumbnails) >/dev/null 2>&1 & ;;
+ esac
+ end_of_set_args
+ fi
- print_debug "[INTERFACE]: Running interface: $_interface\n"
+ _interface="interface_${interface:-text}"
+ print_debug "[INTERFACE]: Running interface: $_interface\n"
- $(printf "%s" "$_interface" | sed 's/-/_/g') "$ytfzf_video_json_file" "$ytfzf_selected_urls"
- unset _interface
+ $(printf "%s" "$_interface" | sed 's/-/_/g') "$ytfzf_video_json_file" "$ytfzf_selected_urls"
+ unset _interface
}
-_video_info_text () {
- [ "${views#"|"}" -eq "${views#"|"}" ] 2>/dev/null && views="|$(printf "%s" "${views#"|"}" | add_commas)"
- printf "%-${title_len}.${title_len}s\t" "$title"
- printf "%-${channel_len}.${channel_len}s\t" "$channel"
- printf "%-${dur_len}.${dur_len}s\t" "$duration"
- printf "%-${view_len}.${view_len}s\t" "$views"
- printf "%-${date_len}.${date_len}s\t" "$date"
- printf "%s" "$url"
- printf "\n"
+_video_info_text() {
+ [ "${views#"|"}" -eq "${views#"|"}" ] 2>/dev/null && views="|$(printf "%s" "${views#"|"}" | add_commas)"
+ printf "%-${title_len}.${title_len}s\t" "$title"
+ printf "%-${channel_len}.${channel_len}s\t" "$channel"
+ printf "%-${dur_len}.${dur_len}s\t" "$duration"
+ printf "%-${view_len}.${view_len}s\t" "$views"
+ printf "%-${date_len}.${date_len}s\t" "$date"
+ printf "%s" "$url"
+ printf "\n"
}
#This function generates a series of lines that will be displayed in fzf, or some other interface
#takes in a series of jsonl lines, each jsonl should follow the VIDEO JSON FORMAT
-video_info_text () {
- jq -r '[.title, .channel, .duration, .views, .date, .viewed, .url, .scraper]|join("\t|")' | while IFS="$tab_space" read -r title channel duration views date viewed url scraper; do
- scraper="${scraper#"|"}"
- fn_name=video_info_text_"${scraper}"
- if command_exists "$fn_name"; then
- "$fn_name" "$title" "$channel" "$duration" "$views" "$date" "$viewed" "$url" "$scraper"
- else
- _video_info_text "$title" "$channel" "$duration" "$views" "$date" "$viewed" "$url" "$scraper"
- fi
- done
- unset title channel duration views date viewed url scraper
+video_info_text() {
+ jq -r '[.title, .channel, .duration, .views, .date, .viewed, .url, .scraper]|join("\t|")' | while IFS="$tab_space" read -r title channel duration views date viewed url scraper; do
+ scraper="${scraper#"|"}"
+ fn_name=video_info_text_"${scraper}"
+ if command_exists "$fn_name"; then
+ "$fn_name" "$title" "$channel" "$duration" "$views" "$date" "$viewed" "$url" "$scraper"
+ else
+ _video_info_text "$title" "$channel" "$duration" "$views" "$date" "$viewed" "$url" "$scraper"
+ fi
+ done
+ unset title channel duration views date viewed url scraper
}
# This is completely unrelated to video_info_text
# It is used in preview_img for when text should appear in the preview in fzf
-thumbnail_video_info_text () {
+thumbnail_video_info_text() {
[ "$views" -eq "$views" ] 2>/dev/null && views="$(printf "%s" "$views" | add_commas)"
[ -n "$title" ] && printf "\n ${c_cyan}%s" "$title"
[ -n "$channel" ] && printf "\n ${c_blue}Channel ${c_green}%s" "$channel"
@@ -976,20 +979,20 @@ thumbnail_video_info_text () {
# Extension stuff{{{
-do_an_event_function () {
- event="$1"
- shift
- print_debug "[EVENT]: doing event: $event\n"
- command_exists "$event" && $event "$@"
- prepare_for_set_args " "
- for ext in $loaded_extensions; do
-
- command_exists "${event}_$ext" && print_debug "[EVENT]: $ext running $event\n" && "${event}_$ext" "$@"
- done
- end_of_set_args
+do_an_event_function() {
+ event="$1"
+ shift
+ print_debug "[EVENT]: doing event: $event\n"
+ command_exists "$event" && $event "$@"
+ prepare_for_set_args " "
+ for ext in $loaded_extensions; do
+
+ command_exists "${event}_$ext" && print_debug "[EVENT]: $ext running $event\n" && "${event}_$ext" "$@"
+ done
+ end_of_set_args
}
-source_scrapers () {
+source_scrapers() {
prepare_for_set_args ","
for _scr in $scrape; do
if [ -f "$YTFZF_CUSTOM_SCRAPERS_DIR/$_scr" ]; then
@@ -1000,131 +1003,150 @@ source_scrapers () {
. "${YTFZF_SYSTEM_ADDON_DIR}/scrapers/$_scr"
fi
[ "$__is_fzf_preview" -eq 0 ] && command_exists "on_startup_$_scr" && "on_startup_$_scr"
- print_debug "[LOADING]: Loaded scraper: $_scr\n"
+ print_debug "[LOADING]: Loaded scraper: $_scr\n"
done
end_of_set_args
}
-extension_is_loaded () {
- case "$loaded_extensions" in
- #the extension may be at the middle, beginning, or end
- #spaces must be accounted differently
- *" $1 "*|"$1 "*|*" $1") return 0 ;;
- *) return 1 ;;
- esac
+extension_is_loaded() {
+ case "$loaded_extensions" in
+ #the extension may be at the middle, beginning, or end
+ #spaces must be accounted differently
+ *" $1 "* | "$1 "* | *" $1") return 0 ;;
+ *) return 1 ;;
+ esac
}
-load_extension () {
- ext=$1
- loaded_extensions="$loaded_extensions $(printf "%s" "${ext##*/}" | sed 's/[ -]/_/g')"
- loaded_extensions="${loaded_extensions# }"
+load_extension() {
+ ext=$1
+ loaded_extensions="$loaded_extensions $(printf "%s" "${ext##*/}" | sed 's/[ -]/_/g')"
+ loaded_extensions="${loaded_extensions# }"
- prepare_for_set_args
- for path in "${YTFZF_EXTENSIONS_DIR}/${ext}" "${YTFZF_SYSTEM_ADDON_DIR}/extensions/${ext}" "${ext}"; do
- if [ -f "${path}" ]; then
- __loaded_path="${path}" . "${path}"
- rv="$?"
- break
- else rv=127
- fi
- done
- end_of_set_args
+ prepare_for_set_args
+ for path in "${YTFZF_EXTENSIONS_DIR}/${ext}" "${YTFZF_SYSTEM_ADDON_DIR}/extensions/${ext}" "${ext}"; do
+ if [ -f "${path}" ]; then
+ __loaded_path="${path}" . "${path}"
+ rv="$?"
+ break
+ else
+ rv=127
+ fi
+ done
+ end_of_set_args
- print_debug "[LOADING]: loaded extension: ${ext} with exit code: ${rv}\n"
+ print_debug "[LOADING]: loaded extension: ${ext} with exit code: ${rv}\n"
- return $rv
+ return $rv
}
#for extensions succh as __ytfzf__
-load_fake_extension () {
- _should_be_first="$2"
- if [ "${_should_be_first:-0}" -eq 1 ]; then
- loaded_extensions="$1 ${loaded_extensions}"
- else
- loaded_extensions="${loaded_extensions} $1"
- loaded_extensions="${loaded_extensions# }"
- fi
+load_fake_extension() {
+ _should_be_first="$2"
+ if [ "${_should_be_first:-0}" -eq 1 ]; then
+ loaded_extensions="$1 ${loaded_extensions}"
+ else
+ loaded_extensions="${loaded_extensions} $1"
+ loaded_extensions="${loaded_extensions# }"
+ fi
- command_exists "on_load_fake_extension_$1" && on_load_fake_extension_"$1"
- print_debug "[LOADING]: fake extension: $1 loaded\n"
-}
-
-load_sort_name () {
- _sort_name=$1
- # shellcheck disable=SC1090
- # shellcheck disable=SC2015
- case "$_sort_name" in
- ./*|../*|/*|~/*) command_exists "$_sort_name" && . "$_sort_name" ;;
- *)
- if [ -f "${YTFZF_SORT_NAMES_DIR}/${_sort_name}" ]; then
- . "${YTFZF_SORT_NAMES_DIR}/${_sort_name}"
- elif [ -f "${YTFZF_SYSTEM_ADDON_DIR}/sort-names/${_sort_name}" ]; then
- . "${YTFZF_SYSTEM_ADDON_DIR}/sort-names/${_sort_name}"
- else false
- fi ;;
- esac
- rv="$?"
- unset "$_sort_name"
- print_debug "[LOADING]: loaded sort name: ${_sort_name} with exit code: ${rv}\n"
- return "$rv"
-}
-
-load_url_handler () {
- requested_url_handler=$1
- if command_exists "$requested_url_handler"; then
- url_handler="${requested_url_handler:-multimedia_player}"
- else
- for path in "$YTFZF_URL_HANDLERS_DIR" "$YTFZF_SYSTEM_ADDON_DIR/url-handlers"; do
- [ -f "${path}/${requested_url_handler}" ] && url_handler="${path}/${requested_url_handler}" && return
- done
- die 2 "$1 is not a url-handler\n"
- fi
- print_debug "[LOADING]: loaded url handler: ${requested_url_handler}\n"
-}
-
-load_interface () {
- requested_interface="$1"
- # if we don't check which interface, itll try to source $YTFZF_CUSTOM_INTERFACES_DIR/{ext,scripting} which won't work
- # shellcheck disable=SC1090
- case "$requested_interface" in
- "ext"|"scripting"|"") interface=$requested_interface; true ;;
- ./*|../*|/*|~/*) [ -f "$requested_interface" ] && . "$requested_interface" && interface="${requested_interface##*/}"; false ;;
- *)
- if [ -f "${YTFZF_CUSTOM_INTERFACES_DIR}/${requested_interface}" ]; then
+ command_exists "on_load_fake_extension_$1" && on_load_fake_extension_"$1"
+ print_debug "[LOADING]: fake extension: $1 loaded\n"
+}
+
+load_sort_name() {
+ _sort_name=$1
+ # shellcheck disable=SC1090
+ # shellcheck disable=SC2015
+ case "$_sort_name" in
+ ./* | ../* | /* | ~/*) command_exists "$_sort_name" && . "$_sort_name" ;;
+ *)
+ if [ -f "${YTFZF_SORT_NAMES_DIR}/${_sort_name}" ]; then
+ . "${YTFZF_SORT_NAMES_DIR}/${_sort_name}"
+ elif [ -f "${YTFZF_SYSTEM_ADDON_DIR}/sort-names/${_sort_name}" ]; then
+ . "${YTFZF_SYSTEM_ADDON_DIR}/sort-names/${_sort_name}"
+ else
+ false
+ fi
+ ;;
+ esac
+ rv="$?"
+ unset "$_sort_name"
+ print_debug "[LOADING]: loaded sort name: ${_sort_name} with exit code: ${rv}\n"
+ return "$rv"
+}
+
+load_url_handler() {
+ requested_url_handler=$1
+ if command_exists "$requested_url_handler"; then
+ url_handler="${requested_url_handler:-multimedia_player}"
+ else
+ for path in "$YTFZF_URL_HANDLERS_DIR" "$YTFZF_SYSTEM_ADDON_DIR/url-handlers"; do
+ [ -f "${path}/${requested_url_handler}" ] && url_handler="${path}/${requested_url_handler}" && return
+ done
+ die 2 "$1 is not a url-handler\n"
+ fi
+ print_debug "[LOADING]: loaded url handler: ${requested_url_handler}\n"
+}
+
+load_interface() {
+ requested_interface="$1"
+ # if we don't check which interface, itll try to source $YTFZF_CUSTOM_INTERFACES_DIR/{ext,scripting} which won't work
+ # shellcheck disable=SC1090
+ case "$requested_interface" in
+ "ext" | "scripting" | "")
+ interface=$requested_interface
+ true
+ ;;
+ ./* | ../* | /* | ~/*)
+ [ -f "$requested_interface" ] && . "$requested_interface" && interface="${requested_interface##*/}"
+ false
+ ;;
+ *)
+ if [ -f "${YTFZF_CUSTOM_INTERFACES_DIR}/${requested_interface}" ]; then
interface=$requested_interface
. "$YTFZF_CUSTOM_INTERFACES_DIR/$requested_interface"
- elif [ -f "${YTFZF_SYSTEM_ADDON_DIR}/interfaces/${requested_interface}" ]; then
+ elif [ -f "${YTFZF_SYSTEM_ADDON_DIR}/interfaces/${requested_interface}" ]; then
interface=$requested_interface
. "${YTFZF_SYSTEM_ADDON_DIR}/interfaces/${requested_interface}"
true
- fi ;;
- esac
- rv="$?"
- unset requested_interface
- print_debug "[LOADING]: loaded interface: ${requested_interface}\n"
- return "$rv"
-}
-
-load_thumbnail_viewer () {
- _thumbnail_viewer="$1"
- case "$_thumbnail_viewer" in
- # these are special cases, where they are not themselves commands
- chafa-16|chafa|chafa-tty|catimg|catimg-256|imv|ueberzug|swayimg|mpv) thumbnail_viewer="$_thumbnail_viewer" ; true ;;
- swayimg-hyprland)
- print_warning "swayimg-hyprland thumbnail viewer may mess up any rules you have for swayimg\n"; thumbnail_viewer="$_thumbnail_viewer"
- ;;
- ./*|/*|../*|~/*) thumbnail_viewer="$_thumbnail_viewer"; false ;;
- *)
- if [ -f "${YTFZF_THUMBNAIL_VIEWERS_DIR}/${_thumbnail_viewer}" ]; then
- thumbnail_viewer="${YTFZF_THUMBNAIL_VIEWERS_DIR}/${_thumbnail_viewer}"
- else
- thumbnail_viewer="${YTFZF_SYSTEM_ADDON_DIR}/thumbnail-viewers/$_thumbnail_viewer"
- fi; false
- esac
- rv="$?"
- print_debug "[LOADING]: loaded thumbnail viewer: ${_thumbnail_viewer}\n"
- unset _thumbnail_viewer
- return $rv
+ fi
+ ;;
+ esac
+ rv="$?"
+ unset requested_interface
+ print_debug "[LOADING]: loaded interface: ${requested_interface}\n"
+ return "$rv"
+}
+
+load_thumbnail_viewer() {
+ _thumbnail_viewer="$1"
+ case "$_thumbnail_viewer" in
+ # these are special cases, where they are not themselves commands
+ chafa-16 | chafa | chafa-tty | catimg | catimg-256 | imv | ueberzug | iterm2 | swayimg | mpv | sixel | kitty)
+ thumbnail_viewer="$_thumbnail_viewer"
+ true
+ ;;
+ swayimg-hyprland)
+ print_warning "swayimg-hyprland thumbnail viewer may mess up any rules you have for swayimg\n"
+ thumbnail_viewer="$_thumbnail_viewer"
+ ;;
+ ./* | /* | ../* | ~/*)
+ thumbnail_viewer="$_thumbnail_viewer"
+ false
+ ;;
+ *)
+ if [ -f "${YTFZF_THUMBNAIL_VIEWERS_DIR}/${_thumbnail_viewer}" ]; then
+ thumbnail_viewer="${YTFZF_THUMBNAIL_VIEWERS_DIR}/${_thumbnail_viewer}"
+ else
+ thumbnail_viewer="${YTFZF_SYSTEM_ADDON_DIR}/thumbnail-viewers/$_thumbnail_viewer"
+ fi
+ false
+ ;;
+ esac
+ rv="$?"
+ print_debug "[LOADING]: loaded thumbnail viewer: ${_thumbnail_viewer}\n"
+ unset _thumbnail_viewer
+ return $rv
}
#}}}
@@ -1132,45 +1154,45 @@ load_thumbnail_viewer () {
#write to stderr if the logfile is that
if [ -z "$YTFZF_LOGFILE" ]; then
- print_debug () {
- [ "$log_level" -ge 3 ] && printf -- "${c_blue}[DEBUG]${c_reset}: $1" >&2
- return 0
- }
- print_info () {
- # information goes to stdout ( does not disturb show_link_only )
- # shellcheck disable=2059
- [ "$log_level" -ge 2 ] && printf -- "$1" >&2
- }
- print_warning () {
- # shellcheck disable=2059
- [ "$log_level" -ge 1 ] && printf -- "${c_yellow}[WARNING]${c_reset}: ${c_yellow}${1}${c_reset}" >&2
- }
- print_error () {
- # shellcheck disable=2059
- [ "$log_level" -ge 0 ] && printf -- "${c_red}[ERROR]${c_reset}: ${c_red}${1}${c_reset}" >&2
- }
+ print_debug() {
+ [ "$log_level" -ge 3 ] && printf -- "${c_blue}[DEBUG]${c_reset}: $1" >&2
+ return 0
+ }
+ print_info() {
+ # information goes to stdout ( does not disturb show_link_only )
+ # shellcheck disable=2059
+ [ "$log_level" -ge 2 ] && printf -- "$1" >&2
+ }
+ print_warning() {
+ # shellcheck disable=2059
+ [ "$log_level" -ge 1 ] && printf -- "${c_yellow}[WARNING]${c_reset}: ${c_yellow}${1}${c_reset}" >&2
+ }
+ print_error() {
+ # shellcheck disable=2059
+ [ "$log_level" -ge 0 ] && printf -- "${c_red}[ERROR]${c_reset}: ${c_red}${1}${c_reset}" >&2
+ }
#Otherwise do a bit of magic to remove ansi escape sequences, print to stderr, and to the log file
else
- print_debug () {
- [ "$log_level" -ge 3 ] && printf -- "[DEBUG]: $1" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
- return 0
- }
- print_info () {
- # information goes to stdout ( does not disturb show_link_only )
- # shellcheck disable=2059
- [ "$log_level" -ge 2 ] && printf -- "$1" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
- }
- print_warning () {
- # shellcheck disable=2059
- [ "$log_level" -ge 1 ] && printf -- "[WARNING]: ${1}" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
- }
- print_error () {
- # shellcheck disable=2059
- [ "$log_level" -ge 0 ] && printf -- "[ERROR]: ${1}" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
- }
+ print_debug() {
+ [ "$log_level" -ge 3 ] && printf -- "[DEBUG]: $1" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
+ return 0
+ }
+ print_info() {
+ # information goes to stdout ( does not disturb show_link_only )
+ # shellcheck disable=2059
+ [ "$log_level" -ge 2 ] && printf -- "$1" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
+ }
+ print_warning() {
+ # shellcheck disable=2059
+ [ "$log_level" -ge 1 ] && printf -- "[WARNING]: ${1}" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
+ }
+ print_error() {
+ # shellcheck disable=2059
+ [ "$log_level" -ge 0 ] && printf -- "[ERROR]: ${1}" | remove_ansi_escapes | tee -a "$YTFZF_LOGFILE" >&2
+ }
fi
-
-die () {
+
+die() {
_return_status=$1
print_error "$2"
exit "$_return_status"
@@ -1182,87 +1204,82 @@ die () {
# job of url handlers is:
# handle the given urls, and take into account some requested attributes, eg: video_pref, and --detach
# print what the handler is doing
-video_player () {
+video_player() {
# this function should not be set as the url_handler as it is part of multimedia_player
command_exists "mpv" || die 3 "mpv is not installed\n"
- [ "$is_detach" -eq 1 ] && use_detach_cmd=detach_cmd || use_detach_cmd=''
- # shellcheck disable=SC2086
- unset IFS
- $use_detach_cmd mpv --ytdl-format="$ytdl_pref" $(eval echo "$url_handler_opts") "$@"
+ [ "$is_detach" -eq 1 ] && use_detach_cmd=detach_cmd || use_detach_cmd=''
+ # shellcheck disable=SC2086
+ unset IFS
+ $use_detach_cmd mpv --ytdl-format="$ytdl_pref" $(eval echo "$url_handler_opts") "$@"
}
-audio_player () {
+audio_player() {
# this function should not be set as the url_handler as it is part of multimedia_player
command_exists "mpv" || die 3 "mpv is not installed\n"
# shellcheck disable=SC2086
- unset IFS
+ unset IFS
case "$is_detach" in
- 0) mpv --no-video --ytdl-format="$ytdl_pref" $(eval echo "$url_handler_opts") "$@" ;;
- 1) detach_cmd mpv --force-window --no-video --ytdl-format="$ytdl_pref" $(eval echo "$url_handler_opts") "$@" ;;
+ 0) mpv --no-video --ytdl-format="$ytdl_pref" $(eval echo "$url_handler_opts") "$@" ;;
+ 1) detach_cmd mpv --force-window --no-video --ytdl-format="$ytdl_pref" $(eval echo "$url_handler_opts") "$@" ;;
esac
}
-multimedia_player () {
+multimedia_player() {
# this function differentiates whether or not audio_only was requested
case "$is_audio_only" in
- 0) video_player "$@" ;;
- 1) audio_player "$@" ;;
+ 0) video_player "$@" ;;
+ 1) audio_player "$@" ;;
esac
}
-downloader () {
+downloader() {
command_exists "${ytdl_path}" || die 3 "${ytdl_path} is not installed\n"
- [ "$is_detach" -eq 1 ] && use_detach_cmd=detach_cmd || use_detach_cmd=''
+ [ "$is_detach" -eq 1 ] && use_detach_cmd=detach_cmd || use_detach_cmd=''
+ prepare_for_set_args
# shellcheck disable=SC2086
case $is_audio_only in
- 0) $use_detach_cmd "${ytdl_path}" -f "${ytdl_pref}" $ytdl_opts "$@" ;;
- 1) $use_detach_cmd "${ytdl_path}" -x -f "${audio_pref}" $ytdl_opts "$@" ;;
- esac && [ "$notify_playing" -eq 1 ] && notify-send -c ytfzf "Ytfzf Info" "Download complete"
+ 0) $use_detach_cmd "${ytdl_path}" -f "${ytdl_pref}" $ytdl_opts "$@" ;;
+ 1) $use_detach_cmd "${ytdl_path}" -x -f "${audio_pref}" $ytdl_opts "$@" ;;
+ esac && _success="finished" || _success="failed"
+ [ "$notify_playing" -eq 1 ] && notify-send -c ytfzf "Ytfzf Info" "Download $_success"
+
+ end_of_set_args
}
# }}}
# Searching {{{
-get_search_from_source () {
- source=$1
- shift
- prepare_for_set_args ":"
- for src in $source; do
- end_of_set_args
- case "$src" in
- args) _search="$initial_search" ;;
- prompt) search_prompt_menu_wrapper ;;
- fn-args) _search="$*" ;;
- *) command_exists "get_search_from_$src" && get_search_from_"$src" "$@" ;;
- esac
- [ "$_search" ] && break
- done
+get_search_from_source() {
+ source=$1
+ shift
+ prepare_for_set_args ":"
+ for src in $source; do
+ end_of_set_args
+ case "$src" in
+ args) _search="$initial_search" ;;
+ prompt) search_prompt_menu_wrapper ;;
+ fn-args) _search="$*" ;;
+ *) command_exists "get_search_from_$src" && get_search_from_"$src" "$@" ;;
+ esac
+ [ "$_search" ] && break
+ done
}
-
# }}}
#Misc{{{
-clean_up () {
- do_an_event_function on_clean_up
+clean_up() {
+ do_an_event_function on_clean_up
}
-usage () {
- unset IFS
- set -f
- for ext in $loaded_extensions; do
- if command_exists "print_help_$ext"; then
- printf "\n----%s----\n" "$ext"
- "print_help_$ext"
- fi
- done
-}
-
-start_ytfzf_server () {
- ! [ -p "$session_cache_dir/var-fifo" ] && mkfifo "$session_cache_dir/var-fifo"
- while :; do
- read -r var_name < "$session_cache_dir/var-fifo"
- eval echo "\$$var_name" > "$session_cache_dir/var-fifo"
- done
+usage() {
+ unset IFS
+ set -f
+ for ext in $loaded_extensions; do
+ if command_exists "print_help_$ext"; then
+ printf "\n----%s----\n" "$ext"
+ "print_help_$ext"
+ fi
+ done
}
# }}}
@@ -1275,150 +1292,148 @@ start_ytfzf_server () {
# Global Variables and Start Up {{{
-set_vars () {
+set_vars() {
- check_exists="${1:-1}"
+ check_exists="${1:-1}"
- # save the ecurrent environment so that any user set variables will be saved
- if [ "$check_exists" -eq 1 ]; then
- tmp_env="${YTFZF_TEMP_DIR}/ytfzf-env-$$"
- export -p > "$tmp_env"
- fi
-
- # debugging
- log_level="2" thumbnail_debug_log="/dev/null"
+ # save the ecurrent environment so that any user set variables will be saved
+ if [ "$check_exists" -eq 1 ]; then
+ tmp_env="${YTFZF_TEMP_DIR}/ytfzf-env-$$"
+ export -p >"$tmp_env"
+ fi
- # global vars
-
- gap_space=" "
- new_line='
- ' tab_space=$(printf '\t')
- #necessary as a seperator for -W
- EOT="$(printf '\003')"
-
- if [ "$COLUMNS" ] && [ "$LINES" ]; then
- TTY_COLS="${COLUMNS}"
- TTY_LINES="${LINES}"
- elif command_exists "tput"; then
- TTY_COLS=$(tput cols 2> /dev/null)
- TTY_LINES=$(tput lines 2> /dev/null)
- elif [ "${stty_cols_lines:=$(stty size 2> /dev/null)}" ]; then #set the var here to avoid running stty size twice.
- TTY_LINES="${stty_cols_lines% *}"
- TTY_COLS="${stty_cols_lines#* }"
- else
- print_warning "Could not determine terminal size, defaulting to 80 COLUMNS x 25 LINES\n"
- TTY_COLS=80
- TTY_LINES=25
- fi
+ # debugging
+ log_level="2" thumbnail_debug_log="/dev/null"
- #config vars
+ # global vars
- search_source=args:prompt
+ gap_space=" "
+ new_line='
+ ' tab_space=$(printf '\t')
+ #necessary as a seperator for -W
+ EOT="$(printf '\003')"
+
+ if [ "$COLUMNS" ] && [ "$LINES" ]; then
+ TTY_COLS="${COLUMNS}"
+ TTY_LINES="${LINES}"
+ elif command_exists "tput"; then
+ TTY_COLS=$(tput cols 2>/dev/null)
+ TTY_LINES=$(tput lines 2>/dev/null)
+ elif [ "${stty_cols_lines:=$(stty size 2>/dev/null)}" ]; then #set the var here to avoid running stty size twice.
+ TTY_LINES="${stty_cols_lines% *}"
+ TTY_COLS="${stty_cols_lines#* }"
+ else
+ print_warning "Could not determine terminal size, defaulting to 80 COLUMNS x 25 LINES\n"
+ TTY_COLS=80
+ TTY_LINES=25
+ fi
+ #config vars
- # scraping
- useragent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36"
+ search_source=args:prompt
- # menu options
+ # scraping
+ useragent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36"
- enable_submenus="1" submenu_opts="" submenu_scraping_opts="" enable_back_button="1"
- keep_vars=0
+ # menu options
- interface=""
+ enable_submenus="1" submenu_opts="" submenu_scraping_opts="" enable_back_button="1"
+ keep_vars=0
- fancy_subs="0" fancy_subs_left="-------------" fancy_subs_right="${fancy_subs_right=$fancy_subs_left}"
+ interface=""
- fzf_preview_side="left" thumbnail_viewer="ueberzug"
+ fancy_subs="0" fancy_subs_left="-------------" fancy_subs_right="${fancy_subs_right=$fancy_subs_left}"
- #actions are slow, disable if you want to increase runtime speed by 15ms
- enable_actions=1
+ fzf_preview_side="left" thumbnail_viewer="ueberzug"
- # shortcuts
- download_shortcut="alt-d" video_shortcut="alt-v" audio_shortcut="alt-m" detach_shortcut="alt-e" print_link_shortcut="alt-l" show_formats_shortcut="alt-f" info_shortcut="alt-i" search_again_shortcut="alt-s"
- custom_shortcut_binds=""
+ #actions are slow, disable if you want to increase runtime speed by 15ms
+ enable_actions=1
- next_page_action_shortcut="ctrl-p"
+ # shortcuts
+ download_shortcut="alt-d" video_shortcut="alt-v" audio_shortcut="alt-m" detach_shortcut="alt-e" print_link_shortcut="alt-l" show_formats_shortcut="alt-f" info_shortcut="alt-i" search_again_shortcut="alt-s"
- # interface design
- show_thumbnails="0" is_sort="0" skip_thumb_download="0" external_menu_len="210"
+ next_page_action_shortcut="ctrl-p"
- is_loop="0" search_again="0"
+ # interface design
+ show_thumbnails="0" is_sort="0" skip_thumb_download="0" external_menu_len="210"
- # Notifications
+ is_loop="0" search_again="0"
- notify_playing="0"
+ # Notifications
- # directories
- cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/ytfzf" keep_cache="0"
+ notify_playing="0"
- # history
- enable_hist="0" enable_search_hist="0" # MODIFIED
+ # directories
+ cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/ytfzf" keep_cache="0"
- # format options
- # variable for switching on sort (date)
- is_detach="0" is_audio_only="0"
- url_handler="multimedia_player"
- url_handler_opts=""
- info_to_print="" info_wait="0" info_wait_action="q"
- video_pref="bestvideo" audio_pref="bestaudio"
- show_formats="0" format_selection_screen="simple" format_selection_sort="height"
+ # history
+ enable_hist="0" enable_search_hist="0" # MODIFIED
- scripting_video_count="1"
- is_random_select="0" is_auto_select="0" is_specific_select="0"
+ # format options
+ # variable for switching on sort (date)
+ is_detach="0" is_audio_only="0"
+ url_handler="multimedia_player"
+ url_handler_opts=""
+ info_to_print="" info_wait="0" info_wait_action="q"
+ video_pref="bestvideo" audio_pref="bestaudio"
+ show_formats="0" format_selection_screen="simple" format_selection_sort="height"
- # option parsing
- long_opt_char="-"
+ scripting_video_count="1"
+ is_random_select="0" is_auto_select="0" is_specific_select="0"
- # scrape
- scrape="youtube"
- # this comes from invidious' api
- thumbnail_quality="high"
- sub_link_count="2"
+ # option parsing
+ long_opt_char="-"
- yt_video_link_domain="https://youtube.com"
- search_sort_by="relevance" search_upload_date="" search_video_duration="" search_result_type="video" search_result_features="" search_region="US"
- pages_to_scrape="" pages_start=""
- nsfw="false" odysee_video_search_count="30"
+ # scrape
+ scrape="youtube"
+ # this comes from invidious' api
+ thumbnail_quality="high"
+ sub_link_count="2"
- multi_search="0"
+ yt_video_link_domain="https://youtube.com"
+ search_sort_by="relevance" search_upload_date="" search_video_duration="" search_result_type="video" search_result_features="" search_region="US"
+ pages_to_scrape="" pages_start=""
+ nsfw="false" odysee_video_search_count="30"
- custom_scrape_search_exclude="" scrape_search_exclude=" youtube-subscriptions S SI SL T youtube-trending H history "
+ multi_search="0"
- max_thread_count="20"
+ custom_scrape_search_exclude="" scrape_search_exclude=" youtube-subscriptions S SI SL T youtube-trending H history "
- # When set to 1, instead of having to wait for thumbnails to download
- # The menu opens immediately while thumbnails download in the background
- async_thumbnails="0"
+ max_thread_count="20"
- #misc
- instances_url="https://api.invidious.io/instances.json?sort_by=type,health,api"
- instances_file="$cache_dir/instancesV2.json"
+ # When set to 1, instead of having to wait for thumbnails to download
+ # The menu opens immediately while thumbnails download in the background
+ async_thumbnails="0"
- # read from environment to reset any variables to what the user set
- if [ "$check_exists" -eq 1 ]; then
- _current_var_name=
- _current_var_value=
- while read -r _var; do
- [ -z "$_var" ] && continue
- case "$_var" in
- export\ *)
- [ "$_current_var_name" ] && {
- [ "$_current_var_name" = "invidious_instance" ] && notify-send "$_current_var_value"
- export "${_current_var_name}"="$(eval echo "$_current_var_value")"
- _current_var_name=""
- _current_var_value=""
- }
- _current_var_name="${_var#"export "}"
- _current_var_name="${_current_var_name%%=*}"
- _current_var_value="${_var#*=}"
- ;;
- *) _current_var_value="${_current_var_value}${new_line}${_var}" ;;
- esac
- done < "$tmp_env"
+ #misc
+ instances_url="https://api.invidious.io/instances.json?sort_by=type,health,api"
+ instances_file="$cache_dir/instancesV2.json"
- rm "$tmp_env"
- fi
- unset check_exists _var _current_var_name _current_var_value
+ # read from environment to reset any variables to what the user set
+
+ if [ "$check_exists" -eq 1 ]; then
+ _current_var_name=
+ _current_var_value=
+ while read -r _var; do
+ [ -z "$_var" ] && continue
+ case "$_var" in
+ export" "*)
+ [ "$_current_var_name" ] && {
+ export "${_current_var_name}"="$(eval echo "$_current_var_value")"
+ _current_var_name=""
+ _current_var_value=""
+ }
+ _current_var_name="${_var#"export "}"
+ _current_var_name="${_current_var_name%%=*}"
+ _current_var_value="${_var#*=}"
+ ;;
+ (*) _current_var_value="${_current_var_value}${new_line}${_var}" ;;
+ esac
+ done <"$tmp_env"
+
+ rm "$tmp_env"
+ fi
+ unset check_exists _var _current_var_name _current_var_value
}
set_vars "${check_vars_exists}"
@@ -1426,7 +1441,7 @@ set_vars "${check_vars_exists}"
# hard dependency checks{{{
missing_deps=""
for dep in jq curl; do
- command_exists "$dep" || missing_deps="${missing_deps}, ${dep}"
+ command_exists "$dep" || missing_deps="${missing_deps}, ${dep}"
done
[ "$missing_deps" ] && die 3 "You are missing the following required dependencies${missing_deps}, Please install them.\n"
unset missing_deps
@@ -1450,16 +1465,16 @@ load_fake_extension "__ytfzf__" "1"
#############
# Scrapers take 2 arguments:
- # 1: the search query (do not store in a variable called $_search, it is preferable to use $search, $c_search, or $const_search)
- # Do not use the global $_search variable directly as $1 may be different.
- # 2: the file to write the finished json to (the standard name for this variable is $output_json_file)
+# 1: the search query (do not store in a variable called $_search, it is preferable to use $search, $c_search, or $const_search)
+# Do not use the global $_search variable directly as $1 may be different.
+# 2: the file to write the finished json to (the standard name for this variable is $output_json_file)
#############
# FILES #
#############
# Store all temp files in $session_temp_dir with a name prefix that matches the scraper name.
- # Even better all temp files can be in $session_temp_dir/$scrape_name, no name prefix required.
+# Even better all temp files can be in $session_temp_dir/$scrape_name, no name prefix required.
##############
# EXIT CODES #
@@ -1480,50 +1495,50 @@ load_fake_extension "__ytfzf__" "1"
# Scrapers are responsible for the following
# * If the search query is exactly: ":help", it should:
- # 1. print a little information on what the scraper does, and how to use it
- # 2. return 100
+# 1. print a little information on what the scraper does, and how to use it
+# 2. return 100
# * All other search words starting with : may be treated as special operators, you can do anything you want with them.
- # * Currently :help is the only standard operator.
+# * Currently :help is the only standard operator.
# * Lastly the scraper should create a json and store it in $2
- # The required keys are:
- # ID (string): (a unique identifier for the video can be anything really)
- # url (string): the url that will be opened when the video is selected.
- # title (string): The tittle of the video.
- # scraper (string): the name of the scraper.
- # Optional keys include:
- # thumbs (string): Link to a thumbnail (no, "thumbs" is not a typo)
- # channel (string): The author of the video (Should be human readable)
- # duration (string): Length of the video (Should be human readable)
- # views (string): view count
- # date (string): The upload date (should be human readable).
- # action (string): An action that should be read by the handle_actions function when the video is selected.
- # For information on how to format an action, see the handle_actions function.
+# The required keys are:
+# ID (string): (a unique identifier for the video can be anything really)
+# url (string): the url that will be opened when the video is selected.
+# title (string): The tittle of the video.
+# scraper (string): the name of the scraper.
+# Optional keys include:
+# thumbs (string): Link to a thumbnail (no, "thumbs" is not a typo)
+# channel (string): The author of the video (Should be human readable)
+# duration (string): Length of the video (Should be human readable)
+# views (string): view count
+# date (string): The upload date (should be human readable).
+# action (string): An action that should be read by the handle_actions function when the video is selected.
+# For information on how to format an action, see the handle_actions function.
# Scraping backends {{{
-_start_series_of_threads () {
+_start_series_of_threads() {
_thread_count=0
}
-_thread_started (){
+_thread_started() {
_latest_fork="$1"
- _thread_count=$((_thread_count+1))
+ _thread_count=$((_thread_count + 1))
[ $_thread_count -ge $max_thread_count ] && wait "$_latest_fork" && _thread_count=$(jobs -p | wc -l)
}
-set_real_channel_url_and_id () {
- _input_url="$1"
- case "$_input_url" in
- */videos|*/streams|*/playlists) _input_url="${_input_url%/*}" ;;
- esac
- _id="$(_get_channel_id "$_input_url")"
- [ "$_id" = "$_input_url" ] &&\
- _url="$(_get_real_channel_link "$_input_url")" && _id="$(_get_channel_id "$_url")"
- print_debug "[SCRAPE]: input url: $_input_url, detected url: $_url, detected id: $_id\n"
- channel_url="$_url" channel_id="$_id"
- unset _url _id _input_url
+set_real_channel_url_and_id() {
+ _input_url="$1"
+ case "$_input_url" in
+ */videos | */streams | */playlists) _input_url="${_input_url%/*}" ;;
+ esac
+ _id="$(_get_channel_id "$_input_url")"
+ [ "$_id" = "$_input_url" ] &&
+ _url="$(_get_real_channel_link "$_input_url")" && _id="$(_get_channel_id "$_url")"
+ print_debug "[SCRAPE]: input url: $_input_url, detected url: $_url, detected id: $_id\n"
+ channel_url="$_url" channel_id="$_id"
+ unset _url _id _input_url
}
#}}}
@@ -1531,30 +1546,30 @@ set_real_channel_url_and_id () {
## Youtube {{{
# Youtube backend functions {{{
-_youtube_channel_name () {
+_youtube_channel_name() {
# takes channel page html (stdin) and returns the channel name
sed -n 's/.*[<]title[>]\(.*\) - YouTube[<]\/title[>].*/\1/p' |
sed \
- -e "s/&apos;/'/g" \
- -e "s/&#39;/'/g" \
- -e "s/&quot;/\"/g" \
- -e "s/&#34;/\"/g" \
- -e "s/&amp;/\&/g" \
- -e "s/&#38;/\&/g"
-}
-
-_youtube_get_json (){
- # Separates the json embedded in the youtube html page
- # * removes the content after ytInitialData
- # * removes all newlines and trims the json out
- sed -n '/var *ytInitialData/,$p' |
- tr -d '\n' |
- sed ' s_^.*var ytInitialData *=__ ; s_;</script>.*__ ;'
-}
-
-_youtube_channel_playlists_json () {
- channel_name="$1"
- jq ' [..|.items?|select(.!=null) | flatten(1) | .[] |
+ -e "s/&apos;/'/g" \
+ -e "s/&#39;/'/g" \
+ -e "s/&quot;/\"/g" \
+ -e "s/&#34;/\"/g" \
+ -e "s/&amp;/\&/g" \
+ -e "s/&#38;/\&/g"
+}
+
+_youtube_get_json() {
+ # Separates the json embedded in the youtube html page
+ # * removes the content after ytInitialData
+ # * removes all newlines and trims the json out
+ sed -n '/var *ytInitialData/,$p' |
+ tr -d '\n' |
+ sed ' s_^.*var ytInitialData *=__ ; s_;</script>.*__ ;'
+}
+
+_youtube_channel_playlists_json() {
+ channel_name="$1"
+ jq ' [..|.items?|select(.!=null) | flatten(1) | .[] |
{
scraper: "youtube_channel_playlist",
ID: .gridPlaylistRenderer.playlistId,
@@ -1567,10 +1582,10 @@ _youtube_channel_playlists_json () {
}]'
}
-_youtube_channel_json () {
+_youtube_channel_json() {
channel_name=$1
- __scr="$2"
- jq '[..|.richGridRenderer?|select(.!=null)|..|.contents?|select(.!=null)|..|.richItemRenderer?|select(.!=null) |
+ __scr="$2"
+ jq '[..|.richGridRenderer?|select(.!=null)|..|.contents?|select(.!=null)|..|.richItemRenderer?|select(.!=null) |
{
scraper: "'"$__scr"'",
ID: .content.videoRenderer.videoId,
@@ -1587,174 +1602,179 @@ _youtube_channel_json () {
}
#}}}
-scrape_subscriptions () {
- ! [ -f "$YTFZF_SUBSCRIPTIONS_FILE" ] && die 2 "subscriptions file doesn't exist\n"
+scrape_subscriptions() {
+ ! [ -f "$YTFZF_SUBSCRIPTIONS_FILE" ] && die 2 "subscriptions file doesn't exist\n"
- # if _tmp_subfile does not have a unique name, weird things happen
- __subfile_line=-1
- _start_series_of_threads
- while IFS="" read -r channel_url || [ -n "$channel_url" ]; do
+ # if _tmp_subfile does not have a unique name, weird things happen
+ __subfile_line=-1
+ _start_series_of_threads
+ while IFS="" read -r channel_url || [ -n "$channel_url" ]; do
- __subfile_line=$((__subfile_line+1))
+ __subfile_line=$((__subfile_line + 1))
- channel_url="${channel_url%%#*}"
- #trim whitespace
- read -r channel_url <<-EOF
- $channel_url
-EOF
- [ -z "$channel_url" ] && continue
- __subfile_line=$((__subfile_line+1))
- {
+ channel_url="${channel_url%%#*}"
+ #trim whitespace
+ read -r channel_url <<-EOF
+ $channel_url
+ EOF
+ [ -z "$channel_url" ] && continue
+ __subfile_line=$((__subfile_line + 1))
+ {
_tmp_subfile="${session_temp_dir}/channel-$__subfile_line"
- scrape_youtube_channel "$channel_url" "$_tmp_subfile" < /dev/null || return "$?"
- __new_data="$(jq '.[].scraper="subscriptions"' < "$_tmp_subfile")"
- printf "%s\n" "$__new_data" > "$_tmp_subfile"
+ scrape_youtube_channel "$channel_url" "$_tmp_subfile" </dev/null || return "$?"
+ __new_data="$(jq '.[].scraper="subscriptions"' <"$_tmp_subfile")"
+ printf "%s\n" "$__new_data" >"$_tmp_subfile"
if [ ${fancy_subs} -eq 1 ]; then
- jq --arg left "${fancy_subs_left}" --arg right "${fancy_subs_right}" '"\($left + .[0].channel + $right)" as $div | [{"title": $div, "action": "do-nothing", "url": $div, "ID": "subscriptions-channel:\(.[0].channel)" }] + .[0:'"$sub_link_count"']' < "$_tmp_subfile"
+ jq --arg left "${fancy_subs_left}" --arg right "${fancy_subs_right}" '"\($left + .[0].channel + $right)" as $div | [{"title": $div, "action": "do-nothing", "url": $div, "ID": "subscriptions-channel:\(.[0].channel)" }] + .[0:'"$sub_link_count"']' <"$_tmp_subfile"
else
- jq '.[0:'"$sub_link_count"']' < "$_tmp_subfile"
- fi >> "$ytfzf_video_json_file"
- } &
- _thread_started "$!"
- sleep 0.01
- done < "$YTFZF_SUBSCRIPTIONS_FILE"
- wait
-}
-scrape_youtube_subscriptions (){ scrape_subscriptions "$@"; }
-scrape_S (){ scrape_subscriptions "$@"; }
-
-scrape_SI () {
- output_json_file="$2"
+ jq '.[0:'"$sub_link_count"']' <"$_tmp_subfile"
+ fi >>"$ytfzf_video_json_file"
+ } &
+ _thread_started "$!"
+ sleep 0.01
+ done <"$YTFZF_SUBSCRIPTIONS_FILE"
+ wait
+}
+scrape_youtube_subscriptions() { scrape_subscriptions "$@"; }
+scrape_S() { scrape_subscriptions "$@"; }
+
+scrape_SI() {
+ output_json_file="$2"
_curl_config_file="${session_temp_dir}/curl_config"
- : > "$_curl_config_file"
+ : >"$_curl_config_file"
- while read -r url; do
- url="${url%%#*}"
- [ -z "$url" ] && continue
+ while read -r url; do
+ url="${url%%#*}"
+ [ -z "$url" ] && continue
- set_real_channel_url_and_id "$url"
+ set_real_channel_url_and_id "$url"
- channel_url="$invidious_instance/api/v1/channels/$channel_id"
+ channel_url="$invidious_instance/api/v1/channels/$channel_id"
- _tmp_file="${session_temp_dir}/SI-${channel_id}.json"
+ _tmp_file="${session_temp_dir}/SI-${channel_id}.json"
- printf "url=\"%s\"\noutput=\"%s\"\n" "$channel_url" "$_tmp_file" >> "${_curl_config_file}"
+ printf "url=\"%s\"\noutput=\"%s\"\n" "$channel_url" "$_tmp_file" >>"${_curl_config_file}"
- done < "${YTFZF_SUBSCRIPTIONS_FILE}"
+ done <"${YTFZF_SUBSCRIPTIONS_FILE}"
- _tmp_json="${session_temp_dir}/SI.json"
+ _tmp_json="${session_temp_dir}/SI.json"
- print_info "Scraping subscriptions with instance: $invidious_instance\n"
+ print_info "Scraping subscriptions with instance: $invidious_instance\n"
curl -fLZ --parallel-max "${max_thread_count}" -K "$_curl_config_file"
[ $? -eq 2 ] && curl -fL -K "$_curl_config_file"
- set +f
- #this pipeline does the following:
- # 1. concatinate every channel json downloaded (cat)
- # 2. if the json is the newer-style, convert it to a list of videos (jq part 1)
- # 3. if fancy_subs -eq 1
- # 1. add fancy subs and slice the amount of videos the user wants (jq part 2)
- # 4. else
- # 1. slice the amount of videos the user wants.
- # 5. convert to ytfzf json format
-
- cat "${session_temp_dir}/SI-"*".json" |
- jq 'if (.videos|type) == "array" then .videos elif (.latestVideos|type) == "array" then .latestVideos else null end' | if [ "$fancy_subs" -eq 1 ]; then
- jq --arg left "${fancy_subs_left}" --arg right "${fancy_subs_right}" '"\($left + .[0].author + $right)" as $div | [{"title": $div, "action": "do-nothing", "url": $div, "ID": "subscriptions-channel:\(.[0].channel)" }] + .[0:'"$sub_link_count"']'
- else
- jq '.[0:'"$sub_link_count"']' | _invidious_search_json_generic "SI"
- fi >> "$output_json_file"
- set -f
-}
-
-scrape_youtube_channel () {
+ set +f
+ #this pipeline does the following:
+ # 1. concatinate every channel json downloaded (cat)
+ # 2. if the json is the newer-style, convert it to a list of videos (jq part 1)
+ # 3. if fancy_subs -eq 1
+ # 1. add fancy subs and slice the amount of videos the user wants (jq part 2)
+ # 4. else
+ # 1. slice the amount of videos the user wants.
+ # 5. convert to ytfzf json format
+
+ _get_invidious_thumb_quality_name
+
+ cat "${session_temp_dir}/SI-"*".json" |
+ jq 'if (.videos|type) == "array" then .videos elif (.latestVideos|type) == "array" then .latestVideos else null end' | if [ "$fancy_subs" -eq 1 ]; then
+ jq --arg left "${fancy_subs_left}" --arg right "${fancy_subs_right}" '"\($left + .[0].author + $right)" as $div | [{"title": $div, "action": "do-nothing", "url": $div, "ID": "subscriptions-channel:\(.[0].channel)" }] + .[0:'"$sub_link_count"']'
+ else
+ jq '.[0:'"$sub_link_count"']' | _invidious_search_json_generic "SI"
+ fi >>"$output_json_file"
+ set -f
+}
+
+scrape_youtube_channel() {
channel_url="$1"
- [ "$channel_url" = ":help" ] && print_info "The search should be a link to a youtube channel\nYou can put one or more of the following modifiers followed by a space before the url to specify which type of videos to scrape:\n:videos\n:streams\n:playlists\n:v, :s, and :p may also be used as a shorter version\nYou may also use --type=live, --type=video, --type=playlist, or --type=all\n" && return 100
+ [ "$channel_url" = ":help" ] && print_info "The search should be a link to a youtube channel\nYou can put one or more of the following modifiers followed by a space before the url to specify which type of videos to scrape:\n:videos\n:streams\n:playlists\n:v, :s, and :p may also be used as a shorter version\nYou may also use --type=live, --type=video, --type=playlist, or --type=all\n" && return 100
output_json_file="$2"
- prepare_for_set_args
- #shellcheck disable=2086
- set -- $1
- end_of_set_args
- modifiers=""
-
- # support the --features=live argument
- case "$search_result_features" in
- *live*) modifiers="streams" ;;
- *video*) modifiers="$modifiers videos" ;;
- *playlist*) modifiers="$modifiers playlists" ;;
- esac
+ prepare_for_set_args
+ #shellcheck disable=2086
+ set -- $1
+ end_of_set_args
+ modifiers=""
- #support --type=playlist, etc
- prepare_for_set_args ","
- for _type in $search_result_type; do
- case "$_type" in
- all) modifiers="streams playlists videos" ;;
- video) modifiers="$modifiers videos" ;;
- *) modifiers="$modifiers $_type" ;;
- esac
- done
- end_of_set_args
-
- unset IFS
-
- for arg in "$@"; do
- case "$arg" in
- :videos|:streams|:playlists) modifiers="$modifiers ${arg#:}" ;; #starts with a colon to have consistency with the search operator syntax.
- :v) modifiers="$modifiers videos" ;;
- :p) modifiers="$modifiers playlists" ;;
- :s|:l) modifiers="$modifiers streams" ;;
- *) channel_url=$arg; break ;;
- esac
- done
-
- modifiers="${modifiers##[[:space:]]}"
-
- [ -z "$modifiers" ] && modifiers="videos"
+ # support the --features=live argument
+ case "$search_result_features" in
+ *live*) modifiers="streams" ;;
+ *video*) modifiers="$modifiers videos" ;;
+ *playlist*) modifiers="$modifiers playlists" ;;
+ esac
- set_real_channel_url_and_id "$channel_url"
+ #support --type=playlist, etc
+ prepare_for_set_args ","
+ for _type in $search_result_type; do
+ case "$_type" in
+ all) modifiers="streams playlists videos" ;;
+ video) modifiers="$modifiers videos" ;;
+ *) modifiers="$modifiers $_type" ;;
+ esac
+ done
+ end_of_set_args
+
+ unset IFS
+
+ for arg in "$@"; do
+ case "$arg" in
+ :videos | :streams | :playlists) modifiers="$modifiers ${arg#:}" ;; #starts with a colon to have consistency with the search operator syntax.
+ :v) modifiers="$modifiers videos" ;;
+ :p) modifiers="$modifiers playlists" ;;
+ :s | :l) modifiers="$modifiers streams" ;;
+ *)
+ channel_url=$arg
+ break
+ ;;
+ esac
+ done
+
+ modifiers="${modifiers##[[:space:]]}"
- for mod in $modifiers; do
- print_info "Scraping Youtube channel: https://www.youtube.com/channel/${channel_id}/$mod\n"
- tmp_filename="channel-${channel_id}-$mod"
- _tmp_html="${session_temp_dir}/${tmp_filename}.html"
- _tmp_json="${session_temp_dir}/${tmp_filename}.json"
+ [ -z "$modifiers" ] && modifiers="videos"
- _get_request "https://www.youtube.com/channel/${channel_id}/$mod" > "$_tmp_html"
- _youtube_get_json < "$_tmp_html" > "$_tmp_json"
+ set_real_channel_url_and_id "$channel_url"
- channel_name=$(_youtube_channel_name < "$_tmp_html" )
- if [ "$mod" = "playlists" ]; then
- _youtube_channel_playlists_json "$channel_name" < "$_tmp_json"
- else
- _youtube_channel_json "$channel_name" "youtube_channel_$mod" < "$_tmp_json"
- fi >> "$output_json_file"
- done
+ for mod in $modifiers; do
+ print_info "Scraping Youtube channel: https://www.youtube.com/channel/${channel_id}/$mod\n"
+ tmp_filename="channel-${channel_id}-$mod"
+ _tmp_html="${session_temp_dir}/${tmp_filename}.html"
+ _tmp_json="${session_temp_dir}/${tmp_filename}.json"
+
+ _get_request "https://www.youtube.com/channel/${channel_id}/$mod" >"$_tmp_html"
+ _youtube_get_json <"$_tmp_html" >"$_tmp_json"
+
+ channel_name=$(_youtube_channel_name <"$_tmp_html")
+ if [ "$mod" = "playlists" ]; then
+ _youtube_channel_playlists_json "$channel_name" <"$_tmp_json"
+ else
+ _youtube_channel_json "$channel_name" "youtube_channel_$mod" <"$_tmp_json"
+ fi >>"$output_json_file"
+ done
}
# }}}
## Invidious {{{
# invidious backend functions {{{
-_get_channel_id () {
+_get_channel_id() {
link="$1"
link="${link##*channel/}"
link="${link%/*}"
printf "%s" "$link"
}
-_get_invidious_thumb_quality_name () {
+_get_invidious_thumb_quality_name() {
case "$thumbnail_quality" in
- high) thumbnail_quality="hqdefault" ;;
- medium) thumbnail_quality="mqdefault" ;;
- start) thumbnail_quality="1" ;;
- middle) thumbnail_quality="2" ;;
- end) thumbnail_quality="3" ;;
+ high) thumbnail_quality="hqdefault" ;;
+ medium) thumbnail_quality="mqdefault" ;;
+ start) thumbnail_quality="1" ;;
+ middle) thumbnail_quality="2" ;;
+ end) thumbnail_quality="3" ;;
esac
}
-_invidious_search_json_playlist () {
+_invidious_search_json_playlist() {
jq '[ .[] | select(.type=="playlist") |
{
scraper: "invidious_search",
@@ -1768,7 +1788,7 @@ _invidious_search_json_playlist () {
}
]'
}
-_invidious_search_json_channel () {
+_invidious_search_json_channel() {
jq '
[ .[] | select(.type=="channel") |
{
@@ -1783,7 +1803,7 @@ _invidious_search_json_channel () {
}
]'
}
-_invidious_search_json_live () {
+_invidious_search_json_live() {
jq '[ .[] | select(.type=="video" and .liveNow==true) |
{
scraper: "invidious_search",
@@ -1795,8 +1815,8 @@ _invidious_search_json_live () {
}
]'
}
-_invidious_search_json_videos () {
- __scr="$1"
+_invidious_search_json_videos() {
+ __scr="$1"
jq '
'"$jq_pad_left"'
;
@@ -1815,8 +1835,8 @@ _invidious_search_json_videos () {
}
]'
}
-_invidious_search_json_generic () {
- __scr="$1"
+_invidious_search_json_generic() {
+ __scr="$1"
jq '
'"$jq_pad_left"'
;
@@ -1836,7 +1856,7 @@ _invidious_search_json_generic () {
]'
}
-_invidious_playlist_json () {
+_invidious_playlist_json() {
jq '
'"$jq_pad_left"'
;
@@ -1855,7 +1875,7 @@ _invidious_playlist_json () {
]'
}
-_concatinate_json_file () {
+_concatinate_json_file() {
template="$1"
page_count=$2
_output_json_file="$3"
@@ -1864,15 +1884,15 @@ _concatinate_json_file () {
# this sets the arguments to the files in order for cat
while [ "$__cur_page" -le "$page_count" ]; do
set -- "$@" "${template}${__cur_page}.json.final"
- __cur_page=$((__cur_page+1))
+ __cur_page=$((__cur_page + 1))
done
- cat "$@" 2>/dev/null >> "$_output_json_file"
+ cat "$@" 2>/dev/null >>"$_output_json_file"
}
#}}}
-scrape_invidious_playlist () {
+scrape_invidious_playlist() {
playlist_url=$1
- [ "$playlist_url" = ":help" ] && print_info "The search should be a link to a youtube playlist\n" && return 100
+ [ "$playlist_url" = ":help" ] && print_info "The search should be a link to a youtube playlist\n" && return 100
output_json_file=$2
playlist_id="${playlist_url##*[?]list=}"
@@ -1883,38 +1903,38 @@ scrape_invidious_playlist () {
_full_playlist_json="${session_temp_dir}/full-playlist-$playlist_id.json"
_cur_page=${pages_start:-1}
- pages_to_scrape=${pages_to_scrape:-100}
- pages_start=${pages_start:-1}
- while [ "$_cur_page" -lt "$((pages_start+pages_to_scrape))" ]; do
+ pages_to_scrape=${pages_to_scrape:-100}
+ pages_start=${pages_start:-1}
+ while [ "$_cur_page" -lt "$((pages_start + pages_to_scrape))" ]; do
_tmp_json="${session_temp_dir}/yt-playlist-$playlist_id-$_cur_page.json"
_get_request "$invidious_instance/api/v1/playlists/$playlist_id" \
- -G --data-urlencode "page=$_cur_page" > "$_tmp_json" || return "$?"
- jq -e '.videos==[]' < "$_tmp_json" > /dev/null 2>&1 && break
+ -G --data-urlencode "page=$_cur_page" >"$_tmp_json" || return "$?"
+ jq -e '.videos==[]' <"$_tmp_json" >/dev/null 2>&1 && break
print_info "Scraping Youtube playlist (with $invidious_instance) (playlist: $playlist_url, pg: $_cur_page)\n"
- _invidious_playlist_json < "$_tmp_json" >> "$output_json_file"
- _cur_page=$((_cur_page+1))
+ _invidious_playlist_json <"$_tmp_json" >>"$output_json_file"
+ _cur_page=$((_cur_page + 1))
done
}
-scrape_youtube_playlist (){ scrape_invidious_playlist "$@"; }
+scrape_youtube_playlist() { scrape_invidious_playlist "$@"; }
-scrape_invidious_search () {
- page_query=$1
- [ "$page_query" = ":help" ] && print_info "Make a youtube search\n" && return 100
- output_json_file=$2
+scrape_invidious_search() {
+ page_query=$1
+ [ "$page_query" = ":help" ] && print_info "Make a youtube search\n" && return 100
+ output_json_file=$2
- _ivs_cur_page=${pages_start:-1}
+ _ivs_cur_page=${pages_start:-1}
- page_num=$((_ivs_cur_page + ${pages_to_scrape:-1}))
+ page_num=$((_ivs_cur_page + ${pages_to_scrape:-1}))
- # shellcheck disable=SC2209
- case "$search_sort_by" in
+ # shellcheck disable=SC2209
+ case "$search_sort_by" in
upload_date) search_sort_by="date" ;;
view_count) search_sort_by=views ;;
- esac
+ esac
_start_series_of_threads
- while [ ${_ivs_cur_page} -lt $page_num ]; do
+ while [ ${_ivs_cur_page} -lt $page_num ]; do
{
_tmp_json="${session_temp_dir}/yt-search-$_ivs_cur_page.json"
@@ -1928,56 +1948,56 @@ scrape_invidious_search () {
--data-urlencode "duration=${search_video_duration}" \
--data-urlencode "features=${search_result_features}" \
--data-urlencode "region=${search_region}" \
- --data-urlencode "page=${_ivs_cur_page}" > "$_tmp_json"
+ --data-urlencode "page=${_ivs_cur_page}" >"$_tmp_json"
_get_invidious_thumb_quality_name
{
- _invidious_search_json_live < "$_tmp_json"
- _invidious_search_json_videos "invidious_search" < "$_tmp_json"
- _invidious_search_json_channel < "$_tmp_json"
- _invidious_search_json_playlist < "$_tmp_json"
- } >> "$_tmp_json.final"
+ _invidious_search_json_live <"$_tmp_json"
+ _invidious_search_json_videos "invidious_search" <"$_tmp_json"
+ _invidious_search_json_channel <"$_tmp_json"
+ _invidious_search_json_playlist <"$_tmp_json"
+ } >>"$_tmp_json.final"
} &
- _ivs_cur_page=$((_ivs_cur_page+1))
+ _ivs_cur_page=$((_ivs_cur_page + 1))
_thread_started "$!"
- done
- # hangs for some reason when called frrom scrape_new_page_invidious_search
- # probably cause it's a subprocess of ytfzf
- case "$4" in
+ done
+ # hangs for some reason when called frrom scrape_new_page_invidious_search
+ # probably cause it's a subprocess of ytfzf
+ case "$4" in
1) wait "$!" ;;
*) wait ;;
- esac
- _concatinate_json_file "${session_temp_dir}/yt-search-" "$((_ivs_cur_page-1))" "$output_json_file" "$pages_start"
- printf "%s\n" "$_ivs_cur_page" > "${session_temp_dir}/invidious_search-current-page"
+ esac
+ _concatinate_json_file "${session_temp_dir}/yt-search-" "$((_ivs_cur_page - 1))" "$output_json_file" "$pages_start"
+ printf "%s\n" "$_ivs_cur_page" >"${session_temp_dir}/invidious_search-current-page"
}
-scrape_youtube () { scrape_invidious_search "$@"; }
-scrape_Y () { scrape_invidious_search "$@"; }
+scrape_youtube() { scrape_invidious_search "$@"; }
+scrape_Y() { scrape_invidious_search "$@"; }
-scrape_next_page_invidious_search () {
+scrape_next_page_invidious_search() {
# we can do this because _comment_file is overritten every time, meaning it will contain the latest scrape
- scrape_invidious_search "$_search" "$video_json_file"
+ scrape_invidious_search "$_search" "$video_json_file"
}
-scrape_invidious_video_recommended () {
- video="$1"
- [ "$video" = ":help" ] && print_info "The search should be a link to a youtube video\n" && return 100
- output_json_file="$2"
- case "$video" in
- */*) video="${video##*=}" ;;
- esac
- _tmp_json="${session_temp_dir}/invidious-video-recommended.json"
- _get_request "$invidious_instance/api/v1/videos/$video" | jq '.recommendedVideos' > "$_tmp_json"
- _get_invidious_thumb_quality_name
- _invidious_search_json_generic "invidious_recommended" < "$_tmp_json" >> "$output_json_file"
+scrape_invidious_video_recommended() {
+ video="$1"
+ [ "$video" = ":help" ] && print_info "The search should be a link to a youtube video\n" && return 100
+ output_json_file="$2"
+ case "$video" in
+ */*) video="${video##*=}" ;;
+ esac
+ _tmp_json="${session_temp_dir}/invidious-video-recommended.json"
+ _get_request "$invidious_instance/api/v1/videos/$video" | jq '.recommendedVideos' >"$_tmp_json"
+ _get_invidious_thumb_quality_name
+ _invidious_search_json_generic "invidious_recommended" <"$_tmp_json" >>"$output_json_file"
}
-scrape_video_recommended () { scrape_invidious_video_recommended "$@"; }
-scrape_R () { scrape_invidious_video_recommended "$@"; }
+scrape_video_recommended() { scrape_invidious_video_recommended "$@"; }
+scrape_R() { scrape_invidious_video_recommended "$@"; }
-scrape_invidious_trending () {
+scrape_invidious_trending() {
trending_tab=$(title_str "$1")
- [ "$trending_tab" = ":help" ] && print_info "The search should be one of: Normal, Gaming, Music, News\n" && return 100
+ [ "$trending_tab" = ":help" ] && print_info "The search should be one of: Normal, Gaming, Music, News\n" && return 100
output_json_file=$2
print_info "Scraping YouTube (with $invidious_instance) trending (${trending_tab:-Normal})\n"
@@ -1987,140 +2007,140 @@ scrape_invidious_trending () {
[ -n "$trending_tab" ] && url="${url}?type=${trending_tab}" && _tmp_json="${_tmp_json}-$trending_tab"
_get_request "$url" \
- -G --data-urlencode "region=${search_region}" > "$_tmp_json" || return "$?"
+ -G --data-urlencode "region=${search_region}" >"$_tmp_json" || return "$?"
_get_invidious_thumb_quality_name
- _invidious_search_json_videos "invidious_trending" < "$_tmp_json" >> "$output_json_file"
+ _invidious_search_json_videos "invidious_trending" <"$_tmp_json" >>"$output_json_file"
}
-scrape_youtube_trending (){ scrape_invidious_trending "$@"; }
-scrape_T (){ scrape_invidious_trending "$@"; }
+scrape_youtube_trending() { scrape_invidious_trending "$@"; }
+scrape_T() { scrape_invidious_trending "$@"; }
-scrape_invidious_channel () {
+scrape_invidious_channel() {
channel_url=$1
- [ "$channel_url" = ":help" ] && print_info "The search should be a link to a youtube channel\nYou can put one or more of the following modifiers followed by a space before the url to specify which type of videos to scrape:\n:videos\n:streams\n:playlists\n:v, :s, and :p may also be used as a shorter version\nYou may also use --type=live, --type=video, --type=playlist, or --type=all\n" && return 100
+ [ "$channel_url" = ":help" ] && print_info "The search should be a link to a youtube channel\nYou can put one or more of the following modifiers followed by a space before the url to specify which type of videos to scrape:\n:videos\n:streams\n:playlists\n:v, :s, and :p may also be used as a shorter version\nYou may also use --type=live, --type=video, --type=playlist, or --type=all\n" && return 100
output_json_file=$2
-
tmp_file_name="channel-${channel_id}"
_tmp_html="${session_temp_dir}/${tmp_file_name}.html"
_tmp_json="${session_temp_dir}/${tmp_file_name}.json"
- [ -n "$pages_to_scrape" ] || [ -n "$pages_start" ] && print_warning "If you want to use --pages or --pages-start\nuse -c invidious-playlist where the search is https://www.youtube.com/playlist?list=$channel_id\n"
+ [ -n "$pages_to_scrape" ] || [ -n "$pages_start" ] && print_warning "If you want to use --pages or --pages-start\nuse -c invidious-playlist where the search is https://www.youtube.com/playlist?list=$channel_id\n"
- prepare_for_set_args
- set -- $1
- end_of_set_args
+ prepare_for_set_args
+ set -- $1
+ end_of_set_args
- modifiers=""
+ modifiers=""
- # support the --features=live argument
- case "$search_result_features" in
- *live*) modifiers="streams" ;;
- *video*) modifiers="$modifiers videos" ;;
- *playlist*) modifiers="$modifiers playlists" ;;
- esac
-
- #support --type=playlist, etc
- prepare_for_set_args ","
- for _type in $search_result_type; do
- case "$_type" in
- all) modifiers="streams playlists videos" ;;
- video) modifiers="$modifiers videos" ;;
- *) modifiers="$modifiers $_type" ;;
- esac
- done
- end_of_set_args
- unset IFS
+ # support the --features=live argument
+ case "$search_result_features" in
+ *live*) modifiers="streams" ;;
+ *video*) modifiers="$modifiers videos" ;;
+ *playlist*) modifiers="$modifiers playlists" ;;
+ esac
- for arg in "$@"; do
- case "$arg" in
- :videos|:streams|:playlists) modifiers="$modifiers ${arg#:}" ;; #starts with a colon to have consistency with the search operator syntax.
- :v) modifiers="$modifiers videos" ;;
- :p) modifiers="$modifiers playlists" ;;
- :s|:l) modifiers="$modifiers streams" ;;
- *) channel_url=$arg; break ;;
- esac
- done
+ #support --type=playlist, etc
+ prepare_for_set_args ","
+ for _type in $search_result_type; do
+ case "$_type" in
+ all) modifiers="streams playlists videos" ;;
+ video) modifiers="$modifiers videos" ;;
+ *) modifiers="$modifiers $_type" ;;
+ esac
+ done
+ end_of_set_args
+ unset IFS
+ for arg in "$@"; do
+ case "$arg" in
+ :videos | :streams | :playlists) modifiers="$modifiers ${arg#:}" ;; #starts with a colon to have consistency with the search operator syntax.
+ :v) modifiers="$modifiers videos" ;;
+ :p) modifiers="$modifiers playlists" ;;
+ :s | :l) modifiers="$modifiers streams" ;;
+ *)
+ channel_url=$arg
+ break
+ ;;
+ esac
+ done
- modifiers="${modifiers##[[:space:]]}"
+ modifiers="${modifiers##[[:space:]]}"
- [ -z "$modifiers" ] && modifiers="videos"
+ [ -z "$modifiers" ] && modifiers="videos"
- # Converting channel title page url to channel video url
- set_real_channel_url_and_id "$channel_url"
+ # Converting channel title page url to channel video url
+ set_real_channel_url_and_id "$channel_url"
- for modifier in $modifiers; do
- channel_url="$invidious_instance/api/v1/channels/$channel_id/$modifier"
-
+ for modifier in $modifiers; do
+ channel_url="$invidious_instance/api/v1/channels/$channel_id/$modifier"
- print_info "Scraping Youtube (with $invidious_instance) channel: $channel_url\n"
+ print_info "Scraping Youtube (with $invidious_instance) channel: $channel_url\n"
- _get_invidious_thumb_quality_name
+ _get_invidious_thumb_quality_name
- _get_request "${channel_url##* }" \
- -G --data-urlencode "page=$_cur_page" \
- | jq 'if (.videos|type) == "array" then .videos elif (.latestVideos|type) == "array" then .latestVideos else null end' \
- | _invidious_search_json_generic "invidious_channel" \
- | jq 'select(.!=[])' >> "$output_json_file" || return "$?"
- done
+ _get_request "${channel_url##* }" \
+ -G --data-urlencode "page=$_cur_page" |
+ jq 'if (.videos|type) == "array" then .videos elif (.latestVideos|type) == "array" then .latestVideos else null end' |
+ _invidious_search_json_generic "invidious_channel" |
+ jq 'select(.!=[])' >>"$output_json_file" || return "$?"
+ done
}
## }}}
## Ytfzf {{{
-scrape_multi () {
- [ "$1" = ":help" ] && print_info "Perform multiple ytfzf calls and present them in 1 menu, a more powerful multi-scrape
+scrape_multi() {
+ [ "$1" = ":help" ] && print_info "Perform multiple ytfzf calls and present them in 1 menu, a more powerful multi-scrape
Eg:
ytfzf -cM search 1 :NEXT search 2 :NEXT -c O odysee search :NEXT --pages=3 3 pages of youtube
" && return 100
- PARENT_OUTPUT_JSON_FILE=$2
- PARENT_invidious_instance="$invidious_instance"
- unset IFS
- set -f
- while read -r params; do
- [ -z "$params" ] && continue
- # shellcheck disable=SC2086
- set -- $params
- (
- set_vars 0
- # shellcheck disable=SC2030
- invidious_instance="$PARENT_invidious_instance"
- cache_dir="$session_cache_dir"
- on_opt_parse_s () {
- print_warning "-s is not supported in multi search\n"
- }
- _getopts "$@"
- source_scrapers
- shift $((OPTIND-1))
- search_again=0
- unset IFS
- init_and_make_search "$*" "fn-args"
- something_was_scraped || exit 4
- cat "$ytfzf_video_json_file" >> "$PARENT_OUTPUT_JSON_FILE"
- clean_up
- )
- done <<-EOF
- $(printf "%s" "$1" | sed 's/ *:N\(EXT\)* */\n/g')
-EOF
- unset PARENT_invidious_instance PARENT_OUTPUT_JSON_FILE
- return 0
+ PARENT_OUTPUT_JSON_FILE=$2
+ PARENT_invidious_instance="$invidious_instance"
+ unset IFS
+ set -f
+ while read -r params; do
+ [ -z "$params" ] && continue
+ # shellcheck disable=SC2086
+ set -- $params
+ (
+ set_vars 0
+ # shellcheck disable=SC2030
+ invidious_instance="$PARENT_invidious_instance"
+ cache_dir="$session_cache_dir"
+ on_opt_parse_s() {
+ print_warning "-s is not supported in multi search\n"
+ }
+ _getopts "$@"
+ source_scrapers
+ shift $((OPTIND - 1))
+ search_again=0
+ unset IFS
+ init_and_make_search "$*" "fn-args"
+ something_was_scraped || exit 4
+ cat "$ytfzf_video_json_file" >>"$PARENT_OUTPUT_JSON_FILE"
+ clean_up
+ )
+ done <<-EOF
+ $(printf "%s" "$1" | sed 's/ *:N\(EXT\)* */\n/g')
+ EOF
+ unset PARENT_invidious_instance PARENT_OUTPUT_JSON_FILE
+ return 0
}
-scrrape_M (){ scrape_multi "$@"; }
+scrrape_M() { scrape_multi "$@"; }
## }}}
## Peertube {{{
-scrape_peertube () {
+scrape_peertube() {
page_query=$1
- [ "$page_query" = ":help" ] && print_info "Search peertube\n" && return 100
+ [ "$page_query" = ":help" ] && print_info "Search peertube\n" && return 100
output_json_file=$2
print_info "Scraping Peertube ($page_query)\n"
_tmp_json="${session_temp_dir}/peertube.json"
# gets a list of videos
- _get_request "https://sepiasearch.org/api/v1/search/videos" -G --data-urlencode "search=$1" > "$_tmp_json" || return "$?"
+ _get_request "https://sepiasearch.org/api/v1/search/videos" -G --data-urlencode "search=$1" >"$_tmp_json" || return "$?"
jq '
def pad_left(n; num):
@@ -2139,60 +2159,60 @@ scrape_peertube () {
views: "\(.views)",
date: .publishedAt
}
- ]' < "$_tmp_json" >> "$output_json_file"
+ ]' <"$_tmp_json" >>"$output_json_file"
}
-scrape_P (){ scrape_peertube "$@"; }
+scrape_P() { scrape_peertube "$@"; }
## }}}
## Odysee {{{
-scrape_odysee () {
- [ "$odysee_video_search_count" -gt 50 ] && die 1 "--odysee-video-count must be <= 50"
+scrape_odysee() {
+ [ "$odysee_video_search_count" -gt 50 ] && die 1 "--odysee-video-count must be <= 50"
page_query=$1
- [ "$page_query" = ":help" ] && print_info "Search odysee\n" && return 100
+ [ "$page_query" = ":help" ] && print_info "Search odysee\n" && return 100
[ "${#page_query}" -le 2 ] && die 4 "Odysee searches must be 3 or more characters\n"
output_json_file=$2
- # for scrape_next_page_odysee_search
- [ -z "$_initial_odysee_video_search_count" ] && _initial_odysee_video_search_count=$odysee_video_search_count
+ # for scrape_next_page_odysee_search
+ [ -z "$_initial_odysee_video_search_count" ] && _initial_odysee_video_search_count=$odysee_video_search_count
print_info "Scraping Odysee ($page_query)\n"
_tmp_json="${session_temp_dir}/odysee.json"
case "$search_sort_by" in
- upload_date|newest_first) search_sort_by="release_time" ;;
- oldest_first) search_sort_by="^release_time" ;;
- relevance) search_sort_by="" ;;
+ upload_date | newest_first) search_sort_by="release_time" ;;
+ oldest_first) search_sort_by="^release_time" ;;
+ relevance) search_sort_by="" ;;
esac
case "$search_upload_date" in
- week|month|year) search_upload_date="this${search_upload_date}" ;;
- day) search_upload_date="today" ;;
+ week | month | year) search_upload_date="this${search_upload_date}" ;;
+ day) search_upload_date="today" ;;
esac
case "$nsfw" in
- 1) nsfw=true ;;
- 0) nsfw=false ;;
+ 1) nsfw=true ;;
+ 0) nsfw=false ;;
esac
# this if is because when search_sort_by is empty, it breaks lighthouse
if [ -n "$search_sort_by" ]; then
_get_request "https://lighthouse.lbry.com/search" -G \
- --data-urlencode "s=$page_query" \
- --data-urlencode "mediaType=video,audio" \
- --data-urlencode "include=channel,title,thumbnail_url,duration,cq_created_at,description,view_cnt" \
- --data-urlencode "sort_by=$search_sort_by" \
- --data-urlencode "time_filter=$search_upload_date" \
- --data-urlencode "nsfw=$nsfw" \
- --data-urlencode "size=$odysee_video_search_count" > "$_tmp_json" || return "$?"
+ --data-urlencode "s=$page_query" \
+ --data-urlencode "mediaType=video,audio" \
+ --data-urlencode "include=channel,title,thumbnail_url,duration,cq_created_at,description,view_cnt" \
+ --data-urlencode "sort_by=$search_sort_by" \
+ --data-urlencode "time_filter=$search_upload_date" \
+ --data-urlencode "nsfw=$nsfw" \
+ --data-urlencode "size=$odysee_video_search_count" >"$_tmp_json" || return "$?"
else
_get_request "https://lighthouse.lbry.com/search" -G \
- --data-urlencode "s=$page_query" \
- --data-urlencode "mediaType=video,audio" \
- --data-urlencode "include=channel,title,thumbnail_url,duration,cq_created_at,description,view_cnt" \
- --data-urlencode "time_filter=$search_upload_date" \
- --data-urlencode "nsfw=$nsfw" \
- --data-urlencode "size=$odysee_video_search_count" > "$_tmp_json" || return "$?"
+ --data-urlencode "s=$page_query" \
+ --data-urlencode "mediaType=video,audio" \
+ --data-urlencode "include=channel,title,thumbnail_url,duration,cq_created_at,description,view_cnt" \
+ --data-urlencode "time_filter=$search_upload_date" \
+ --data-urlencode "nsfw=$nsfw" \
+ --data-urlencode "size=$odysee_video_search_count" >"$_tmp_json" || return "$?"
fi
# select(.duration != null) selects videos that aren't live, there is no .is_live key
@@ -2213,62 +2233,62 @@ scrape_odysee () {
views: "\(.view_cnt)",
date: .cq_created_at
}
- ]' < "$_tmp_json" >> "$output_json_file"
+ ]' <"$_tmp_json" >>"$output_json_file"
}
-scrape_O (){ scrape_odysee "$@"; }
+scrape_O() { scrape_odysee "$@"; }
## }}}
# ytfzf json format{{{
-scrape_json_file () {
+scrape_json_file() {
search="$1"
output_json_file="$2"
cp "$search" "$output_json_file" 2>/dev/null
}
-scrape_playlist (){ scrape_json_file "$@"; }
-scrape_p (){ scrape_json_file "$@"; }
+scrape_playlist() { scrape_json_file "$@"; }
+scrape_p() { scrape_json_file "$@"; }
#}}}
# Comments{{{
-scrape_comments () {
+scrape_comments() {
video_id="$1"
- [ "$video_id" = ":help" ] && print_info "Search should be a link to a youtube video\n" && return 100
+ [ "$video_id" = ":help" ] && print_info "Search should be a link to a youtube video\n" && return 100
case "$video_id" in
- */*) video_id="${video_id##*=}" ;;
+ */*) video_id="${video_id##*=}" ;;
esac
output_json_file="$2"
_comment_file="${session_temp_dir}/comments-$video_id.tmp.json"
i="${pages_start:-1}"
- page_count="$((i+${pages_to_scrape:-1}))"
+ page_count="$((i + ${pages_to_scrape:-1}))"
while [ "$i" -le "$page_count" ]; do
print_info "Scraping comments (pg: $i)\n"
_out_comment_file="${session_temp_dir}/comments-$i.json.final"
_get_request "$invidious_instance/api/v1/comments/${video_id}" -G \
- --data-urlencode "continuation=$continuation" > "$_comment_file"
- continuation=$(jq -r '.continuation' < "$_comment_file")
- jq --arg continuation "$continuation" '[ .comments[] | {"scraper": "comments", "channel": .author, "date": .publishedText, "ID": .commentId, "title": .author, "description": .content, "url": "'"$yt_video_link_domain"'/watch?v='"$video_id"'&lc=\(.commentId)", "action": "do-nothing", "thumbs": .authorThumbnails[2].url, "continuation": $continuation} ]' < "$_comment_file" >> "$output_json_file"
- i=$((i+1))
+ --data-urlencode "continuation=$continuation" >"$_comment_file"
+ continuation=$(jq -r '.continuation' <"$_comment_file")
+ jq --arg continuation "$continuation" '[ .comments[] | {"scraper": "comments", "channel": .author, "date": .publishedText, "ID": .commentId, "title": .author, "description": .content, "url": "'"$yt_video_link_domain"'/watch?v='"$video_id"'&lc=\(.commentId)", "action": "do-nothing", "thumbs": .authorThumbnails[2].url, "continuation": $continuation} ]' <"$_comment_file" >>"$output_json_file"
+ i=$((i + 1))
done
- printf "%s\n" "$i" > "${session_temp_dir}/comments-current-page"
+ printf "%s\n" "$i" >"${session_temp_dir}/comments-current-page"
}
-scrape_next_page_comments () {
+scrape_next_page_comments() {
# we can do this because _comment_file is overritten every time, meaning it will contain the latest scrape
scrape_comments "$_search" "$video_json_file"
}
#}}}
# url {{{
-scrape_url () {
- printf "%s\n" "$1" > "$ytfzf_selected_urls"
- open_format_selection_if_requested "$ytfzf_selected_urls"
- open_url_handler "$ytfzf_selected_urls"
- close_url_handler "$url_handler"
- exit
-}
-scrape_U (){ scrape_url "$@"; }
-scrape_u (){ printf '[{"ID": "%s", "title": "%s", "url": "%s"}]\n' "URL-${1##*/}" "$1" "$1" >> "$2"; }
+scrape_url() {
+ printf "%s\n" "$1" >"$ytfzf_selected_urls"
+ open_format_selection_if_requested "$ytfzf_selected_urls"
+ open_url_handler "$ytfzf_selected_urls"
+ close_url_handler "$url_handler"
+ exit
+}
+scrape_U() { scrape_url "$@"; }
+scrape_u() { printf '[{"ID": "%s", "title": "%s", "url": "%s"}]\n' "URL-${1##*/}" "$1" "$1" >>"$2"; }
#}}}
# }}}
@@ -2280,8 +2300,8 @@ scrape_u (){ printf '[{"ID": "%s", "title": "%s", "url": "%s"}]\n' "URL-${1##*/}
############################
# There is a 2 step soring process.
- # 1. the get_sort_by function is called
- # 2. the data_sort_fn function is called
+# 1. the get_sort_by function is called
+# 2. the data_sort_fn function is called
# The result of those 2 steps is then printed to stdout.
#TODO: refactor sorting to not rely on video_info_text, and instead be based on json
@@ -2289,26 +2309,25 @@ scrape_u (){ printf '[{"ID": "%s", "title": "%s", "url": "%s"}]\n' "URL-${1##*/}
# Take a json line as the first argument, the line should follow VIDEO JSON FORMAT
# This function should print the information from the line to sort by (or something else)
# This specific implementation of get_sort_by prints the upload date in unix time
-command_exists "get_sort_by" || get_sort_by () {
+command_exists "get_sort_by" || get_sort_by() {
_video_json_line="$1"
date="${_video_json_line##*'"date":"'}"
- date="${date%%\"*}"
+ date="${date%%\"*}"
# youtube specific
date=${date#*Streamed}
date=${date#*Premiered}
- date -d "$date" '+%s' 2>/dev/null || date -f "$date" '+%s' 2> /dev/null || printf "null"
+ date -d "$date" '+%s' 2>/dev/null || date -f "$date" '+%s' 2>/dev/null || printf "null"
}
# This function sorts the data being piped into it.
-command_exists "data_sort_fn" || data_sort_fn () {
+command_exists "data_sort_fn" || data_sort_fn() {
sort -nr
}
#This function reads all lines being piped in, and sorts them.
-sort_video_data_fn () {
+sort_video_data_fn() {
if [ $is_sort -eq 1 ]; then
- while IFS= read -r _video_json_line
- do
+ while IFS= read -r _video_json_line; do
# run the key function to get the value to sort by
get_sort_by "$_video_json_line" | tr -d '\n'
printf "\t%s\n" "$_video_json_line"
@@ -2331,9 +2350,9 @@ sort_video_data_fn () {
# The interface takes 2 arguments
# 1: The video json file to read from
- # The json file will be in the VIDEO JSON FORMAT (see ytfzf(5)) for more information
+# The json file will be in the VIDEO JSON FORMAT (see ytfzf(5)) for more information
# 2: The url file to write to
- # each url should be seperated by a new line when written to the url file.
+# each url should be seperated by a new line when written to the url file.
# Interfaces are responsible for the following:
@@ -2341,28 +2360,28 @@ sort_video_data_fn () {
# or use the create_sorted_video_data to get a jsonl string of sorted videos.
# * Checking if the menu it wants to use is installed.
- # * Example: interface_text checks if fzf is installed and exits with code 3 if it can't.
+# * Example: interface_text checks if fzf is installed and exits with code 3 if it can't.
# * If the interface uses shortcuts, it is responsible for calling handle_post_keypress if the $keypress_file exists.
# * The interface should display thumbnails if thumbnails are enabled, and the interface supports it
# * It is not required, but interfaces (especially tui interfaces) should use the output from the output from the video_info_text function to display the results.
- # * The interface needs to define the following variables for video_info_text to work properly:
- # * title_len
- # * channel_len
- # * dur_len
- # * view_len
- # * date_len
- # Each of these variables should equal the amount of columns (characters) each segment should take
+# * The interface needs to define the following variables for video_info_text to work properly:
+# * title_len
+# * channel_len
+# * dur_len
+# * view_len
+# * date_len
+# Each of these variables should equal the amount of columns (characters) each segment should take
# * Lastly, if a key, or key combination was pressed (and the interface supports it), it should be written to $keypress_file.
- # * $keypress_file will be used *after* the interface is closed, If the interface does not function in a way similar to fzf do not use this file for shortcuts.
- # * When handling keypresses manually, it is preferrable to use the keybinds listed in $shortcut_binds,
- # * For example, the download shortcut to check against should be $download_shortcut
+# * $keypress_file will be used *after* the interface is closed, If the interface does not function in a way similar to fzf do not use this file for shortcuts.
+# * When handling keypresses manually, it is preferrable to use the keybinds listed in $shortcut_binds,
+# * For example, the download shortcut to check against should be $download_shortcut
# Keypresses {{{
-set_keypress () {
+set_keypress() {
# this function uses echo to keep new lines
read -r keypress
while read -r line; do
@@ -2370,7 +2389,7 @@ set_keypress () {
done
# this if statement checks if there is a keypress, if so, print the input, otherwise print everything
# $keypress could also be a standalone variable, but it's nice to be able to interact with it externally
- if printf "%s" "$keypress" | grep -E '^[[:alnum:]-]+$' > "$keypress_file"; then
+ if printf "%s" "$keypress" | grep -E '^[[:alnum:]-]+$' >"$keypress_file"; then
echo "$input" | sed -n '2,$p'
else
# there was no key press, remove all blank lines
@@ -2379,94 +2398,118 @@ set_keypress () {
unset keypress
}
-handle_post_keypress () {
- read -r keypress < "$keypress_file"
+handle_post_keypress() {
+ read -r keypress <"$keypress_file"
command_exists "handle_custom_post_keypresses" && { handle_custom_post_keypresses "$keypress" || return "$?"; }
case "$keypress" in
- "$download_shortcut"|"$video_shortcut"|"$audio_shortcut") url_handler=$_last_url_handler ;;
- "$detach_shortcut") is_detach=0 ;;
- "$print_link_shortcut"|"$info_shortcut") info_to_print="$_last_info_to_print" ;;
- "$show_formats_shortcut") show_formats=0 ;;
- "$search_again_shortcut") : ;;
- *)
- _fn_name=handle_post_keypress_$(sed 's/-/_/g' <<-EOF
- $keypress
- EOF
- )
- command_exists "$_fn_name" && $_fn_name ;;
+ "$download_shortcut" | "$video_shortcut" | "$audio_shortcut") url_handler=$_last_url_handler ;;
+ "$detach_shortcut") is_detach=0 ;;
+ "$print_link_shortcut" | "$info_shortcut") info_to_print="$_last_info_to_print" ;;
+ "$show_formats_shortcut") show_formats=0 ;;
+ "$search_again_shortcut") : ;;
+ *)
+ _fn_name=handle_post_keypress_$(
+ sed 's/-/_/g' <<-EOF
+ $keypress
+ EOF
+ )
+ command_exists "$_fn_name" && $_fn_name
+ ;;
esac
unset keypress
}
-handle_keypress () {
- read -r keypress < "$1"
+handle_keypress() {
+ read -r keypress <"$1"
- print_debug "[KEYPRESS]: handling keypress: $keypress\n"
+ print_debug "[KEYPRESS]: handling keypress: $keypress\n"
command_exists "handle_custom_keypresses" && { handle_custom_keypresses "$keypress" || return "$?"; }
case "$keypress" in
- "$download_shortcut") _last_url_handler=$url_handler; url_handler=downloader ;;
- "$video_shortcut") _last_url_handler=$url_handler; url_handler=video_player ;;
- "$audio_shortcut") _last_url_handler=$url_handler; url_handler=audio_player ;;
- "$detach_shortcut") is_detach=1 ;;
- "$print_link_shortcut") _last_info_to_print="$info_to_print"; info_to_print="L" ;;
- "$show_formats_shortcut") show_formats=1 ;;
- "$info_shortcut") _last_info_to_print="$info_to_print"; info_to_print="VJ" ;;
- "$search_again_shortcut")
- clean_up
- initial_search="" init_and_make_search "" "$search_source"
- return 3 ;;
- *)
- _fn_name=handle_keypress_$(sed 's/-/_/g' <<-EOF
- $keypress
- EOF
- )
- command_exists "$_fn_name" && $_fn_name
- rv="$?"
- ;;
+ "$download_shortcut")
+ _last_url_handler=$url_handler
+ url_handler=downloader
+ ;;
+ "$video_shortcut")
+ _last_url_handler=$url_handler
+ url_handler=video_player
+ ;;
+ "$audio_shortcut")
+ _last_url_handler=$url_handler
+ url_handler=audio_player
+ ;;
+ "$detach_shortcut") is_detach=1 ;;
+ "$print_link_shortcut")
+ _last_info_to_print="$info_to_print"
+ info_to_print="L"
+ ;;
+ "$show_formats_shortcut") show_formats=1 ;;
+ "$info_shortcut")
+ _last_info_to_print="$info_to_print"
+ info_to_print="VJ"
+ ;;
+ "$search_again_shortcut")
+ clean_up
+ initial_search="" init_and_make_search "" "$search_source"
+ return 3
+ ;;
+ *)
+ _fn_name=handle_keypress_$(
+ sed 's/-/_/g' <<-EOF
+ $keypress
+ EOF
+ )
+ command_exists "$_fn_name" && $_fn_name
+ rv="$?"
+ ;;
esac
unset keypress
- return "${rv:-0}"
+ return "${rv:-0}"
}
#}}}
-command_exists "thumbnail_video_info_text_comments" || thumbnail_video_info_text_comments () {
+command_exists "thumbnail_video_info_text_comments" || thumbnail_video_info_text_comments() {
[ -n "$title" ] && printf "${c_bold}%s\n${c_reset}" "$title"
[ -n "$description" ] && printf "\n%s" "$description"
}
-# Scripting interfaces {{{
-interface_scripting () {
+
+# Scripting selection {{{
+auto_select() {
video_json_file=$1
selected_id_file=$2
# shellcheck disable=SC2194
case 1 in
- #sed is faster than jq, lets use it
- #this sed command finds `"url": "some-url"`, and prints all urls then selects the first $scripting_video_count urls.
- "$is_auto_select") sed -n 's/[[:space:]]*"url":[[:space:]]*"\([^"]\+\)",*$/\1/p' < "$video_json_file" | sed -n "1,${scripting_video_count}p" ;;
- "$is_random_select") sed -n 's/[[:space:]]*"url":[[:space:]]*"\([^"]\+\)",*$/\1/p' < "$video_json_file" | shuf | sed -n "1,$scripting_video_count"p ;;
- "$is_specific_select") jq -r '.[]|"\(.title)\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.viewed)\t|\(.url)"' < "$ytfzf_video_json_file" | sed -n "$scripting_video_count"p | trim_url ;;
- esac > "$selected_id_file"
+ #sed is faster than jq, lets use it
+ #this sed command finds `"url": "some-url"`, and prints all urls then selects the first $scripting_video_count urls.
+ "$is_auto_select") sed -n 's/[[:space:]]*"url":[[:space:]]*"\([^"]\+\)",*$/\1/p' <"$video_json_file" | sed -n "1,${scripting_video_count}p" ;;
+ "$is_random_select") sed -n 's/[[:space:]]*"url":[[:space:]]*"\([^"]\+\)",*$/\1/p' <"$video_json_file" | shuf | sed -n "1,$scripting_video_count"p ;;
+ "$is_specific_select") jq -r '.[]|"\(.title)\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.viewed)\t|\(.url)"' <"$ytfzf_video_json_file" | sed -n "$scripting_video_count"p | trim_url ;;
+ *) return 1 ;;
+ esac >"$selected_id_file"
+ return 0
# jq '.[]' < "$video_json_file" | jq -s -r --arg N "$scripting_video_count" '.[0:$N|tonumber]|.[]|.ID' > "$selected_id_file"
}
# }}}
-
# Text interface {{{
-interface_text () {
+interface_text() {
command_exists "fzf" || die 3 "fzf not installed, cannot use the default menu\n"
- # if it doesn't exist, this menu has not opened yet, no need to revert the actions of the last keypress
+ # if it doesn't exist, this menu has not opened yet, no need to revert the actions of the last keypress
[ -f "$keypress_file" ] && handle_post_keypress
- _fzf_start_bind=""
- if [ "${_const_fzf_selected_line_no:-0}" -gt 0 ]; then
- #if line n (where n != 0) was selected, add a start bind that moves the cursor down (n) times
- _fzf_start_bind="--bind start:$(mul_str "down+" "${_const_fzf_selected_line_no}")"
- _fzf_start_bind="${_fzf_start_bind%"+"}"
- fi
+ _fzf_start_bind=""
+ if [ "${_const_fzf_selected_line_no:-0}" -gt 0 ]; then
+ #if line n (where n != 0) was selected, add a start bind that moves the cursor down (n) times
+ _fzf_start_bind="--bind start:$(mul_str "down+" "${_const_fzf_selected_line_no}")"
+ _fzf_start_bind="${_fzf_start_bind%"+"}"
+ fi
- [ "$show_thumbnails" -eq 1 ] && { interface_thumbnails "$@"; return; }
+ [ "$show_thumbnails" -eq 1 ] && {
+ interface_thumbnails "$@"
+ return
+ }
# shellcheck disable=SC2015
command_exists "column" && use_column=1 || { print_warning "command \"column\" not found, the menu may look very bad\n" && use_column=0; }
@@ -2474,346 +2517,367 @@ interface_text () {
video_json_file=$1
selected_id_file=$2
- title_len=$((TTY_COLS/2))
- channel_len=$((TTY_COLS/5))
+ title_len=$((TTY_COLS / 2))
+ channel_len=$((TTY_COLS / 5))
dur_len=7
view_len=10
date_len=14
unset IFS
- _c_SORTED_VIDEO_DATA="$(create_sorted_video_data)"
+ _c_SORTED_VIDEO_DATA="$(create_sorted_video_data)"
# shellcheck disable=2015
- printf "%s\n" "$_c_SORTED_VIDEO_DATA" |
- video_info_text |
- { [ $use_column -eq 1 ] && column -t -s "$tab_space" || cat; } |
+ printf "%s\n" "$_c_SORTED_VIDEO_DATA" |
+ video_info_text |
+ { [ $use_column -eq 1 ] && column -t -s "$tab_space" || cat; } |
fzf -m --sync --tabstop=1 --layout=reverse --expect="$shortcut_binds" \
- $_fzf_start_bind \
- --bind "${next_page_action_shortcut}:reload(__is_fzf_preview=1 YTFZF_CHECK_VARS_EXISTS=1 session_cache_dir='$session_cache_dir' session_temp_dir='$session_temp_dir' $0 -W \"next_page"$EOT"{f}\")" | set_keypress |
- trim_url > "$selected_id_file"
+ $_fzf_start_bind \
+ --bind "${next_page_action_shortcut}:reload(__is_fzf_preview=1 YTFZF_CHECK_VARS_EXISTS=1 session_cache_dir='$session_cache_dir' ytfzf_video_json_file='$ytfzf_video_json_file' invidious_instance='$invidious_instance' yt_video_link_domain='$yt_video_link_domain' pages_to_scrape='$pages_to_scrape' session_temp_dir='$session_temp_dir' $0 -W \"next_page"$EOT"{f}\")" | set_keypress |
+ trim_url >"$selected_id_file"
- _const_top_url="$(head -n 1 "$selected_id_file")"
- _const_fzf_selected_line_no="$(jq -s -r --arg url "$_const_top_url" 'flatten|[.[]|.url]|index($url)' <<-EOF
- ${_c_SORTED_VIDEO_DATA}
-EOF
-)"
+ _const_top_url="$(head -n 1 "$selected_id_file")"
+ _const_fzf_selected_line_no="$(
+ jq -s -r --arg url "$_const_top_url" 'flatten|[.[]|.url]|index($url)' <<-EOF
+ ${_c_SORTED_VIDEO_DATA}
+ EOF
+ )"
}
#}}}
# External interface {{{
-interface_ext () {
+interface_ext() {
video_json_file=$1
selected_id_file=$2
# video_info_text can be set in the conf.sh, if set it will be preferred over the default given below
TTY_COLS=$external_menu_len
- title_len=$((TTY_COLS/2))
- channel_len=$((TTY_COLS/5))
+ title_len=$((TTY_COLS / 2))
+ channel_len=$((TTY_COLS / 5))
dur_len=7
date_len=14
- create_sorted_video_data |
- video_info_text |
+ create_sorted_video_data |
+ video_info_text |
external_menu 'Select Video:' | # MODIFIED
- trim_url > "$selected_id_file"
+ trim_url >"$selected_id_file"
}
#}}}
# Thumbnail Interface {{{
-_get_video_json_attr () {
+_get_video_json_attr() {
sed -n 's/^[[:space:]]*"'"$1"'":[[:space:]]*"\([^\n]*\)",*/\1/p' <<-EOF | sed 's/\\\([\\"]\)/\1/g'
- $_correct_json
-EOF
+ $_correct_json
+ EOF
}
# Image preview {{{
-preview_start () {
+preview_start() {
thumbnail_viewer=$1
case $thumbnail_viewer in
- ueberzug)
- # starts uberzug to this fifo
- command_exists "ueberzug" || die 3 "ueberzug is not installed\n"
- export UEBERZUG_FIFO="$session_temp_dir/ytfzf-ueberzug-fifo"
- rm -f "$UEBERZUG_FIFO"
- mkfifo "$UEBERZUG_FIFO"
- ueberzug layer --parser json < "$UEBERZUG_FIFO" > "$thumbnail_debug_log" 2>&1 &
- exec 3>"$UEBERZUG_FIFO"
- ;;
- chafa|chafa-16|chafa-tty|catimg|catimg-256|swayimg|swayimg-hyprland) : ;;
- imv)
- first_img="$(jq -r '.[0].ID|select(.!=null)' < "$ytfzf_video_json_file")"
- imv "$thumb_dir/${first_img}.jpg" > "$thumbnail_debug_log" 2>&1 &
- export imv_pid="$!"
- # helps prevent imv seg fault
- sleep 0.1
- ;;
- mpv)
- command_exists "socat" || die 3 "socat is not installed, and is required for the mpv image viewer\n"
- command_exists "mpv" || die 3 "mpv is not installed\n"
- first_img="$(jq -r '.[0].ID|select(.!=null)' < "$ytfzf_video_json_file")"
- export MPV_SOCKET="$session_temp_dir/mpv.socket"
- rm -f "$MPV_SOCKET" > /dev/null 2>&1
- mpv --input-ipc-server="$MPV_SOCKET" --loop-file=inf --idle=yes "$thumb_dir/${first_img}.jpg" > "$thumbnail_debug_log" 2>&1 &
- export mpv_pid=$!
- ;;
- *)
- "$thumbnail_viewer" "start" "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" 2>/dev/null ;;
+ ueberzug | sixel | kitty | iterm2)
+ command_exists "ueberzug" || {
+ [ "$thumbnail_viewer" = "ueberzug" ] && die 3 "ueberzug is not installed\n" || die 3 "ueberzugpp is not installed\n"
+ }
+ export UEBERZUG_FIFO="$session_temp_dir/ytfzf-ueberzug-fifo"
+ rm -f "$UEBERZUG_FIFO"
+ mkfifo "$UEBERZUG_FIFO"
+ [ "$thumbnail_viewer" = "ueberzug" ] && o="x11" || o="${thumbnail_viewer}"
+ if command_exists ueberzugpp; then
+ ueberzugpp layer -o "${o}" --parser json <"$UEBERZUG_FIFO" 2>"$thumbnail_debug_log" &
+ else
+ ueberzug layer --parser json <"$UEBERZUG_FIFO" 2>"$thumbnail_debug_log" &
+ fi
+ exec 3>"$UEBERZUG_FIFO"
+ ;;
+ chafa | chafa-16 | chafa-tty | catimg | catimg-256 | swayimg | swayimg-hyprland) : ;;
+ imv)
+ first_img="$(jq -r '.[0].ID|select(.!=null)' <"$ytfzf_video_json_file")"
+ imv "$thumb_dir/${first_img}.jpg" >"$thumbnail_debug_log" 2>&1 &
+ export imv_pid="$!"
+ # helps prevent imv seg fault
+ sleep 0.1
+ ;;
+ mpv)
+ command_exists "socat" && command_exists "mpv" || die 3 "socat, and mpv must be installed for the mpv thumbnail viewer"
+ first_img="$(jq -r '.[0].ID|select(.!=null)' <"$ytfzf_video_json_file")"
+ export MPV_SOCKET="$session_temp_dir/mpv.socket"
+ rm -f "$MPV_SOCKET" >/dev/null 2>&1
+ mpv --input-ipc-server="$MPV_SOCKET" --loop-file=inf --idle=yes "$thumb_dir/${first_img}.jpg" >"$thumbnail_debug_log" 2>&1 &
+ export mpv_pid=$!
+ ;;
+ *)
+ "$thumbnail_viewer" "start" "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" 2>/dev/null
+ ;;
esac
}
-preview_stop () {
+preview_stop() {
thumbnail_viewer=$1
case $thumbnail_viewer in
- ueberzug) exec 3>&- ;;
- chafa|chafa-16|chafa-tty|catimg|catimg-256) : ;;
- mpv)
+ ueberzug | sixel | kitty | iterm2) exec 3>&- ;;
+ chafa | chafa-16 | chafa-tty | catimg | catimg-256) : ;;
+ mpv)
kill "$mpv_pid"
- rm "$MPV_SOCKET" > /dev/null 2>&1
+ rm "$MPV_SOCKET" >/dev/null 2>&1
+ ;;
+ swayimg | swayimg-hyprland) killall swayimg 2>/dev/null ;;
+ imv) kill "$imv_pid" ;;
+ *)
+ "$thumbnail_viewer" "stop" "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" 2>/dev/null
;;
- swayimg|swayimg-hyprland) killall swayimg 2> /dev/null;;
- imv) kill "$imv_pid" ;;
- *)
- "$thumbnail_viewer" "stop" "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" 2>/dev/null ;;
esac
}
-command_exists "on_no_thumbnail" || on_no_thumbnail () {
- die 1 "No image\n"
+command_exists "on_no_thumbnail" || on_no_thumbnail() {
+ die 1 "No image\n"
}
-preview_no_img (){
+preview_no_img() {
thumbnail_viewer="$1"
case $thumbnail_viewer in
- chafa|chafa-16|chafa-tty|catimg|catimg-256|imv|mpv) : ;;
- ueberzug)
+ chafa | chafa-16 | chafa-tty | catimg | catimg-256 | imv | mpv) : ;;
+ ueberzug | sixel | kitty | iterm2)
{
- printf "{"
- printf "\"%s\": \"%s\"," "action" "remove" "identifier" "ytfzf"
- printf '"%s": "%s"' "draw" "True"
- printf "}\n"
- } > "$UEBERZUG_FIFO"
- ;;
- swayimg|swayimg-hyprland) killall swayimg 2> /dev/null; true ;; # we want this to be true so that the && at the bottom happens
- *) "$thumbnail_viewer" "no-img" ;;
+ printf "{"
+ printf "\"%s\": \"%s\"," "action" "remove" "identifier" "ytfzf"
+ printf '"%s": "%s"' "draw" "True"
+ printf "}\n"
+ } >"$UEBERZUG_FIFO"
+ ;;
+ swayimg | swayimg-hyprland)
+ killall swayimg 2>/dev/null
+ true
+ ;; # we want this to be true so that the && at the bottom happens
+ *) "$thumbnail_viewer" "no-img" ;;
esac && do_an_event_function "on_no_thumbnail"
}
# ueberzug positioning{{{
-command_exists "get_ueberzug_positioning_left" || get_ueberzug_positioning_left (){
+command_exists "get_ueberzug_positioning_left" || get_ueberzug_positioning_left() {
width=$1
- height=$(($2-__text_line_count+2))
+ height=$(($2 - __text_line_count + 2))
x=2
- y=$((__text_line_count+2))
+ y=$((__text_line_count + 2))
}
-command_exists "get_ueberzug_positioning_right" || get_ueberzug_positioning_right (){
- get_ueberzug_positioning_left "$@"
+command_exists "get_ueberzug_positioning_right" || get_ueberzug_positioning_right() {
+ get_ueberzug_positioning_left "$@"
width=$1
- x=$(($1+6))
+ x=$(($1 + 6))
}
-command_exists "get_ueberzug_positioning_up" || get_ueberzug_positioning_up (){
+command_exists "get_ueberzug_positioning_up" || get_ueberzug_positioning_up() {
width=$1
- height=$(($2-__text_line_count))
+ height=$(($2 - __text_line_count))
x=2
y=9
}
-command_exists "get_ueberzug_positioning_down" || get_ueberzug_positioning_down (){
+command_exists "get_ueberzug_positioning_down" || get_ueberzug_positioning_down() {
width=$1
- height=$(($2-__text_line_count))
+ height=$(($2 - __text_line_count))
#$2*2 goes to the bottom subtracts height, adds padding
- y=$(($2*2-height+2))
+ y=$(($2 * 2 - height + 2))
x=2
}
-command_exists "get_swayimg_positioning_left" || get_swayimg_positioning_left () {
- # allows space for text
- y_gap=$((line_px_height*(__text_line_count+3))) #the plus 3 just seems to work better
+command_exists "get_swayimg_positioning_left" || get_swayimg_positioning_left() {
+ # allows space for text
+ y_gap=$((__text_line_count + 3)) #the plus 3 just seems to work better
+ y_gap=$((line_px_height * y_gap))
+ #these are seperate because treesitter syntax highlighting dies when parentheses are inside of math
- # it's subtracting the gap between the border and the edge of terminal
- w_correct=$((max_width/2-2*col_px_width))
- h_correct=$((max_height-3*line_px_height-y_gap))
+ # it's subtracting the gap between the border and the edge of terminal
+ w_correct=$((max_width / 2 - 2 * col_px_width))
+ h_correct=$((max_height - 3 * line_px_height - y_gap))
- # offset from the edge by half a column
- x=$((term_x+col_px_width/2))
- # move down to allow for text
- y=$((term_y+y_gap))
- [ "$img_w" -gt "$w_correct" ] && img_w=$((w_correct))
- #-20 is to leave space for the text
- [ "$img_h" -gt "$h_correct" ] && img_h=$((h_correct))
+ # offset from the edge by half a column
+ x=$((term_x + col_px_width / 2))
+ # move down to allow for text
+ y=$((term_y + y_gap))
+ [ "$img_w" -gt "$w_correct" ] && img_w=$((w_correct))
+ #-20 is to leave space for the text
+ [ "$img_h" -gt "$h_correct" ] && img_h=$((h_correct))
}
-command_exists "get_swayimg_positioning_right" || get_swayimg_positioning_right () {
- get_swayimg_positioning_left "$@"
- # after setting the positioning as if side was `left` set x to the correct place
- x=$((term_x+max_width-w_correct))
+command_exists "get_swayimg_positioning_right" || get_swayimg_positioning_right() {
+ get_swayimg_positioning_left "$@"
+ # after setting the positioning as if side was `left` set x to the correct place
+ x=$((term_x + max_width - w_correct))
}
-command_exists "get_swayimg_positioning_up" || get_swayimg_positioning_up () {
- w_correct=$((max_width/2))
- # offset from border slightly
- h_correct=$((max_height-2*line_px_height))
+command_exists "get_swayimg_positioning_up" || get_swayimg_positioning_up() {
+ w_correct=$((max_width / 2))
+ # offset from border slightly
+ h_correct=$((max_height - 2 * line_px_height))
- # offset from info text by 30 columns
- x=$((term_x+(max_width-w_correct)))
- # go down from the top by 2 lines
- y=$((term_y+2*line_px_height))
+ # offset from info text by 30 columns
+ x=$((max_width - w_correct))
+ x=$((term_x + x))
+ # go down from the top by 2 lines
+ y=$((term_y + 2 * line_px_height))
- [ "$img_w" -gt "$w_correct" ] && img_w=$((w_correct))
- #-20 is to leave space for the text
- [ "$img_h" -gt "$h_correct" ] && img_h=$((h_correct))
+ [ "$img_w" -gt "$w_correct" ] && img_w=$((w_correct))
+ #-20 is to leave space for the text
+ [ "$img_h" -gt "$h_correct" ] && img_h=$((h_correct))
}
-command_exists "get_swayimg_positioning_down" || get_swayimg_positioning_down () {
- get_swayimg_positioning_up "$@"
- # after setting the positioning as if side was `up` set y to the correct place
- y=$((term_y+max_height/2+2*line_px_height))
+command_exists "get_swayimg_positioning_down" || get_swayimg_positioning_down() {
+ get_swayimg_positioning_up "$@"
+ # after setting the positioning as if side was `up` set y to the correct place
+ y=$((term_y + max_height / 2 + 2 * line_px_height))
}
-get_swayimg_positioning () {
- max_width=$1
- max_height=$2
- term_x=$3
- term_y=$4
- col_px_width=$5
- line_px_height=$6
+get_swayimg_positioning() {
+ max_width=$1
+ max_height=$2
+ term_x=$3
+ term_y=$4
+ col_px_width=$5
+ line_px_height=$6
- img_size="$(identify -format "%w %h" "$thumb_path")"
- img_w=${img_size% *}
- img_h=${img_size#* }
+ img_size="$(identify -format "%w %h" "$thumb_path")"
+ img_w=${img_size% *}
+ img_h=${img_size#* }
- get_swayimg_positioning_$fzf_preview_side "${img_size% *}" "${img_size#* }" "$max_width" "$max_height" "$term_x" "$term_y" "$col_px_width" "$line_px_height"
+ get_swayimg_positioning_$fzf_preview_side "${img_size% *}" "${img_size#* }" "$max_width" "$max_height" "$term_x" "$term_y" "$col_px_width" "$line_px_height"
}
-get_ueberzug_positioning () {
- max_width=$1
- max_height=$2
- "get_ueberzug_positioning_$fzf_preview_side" "$max_width" "$max_height"
+get_ueberzug_positioning() {
+ max_width=$1
+ max_height=$2
+ "get_ueberzug_positioning_$fzf_preview_side" "$max_width" "$max_height"
}
#}}}
-preview_display_image () {
- thumbnail_viewer=$1
- id=$2
-
- for path in "${YTFZF_CUSTOM_THUMBNAILS_DIR}/$id.jpg" "${thumb_dir}/${id}.jpg" "${YTFZF_CUSTOM_THUMBNAILS_DIR}/YTFZF:DEFAULT.jpg"; do
- thumb_path="$path"
- [ -f "${thumb_path}" ] && break
- done || preview_no_img "$thumbnail_viewer"
- # this is separate becuase, preview_no_img will not happen if thumb_path = YTFZF:DEFAULT, but on_no_thumbnail should still happen
- [ "$thumb_path" = "${YTFZF_CUSTOM_THUMBNAILS_DIR}/YTFZF:DEFAULT.jpg" ] && do_an_event_function "on_no_thumbnail"
-
- get_ueberzug_positioning "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" "$fzf_preview_side"
+preview_display_image() {
+ thumbnail_viewer=$1
+ id=$2
+
+ for path in "${YTFZF_CUSTOM_THUMBNAILS_DIR}/$id.jpg" "${thumb_dir}/${id}.jpg" "${YTFZF_CUSTOM_THUMBNAILS_DIR}/YTFZF:DEFAULT.jpg"; do
+ thumb_path="$path"
+ [ -f "${thumb_path}" ] && break
+ done || preview_no_img "$thumbnail_viewer"
+ # this is separate becuase, preview_no_img will not happen if thumb_path = YTFZF:DEFAULT, but on_no_thumbnail should still happen
+ [ "$thumb_path" = "${YTFZF_CUSTOM_THUMBNAILS_DIR}/YTFZF:DEFAULT.jpg" ] && do_an_event_function "on_no_thumbnail"
+
+ get_ueberzug_positioning "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" "$fzf_preview_side"
case $thumbnail_viewer in
- ueberzug)
+ ueberzug | sixel | kitty | iterm2)
{
- printf "{"
- printf '"%s": "%s",' \
- 'action' 'add' \
- 'identifier' 'ytfzf' \
- 'path' "$thumb_path" \
- 'x' "$x" \
- 'y' "$y" \
- 'scaler' 'fit_contain' \
- 'width' "$width"
- printf '"%s": "%s"' 'height' "$height"
- printf "}\n"
- } > "$UEBERZUG_FIFO" 2> "$thumbnail_debug_log"
+ printf "{"
+ printf '"%s": "%s",' \
+ 'action' 'add' \
+ 'identifier' 'ytfzf' \
+ 'path' "$thumb_path" \
+ 'x' "$x" \
+ 'y' "$y" \
+ 'width' "$width"
+ printf '"%s": "%s"' 'height' "$height"
+ printf "}\n"
+ } >"$UEBERZUG_FIFO" 2>"$thumbnail_debug_log"
;;
- swayimg-hyprland)
- command_exists "hyprctl" || die 3 "hyprctl is required for this thumbnail viewer\n"
- _swayimg_pid_file="${session_temp_dir}/_swayimg.pid"
- [ -f "$_swayimg_pid_file" ] && kill "$(cat "$_swayimg_pid_file")" 2> /dev/null
-
- window_data="$(hyprctl activewindow -j)"
-
- IFS=" " read -r x y w h <<-EOF
- $(printf "%s" "$window_data" | jq -r '"\(.at[0]) \(.at[1]) \(.size[0]) \(.size[1])"')
-EOF
- read -r output_x output_y <<-EOF
- $(hyprctl monitors -j | jq -r '.[]|select(.focused==true) as $mon | "\($mon.x) \($mon.y)"')
-EOF
- x=$((x-output_x))
- y=$((y-output_y))
- # shellcheck disable=SC2034
- w_half=$((w/2)) h_half=$((h/2))
- # how many pixels per col
- col_px_width=$((w/TTY_COLS))
- # how many pixels per line
- line_px_height=$((h/TTY_LINES))
- get_swayimg_positioning "$((w))" "$((h))" "$x" "$y" "$col_px_width" "$line_px_height"
- hyprctl keyword windowrulev2 "move $((x+10)) $y,title:swayimg" > /dev/null 2>&1
- hyprctl keyword windowrulev2 float,title:swayimg > /dev/null 2>&1
- hyprctl keyword windowrulev2 nofocus,title:swayimg > /dev/null 2>&1
- hyprctl keyword windowrulev2 "noborder,title:swayimg" > /dev/null 2>&1
- swayimg -s fit -g $((x+10)),$y,$((img_w)),$((img_h)) "$thumb_path" 2> "$thumbnail_debug_log" >&2 &
- printf "%s" "$!" > "$_swayimg_pid_file"
- # without this there are weird flushing issues (maybe)
- ;;
- swayimg)
- _swayimg_pid_file="${session_temp_dir}/_swayimg.pid"
- [ -f "$_swayimg_pid_file" ] && kill "$(cat "$_swayimg_pid_file")" 2> /dev/null
- # this jq call finds the id of the selected monitor and saves it as $focused_id
- # then finds x, and y of the focused monitor and saves it as the var $d1
- # then it finds the geometry of the focused window and saves it as the var $d2
- # at the end it concatinates the two strings with a tab in the middle so that read can read it into 6 vars
- read -r output_x output_y x y w h <<-EOF
- $(swaymsg -t get_tree | jq -r '. as $data |
- .focus[0] as $focused_id |
- ..| try select(.type=="output" and .id==$focused_id) |
- [.rect.x,.rect.y] | @tsv as $d1 |
- $data | .. | try select(.focused==true) |
- [.rect.x,.rect.y,.rect.width,.rect.height] | @tsv as $d2 |
- $d1 + "\t" + $d2')
-EOF
- # we're subtracting output_* to make sure swayimg places on correct monitor
- x=$((x-output_x))
- y=$((y-output_y))
- # shellcheck disable=SC2034
- w_half=$((w/2)) h_half=$((h/2))
- # how many pixels per col
- col_px_width=$((w/TTY_COLS))
- # how many pixels per line
- line_px_height=$((h/TTY_LINES))
- get_swayimg_positioning "$((w))" "$((h))" "$x" "$y" "$col_px_width" "$line_px_height"
- swaymsg 'no_focus [app_id="swayimg_.*"]' > /dev/null 2>&1
- swayimg -s fit -g $x,$y,$((img_w)),$((img_h)) "$thumb_path" 2> "$thumbnail_debug_log" >&2 &
- printf "%s" "$!" > "$_swayimg_pid_file"
- # without this there are weird flushing issues (maybe)
- echo ;;
- chafa)
+ swayimg-hyprland)
+ command_exists "hyprctl" || die 3 "hyprctl is required for this thumbnail viewer\n"
+ _swayimg_pid_file="${session_temp_dir}/_swayimg.pid"
+ [ -f "$_swayimg_pid_file" ] && kill "$(cat "$_swayimg_pid_file")" 2>/dev/null
+
+ window_data="$(hyprctl activewindow -j)"
+
+ IFS=" " read -r x y w h <<-EOF
+ $(printf "%s" "$window_data" | jq -r '"\(.at[0]) \(.at[1]) \(.size[0]) \(.size[1])"')
+ EOF
+ read -r output_x output_y <<-EOF
+ $(hyprctl monitors -j | jq -r '.[]|select(.focused==true) as $mon | "\($mon.x) \($mon.y)"')
+ EOF
+ x=$((x - output_x))
+ y=$((y - output_y))
+ # shellcheck disable=SC2034
+ w_half=$((w / 2)) h_half=$((h / 2))
+ # how many pixels per col
+ col_px_width=$((w / TTY_COLS))
+ # how many pixels per line
+ line_px_height=$((h / TTY_LINES))
+ get_swayimg_positioning "$((w))" "$((h))" "$x" "$y" "$col_px_width" "$line_px_height"
+ hyprctl keyword windowrulev2 "move $((x + 10)) $y,title:swayimg" >/dev/null 2>&1
+ hyprctl keyword windowrulev2 float,title:swayimg >/dev/null 2>&1
+ hyprctl keyword windowrulev2 nofocus,title:swayimg >/dev/null 2>&1
+ hyprctl keyword windowrulev2 "noborder,title:swayimg" >/dev/null 2>&1
+ swayimg -s fit -g $((x + 10)),$y,$((img_w)),$((img_h)) "$thumb_path" 2>"$thumbnail_debug_log" >&2 &
+ printf "%s" "$!" >"$_swayimg_pid_file"
+ # without this there are weird flushing issues (maybe)
+ ;;
+ swayimg)
+ _swayimg_pid_file="${session_temp_dir}/_swayimg.pid"
+ [ -f "$_swayimg_pid_file" ] && kill "$(cat "$_swayimg_pid_file")" 2>/dev/null
+ # this jq call finds the id of the selected monitor and saves it as $focused_id
+ # then finds x, and y of the focused monitor and saves it as the var $d1
+ # then it finds the geometry of the focused window and saves it as the var $d2
+ # at the end it concatinates the two strings with a tab in the middle so that read can read it into 6 vars
+ read -r output_x output_y x y w h <<-EOF
+ $(swaymsg -t get_tree | jq -r '. as $data |
+ .focus[0] as $focused_id |
+ ..| try select(.type=="output" and .id==$focused_id) |
+ [.rect.x,.rect.y] | @tsv as $d1 |
+ $data | .. | try select(.focused==true) |
+ [.rect.x,.rect.y,.rect.width,.rect.height] | @tsv as $d2 |
+ $d1 + "\t" + $d2')
+ EOF
+ # we're subtracting output_* to make sure swayimg places on correct monitor
+ x=$((x - output_x))
+ y=$((y - output_y))
+ # shellcheck disable=SC2034
+ w_half=$((w / 2)) h_half=$((h / 2))
+ # how many pixels per col
+ col_px_width=$((w / TTY_COLS))
+ # how many pixels per line
+ line_px_height=$((h / TTY_LINES))
+ get_swayimg_positioning "$((w))" "$((h))" "$x" "$y" "$col_px_width" "$line_px_height"
+ swaymsg 'no_focus [app_id="swayimg_.*"]' >/dev/null 2>&1
+ swayimg -s fit -g $x,$y,$((img_w)),$((img_h)) "$thumb_path" 2>"$thumbnail_debug_log" >&2 &
+ printf "%s" "$!" >"$_swayimg_pid_file"
+ # without this there are weird flushing issues (maybe)
+ echo
+ ;;
+ chafa)
+ printf '\n'
+ command_exists "chafa" || die 3 "\nchafa is not installed\n"
+ chafa --format=symbols -s "$((width - 4))x$height" "$thumb_path" 2>"$thumbnail_debug_log"
+ ;;
+ chafa-16)
+ printf '\n'
+ command_exists "chafa" || die 3 "\nchafa is not installed\n"
+ chafa --format=symbols -c 240 -s "$((width - 2))x$((height - 10))" "$thumb_path" 2>"$thumbnail_debug_log"
+ ;;
+ chafa-tty)
printf '\n'
command_exists "chafa" || die 3 "\nchafa is not installed\n"
- chafa --format=symbols -s "$((width-4))x$height" "$thumb_path" 2> "$thumbnail_debug_log";;
- chafa-16)
- printf '\n'
- command_exists "chafa" || die 3 "\nchafa is not installed\n"
- chafa --format=symbols -c 240 -s "$((width-2))x$((height-10))" "$thumb_path" 2> "$thumbnail_debug_log" ;;
- chafa-tty)
- printf '\n'
- command_exists "chafa" || die 3 "\nchafa is not installed\n"
- chafa --format=symbols -c 16 -s "$((width-2))x$((height-10))" "$thumb_path" "$thumbnail_debug_log" ;;
- catimg)
- printf '\n'
- command_exists "catimg" || die 3 "\ncatimg is not installed\n"
- catimg -w "$width" "$thumb_path" 2> "$thumbnail_debug_log" ;;
- catimg-256)
- printf '\n'
- command_exists "catimg" || die 3 "\ncatimg is not installed\n"
- catimg -c -w "$width" "$thumb_path" 2> "$thumbnail_debug_log" ;;
- imv)
- imv-msg "$imv_pid" open "$thumb_path" 2> "$thumbnail_debug_log" >&2
- imv-msg "$imv_pid" next 2> "$thumbnail_debug_log" >&2
- ;;
- mpv)
- echo "loadfile '$thumb_path'" | socat - "$MPV_SOCKET" > "$thumbnail_debug_log" 2>&1 ;;
- *)
+ chafa --format=symbols -c 16 -s "$((width - 2))x$((height - 10))" "$thumb_path" "$thumbnail_debug_log"
+ ;;
+ catimg)
+ printf '\n'
+ command_exists "catimg" || die 3 "\ncatimg is not installed\n"
+ catimg -w "$width" "$thumb_path" 2>"$thumbnail_debug_log"
+ ;;
+ catimg-256)
+ printf '\n'
+ command_exists "catimg" || die 3 "\ncatimg is not installed\n"
+ catimg -c -w "$width" "$thumb_path" 2>"$thumbnail_debug_log"
+ ;;
+ imv)
+ imv-msg "$imv_pid" open "$thumb_path" 2>"$thumbnail_debug_log" >&2
+ imv-msg "$imv_pid" next 2>"$thumbnail_debug_log" >&2
+ ;;
+ mpv)
+ echo "loadfile '$thumb_path'" | socat - "$MPV_SOCKET" >"$thumbnail_debug_log" 2>&1
+ ;;
+ *)
get_ueberzug_positioning "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" "$fzf_preview_side"
- "$thumbnail_viewer" "view" "$thumb_path" "$x" "$y" "$width" "$height" "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" "$fzf_preview_side";;
+ "$thumbnail_viewer" "view" "$thumb_path" "$x" "$y" "$width" "$height" "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" "$fzf_preview_side"
+ ;;
esac
}
#}}}
-preview_img () {
+preview_img() {
# This function is common to every thumbnail viewer
thumbnail_viewer=$1
line=$2
@@ -2821,7 +2885,7 @@ preview_img () {
url=${line##*"|"}
# make sure all variables are set{{{
- _correct_json=$(jq -nr --arg url "$url" '[inputs[]|select(.url==$url)][0]' < "$video_json_file")
+ _correct_json=$(jq -nr --arg url "$url" '[inputs[]|select(.url==$url)][0]' <"$video_json_file")
id="$(_get_video_json_attr "ID")"
title="$(_get_video_json_attr "title")"
channel="$(_get_video_json_attr "channel")"
@@ -2830,26 +2894,27 @@ preview_img () {
scraper="$(_get_video_json_attr "scraper")"
duration="$(_get_video_json_attr "duration")"
viewed="$(_get_video_json_attr "viewed")"
- description="$(_get_video_json_attr "description" | sed 's/\\n/\n/g')"
-#}}}
+ description="$(_get_video_json_attr "description" | sed 's/\\n/\n/g')"
+ #}}}
_const_text=$(if command_exists "thumbnail_video_info_text${scraper:+_$scraper}"; then
thumbnail_video_info_text${scraper:+_$scraper}
else
thumbnail_video_info_text
- fi)
+ fi)
- __text_line_count=$(wc -l <<-EOF
- $_const_text
-EOF
-)
+ __text_line_count=$(
+ wc -l <<-EOF
+ $_const_text
+ EOF
+ )
- echo "$_const_text"
+ echo "$_const_text"
preview_display_image "$thumbnail_viewer" "$id"
}
-interface_thumbnails () {
+interface_thumbnails() {
# Takes video json file and downloads the thumnails as ${ID}.png to thumb_dir
video_json_file=$1
selected_id_file=$2
@@ -2861,162 +2926,165 @@ interface_thumbnails () {
# ytfzf -U preview_img ueberzug {} "$video_json_file"
# fzf_preview_side will get reset if we don't pass it in
- _c_SORTED_VIDEO_DATA="$(create_sorted_video_data)"
-
- start_ytfzf_server &
+ _c_SORTED_VIDEO_DATA="$(create_sorted_video_data)"
- YTFZF_SERVER_PID="$!"
+ YTFZF_SERVER_PID="$!"
- printf "%s\n" "$_c_SORTED_VIDEO_DATA" |
- jq -r '"\(.title)'"$gap_space"'\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.viewed)\t|\(.url)"' |
- SHELL="$(command -v sh)" fzf -m --sync \
- --expect="$shortcut_binds" \
- --preview "__is_fzf_preview=1 YTFZF_CHECK_VARS_EXISTS=1 session_cache_dir='$session_cache_dir' session_temp_dir='$session_temp_dir' $0 -W \"preview_img"$EOT"{f}\"" \
- $_fzf_start_bind \
- --bind "${next_page_action_shortcut}:reload(__is_fzf_preview=1 YTFZF_CHECK_VARS_EXISTS=1 session_cache_dir='$session_cache_dir' session_temp_dir='$session_temp_dir' $0 -W \"next_page"$EOT"{f}\")" \
- --preview-window "$fzf_preview_side:50%:wrap" --layout=reverse | set_keypress |
- trim_url > "$selected_id_file"
+ printf "%s\n" "$_c_SORTED_VIDEO_DATA" |
+ jq -r '"\(.title)'"$gap_space"'\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.viewed)\t|\(.url)"' |
+ SHELL="$(command -v sh)" fzf -m --sync \
+ --expect="$shortcut_binds" \
+ --preview "__is_fzf_preview=1 YTFZF_CHECK_VARS_EXISTS=1 session_cache_dir='$session_cache_dir' session_temp_dir='$session_temp_dir' fzf_preview_side='$fzf_preview_side' scrape='$scrape' thumbnail_viewer='$thumbnail_viewer' ytfzf_video_json_file='$ytfzf_video_json_file' $0 -W \"preview_img"$EOT"{f}\"" \
+ $_fzf_start_bind \
+ --bind "${next_page_action_shortcut}:reload(__is_fzf_preview=1 YTFZF_CHECK_VARS_EXISTS=1 session_cache_dir='$session_cache_dir' ytfzf_video_json_file='$ytfzf_video_json_file' invidious_instance='$invidious_instance' yt_video_link_domain='$yt_video_link_domain' pages_to_scrape='$pages_to_scrape' session_temp_dir='$session_temp_dir' $0 -W \"next_page"$EOT"{f}\")" \
+ --preview-window "$fzf_preview_side:50%:wrap" --layout=reverse | set_keypress |
+ trim_url >"$selected_id_file"
preview_stop "$thumbnail_viewer"
- _const_top_url="$(head -n 1 "$selected_id_file")"
- _const_fzf_selected_line_no="$(jq -s -r --arg url "$_const_top_url" 'flatten|[.[]|.url]|index($url)' <<-EOF
- $_c_SORTED_VIDEO_DATA
-EOF
-)"
-
- kill "$YTFZF_SERVER_PID"
+ _const_top_url="$(head -n 1 "$selected_id_file")"
+ _const_fzf_selected_line_no="$(
+ jq -s -r --arg url "$_const_top_url" 'flatten|[.[]|.url]|index($url)' <<-EOF
+ $_c_SORTED_VIDEO_DATA
+ EOF
+ )"
}
#}}}
#}}}
# Handling selection from interface {{{
-get_requested_info () {
- url_list="$1"
- prepare_for_set_args ","
- set -- $info_to_print
- urls="[$(sed 's/^\(.*\)$/"\1",/' "$url_list")"
- urls="${urls%,}]"
- for request in "$@"; do
- case "$request" in
- [Ll]|link)
- # cat is better here because a lot of urls could be selected
- cat "$url_list" ;;
- VJ|vj|video-json) jq '(.[] | if( [.url] | inside('"$urls"')) then . else "" end)' < "$ytfzf_video_json_file" | jq -s '[ .[]|select(.!="") ]' ;;
- [Jj]|json) jq < "$ytfzf_video_json_file" ;;
- [Ff]|format) printf "%s\n" "$ytdl_pref" ;;
- [Rr]|raw)
- jq -r '.[] | if( [.url] | inside('"$urls"')) then "\(.title)\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.url)" else "" end' < "$ytfzf_video_json_file" | { command_exists "column" && column -s "$tab_space" -t; } ;;
- *)
- command_exists "get_requested_info_$request" && "get_requested_info_$request"
- esac
- done
- end_of_set_args
- return 0
-}
-
-handle_info_wait_action () {
- if [ "$info_wait" -eq 1 ]; then
- info_wait_prompt_wrapper
- fi
+get_requested_info() {
+ url_list="$1"
+ prepare_for_set_args ","
+ set -- $info_to_print
+ urls="[$(sed 's/^\(.*\)$/"\1",/' "$url_list")"
+ urls="${urls%,}]"
+ for request in "$@"; do
+ case "$request" in
+ [Ll] | link)
+ # cat is better here because a lot of urls could be selected
+ cat "$url_list"
+ ;;
+ VJ | vj | video-json) jq '(.[] | if( [.url] | inside('"$urls"')) then . else "" end)' <"$ytfzf_video_json_file" | jq -s '[ .[]|select(.!="") ]' ;;
+ [Jj] | json) jq <"$ytfzf_video_json_file" ;;
+ [Ff] | format) printf "%s\n" "$ytdl_pref" ;;
+ [Rr] | raw)
+ jq -r '.[] | if( [.url] | inside('"$urls"')) then "\(.title)\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.url)" else "" end' <"$ytfzf_video_json_file" | { command_exists "column" && column -s "$tab_space" -t; }
+ ;;
+ *)
+ command_exists "get_requested_info_$request" && "get_requested_info_$request"
+ ;;
+ esac
+ done
+ end_of_set_args
+ return 0
+}
+
+handle_info() {
+ display_text_wrapper "$(get_requested_info "$ytfzf_selected_urls")"
+
+ [ "$info_wait" -eq 1 ] && info_wait_prompt_wrapper
+
case "$info_wait_action" in
- # simulates old behavior of when alt-l or alt-i is pressed and -l is enabled
- q) [ "$is_loop" -eq 1 ] && return 3 || return 2 ;;
- Q) return 2 ;;
- [MmCc]) return 3 ;;
- '') return 0 ;;
- *) if command_exists "custom_info_wait_action_$info_wait_action"; then custom_info_wait_action_"$info_wait_action"; else print_error "info_wait_action is set to $info_wait_action but custom_info_wait_action_$info_wait_action does not exist\n"; fi ;;
+ # simulates old behavior of when alt-l or alt-i is pressed and -l is enabled
+ q) [ "$is_loop" -eq 1 ] && return 3 || return 2 ;;
+ Q) return 2 ;;
+ [MmCc]) return 3 ;;
+ '') return 0 ;;
+ *) if command_exists "custom_info_wait_action_$info_wait_action"; then custom_info_wait_action_"$info_wait_action"; else print_error "info_wait_action is set to $info_wait_action but custom_info_wait_action_$info_wait_action does not exist\n"; fi ;;
esac
return 0
}
-submenu_handler () {
+submenu_handler() {
# eat stdin and close it
- cat > /dev/null
- old_interface="$interface"
+ cat >/dev/null
+ old_interface="$interface"
+ old_thumbnail_viewer="$thumbnail_viewer"
[ "$keep_vars" -eq 0 ] && set_vars 0
search="$(get_key_value "$_submenu_actions" "search")"
__scrape="$(get_key_value "$_submenu_actions" "type")"
submenu_opts="$old_submenu_scraping_opts $old_submenu_opts -c${__scrape}"
# this needs to be here as well as close_url_handler because it will not happen inside this function if it's not here
url_handler="$old_url_handler"
- interface="$old_interface"
- unset old_interface
+ thumbnail_viewer="$old_thumbnail_viewer"
+ interface="$old_interface"
+ unset old_interface
(
- # shellcheck disable=2030
- export __is_submenu=1
-
- # shellcheck disable=2030
- cache_dir="${session_cache_dir}"
-
- if [ -f "$YTFZF_CONFIG_DIR/submenu-conf.sh" ]; then
- # shellcheck disable=1091
- . "$YTFZF_CONFIG_DIR/submenu-conf.sh"
- elif [ -f "$YTFZF_CONFIG_FILE" ]; then
- # shellcheck disable=1091
- # shellcheck disable=1090
- . "$YTFZF_CONFIG_FILE"
- fi
-
- prepare_for_set_args
- # shellcheck disable=2086
- set -- $submenu_opts "$search"
- end_of_set_args
-
- on_opt_parse_s () {
- print_warning "-s is not supported in submenus\n"
- }
+ # shellcheck disable=2030
+ export __is_submenu=1
+
+ # shellcheck disable=2030
+ cache_dir="${session_cache_dir}"
+
+ if [ -f "$YTFZF_CONFIG_DIR/submenu-conf.sh" ]; then
+ # shellcheck disable=1091
+ . "$YTFZF_CONFIG_DIR/submenu-conf.sh"
+ elif [ -f "$YTFZF_CONFIG_FILE" ]; then
+ # shellcheck disable=1091
+ # shellcheck disable=1090
+ . "$YTFZF_CONFIG_FILE"
+ fi
- _getopts "$@"
+ prepare_for_set_args
+ # shellcheck disable=2086
+ set -- $submenu_opts "$search"
+ end_of_set_args
- source_scrapers
+ on_opt_parse_s() {
+ print_warning "-s is not supported in submenus\n"
+ }
+
+ _getopts "$@"
+
+ source_scrapers
- search_again=0
+ search_again=0
- shift $((OPTIND-1))
+ shift $((OPTIND - 1))
- init_and_make_search "$*" "fn-args"
- if [ "$enable_back_button" -eq 1 ]; then
- data="$(cat "$ytfzf_video_json_file")"
- echo '[{"ID": "BACK-BUTTON", "title": "[BACK]", "url": "back", "action": "back"}]' "$data" > "$ytfzf_video_json_file"
- fi
+ init_and_make_search "$*" "fn-args"
+ if [ "$enable_back_button" -eq 1 ]; then
+ data="$(cat "$ytfzf_video_json_file")"
+ echo '[{"ID": "BACK-BUTTON", "title": "[BACK]", "url": "back", "action": "back"}]' "$data" >"$ytfzf_video_json_file"
+ fi
- something_was_scraped || exit 4
+ something_was_scraped || exit 4
- main
+ main
)
submenu_opts="$old_submenu_opts"
submenu_scraping_opts="$old_submenu_scraping_opts"
}
-close_url_handler_submenu_handler () {
- url_handler="$old_url_handler"
- submenu_opts="$old_submenu_opts"
- submenu_scraping_opts="$old_submenu_scraping_opts"
+close_url_handler_submenu_handler() {
+ url_handler="$old_url_handler"
+ submenu_opts="$old_submenu_opts"
+ submenu_scraping_opts="$old_submenu_scraping_opts"
}
-close_url_handler () {
- fn_name="$(printf "%s" "$1" | tr '-' '_')"
- command_exists "close_url_handler_$fn_name" && close_url_handler_"$fn_name"
- print_debug "[URL HANDLER]: Closing url handler: ${c_blue}${1}${c_reset} with function: ${c_bold}close_url_handler_${fn_name}${c_reset}\n"
- do_an_event_function "after_close_url_handler" "$1"
+close_url_handler() {
+ fn_name="$(printf "%s" "$1" | tr '-' '_')"
+ command_exists "close_url_handler_$fn_name" && close_url_handler_"$fn_name"
+ print_debug "[URL HANDLER]: Closing url handler: ${c_blue}${1}${c_reset} with function: ${c_bold}close_url_handler_${fn_name}${c_reset}\n"
+ do_an_event_function "after_close_url_handler" "$1"
}
-open_url_handler () {
+open_url_handler() {
# isaudio, isdownload, ytdl_pref
- urls="$(tr '\n' ' ' < "$1")"
+ urls="$(tr '\n' ' ' <"$1")"
prepare_for_set_args ' '
# shellcheck disable=SC2086
set -- $urls
[ -z "$*" ] && print_info "No urls selected\n" && return 0
- end_of_set_args
+ end_of_set_args
- do_an_event_function "on_open_url_handler" "$@"
+ do_an_event_function "on_open_url_handler" "$@"
- print_debug "[URL HANDLER]: Opening links: ${c_bold}${urls}${c_reset} with ${c_blue}${url_handler}${c_reset}\n"
+ print_debug "[URL HANDLER]: Opening links: ${c_bold}${urls}${c_reset} with ${c_blue}${url_handler}${c_reset}\n"
# if we provide video_pref etc as arguments, we wouldn't be able to add more as it would break every url handler function
# shellcheck disable=2031
@@ -3026,55 +3094,53 @@ open_url_handler () {
#}}}
# Format selection {{{
-get_video_format_simple () {
- # select format if flag given
- formats=$(${ytdl_path} -F "$1" | grep -v "storyboard")
+get_video_format_simple() {
+ # select format if flag given
+ formats=$(${ytdl_path} -F "$1" | grep -v "storyboard")
# shellcheck disable=2059
- quality="$(printf "$formats" | grep -v "audio only" | sed -n '/^[[:digit:]]/s/.*[[:digit:]]\+x\([[:digit:]]\+\).*/\1p/p; 1i\Audio' | sort -n | uniq | quick_menu_wrapper "Video Quality" | sed "s/p//g")"
- if [ "$quality" = "Audio" ]; then
- is_audio_only=1
- elif expr "$formats" ":" ".*audio only" > /dev/null 2>&1; then
- video_format_no=$(printf "%s" "$formats" | grep -F "x$quality" | sed -n 1p)
- video_format_no="${video_format_no%% *}"
- ytdl_pref="${video_format_no}+bestaudio/bestaudio"
- else
- ytdl_pref="best[height=$quality]/best[height<=?$quality]/bestaudio"
- fi
- unset max_quality quality
-}
-
-get_video_format(){
- # the sed gets rid of random information
- _audio_choices=$(case "$YTDL_EXEC_NAME" in
- youtube-dl) ${ytdl_path} -F "$1" ;;
- *) ${ytdl_path} -q -F "$1" --format-sort "$format_selection_sort"
- esac | grep 'audio only')
- [ "$_audio_choices" ] && audio_pref="$(echo "$_audio_choices" | quick_menu_wrapper "Audio format: " | awk '{print $1}')"
- if [ "$is_audio_only" -eq 0 ]; then
- video_pref=$(case "$YTDL_EXEC_NAME" in
- youtube-dl) ${ytdl_path} -F "$1" | sed 1,3d;;
- *) ${ytdl_path} -q -F "$1" --format-sort "$format_selection_sort" : ;;
- esac | sed 's/\\033\[[[:digit:]]*m//g' | grep -v 'audio only' | quick_menu_wrapper "Video Format: "| awk '{print $1}')
- fi
- ytdl_pref="${video_pref}+${audio_pref}/${video_pref}/${audio_pref}"
-}
-
-open_format_selection_if_requested (){
- if [ "$show_formats" -eq 1 ]; then
- prepare_for_set_args
- #read from $ytfzf_selected_urls
- set -- $(tr '\n' ' ' < "$1")
- end_of_set_args
-
- case "$format_selection_screen" in
- normal)
- print_debug "[INTERFACE]: [FORMAT SELECTION]: open format screen: ${c_blue}normal${c_reset}\n"
- get_video_format "$1" ;;
- *)
- print_debug "[INTERFACE]: [FORMAT SELECTION]: open format screen: ${c_blue}${format_selection_screen}${c_reset}\n"
- get_video_format_$format_selection_screen "$@" ;;
- esac
- fi
+ quality="$(printf "$formats" | grep -v "audio only" | sed -n '/^[[:digit:]]/s/.*[[:digit:]]\+x\([[:digit:]]\+\).*/\1p/p; 1i\Audio' | sort -n | uniq | quick_menu_wrapper "Video Quality" | sed "s/p//g")"
+ if [ "$quality" = "Audio" ]; then
+ is_audio_only=1
+ elif expr "$formats" ":" ".*audio only" >/dev/null 2>&1; then
+ video_format_no=$(printf "%s" "$formats" | grep -F "x$quality" | sed -n 1p)
+ video_format_no="${video_format_no%% *}"
+ ytdl_pref="${video_format_no}+bestaudio/bestaudio"
+ else
+ ytdl_pref="best[height=$quality]/best[height<=?$quality]/bestaudio"
+ fi
+ unset max_quality quality
+}
+
+get_video_format() {
+ case "$YTDL_EXEC_NAME" in
+ (youtube-dl) _format_options=$("${ytdl_path}" -F "$1" | sed 1,3d) ;;
+ (*) _format_options=$("${ytdl_path}" -q -F "$1" --format-sort "$format_selection_sort" | sed 1,3d) ;;
+ esac
+ _audio_choices="$(echo "$_format_options" | grep "audio only")"
+ [ "$_audio_choices" ] && audio_pref="$(echo "$_audio_choices" | quick_menu_wrapper "Audio format: " | awk '{print $1}')"
+ if [ "$is_audio_only" -eq 0 ]; then
+ video_pref=$(echo "$_format_options" | sed 's/\\033\[[[:digit:]]*m//g' | grep -v 'audio only' | quick_menu_wrapper "Video Format: " | awk '{print $1}')
+ fi
+ ytdl_pref="${video_pref}+${audio_pref}/${video_pref}/${audio_pref}"
+}
+
+open_format_selection_if_requested() {
+ [ "$show_formats" -eq 0 ] && return
+
+ prepare_for_set_args
+ #read from $ytfzf_selected_urls
+ set -- $(tr '\n' ' ' <"$1")
+ end_of_set_args
+
+ print_debug "[INTERFACE]: [FORMAT SELECTION]: open format screen: ${c_blue}${format_selection_screen}${c_reset}\n"
+ case "$format_selection_screen" in
+ normal)
+ get_video_format "$1"
+ ;;
+ *)
+ get_video_format_$format_selection_screen "$@"
+ ;;
+ esac
}
#}}}
@@ -3084,8 +3150,8 @@ open_format_selection_if_requested (){
# Internal actions are usually called from fzf with the -W option.
# The point of these actions is to do something that can only be done given a specific ytfzf process instance. Such as displaying thumbnails
-internal_action_help () {
- printf "%s\n" "Usage: ytfzf -W [help|preview_img]<EOT>[args...]
+internal_action_help() {
+ printf "%s\n" "Usage: ytfzf -W [help|preview_img]<EOT>[args...]
An action followed by \\003 (ascii: EOT) followed by args which are seperated by \\003.
Actions:
help:
@@ -3098,223 +3164,223 @@ preview_img <ytfzf-line-format>:
session_cache_dir set to the ytfzf process instance's cache dir"
}
-internal_action_next_page () {
- shift
-
- ! [ -p "$var_fifo" ] && die 1 "ytfzf var fifo server not running\n"
+internal_action_next_page() {
+ shift
- read -r line < "$*"
- url="${line##*"|"}"
+ read -r line <"$*"
+ url="${line##*"|"}"
- printf "%s" ytfzf_video_json_file > "$var_fifo"
- read -r ytfzf_video_json_file < "$var_fifo"
- printf "%s" invidious_instance > "$var_fifo"
- read -r invidious_instance < "$var_fifo"
- printf "%s" yt_video_link_domain > "$var_fifo"
- read -r yt_video_link_domain < "$var_fifo"
+ video_json_file="$ytfzf_video_json_file"
- video_json_file="$ytfzf_video_json_file"
-
- hovered_scraper="$(jq -r '.[]|select(.url=="'"$url"'").scraper' < "$ytfzf_video_json_file")"
+ hovered_scraper="$(jq -r '.[]|select(.url=="'"$url"'").scraper' <"$ytfzf_video_json_file")"
- if command_exists "scrape_next_page_$hovered_scraper"; then
- _search="$(cat "${session_cache_dir}/searches.list")"
- printf "%s" pages_to_scrape > "$var_fifo"
- read -r pages_to_scrape < "$var_fifo"
+ if command_exists "scrape_next_page_$hovered_scraper"; then
+ _search="$(cat "${session_cache_dir}/searches.list")"
- pages_start="$(cat "${session_temp_dir}/${hovered_scraper}-current-page")"
- pages_start="${pages_start#[\"\']}"
- pages_start="${pages_start%[\"\']}"
+ pages_start="$(cat "${session_temp_dir}/${hovered_scraper}-current-page")"
+ pages_start="${pages_start#[\"\']}"
+ pages_start="${pages_start%[\"\']}"
- scrape_next_page_"$hovered_scraper"
- fi
- jq -c -r 'select(.!=[])|.[]' < "$ytfzf_video_json_file" |
- sort_video_data_fn |
- jq -r '"\(.title)'"$gap_space"'\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.url)"'
+ scrape_next_page_"$hovered_scraper"
+ fi
+ jq -c -r 'select(.!=[])|.[]' <"$ytfzf_video_json_file" |
+ sort_video_data_fn |
+ jq -r '"\(.title)'"$gap_space"'\t|\(.channel)\t|\(.duration)\t|\(.views)\t|\(.date)\t|\(.url)"'
}
-internal_action_preview_img () {
- shift
-
- ! [ -p "$var_fifo" ] && die 1 "ytfzf var fifo server not running\n"
-
- thumb_dir="${session_cache_dir}/thumbnails"
+internal_action_preview_img() {
+ shift
- printf "%s" YTFZF_PID > "$var_fifo"
- read -r YTFZF_PID < "$var_fifo"
- printf "%s" fzf_preview_side > "$var_fifo"
- read -r fzf_preview_side < "$var_fifo"
- printf "%s" scrape > "$var_fifo"
- read -r scrape < "$var_fifo"
- printf "%s" thumbnail_viewer > "$var_fifo"
- read -r thumbnail_viewer < "$var_fifo"
- printf "%s" ytfzf_video_json_file > "$var_fifo"
- read -r ytfzf_video_json_file < "$var_fifo"
+ thumb_dir="${session_cache_dir}/thumbnails"
- video_json_file="$ytfzf_video_json_file"
+ video_json_file="$ytfzf_video_json_file"
- line_file="$*"
- read -r line < "$line_file"
- line="${line#\'}" line="${line%\'}"
- source_scrapers
- preview_img "$thumbnail_viewer" "$line" "$ytfzf_video_json_file"
+ line_file="$*"
+ read -r line <"$line_file"
+ line="${line#\'}" line="${line%\'}"
+ source_scrapers
+ preview_img "$thumbnail_viewer" "$line" "$ytfzf_video_json_file"
}
# }}}
# Options {{{
-parse_opt () {
+parse_opt() {
opt=$1
optarg=$2
# for some reason optarg may equal opt intentionally,
# this checks the unmodified optarg, which will only be equal if there is no = sign
[ "$opt" = "$OPTARG" ] && optarg=""
- print_debug "[OPTIONS]: Parsing opt: $opt=$optarg\n"
+ print_debug "[OPTIONS]: Parsing opt: $opt=$optarg\n"
# shellcheck disable=SC2031
command_exists "on_opt_parse" && { on_opt_parse "$opt" "$optarg" "$OPT" "$OPTARG" || return 0; }
fn_name="on_opt_parse_$(printf "%s" "$opt" | tr '-' '_')"
# shellcheck disable=SC2031
command_exists "$fn_name" && { $fn_name "$optarg" "$OPT" "$OPTARG" || return 0; }
case $opt in
- h|help) usage; exit 0 ;;
- D|external-menu) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && interface='ext' ;;
- m|audio-only) is_audio_only=${optarg:-1};;
- d|download) url_handler=downloader ;;
- f|formats) show_formats=${optarg:-1} ;;
- S|select) interface="scripting" && is_specific_select="1" && scripting_video_count="$optarg" ;;
- a|auto-select) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && interface="scripting" && is_auto_select=${optarg:-1} ;;
- A|select-all) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && interface="scripting" && is_auto_select=${optarg:-1} && scripting_video_count='$' ;;
- r|random-select) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && interface="scripting" && is_random_select=${optarg:-1} ;;
- n|link-count) scripting_video_count=$optarg;;
- l|loop) is_loop=${optarg:-1} ;;
- s|search-again) search_again=${optarg:-1} ;;
- t|show-thumbnails) show_thumbnails=${optarg:-1} ;;
- version) printf 'ytfzf: %s \n' "$YTFZF_VERSION"; exit 0;;
- version-all) printf -- '---\n%s: %s\n' "ytfzf" "$YTFZF_VERSION" "jq" "$(jq --version)" "curl" "$(curl --version)"; exit 0 ;;
- L) info_to_print="$info_to_print,L" ;;
- pages) pages_to_scrape="$optarg" ;;
- pages-start) pages_start="$optarg" ;;
- thumbnail-log) thumbnail_debug_log="${optarg:-/dev/stderr}" ;;
- odysee-video-count) odysee_video_search_count="$optarg" ;;
- ii|inv-instance) invidious_instance="$optarg" ;;
- rii|refresh-inv-instances) refresh_inv_instances ;;
- i|interface) load_interface "$optarg" || die 2 "$optarg is not an interface\n" ;;
- c|scrape) scrape=$optarg ;;
- scrape+) scrape="$scrape,$optarg" ;;
- scrape-) scrape="$(printf '%s' "$scrape" | sed 's/'"$optarg"'//; s/,,/,/g')" ;;
- I) info_to_print=$optarg ;;
- notify-playing) notify_playing="${optarg:-1}" ;;
- # long-opt exclusives
- sort)
- : "${optarg:=1}"
- if [ "$optarg" != 1 ] && [ "$optarg" != 0 ]; then
- is_sort="1"
- load_sort_name "$optarg" || die 2 "$optarg is not a sort-name\n"
- elif [ "$optarg" = "0" ]; then
- is_sort=0
- fi ;;
- sort-name)
- # shellcheck disable=SC2015
- load_sort_name "$optarg" && is_sort=1 || die 2 "$optarg is not a sort-name\n" ;;
- video-pref) video_pref=$optarg ;;
- ytdl-pref) ytdl_pref=$optarg ;;
- audio-pref) audio_pref=$optarg ;;
- detach) is_detach=${optarg:-1} ;;
- ytdl-opts) ytdl_opts="$optarg" ;;
- ytdl-path) ytdl_path="$optarg" ;;
- preview-side) fzf_preview_side="${optarg}"; [ -z "$fzf_preview_side" ] && die 2 "no preview side given\n" ;;
- T|thumb-viewer) load_thumbnail_viewer "$optarg" || [ -f "$thumbnail_viewer" ] || die 2 "$optarg is not a thumb-viewer\n" ;;
- force-youtube) yt_video_link_domain="https://www.youtube.com" ;;
- info-print-exit|info-exit) [ "${optarg:-1}" -eq 1 ] && info_wait_action=q ;;
- info-action) info_wait_action="$optarg" ;;
- info-wait) info_wait="${optarg:-1}" ;;
- sort-by) search_sort_by="$optarg" ;;
- upload-date) search_upload_date="$optarg" ;;
- video-duration) search_video_duration=$optarg ;;
- type) search_result_type=$optarg ;;
- features) search_result_features=$optarg ;;
- region) search_region=$optarg ;;
- channel-link)
- invidious_instance=$(get_random_invidious_instance)
- _get_real_channel_link "$optarg"; exit 0 ;;
- available-inv-instances) get_invidious_instances; exit 0 ;;
- disable-submenus) enable_submenus="${optarg:-0}" ;;
- disable-actions) enable_actions="$((${optarg:-1}^1))" ;;
- thumbnail-quality) thumbnail_quality="$optarg" ;;
- u|url-handler) load_url_handler "$optarg";;
- keep-cache) keep_cache="${optarg:-1}" ;;
- submenu-opts|submenu-scraping-opts) submenu_opts="${optarg}" ;;
- keep-vars) keep_vars="${optarg:-1}" ;;
- nsfw) nsfw="${optarg:-true}" ;;
- max-threads|single-threaded) max_thread_count=${optarg:-1} ;;
- # flip the bit
- disable-back) enable_back_button=${optarg:-0} ;;
- skip-thumb-download) skip_thumb_download=${optarg:-1} ;;
- multi-search) multi_search=${optarg:-1} ;;
- search-source) search_source="${optarg:-args}" ;;
- format-selection) format_selection_screen=${optarg:-normal};;
- format-sort) format_selection_sort="$optarg" ;;
- e|ext) load_extension "$optarg" ;;
- url-handler-opts) url_handler_opts="$optarg" ;;
- list-addons)
- for path in "$YTFZF_THUMBNAIL_VIEWERS_DIR" "$YTFZF_SORT_NAMES_DIR"\
- "$YTFZF_CUSTOM_INTERFACES_DIR" "$YTFZF_URL_HANDLERS_DIR" "$YTFZF_EXTENSIONS_DIR"; do
- ! [ -d "$path" ] && continue
- printf "${c_bold}%s:${c_reset}\n" "user addon, ${path##*/}"
- ls "$path"
- done
-
- echo ----------------
-
- [ ! -d "$YTFZF_SYSTEM_ADDON_DIR" ] && exit
-
- set +f
- for path in "$YTFZF_SYSTEM_ADDON_DIR"/*; do
- printf "${c_bold}%s:${c_reset}\n" "system addon, ${path##*/}"
- ls "$path"
- done
- exit
- ;;
- async-thumbnails) async_thumbnails="${optarg:-1}" ;;
- fancy-subs)
- fancy_subs=${optarg:-1}
- [ "$fancy_subs" -eq 1 ] && is_sort=0 ;;
- W)
- prepare_for_set_args "$EOT"
- set -- $optarg
- end_of_set_args
- action="$1"
- var_fifo="$session_cache_dir/var-fifo"
- command_exists "internal_action_$1" && internal_action_"$1" "$@"
- exit 0 ;;
- *)
- # shellcheck disable=SC2031
- [ "$OPT" = "$long_opt_char" ] && print_info "$0: illegal long option -- $opt\n";;
+ h | help)
+ usage
+ exit 0
+ ;;
+ D | external-menu) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && interface='ext' ;;
+ m | audio-only) is_audio_only=${optarg:-1} ;;
+ d | download) url_handler=downloader ;;
+ f | formats) show_formats=${optarg:-1} ;;
+ S | select) interface="scripting" && is_specific_select="1" && scripting_video_count="$optarg";;
+ a | auto-select) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && is_auto_select=${optarg:-1};;
+ A | select-all) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && is_auto_select=${optarg:-1} && scripting_video_count='$';;
+ r | random-select) [ -z "$optarg" ] || [ "$optarg" -eq 1 ] && is_random_select=${optarg:-1};;
+ n | link-count) scripting_video_count=$optarg ;;
+ l | loop) is_loop=${optarg:-1} ;;
+ s | search-again) search_again=${optarg:-1} ;;
+ t | show-thumbnails) show_thumbnails=${optarg:-1} ;;
+ version)
+ printf 'ytfzf: %s \n' "$YTFZF_VERSION"
+ exit 0
+ ;;
+ version-all)
+ printf -- '---\n%s: %s\n' "ytfzf" "$YTFZF_VERSION" "jq" "$(jq --version)" "curl" "$(curl --version)"
+ exit 0
+ ;;
+ L) info_to_print="$info_to_print,L" ;;
+ pages) pages_to_scrape="$optarg" ;;
+ pages-start) pages_start="$optarg" ;;
+ thumbnail-log) thumbnail_debug_log="${optarg:-/dev/stderr}" ;;
+ odysee-video-count) odysee_video_search_count="$optarg" ;;
+ ii | inv-instance) invidious_instance="$optarg" ;;
+ rii | refresh-inv-instances) refresh_inv_instances ;;
+ i | interface) load_interface "$optarg" || die 2 "$optarg is not an interface\n" ;;
+ c | scrape) scrape=$optarg ;;
+ scrape+) scrape="$scrape,$optarg" ;;
+ scrape-) scrape="$(printf '%s' "$scrape" | sed 's/'"$optarg"'//; s/,,/,/g')" ;;
+ I) info_to_print=$optarg ;;
+ notify-playing) notify_playing="${optarg:-1}" ;;
+ # long-opt exclusives
+ sort)
+ : "${optarg:=1}"
+ if [ "$optarg" != 1 ] && [ "$optarg" != 0 ]; then
+ is_sort="1"
+ load_sort_name "$optarg" || die 2 "$optarg is not a sort-name\n"
+ else
+ is_sort=${optarg}
+ fi
+ ;;
+ sort-name)
+ # shellcheck disable=SC2015
+ load_sort_name "$optarg" && is_sort=1 || die 2 "$optarg is not a sort-name\n"
+ ;;
+ video-pref) video_pref=$optarg ;;
+ ytdl-pref) ytdl_pref=$optarg ;;
+ audio-pref) audio_pref=$optarg ;;
+ detach) is_detach=${optarg:-1} ;;
+ ytdl-opts) ytdl_opts="$optarg" ;;
+ ytdl-path) ytdl_path="$optarg" ;;
+ preview-side)
+ fzf_preview_side="${optarg}"
+ [ -z "$fzf_preview_side" ] && die 2 "no preview side given\n"
+ ;;
+ T | thumb-viewer) load_thumbnail_viewer "$optarg" || [ -f "$thumbnail_viewer" ] || die 2 "$optarg is not a thumb-viewer\n" ;;
+ force-youtube) yt_video_link_domain="https://www.youtube.com" ;;
+ info-print-exit | info-exit) [ "${optarg:-1}" -eq 1 ] && info_wait_action=q ;;
+ info-action) info_wait_action="$optarg" ;;
+ info-wait) info_wait="${optarg:-1}" ;;
+ sort-by) search_sort_by="$optarg" ;;
+ upload-date) search_upload_date="$optarg" ;;
+ video-duration) search_video_duration=$optarg ;;
+ type) search_result_type=$optarg ;;
+ features) search_result_features=$optarg ;;
+ region) search_region=$optarg ;;
+ channel-link)
+ invidious_instance=$(get_random_invidious_instance)
+ _get_real_channel_link "$optarg"
+ exit 0
+ ;;
+ available-inv-instances)
+ get_invidious_instances
+ exit 0
+ ;;
+ disable-submenus) enable_submenus="${optarg:-0}" ;;
+ disable-actions) enable_actions="$((${optarg:-1} ^ 1))" ;;
+ thumbnail-quality) thumbnail_quality="$optarg" ;;
+ u | url-handler) load_url_handler "$optarg" ;;
+ keep-cache) keep_cache="${optarg:-1}" ;;
+ submenu-opts | submenu-scraping-opts) submenu_opts="${optarg}" ;;
+ keep-vars) keep_vars="${optarg:-1}" ;;
+ nsfw) nsfw="${optarg:-true}" ;;
+ max-threads | single-threaded) max_thread_count=${optarg:-1} ;;
+ # flip the bit
+ disable-back) enable_back_button=${optarg:-0} ;;
+ skip-thumb-download) skip_thumb_download=${optarg:-1} ;;
+ multi-search) multi_search=${optarg:-1} ;;
+ search-source) search_source="${optarg:-args}" ;;
+ format-selection) format_selection_screen=${optarg:-normal} ;;
+ format-sort) format_selection_sort="$optarg" ;;
+ e | ext) load_extension "$optarg" ;;
+ url-handler-opts) url_handler_opts="$optarg" ;;
+ list-addons)
+ for path in "$YTFZF_THUMBNAIL_VIEWERS_DIR" "$YTFZF_SORT_NAMES_DIR" \
+ "$YTFZF_CUSTOM_INTERFACES_DIR" "$YTFZF_URL_HANDLERS_DIR" "$YTFZF_EXTENSIONS_DIR"; do
+ ! [ -d "$path" ] && continue
+ printf "${c_bold}%s:${c_reset}\n" "user addon, ${path##*/}"
+ ls "$path"
+ done
+
+ echo ----------------
+
+ [ ! -d "$YTFZF_SYSTEM_ADDON_DIR" ] && exit
+
+ set +f
+ for path in "$YTFZF_SYSTEM_ADDON_DIR"/*; do
+ printf "${c_bold}%s:${c_reset}\n" "system addon, ${path##*/}"
+ ls "$path"
+ done
+ exit
+ ;;
+ async-thumbnails) async_thumbnails="${optarg:-1}" ;;
+ fancy-subs)
+ fancy_subs=${optarg:-1}
+ [ "$fancy_subs" -eq 1 ] && is_sort=0
+ ;;
+ W)
+ prepare_for_set_args "$EOT"
+ set -- $optarg
+ end_of_set_args
+ action="$1"
+ var_fifo="$session_cache_dir/var-fifo"
+ command_exists "internal_action_$1" && internal_action_"$1" "$@"
+ exit 0
+ ;;
+ *)
+ # shellcheck disable=SC2031
+ [ "$OPT" = "$long_opt_char" ] && print_info "$0: illegal long option -- $opt\n"
+ ;;
esac
}
-_getopts () {
- case "$long_opt_char" in
- [a-uw-zA-UW-Z0-9]) die 2 "long_opt_char must be v or non alphanumeric\n" ;;
- #? = 1 char, * = 1+ chars; ?* = 2+ chars
- ??*) die 2 "long_opt_char must be 1 char\n" ;;
- esac
+_getopts() {
+ case "$long_opt_char" in
+ [a-uw-zA-UW-Z0-9]) die 2 "long_opt_char must be v or non alphanumeric\n" ;;
+ #? = 1 char, * = 1+ chars; ?* = 2+ chars
+ ??*) die 2 "long_opt_char must be 1 char\n" ;;
+ esac
- OPTIND=0
+ OPTIND=0
- while getopts "${optstring:=ac:de:fhi:lmn:qrstu:xADHI:LS:T:W:${long_opt_char}:}" OPT; do
- case $OPT in
- "$long_opt_char")
- parse_opt "${OPTARG%%=*}" "${OPTARG#*=}" ;;
- *)
- parse_opt "${OPT}" "${OPTARG}" ;;
- esac
- done
+ while getopts "${optstring:=ac:de:fhi:lmn:qrstu:xADHI:LS:T:W:${long_opt_char}:}" OPT; do
+ case $OPT in
+ "$long_opt_char")
+ parse_opt "${OPTARG%%=*}" "${OPTARG#*=}"
+ ;;
+ *)
+ parse_opt "${OPT}" "${OPTARG}"
+ ;;
+ esac
+ done
}
_getopts "$@"
# shellcheck disable=SC2031
-shift $((OPTIND-1))
+shift $((OPTIND - 1))
#}}}
do_an_event_function "on_post_set_vars"
@@ -3328,31 +3394,22 @@ unset IFS
#}}}
# files {{{
-init_files (){
- YTFZF_PID=$$
-
+init_files() {
#$1 will be a search
SEARCH_PREFIX=$(printf "%s" "$1" | tr '/' '_' | tr -d "\"'")
# shellcheck disable=SC2031
if [ "$__is_submenu" -eq 1 ]; then
- SEARCH_PREFIX=$(jq -r --arg url "$1" '.[]|select(.url==$url).title' < "${cache_dir}/videos_json")
+ SEARCH_PREFIX=$(jq -r --arg url "$1" '.[]|select(.url==$url).title' <"${cache_dir}/videos_json")
fi
# if no search is provided, use a fallback value of SCRAPE-$scrape
SEARCH_PREFIX="${SEARCH_PREFIX:-SCRAPE-$scrape}"
[ "${#SEARCH_PREFIX}" -gt 200 ] && SEARCH_PREFIX="SCRAPE-$scrape"
- # shellcheck disable=SC2031
- if [ "$__is_submenu" -eq 1 ]; then
- #if we are in a submenu, cache_dir will be the previous session_cache_dir
- session_cache_dir="${cache_dir}/${SEARCH_PREFIX}-${YTFZF_PID}"
- elif [ "$keep_cache" -eq 0 ]; then
- #by default we should store in ${TMPDIR:-/tmp} to reduce io usage
- session_cache_dir="${YTFZF_TEMP_DIR}/${SEARCH_PREFIX}-${YTFZF_PID}"
- else
- #if we want to keep the cache, use real cache dir
- session_cache_dir="${cache_dir}/${SEARCH_PREFIX}-${YTFZF_PID}"
- fi
+ #if we are in a submenu, cache_dir will be the previous session_cache_dir
+
+ [ "$__is_submenu" -eq 1 ] && _session_cache_dir_prefix="${cache_dir}" || _session_cache_dir_prefix="${YTFZF_TEMP_DIR}"
+ session_cache_dir="${_session_cache_dir_prefix}/${SEARCH_PREFIX}-${YTFZF_PID}"
session_temp_dir="${session_cache_dir}/tmp"
@@ -3365,9 +3422,11 @@ init_files (){
keypress_file="${session_temp_dir}/menu_keypress"
- : > "$ytfzf_video_json_file" 2> "$ytfzf_selected_urls" 3> "$keypress_file"
+ : >"$ytfzf_video_json_file" 2>"$ytfzf_selected_urls" 3>"$keypress_file"
+
+ [ "$1" ] && printf "%s\n" "$1" >"${session_cache_dir}/searches.list"
- [ "$1" ] && printf "%s\n" "$1" > "${session_cache_dir}/searches.list"
+ unset _session_cache_dir_prefix
}
# }}}
@@ -3381,119 +3440,123 @@ init_files (){
# Actions happen after a video is selected, and after the keypresses are handled
# Actions are formatted in the following way
- # action-name data...
+# action-name data...
# for the scrape action, data must be formatted in the following way,
# For custom actions, data can be formatted in any way
- # scrape search=search-in-one-word type=scraper
+# scrape search=search-in-one-word type=scraper
# actions are attached to videos/items in the menu
-handle_actions () {
+handle_actions() {
unset _submenu_actions IFS
- [ "$enable_actions" -eq 0 ] && return 0
- actions=$(jq -r --arg urls "$(cat "$1")" '.[] | [.url, .action] as $data | if ( ($urls | split("\n" )) | index($data[0]) and $data[1] != null ) == true then $data[1] else "" end' < "$ytfzf_video_json_file" | sed '/^[[:space:]]*$/d')
+ [ "$enable_actions" -eq 0 ] && return 0
+ actions=$(jq -r --arg urls "$(cat "$1")" '.[] | [.url, .action] as $data | if ( ($urls | split("\n" )) | index($data[0]) and $data[1] != null ) == true then $data[1] else "" end' <"$ytfzf_video_json_file" | sed '/^[[:space:]]*$/d')
while read -r action; do
- print_debug "[ACTION]: handling action: $action\n"
+ print_debug "[ACTION]: handling action: $action\n"
# this wil only be empty after all urls with actions have happened
# shellcheck disable=SC2031
# shellcheck disable=SC2086
case "$action" in
- back*) [ $__is_submenu -eq 1 ] && exit ;;
- scrape*)
- [ $enable_submenus -eq 0 ] && continue
- old_url_handler="$url_handler"
- old_submenu_opts="$submenu_opts"
- old_submenu_scraping_opts="$submenu_scraping_opts"
- url_handler=submenu_handler
- _submenu_actions="${_submenu_actions}${new_line}${action}" ;;
- do-nothing*) return 1 ;;
- *)
- fn_name="handle_custom_action_$(printf "%s" "${action%% *}" | tr '-' '_')"
- if command_exists "$fn_name"; then
- $fn_name "${action#* }"
- elif command_exists "handle_custom_action"; then
- handle_custom_action "$action"
- fi || return $? ;;
+ back*) [ $__is_submenu -eq 1 ] && exit ;;
+ scrape*)
+ [ $enable_submenus -eq 0 ] && continue
+ old_url_handler="$url_handler"
+ old_submenu_opts="$submenu_opts"
+ old_submenu_scraping_opts="$submenu_scraping_opts"
+ url_handler=submenu_handler
+ _submenu_actions="${_submenu_actions}${new_line}${action}"
+ ;;
+ do-nothing*) return 1 ;;
+ *)
+ fn_name="handle_custom_action_$(printf "%s" "${action%% *}" | tr '-' '_')"
+ if command_exists "$fn_name"; then
+ $fn_name "${action#* }"
+ elif command_exists "handle_custom_action"; then
+ handle_custom_action "$action"
+ fi || return $?
+ ;;
esac
break # TODO: allow multiple actions or at least let the action decide whether or not it can handle multiple actions
done <<-EOF
- $actions
-EOF
+ $actions
+ EOF
}
#}}}
# scraping wrappers {{{
-set_scrape_count () {
- prepare_for_set_args ","
- # shellcheck disable=SC2086
- set -- $scrape
- end_of_set_args
- __total_scrape_count="$#"
+set_scrape_count() {
+ prepare_for_set_args ","
+ # shellcheck disable=SC2086
+ set -- $scrape
+ end_of_set_args
+ __total_scrape_count="$#"
}
-handle_scrape_error () {
+handle_scrape_error() {
_scr="$2"
case "$1" in
- 1) print_info "$_scr failed to load website\n" ;;
- 6) print_error "Website ($_scr) unresponsive (do you have internet?)\n" ;;
- 9) print_info "$_scr does not have a configuration file\n" ;;
- 22)
- case "$_scr" in
- youtube|Y|youtube-trending|T)
- print_error "There was an error scraping $_scr ($invidious_instance)\nTry changing invidious instances\n" ;;
- *) print_error "There was an error scraping $_scr\n" ;;
- esac ;;
- #:help search operator
- 100) print_info "---------\n" && return 100;;
- 126) print_info "$_scr does not have execute permissions\n" ;;
- 127) die 2 "invalid scraper: $_scr\n" ;;
- *) print_error "An error occured while scraping: $_scr (scraper returned error: $1)\n" ;;
+ 1) print_info "$_scr failed to load website\n" ;;
+ 6) print_error "Website ($_scr) unresponsive (do you have internet?)\n" ;;
+ 9) print_info "$_scr does not have a configuration file\n" ;;
+ 22)
+ case "$_scr" in
+ youtube | Y | youtube-trending | T)
+ print_error "There was an error scraping $_scr ($invidious_instance)\nTry changing invidious instances\n"
+ ;;
+ *) print_error "There was an error scraping $_scr\n" ;;
+ esac
+ ;;
+ #:help search operator
+ 100) print_info "---------\n" && return 100 ;;
+ 126) print_info "$_scr does not have execute permissions\n" ;;
+ 127) die 2 "invalid scraper: $_scr\n" ;;
+ *) print_error "An error occured while scraping: $_scr (scraper returned error: $1)\n" ;;
esac
}
-handle_scraping (){
- _search="$1"
- prepare_for_set_args ","
- # if there is only 1 scraper used, multi search is on, then multiple searches will be performed seperated by ,
- __scrape_count=0
- for curr_scrape in $scrape; do
- __scrape_count=$((__scrape_count+1))
- do_an_event_function "ext_on_search" "$_search" "$curr_scrape"
- command_exists "on_search_$_search" && "on_search_$_search" "$curr_scrape"
- if ! "scrape_$(printf '%s' "$curr_scrape" | sed 's/-/_/g')" "$_search" "$ytfzf_video_json_file"; then
- handle_scrape_error "$?" "$curr_scrape"
- fi
- done
- [ "$?" -eq 100 ] && exit 0
- end_of_set_args
+handle_scraping() {
+ _search="$1"
+ prepare_for_set_args ","
+ # if there is only 1 scraper used, multi search is on, then multiple searches will be performed seperated by ,
+ __scrape_count=0
+ for curr_scrape in $scrape; do
+ __scrape_count=$((__scrape_count + 1))
+ do_an_event_function "ext_on_search" "$_search" "$curr_scrape"
+ command_exists "on_search_$_search" && "on_search_$_search" "$curr_scrape"
+ if ! "scrape_$(printf '%s' "$curr_scrape" | sed 's/-/_/g')" "$_search" "$ytfzf_video_json_file"; then
+ handle_scrape_error "$?" "$curr_scrape"
+ fi
+ done
+ [ "$?" -eq 100 ] && exit 0
+ end_of_set_args
}
# check if nothing was scraped{{{
-something_was_scraped () {
- #this MUST be `! grep -q -v -e '\[\]` because it's possible that [] exists in the file IN ADDITION to a list of actual results, we want to see if those actual results exists.
- if ! [ -s "${ytfzf_video_json_file}" ] || ! grep -q -v -e '\[\]' "$ytfzf_video_json_file"; then
- notify 'Empty Response' && print_error "Nothing was scraped\n" && return 1 # MODIFIED
- fi
- return 0
+something_was_scraped() {
+ #this MUST be `! grep -q -v -e '\[\]` because it's possible that [] exists in the file IN ADDITION to a list of actual results, we want to see if those actual results exists.
+ if ! [ -s "${ytfzf_video_json_file}" ] || ! grep -q -v -e '\[\]' "$ytfzf_video_json_file"; then
+ notify 'Empty Response' && print_error "Nothing was scraped\n" && return 1 # MODIFIED
+ fi
+ return 0
}
#}}}
-is_asking_for_search_necessary () {
- prepare_for_set_args ","
- for _scr in $scrape; do
- [ "${scrape_search_exclude#*" $_scr "}" = "${scrape_search_exclude}" ] && return 0
- done
- end_of_set_args
- return 1
+is_asking_for_search_necessary() {
+ prepare_for_set_args ","
+ for _scr in $scrape; do
+ [ "${scrape_search_exclude#*" $_scr "}" = "${scrape_search_exclude}" ] && return 0
+ done
+ end_of_set_args
+ return 1
}
-init_search () {
+init_search() {
_search="$1"
_search_source="$2"
- print_debug "[SEARCH]: initializing search with search: $_search, and sources: $_search_source\n"
+ print_debug "[SEARCH]: initializing search with search: $_search, and sources: $_search_source\n"
# only ask for search if scrape isn't something like S or T
is_asking_for_search_necessary && { get_search_from_source "$_search_source" "$_search" || die 5 "No search query\n"; }
@@ -3502,17 +3565,17 @@ init_search () {
# shellcheck disable=SC2031
- do_an_event_function "on_init_search" "$_search"
+ do_an_event_function "on_init_search" "$_search"
}
-init_and_make_search () {
+init_and_make_search() {
_search=$1
_search_source=$2
init_search "$_search" "$_search_source"
make_search "$_search"
}
-make_search () {
+make_search() {
_search="$1"
handle_scraping "$_search"
do_an_event_function post_scrape
@@ -3523,48 +3586,49 @@ make_search () {
init_and_make_search "$initial_search" "$search_source"
until something_was_scraped; do
- case "$search_again" in
- 0) exit 4 ;;
- 1) init_and_make_search "" "$search_source" ;;
- esac
+ case "$search_again" in
+ 0) exit 4 ;;
+ 1) init_and_make_search "" "$search_source" ;;
+ esac
done
main() {
while :; do
+ # calls the interface only if we shouldn't auto select
+ auto_select "$ytfzf_video_json_file" "$ytfzf_selected_urls" || run_interface
- # calls the interface
- run_interface
- handle_keypress "$keypress_file" || case "$?" in 2) break ;; 3) continue ;; esac
- handle_actions "$ytfzf_selected_urls" || case "$?" in 2) break ;; 3) continue ;; esac
+ handle_keypress "$keypress_file" || case "$?" in
+ 2) break ;; 3) continue ;; esac
+
+ handle_actions "$ytfzf_selected_urls" || case "$?" in
+ 2) break ;; 3) continue ;; esac
# nothing below needs to happen if this is empty (causes bugs when this is not here)
[ ! -s "$ytfzf_selected_urls" ] && break
# shellcheck disable=SC2015
if [ "$info_to_print" ]; then
- data="$(get_requested_info "$ytfzf_selected_urls")"
- display_text_wrapper "$data"
- handle_info_wait_action
- case "$?" in 2) break ;; 3) continue ;; esac
+ handle_info
+ case "$?" in
+ 2) break ;; 3) continue ;; esac
fi
- open_format_selection_if_requested "$ytfzf_selected_urls"
+ open_format_selection_if_requested "$ytfzf_selected_urls"
open_url_handler "$ytfzf_selected_urls"
close_url_handler "$url_handler"
- [ "$is_loop" -eq 0 ] && break
+ [ "$is_loop" -eq 0 ] && break
done
-
- # doing this after the loop allows for -l and -s to coexist
- while [ "$search_again" -eq 1 ] ; do
- clean_up
- init_and_make_search "" "$search_source"
- main
- done
}
+
main
+# doing this after the loop allows for -l and -s to coexist
+while [ "$search_again" -eq 1 ]; do
+ clean_up
+ initial_search= init_and_make_search "" "$search_source"
+ main
+done
#}}}
# vim: foldmethod=marker:shiftwidth=4:tabstop=4
-#