summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLM-LCL <hello@exaltedelite.club>2023-08-31 01:01:57 +0200
committerLM-LCL <hello@exaltedelite.club>2023-09-01 16:14:09 +0200
commit9aa290b9667832c8ae41ed8bc36e57da7a74b9c2 (patch)
tree532a98d4561a85952543c609e4ff7c5c45601b5a
parentfe92c7ac827b18673e1fa89dacfca370dcfd313d (diff)
complete script revamp
-rw-r--r--README.md59
-rwxr-xr-xext/waldl162
-rwxr-xr-xext/ytfzf3634
-rwxr-xr-xlcl-edit293
-rwxr-xr-xlcl-find20
-rwxr-xr-xlcl-kill16
-rwxr-xr-xlcl-playlist57
-rwxr-xr-xlcl-start9
-rwxr-xr-xlcl-system38
-rwxr-xr-xlib/action/Clip23
-rwxr-xr-xlib/action/Clipboard12
-rwxr-xr-xlib/action/Download10
-rwxr-xr-xlib/action/Open6
-rwxr-xr-xlib/action/Play11
-rwxr-xr-xlib/action/Record7
-rwxr-xr-xlib/action/Torrent6
-rwxr-xr-xlib/action/aDownload13
-rwxr-xr-xlib/search/1337x67
-rwxr-xr-xlib/search/Odysee10
-rwxr-xr-xlib/search/PeerTube10
-rwxr-xr-xlib/search/Wallhaven8
-rwxr-xr-xlib/search/YouTube19
-rwxr-xr-xrmt-action125
-rwxr-xr-xrmt-history15
-rwxr-xr-xrmt-search55
-rwxr-xr-xutil/dpath5
-rwxr-xr-xutil/hpath11
-rwxr-xr-xutil/menu/deasy12
-rwxr-xr-xutil/menu/deasy-core10
-rwxr-xr-xutil/notify6
-rwxr-xr-xutil/verify/cbd-core25
-rwxr-xr-xutil/verify/cbd-https8
-rwxr-xr-xutil/verify/cbd-magnet8
33 files changed, 217 insertions, 4553 deletions
diff --git a/README.md b/README.md
index 7aded0e..9890ed7 100644
--- a/README.md
+++ b/README.md
@@ -2,17 +2,12 @@
Build your own digital library.
## Supported Features
-- Search videos on **YouTube**, **Odysee** & **PeerTube**.
-- Search BitTorrent supported files on **1337x**.
-- Search & preview wallpapers on **Wallhaven**.
+- Search videos on **YouTube**
----
-
-After acquiring a link from the clipboard, by search or the search history you may choose what to do with this link:
+After acquiring the link from the clipboard you may choose what to do with this link:
- You may choose to **play** or **record** the video/stream using **mpv**.
- You may choose to **clip** the remote video with **FFmpeg**.
- You may choose to **download** the video with **audio** or **video** format using **yt-dlp**.
-- You may choose to **torrent** the file using **qBittorrent**.
---
@@ -25,40 +20,29 @@ Once you've downloaded the media files to your offline digital library you can:
- **Strip**, **convert format**, **resize**, **trim transparency** & **apply border-radius** using **ImageMagick**.
Workflow specific automation of tasks:
-- Easily access a start menu for installed programs.
- Easily search & end processes running on your computer.
- Easily perform system actions, like restarting your computer.
## Dependencies
- dmenu
-- sxiv
+- xclip
- mpv
+- ytfzf
- ffmpeg
- imagemagick
- exiftool
-- xclip
-- jq
-- qbittorrent
#### Recommendation
-To create comfortable mpv settings. The command below creates a mpv.conf file that limits the quality of streamed videos to 1080p & always creates a 1440x810 mpv window. This might be good enough, but for the specific settings I use, it's available [here](https://git.exaltedelite.club/mpv-settings).
-
- mkdir -p ~/.config/mpv && echo -e "ytdl-format='bestvideo[height<=1080]+bestaudio/best[height<=1080]'\ngeometry='1440x810'" > ~/.config/mpv/mpv.conf
-
-All the scripts do require relative file paths to work, except for lcl-start & lcl-kill. Below is an example of how a keybind command might look like.
+To create comfortable mpv settings. The command below creates a mpv.conf file that limits the quality of streamed videos to 1080p & always creates a 1280x720 mpv window. This might be good enough, but can be configured to your liking in more detail.
- sh -c 'cd ~/Desktop/EZown && ./rmt-search'
+ mkdir -p ~/.config/mpv && echo -e "ytdl-format='bestvideo[height<=1080]+bestaudio/best[height<=1080]'\ngeometry='1280x720'" > ~/.config/mpv/mpv.conf
## Usage
- ./rmt-action [Clipboard Link]
- **Param Nº1:** Skip dmenu Nº1 (./rmt-action 'Play')
- **Param Nº2:** Skip clipboard link (./rmt-action 'Play' 'https://invidious.flokinet.to/watch?v=AhZqOYu5BOw')
- ./rmt-search [Acquire Link & Forward To ./rmt-action]
- - **Param Nº1:** Skip dmenu Nº1 (./rmt-search '1337x')
- - **Param Nº2:** Skip dmenu Nº2 (./rmt-search '1337x' 'Collateral 2004')
- - **Param Nº3:** Skip dmenu Nº4 (./rmt-search '1337x' 'Collateral 2004' 'Torrent')
-- ./rmt-history [Link History & Forward To ./rmt-action]
- - **Param Nº1:** Skip dmenu Nº1 (./rmt-history 'Record')
+ - **Param Nº1:** Skip dmenu Nº1 (./rmt-search 'Collateral 2004')
---
@@ -69,32 +53,12 @@ All the scripts do require relative file paths to work, except for lcl-start & l
---
-- ./lcl-start [Start Menu]
- ./lcl-kill [Kill Local Process]
- ./lcl-system [Perform System Action]
- - **Param Nº1:** Skip dmenu Nº1 (./lcl-system 'Shutdown')
-
-## Usage Flow
-By default, rmt-action uses links stored in the clipboard when used. But rmt-search & rmt-history can be used to discover links. When using rmt-search without a correctly chosen item from the dmenu list, will automatically search YouTube. Therefore you can skip param Nº1 if you're searching YouTube. The command ./rmt-search 'YouTube' 'BugsWriter' 'Play' could be shortened to ./rmt-search 'BugsWriter' 'Play'. But in case you would search Odysee for example, you would explicitly have to specify the platform (./rmt-search 'Odysee' 'BugsWriter' 'Play').
-
-- You could for example bind Shift+Ctrl+P to play copied video links automatically with the ./rmt-action 'Play' command.
-- You could for example bind Shift+Ctrl+B to search local files with the ./lcl-find command.
-- You could for example bind Shift+Ctrl+O to perform an action with the ./rmt-action command.
-- You could for example bind Super+R to search PeerTube videos with the ./rmt-search 'PeerTube' command.
-
-*This should help you get started & better understand the flow of the program.*
#### RSS
This script may act as another key to the puzzle of unfiltered interactions when used in combination with your RSS feed & its links.
-#### Sane Defaults
-- yt-dlp downloads in the .mp4 format & has a max quality of 1080p.
-- Notifications are shown upon errors & not upon appropriate behaviour.
-- Magnet links are opened with qBittorrent & may easily be modified in lib/action/Torrent.
-- You may easily change the default downloads directory & Invidious instance, in util/dpath & lib/search/YouTube.
-- You may modify the "find" command within the different scripts, it looks for files in ~/Desktop & ~/Downloads.
-- ext/ytfzf & ext/waldl are slightly modified, search for "MODIFIED" in these files to see the changes.
-
## Why
You may never use these services, but a lot of good content is hosted on them created by good people, as discovery & revenue is easier than supplying independent content with your own digital infrastructure. But this dependence on a third-party service platform limits interactions with the hosted files. For security reasons- fearing abuse, legal reasons- fearing to support piracy on their platforms or business incentives that may not be inline with your values or use of the platform. One thing generally appreciated is the ability to store digital media offline, you might be going away on a trip without reliable internet or fear the site may not share your enthusiasm for the files being uploaded to their services. But this is made difficult & when available, is most always provided in a janky format that may not be very robust or censorship resistant. Streaming in its essence is interesting when you consider how it works, you stream files to your computer, but you're never supposed to download them to file, fearing you'll not need their services anymore, not use their platform as much or concerns around piracy of copyrighted material. To battle this, these services apply digital rights management or DRM making it as difficult as possible for you to regain this control of ownership of the files you stream to view.
@@ -103,12 +67,3 @@ Without the proper knowledge of the great projects, run by good people that crea
In the past more people may have served content by their own distribution, but centralization makes things easy, & less technical people are attracted to create & distribute their works on platforms that are seemingly becoming more & more restrictive over time, with their increased size, nurtured dependence of their services & larger impact on peoples daily lives. These services & a lot of the major technology platforms are changing with the times of the world, moving their services into an ever more controlled & predictable environment for them over their users.
Building your own digital library is easier than ever before, the tools are there for you to use. It will minimize the dependence on these services, provide more flexibility how these files are used or shared, whether it is online or offline & regaining the control & subsequent power that was given up for the convenience these now very large services provide.
-
-## Credits
-- https://github.com/Bugswriter/notflix
-- https://github.com/pystardust/ytfzf
-- https://github.com/pystardust/waldl
-- https://odysee.com/@bugswriter:8/stream-any-movie-show-in-5-sec.-rip:9
-- https://odysee.com/@bugswriter:8/dmenu-is-killing-my-programs-and-it-s:8
-- https://odysee.com/@bugswriter:8/download-wallhaven-wallpapers-with-waldl:8
-
diff --git a/ext/waldl b/ext/waldl
deleted file mode 100755
index f1f9649..0000000
--- a/ext/waldl
+++ /dev/null
@@ -1,162 +0,0 @@
-#! /bin/bash
-
-source ./util/notify # MODIFIED
-
-# https://github.com/pystardust/waldl
-# script to find and download wallpapers from wallhaven
-version="0.0.1"
-
-# Usage:
-# waldl <query>
-# if query left empty then sh_menu will be used (dmenu by default)
-# after the thumbnails are cached, sxiv would open up with the thumbnails
-# Select the wallpapers using `m` on the image. ( marking the image in sxiv )
-# press `q` to quit sxiv, the marked images would start downloading
-
-####################
-## User variables ##
-####################
-
-# the dir where wallpapers are stored
-walldir="$2/Wallhaven" # MODIFIED
-# the dir used to cache thumbnails
-cachedir="$HOME/.cache/wallhaven"
-# sxiv options
-sxiv_otps=" -tfpob -z 200" # o is needed for selection # MODIFIED
-# number of pages to show in search results
-# each page contains 24 results
-max_pages=4
-# sorting : date_added, relevance, random, views, favorites, toplist
-sorting=relevance
-# quality : large original small
-quality=large
-# atleast : least res
-atleast=1920x1080
-
-# allow the user to customize the defaults
-[ -e "$HOME/.config/waldlrc" ] && . "$HOME/.config/waldlrc"
-
-# the menu command used when no query is provided
-sh_menu () {
- : | dmenu -p "search wallhaven:"
- # ROFI: comment the previous line and uncomment the next line for rofi
- # rofi -dmenu -l 0 -p "search wallpapers"
-}
-
-##########################
-## getting search query ##
-##########################
-
-[ -n "$1" ] && query="$1" || query=$( sh_menu )
-[ -z "$query" ] && exit 1
-query=$(printf '%s' "$query" | tr ' ' '+' )
-
-######################
-## start up commands #
-######################
-
-rm -rf "$cachedir"
-mkdir -p "$walldir" "$cachedir"
-
-# progress display command
-sh_info () {
- printf "%s\n" "$1" >&2
- #notify-send "wallhaven" "$1" # MODIFIED
- [ -n "$2" ] && exit "$2"
-}
-
-# dependency checking
-dep_ck () {
- for pr; do
- command -v $pr >/dev/null 2>&1 || sh_info "command $pr not found, install: $pr" 1
- done
-}
-dep_ck "sxiv" "curl" "jq"
-
-
-# clean up command that would be called when the program exits
-clean_up () {
- printf "%s\n" "cleaning up..." >&2
- rm -rf "$datafile" "$cachedir"
-}
-
-# data file to store the api information
-datafile="/tmp/wald.$$"
-
-# clean up if killed
-trap "exit" INT TERM
-trap "clean_up" EXIT
-
-##################
-## getting data ##
-##################
-
-# request the search results for each page
-get_results () {
- for page_no in $(seq $max_pages)
- do
- {
- json=$(curl -s -G "https://wallhaven.cc/api/v1/search" \
- -d "q=$1" \
- -d "page=$page_no" \
- -d "atleast=$atleast" \
- -d "sorting=$sorting"
- )
- printf "%s\n" "$json" >> "$datafile"
- } &
- sleep 0.001
- done
- wait
-}
-
-# search wallpapers
-sh_info "getting data..."
-get_results "$query"
-
-# check if data file is empty, if so then exit
-[ -s "$datafile" ] || sh_info "no images found" 1
-
-############################
-## downloading thumbnails ##
-############################
-
-# get a list of thumnails from the data
-thumbnails=$( jq -r '.data[]?|.thumbs.'"$quality" < "$datafile")
-
-[ -z "$thumbnails" ] && notify 'Empty Response' # MODIFIED
-
-# download the thumbnails
-sh_info "caching thumbnails..."
-for url in $thumbnails
-do
- printf "url = %s\n" "$url"
- printf "output = %s\n" "$cachedir/${url##*/}"
-done | curl -Z -K -
-#sh_info "downloaded thumbnails..."
-
-###########################
-## user selection (sxiv) ##
-###########################
-
-# extract the id's out of the thumbnail name
-image_ids="$(sxiv $sxiv_otps "$cachedir")"
-[ -z "$image_ids" ] && exit
-
-#########################
-## download wallpapers ##
-#########################
-
-# download the selected wall papers
-cd "$walldir"
-sh_info "downloading wallpapers..."
-for ids in $image_ids
-do
- ids="${ids##*/}"
- ids="${ids%.*}"
- url=$( jq -r '.data[]?|select( .id == "'$ids'" )|.path' < "$datafile" )
- printf "url = %s\n" "$url"
- printf -- "-O\n"
-done | curl -K -
-
-sh_info "wallpapers downloaded in:- '$walldir'"
-sxiv -f $(ls -c) # MODIFIED
diff --git a/ext/ytfzf b/ext/ytfzf
deleted file mode 100755
index ae9fc30..0000000
--- a/ext/ytfzf
+++ /dev/null
@@ -1,3634 +0,0 @@
-#!/usr/bin/env sh
-
-source ./util/notify # MODIFIED
-
-# state variables
-: "${__is_submenu:=0}" "${__is_fzf_preview:=0}"
-
-# versioning system:
-# major.minor.bugs
-YTFZF_VERSION="2.6.0"
-
-#ENVIRONMENT VARIABLES {{{
-: "${YTFZF_CONFIG_DIR:=${XDG_CONFIG_HOME:-$HOME/.config}/ytfzf}"
-: "${YTFZF_CONFIG_FILE:=$YTFZF_CONFIG_DIR/conf.sh}"
-: "${YTFZF_SUBSCRIPTIONS_FILE:=$YTFZF_CONFIG_DIR/subscriptions}"
-: "${YTFZF_THUMBNAIL_VIEWERS_DIR:=$YTFZF_CONFIG_DIR/thumbnail-viewers}"
-: "${YTFZF_SORT_NAMES_DIR:=$YTFZF_CONFIG_DIR/sort-names}"
-: "${YTFZF_CUSTOM_INTERFACES_DIR:=$YTFZF_CONFIG_DIR/interfaces}"
-: "${YTFZF_URL_HANDLERS_DIR:=$YTFZF_CONFIG_DIR/url-handlers}"
-: "${YTFZF_CUSTOM_THUMBNAILS_DIR:=$YTFZF_CONFIG_DIR/thumbnails}"
-: "${YTFZF_EXTENSIONS_DIR:=$YTFZF_CONFIG_DIR/extensions}"
-: "${YTFZF_CUSTOM_SCRAPERS_DIR:=$YTFZF_CONFIG_DIR/scrapers}"
-
-: "${YTFZF_SYSTEM_ADDON_DIR:=/usr/local/share/ytfzf/addons}"
-
-: "${YTFZF_TEMP_DIR:="${TMPDIR:-/tmp}"/ytfzf-$(id -u)}"
-
-: "${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=$$
-#}}}
-
-############################
-# DEBUGGING #
-############################
-
-# There are only a couple tooling methods that I know of for debugging (other than printing stuff everywhere)
-# set log_level to 3, and set YTFZF_LOGFILE=./some-file.log
-# set -x may also be used.
-
-############################
-# CODE STYLE #
-############################
-
-##################
-# VARIABLE NAMES #
-##################
-
-# 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
-
-# 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
-
-# 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.
-
-##################
-# FUNCTION NAMES #
-##################
-
-# 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
-
-# interface functions MUST start with interface_ in order to work properly
-# scraper functions MUST start with scrape_ in order to work properly
-
-############################
-# ERRORS #
-############################
-
-# 0: success
-# 1: general error
-# 2: invalid -opt or command argument, invalid argument for opt, configuration error
-# eg: ytfzf -c terminal (invalid scrape)
-# 3: missing dependency
-# 4: scraping error
-# 5: empty search
-# *: Likely a curl error
-
-############################
-# CODE #
-############################
-
-# colors {{{
-c_red="\033[1;31m"
-c_green="\033[1;32m"
-c_yellow="\033[1;33m"
-c_blue="\033[1;34m"
-c_magenta="\033[1;35m"
-c_cyan="\033[1;36m"
-c_reset="\033[0m"
-c_bold="\033[1m"
-#}}}
-
-: "${check_vars_exists:=${YTFZF_CHECK_VARS_EXISTS:-1}}"
-
-# __ytfzf__ extension {{{
-
-print_help___ytfzf__() {
- #the [2A will clear the \n---__ytfzf__--- garbage (in supported terminals)
- printf "\033[2A%s" \
- "Usage: ytfzf [OPTIONS...] <search-query>
- The search-query can also be read from stdin
- GENERAL OPTIONS:
- -h Show this help text
-
- --version Get the current version
-
- --version-all Get the current version of ytfzf,
- and required dependencies
-
- UTILITY OPTIONS:
- --channel-link=<link> Gets the uuid of a youtube channel from a link.
-
- PLAYING OPTIONS:
- -d Download the selected video(s)
-
- -m Only play audio
-
- -f Select a video format before playing
-
- --format-selection=<type>
- Type can either be normal, or simple
- --format-sort=<sort> The sort used in ytdl for -f.
-
- --video-pref=<pref> The ytdl video preference.
-
- --audio-pref=<pref> The ytdl audio preference.
-
- --ytdl-pref=<pref> The combined ytdl video and audio preference.
-
- -u <url handler> The program to use for handling urls
- (deafult: multimedia_player)
- -L Show the link of selected video(s)
-
- -I <info> Instead of playing the selected video(s),
- get information about them.
- Options can be separated with a comma,
- eg: L,R
- Options for info:
- L: print the link of the video
- VJ: print the json of the video
- J: print the json of all videos
- shown in the search
- R: print the data
- of the selected videos,
- as appears in the menu
- F: print the selected video format
- --info-wait When -I or -L is used,
- wait for user input before continuing
-
- --info-action=<action>
- The action to do when --info-wait is 1.
- action can be one of
- q: exit
- Q: exit (bypass -l)
- '': play video
-
- --detach Detach the url handler from the terminal
-
- --notify-playing Sends a notification when a video is selected.
-
- --url-handler-opts=<opts>
- Pass the given opts to the url handler.
-
- --ytdl-opts=<opts> Pass the opts to ytdl when downloading
-
- --ytdl-path=<path> The path to youtube-dl
-
- MENU OPTIONS:
- -l Reopen the menu when the video stops playing
-
- -t Show thumbnails
-
- -T <viewer> The program to use for displaying thumbnails.
- see ytfzf(1) for a list of viewers.
-
- --async-thumbnails Download thumbnails asynchronously.
-
- --skip-thumb-download Skips the process of downloading thumbnails
-
- --thumbnail-quality=<quality>
- Select quality of thumbnails,
- can be:
- maxres
- maxresdefault
- sddefault
- high (default)
- medium
- default
- start
- middle
- end
-
- -i <interface> The interface to use (default: text)
-
- -D Alias for -i ext
-
- -a Automatically select the first video
-
- -r Automatically select a random video
-
- -A Select all videos
-
- -S <sed address> Automatically selects a specific video
- based on a given sed address
-
- -n <video count> The amount of videos to select with -a and -r
-
- --preview-side=<side> The side to show the preview on in fzf:
- left
- right
- up
- down
- --fancy-subs Adds a divider between each subscription
- when scraping subscriptions
-
- --sort Sorts video results by a sort name,
- The default sort name is upload-date
- To change sort names use --sort=<name>
-
- --sort-name=<name> Load a different sorting algorithm for --sort
- To see usable sort-names, use --list-addons
-
- --disable-submenus Whether or not to disable submenus,
- which are menus for results like:
- playlists and channels
-
- --disable-back Disables the back button in submenus
-
- --disable-actions Disables actions such as submenus, and the back button.
-
- --keep-vars Options passed to ytfzf are kept in submenus.
-
- --submenu-opts=<opts> ytfzf options to pass to submenus.
-
- SEARCH OPTIONS:
- -s After closing fzf make another search
-
- -q Use a search query from search history
- see ytfzf(1) for more info.
-
- --search-source=<source>
- The place to get the search from
- see ytfzf(1) for more information
-
- --multi-search Allow multiple searches seperated by ,
-
- --pages The amount of pages to scrape
- does not work with some scrapers.
- --pages-start=<page> The page number to start on
-
- --odysee-video-count The amount of videos to scrape from odysee
-
- --nsfw Enable nsfw videos (odysee only)
-
- --sort-by=<sort> Searches for videos sorted by:
- relevance
- rating (youtube only)
- upload_date
- oldest_first (odysee only)
- view_count (youtube only)
-
- --upload-date=<time> Searches for videos that were uploaded:
- hour
- today
- week
- month
- year
-
- --video-duration=<time> Searches for vidos that are:
- short
- medium
- long
-
- --type=<type> Searches for uploads of type:
- video
- playlist
- channel
- all
-
- --features=<features> Searches for videos with features:
- hd
- subtitles
- creative_commons
- 3d
- live
- 4k
- 360
- location
- hdr
-
- --region=<country-code> The region to search.
-
- -c <scraper> The scraper to use,
- See ytfzf(1) for a list of builtin scrapers
- you can use multiple scrapers
- by separating each with a comma, eg:
- youtube,odysee
-
- --scrape+=<scraper> Use another scraper
-
- --scrape-=<scraper> Dont use a scraper.
-
- -H alias for -c H
-
- --ii=<instance> The invidious instance to use for scraping.
-
- --force-youtube Converts invidious links to youtube links
- before playing (enabled by default)
-
- ADDON OPTIONS:
- -e <extention> Load an extention
-
- --list-addons Show available addons
-
- MISC OPTIONS:
- -x Clear search and watch history
-
- --history-clear=<type> Clear either search, or watch history.
-
- --max-threads=<count> The amount of threads that should be spawned
- at any given time.
-
- --single-threaded Same as --max-threads=1
-
- --rii Refreseh invidious instance cache
-
- --available-inv-instances
- Shows the invidious instances
- that ytfzf may pick from
-
- --keep-cache Do not delete the cache files.
-
- --thumbnail-log Write thumbnail errors to this file.
-
- See ytfzf(1) and ytfzf(5) for more information.
-"
-}
-
-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
- else
- 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_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"
- while read -r line; do
- [ "$line" ] && kill "$line" 2>/dev/null
- done <"$jobs_file"
- rm -f "$jobs_file"
- #}}}
- 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
-
- #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}"
-
- : "${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}"}"
-
- [ ! -d "$cache_dir" ] && mkdir -p "$cache_dir"
-
- # 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
-
- : "${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__"
-
- scrape_search_exclude="${scrape_search_exclude}${custom_scrape_search_exclude} "
-
- source_scrapers
-
- 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
- fi
-}
-
-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)
-}
-
-# }}}
-
-# __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
- }
-}
-# }}}
-
-# __ytfzf_history__ extension {{{
-on_load_fake_extension___ytfzf_history__() {
-
- ! 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 "$@"
- }
-}
-
-on_open_url_handler___ytfzf_history__() {
- add_to_hist "$ytfzf_video_json_file" <"$ytfzf_selected_urls"
-}
-
-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="$(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() {
- 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"
-}
-scrape_H() { scrape_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"
-}
-# }}}
-
-# __ytfzf_search_history__ extension {{{
-
-on_load_fake_extension___ytfzf_search_history__() {
-
- ! 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 "$@"
- }
-}
-
-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__() {
- [ "$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)"
-}
-
-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"
-}
-
-# }}}
-
-# Utility functions {{{
-
-############################
-# UTILITY FUNCTIONS #
-############################
-
-# 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
-
-## Jq util{{{
-jq_pad_left='
-def pad_left(n; num):
- num | tostring |
- if (n > length) then ((n - length) * "0") + (.) else . end
-'
-# }}}
-
-# 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"
-}
-
-get_invidious_instances() {
- cat "$instances_file"
-}
-
-get_random_invidious_instance() {
- shuf "$instances_file" | head -n 1
-}
-# }}}
-
-# General Scraping{{{
-_get_request() {
- _base_url=$1
- shift 1
- # Get search query from youtube
- curl -f "$_base_url" -s -L \
- "$@" \
- -H "User-Agent: $useragent" \
- -H 'Accept-Language: en-US,en;q=0.9' \
- --compressed
-}
-
-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
- }
- [ "$async_thumbnails" -eq 0 ] && print_info 'Fetching thumbnails...\n'
- curl_config_file="${session_temp_dir}/curl_config"
- [ -z "$*" ] && return 0
- : >"$curl_config_file"
- for line in "$@"; do
- printf "url=\"%s\"\noutput=\"$thumb_dir/%s.jpg\"\n" "${line%%';'*}" "${line##*';'}"
- done >>"$curl_config_file"
- curl -fLZ -K "$curl_config_file"
- [ $? -eq 2 ] && curl -fL -K "$curl_config_file"
-}
-
-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"
-
- # 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"
-
- 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
-}
-end_of_set_args() {
- IFS=$OLD_IFS
-}
-
-modify_ifs() {
- OLD_IFS=$IFS
- IFS=${1:-" ${tab_space}${new_line}"}
-}
-end_modify_ifs() {
- IFS=$OLD_IFS
-}
-# }}}
-
-#general util{{{
-
-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
-$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
-
- 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}"
-}
-
-trim_url() {
- while IFS= read -r _line; do
- printf '%s\n' "${_line##*"|"}"
- done
-}
-
-command_exists() {
- command -v "$1" >/dev/null 2>&1
-}
-
-is_relative_dir() {
- case "$1" in
- ../* | ./* | ~/* | /*) return 0 ;;
- esac
- return 1
-}
-
-get_key_value() {
- sep="${3:- }"
- value="${1##*"${sep}""${2}"=}"
- # this can be used similarly to how you use $REPLY in bash
- KEY_VALUE="${value%%"${sep}"*}"
- printf "%s" "$KEY_VALUE"
- unset value
- [ "$KEY_VALUE" ]
- return "$?"
-}
-
-# 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
-}
-
-# 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
- awk -F'\n' 'BEGIN{srand()} {print rand() " " $0}' <&3 | sort -n | sed 's/[^ ]* //'
- exec 3<&-
-}
-
-add_commas() {
- awk '
- {
- for(i=0; i<length($1); i++){
- if(i % 3 == 0 && i!=0){
- printf ","
- }
- printf "%s", substr($1, length($1) - i, 1)
- }
- }
-
- END{
- print ""
- }' |
- awk '
- {
- for (i=length($1); i>0; i--){
- printf "%s", substr($1, i, 1)
- }
- }
- '
-}
-
-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 &
-}
-
-remove_ansi_escapes() {
- sed -e 's/[[:cntrl:]]\[\([[:digit:]][[:digit:]]*\(;\|m\)\)*//g'
-}
-# }}}
-
-#Menu stuff{{{
-quick_menu() {
- fzf --ansi --reverse --prompt="$1"
-}
-quick_menu_ext() {
- external_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:]')
-}
-
-display_text() {
- printf "%s\n" "$@"
-}
-display_text_ext() {
- display_text "$@"
-}
-
-display_text_wrapper() {
- generic_wrapper "display_text" "$@"
-}
-
-info_wait_prompt_wrapper() {
- generic_wrapper "info_wait_prompt" "$@"
-}
-
-search_prompt_menu_wrapper() {
- generic_wrapper "search_prompt_menu" "$@"
-}
-
-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
-}
-
-# The menu to use instead of fzf when -D is specified
-external_menu() {
- # dmenu extremely laggy when showing tabs
- tr -d '\t' | remove_ansi_escapes | dmenu -i \
- -l 30 \
- -p "$1" \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff' # MODIFIED
-}
-
-search_prompt_menu() {
- printf "Search\n> " >/dev/stderr
- read -r _search
- printf "\033[1A\033[K\r%s\n" "> $_search" >/dev/stderr
-}
-search_prompt_menu_ext() {
- _search="$(printf '' | external_menu "Search: ")"
-}
-
-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}"
-
- print_debug "[INTERFACE]: Running interface: $_interface\n"
-
- $(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"
-}
-
-#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
-}
-
-# 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() {
- [ "$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"
- [ -n "$duration" ] && printf "\n ${c_blue}Duration ${c_yellow}%s" "$duration"
- [ -n "$views" ] && printf "\n ${c_blue}Views ${c_magenta}%s" "$views"
- [ -n "$date" ] && printf "\n ${c_blue}Date ${c_cyan}%s" "$date"
- [ -n "$viewed" ] && printf "\n ${c_blue}Viewed ${c_cyan}%s" "$viewed"
- [ -n "$description" ] && printf "\n ${c_blue}Description ${c_reset}: %s" "$(printf "%s" "$description" | sed 's/\\n/\n/g')"
-}
-# }}}
-
-# 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
-}
-
-source_scrapers() {
- prepare_for_set_args ","
- for _scr in $scrape; do
- if [ -f "$YTFZF_CUSTOM_SCRAPERS_DIR/$_scr" ]; then
- # shellcheck disable=SC1090
- . "${YTFZF_CUSTOM_SCRAPERS_DIR}/$_scr"
- elif [ -f "$YTFZF_SYSTEM_ADDON_DIR/scrapers/$_scr" ]; then
- # shellcheck disable=SC1090
- . "${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"
- 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
-}
-
-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
-
- print_debug "[LOADING]: loaded extension: ${ext} with exit code: ${rv}\n"
-
- 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
-
- 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
- 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 | 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
-}
-#}}}
-
-# Logging {{{
-
-#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
- }
-#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
- }
-fi
-
-die() {
- _return_status=$1
- print_error "$2"
- exit "$_return_status"
-}
-
-#}}}
-
-# urlhandlers{{{
-# 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() {
- # 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") "$@"
-}
-
-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
- 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") "$@" ;;
- esac
-}
-
-multimedia_player() {
- # this function differentiates whether or not audio_only was requested
- case "$is_audio_only" in
- 0) video_player "$@" ;;
- 1) audio_player "$@" ;;
- esac
-}
-
-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=''
- 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 && _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
-}
-
-# }}}
-
-#Misc{{{
-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
-}
-
-# }}}
-# }}}
-
-# Traps {{{
-[ $__is_fzf_preview -eq 0 ] && trap 'clean_up' EXIT
-[ $__is_fzf_preview -eq 0 ] && trap 'exit' INT TERM HUP
-#}}}
-
-# Global Variables and Start Up {{{
-
-set_vars() {
-
- 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"
-
- # 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
-
- #config vars
-
- search_source=args:prompt
-
- # scraping
- useragent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36"
-
- # menu options
-
- enable_submenus="1" submenu_opts="" submenu_scraping_opts="" enable_back_button="1"
- keep_vars=0
-
- interface=""
-
- fancy_subs="0" fancy_subs_left="-------------" fancy_subs_right="${fancy_subs_right=$fancy_subs_left}"
-
- fzf_preview_side="left" thumbnail_viewer="ueberzug"
-
- #actions are slow, disable if you want to increase runtime speed by 15ms
- enable_actions=1
-
- # 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"
-
- next_page_action_shortcut="ctrl-p"
-
- # interface design
- show_thumbnails="0" is_sort="0" skip_thumb_download="0" external_menu_len="210"
-
- is_loop="0" search_again="0"
-
- # Notifications
-
- notify_playing="0"
-
- # directories
- cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/ytfzf" keep_cache="0"
-
- # history
- enable_hist="0" enable_search_hist="0" # MODIFIED
-
- # 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"
-
- scripting_video_count="1"
- is_random_select="0" is_auto_select="0" is_specific_select="0"
-
- # option parsing
- long_opt_char="-"
-
- # scrape
- scrape="youtube"
- # this comes from invidious' api
- thumbnail_quality="high"
- sub_link_count="2"
-
- 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"
-
- multi_search="0"
-
- custom_scrape_search_exclude="" scrape_search_exclude=" youtube-subscriptions S SI SL T youtube-trending H history "
-
- max_thread_count="20"
-
- # 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"
-
- #misc
- instances_url="https://api.invidious.io/instances.json?sort_by=type,health,api"
- instances_file="$cache_dir/instancesV2.json"
-
- # 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}"
-
-# hard dependency checks{{{
-missing_deps=""
-for dep in jq curl; do
- 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
-#}}}
-
-# shellcheck disable=SC1090
-[ -f "$YTFZF_CONFIG_FILE" ] && . "$YTFZF_CONFIG_FILE"
-
-load_fake_extension "__ytfzf__" "1"
-
-# }}}
-
-# Scraping {{{
-
-############################
-# SCRAPERS #
-############################
-
-#############
-# ARGUMENTS #
-#############
-
-# 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)
-
-#############
-# 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.
-
-##############
-# EXIT CODES #
-##############
-
-# 0: success
-# 1: failed to load website, or general error
-# 100: :help search query
-#----curl errors-----
-# Even if not using curl, return these values.
-# 6: Unresponsive website
-# 22: General request error
-
-####################
-# RESPONSIBILITIES #
-####################
-
-# 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
-
-# * 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.
-
-# * 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.
-
-# Scraping backends {{{
-
-_start_series_of_threads() {
- _thread_count=0
-}
-
-_thread_started() {
- _latest_fork="$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
-}
-
-#}}}
-
-## Youtube {{{
-# Youtube backend functions {{{
-
-_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) | .[] |
- {
- scraper: "youtube_channel_playlist",
- ID: .gridPlaylistRenderer.playlistId,
- thumbs: .gridPlaylistRenderer.thumbnail.thumbnails[0].url,
- title: .gridPlaylistRenderer.title.runs[0].text,
- channel: "'"$channel_name"'",
- url: "'"$yt_video_link_domain"'/playlist?list=\(.gridPlaylistRenderer.playlistId)",
- duration: "\(.gridPlaylistRenderer.videoCountText.runs[0].text) videos",
- action: "scrape type=invidious-playlist search='"${yt_video_link_domain}"'/playlist?list=\(.gridPlaylistRenderer.playlistId)"
- }]'
-}
-
-_youtube_channel_json() {
- channel_name=$1
- __scr="$2"
- jq '[..|.richGridRenderer?|select(.!=null)|..|.contents?|select(.!=null)|..|.richItemRenderer?|select(.!=null) |
- {
- scraper: "'"$__scr"'",
- ID: .content.videoRenderer.videoId,
- url: "'"$yt_video_link_domain"'/watch?v=\(.content.videoRenderer.videoId)",
- title: .content.videoRenderer.title.runs[0].text,
- channel: "'"$channel_name"'",
- thumbs: .content.videoRenderer.thumbnail.thumbnails[2].url|sub("\\?.*";""),
- duration: .content.videoRenderer.lengthText.simpleText,
- views: "\(.content.videoRenderer.lengthText.simpleText | split("views")[0])",
- date: .content.videoRenderer.publishedTimeText.simpleText,
- description: .content.videoRenderer.descriptionSnippet.runs[0].text
- }
- ]'
-}
-#}}}
-
-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
-
- __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"
- 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"
- 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"
- _curl_config_file="${session_temp_dir}/curl_config"
- : >"$_curl_config_file"
-
- while read -r url; do
- url="${url%%#*}"
- [ -z "$url" ] && continue
-
- set_real_channel_url_and_id "$url"
-
- channel_url="$invidious_instance/api/v1/channels/$channel_id"
-
- _tmp_file="${session_temp_dir}/SI-${channel_id}.json"
-
- printf "url=\"%s\"\noutput=\"%s\"\n" "$channel_url" "$_tmp_file" >>"${_curl_config_file}"
-
- done <"${YTFZF_SUBSCRIPTIONS_FILE}"
-
- _tmp_json="${session_temp_dir}/SI.json"
-
- 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
-
- _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
- 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
-
- #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"
-
- set_real_channel_url_and_id "$channel_url"
-
- 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() {
- link="$1"
- link="${link##*channel/}"
- link="${link%/*}"
- printf "%s" "$link"
-}
-
-_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" ;;
- esac
-}
-
-_invidious_search_json_playlist() {
- jq '[ .[] | select(.type=="playlist") |
- {
- scraper: "invidious_search",
- ID: .playlistId,
- url: "'"${yt_video_link_domain}"'/playlist?list=\(.playlistId)",
- title: "[playlist] \(.title)",
- channel: .author,
- thumbs: .playlistThumbnail,
- duration: "\(.videoCount) videos",
- action: "scrape type=invidious-playlist search='"${yt_video_link_domain}"'/playlist?list=\(.playlistId)"
- }
- ]'
-}
-_invidious_search_json_channel() {
- jq '
- [ .[] | select(.type=="channel") |
- {
- scraper: "invidious_search",
- ID: .authorId,
- url: "'"${yt_video_link_domain}"'/channel/\(.authorId)",
- title: "[channel] \(.author)",
- channel: .author,
- thumbs: "https:\(.authorThumbnails[4].url)",
- duration: "\(.videoCount) uploaded videos",
- action: "scrape type=invidious-channel search='"${invidious_instance}"'/channel/\(.authorId)"
- }
- ]'
-}
-_invidious_search_json_live() {
- jq '[ .[] | select(.type=="video" and .liveNow==true) |
- {
- scraper: "invidious_search",
- ID: .videoId,
- url: "'"${yt_video_link_domain}"'/watch?v=\(.videoId)",
- title: "[live] \(.title)",
- channel: .author,
- thumbs: "'"${invidious_instance}"'/vi/\(.videoId)/'"$thumbnail_quality"'.jpg"
- }
- ]'
-}
-_invidious_search_json_videos() {
- __scr="$1"
- jq '
- '"$jq_pad_left"'
- ;
- [ .[] | select(.type=="video" and .liveNow==false) |
- {
- scraper: "'"$__scr"'",
- ID: .videoId,
- url: "'"${yt_video_link_domain}"'/watch?v=\(.videoId)",
- title: .title,
- channel: .author,
- thumbs: "'"${invidious_instance}"'/vi/\(.videoId)/'"$thumbnail_quality"'.jpg",
- duration: "\(.lengthSeconds / 60 | floor):\(pad_left(2; .lengthSeconds % 60))",
- views: "\(.viewCount)",
- date: .publishedText,
- description: .description
- }
- ]'
-}
-_invidious_search_json_generic() {
- __scr="$1"
- jq '
- '"$jq_pad_left"'
- ;
- [ .[] |
- {
- scraper: "'"$__scr"'",
- ID: .videoId,
- url: "'"${yt_video_link_domain}"'/watch?v=\(.videoId)",
- title: .title,
- channel: .author,
- thumbs: "'"${invidious_instance}"'/vi/\(.videoId)/'"$thumbnail_quality"'.jpg",
- duration: "\(.lengthSeconds / 60 | floor):\(pad_left(2; .lengthSeconds % 60))",
- views: "\(.viewCount)",
- date: .publishedText,
- description: .description
- }
- ]'
-}
-
-_invidious_playlist_json() {
- jq '
- '"$jq_pad_left"'
- ;
- [ .videos | .[] |
- {
- scraper: "invidious_playlist",
- ID: .videoId,
- url: "'"${yt_video_link_domain}"'/watch?v=\(.videoId)",
- title: .title,
- channel: .author,
- thumbs: "'"${invidious_instance}"'/vi/\(.videoId)/'"$thumbnail_quality"'.jpg",
- duration: "\(.lengthSeconds / 60 | floor):\(pad_left(2; .lengthSeconds % 60))",
- date: .publishedText,
- description: .description
- }
- ]'
-}
-
-_concatinate_json_file() {
- template="$1"
- page_count=$2
- _output_json_file="$3"
- __cur_page=${4:-1}
- set --
- # 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))
- done
- cat "$@" 2>/dev/null >>"$_output_json_file"
-}
-#}}}
-
-scrape_invidious_playlist() {
- playlist_url=$1
- [ "$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=}"
-
- _get_invidious_thumb_quality_name
-
- # used to put the full playlist in, to later remove duplicates
- _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
- _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
- 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))
- done
-}
-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
-
- _ivs_cur_page=${pages_start:-1}
-
- page_num=$((_ivs_cur_page + ${pages_to_scrape:-1}))
-
- # shellcheck disable=SC2209
- case "$search_sort_by" in
- upload_date) search_sort_by="date" ;;
- view_count) search_sort_by=views ;;
- esac
-
- _start_series_of_threads
- while [ ${_ivs_cur_page} -lt $page_num ]; do
- {
- _tmp_json="${session_temp_dir}/yt-search-$_ivs_cur_page.json"
-
- print_info "Scraping YouTube (with $invidious_instance) ($page_query, pg: $_ivs_cur_page)\n"
-
- _get_request "$invidious_instance/api/v1/search" \
- -G --data-urlencode "q=$page_query" \
- --data-urlencode "type=${search_result_type}" \
- --data-urlencode "sort=${search_sort_by}" \
- --data-urlencode "date=${search_upload_date}" \
- --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"
-
- _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"
- } &
- _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
- 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"
-}
-
-scrape_youtube() { scrape_invidious_search "$@"; }
-scrape_Y() { scrape_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_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_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
- output_json_file=$2
- print_info "Scraping YouTube (with $invidious_instance) trending (${trending_tab:-Normal})\n"
-
- _tmp_json="${session_temp_dir}/yt-trending"
-
- url="$invidious_instance/api/v1/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 "$?"
-
- _get_invidious_thumb_quality_name
-
- _invidious_search_json_videos "invidious_trending" <"$_tmp_json" >>"$output_json_file"
-}
-scrape_youtube_trending() { scrape_invidious_trending "$@"; }
-scrape_T() { scrape_invidious_trending "$@"; }
-
-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
- 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"
-
- prepare_for_set_args
- 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
-
- #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"
-
- # 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"
-
- print_info "Scraping Youtube (with $invidious_instance) channel: $channel_url\n"
-
- _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
-}
-
-## }}}
-
-## Ytfzf {{{
-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
-}
-scrrape_M() { scrape_multi "$@"; }
-## }}}
-
-## Peertube {{{
-scrape_peertube() {
- page_query=$1
- [ "$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 "$?"
-
- jq '
- def pad_left(n; num):
- num | tostring |
- if (n > length) then ((n - length) * "0") + (.) else . end
- ;
- [ .data | .[] |
- {
- scraper: "peertube_search",
- ID: .uuid,
- url: .url,
- title: .name,
- channel: .channel.displayName,
- thumbs: .thumbnailUrl,
- duration: "\(.duration / 60 | floor):\(pad_left(2; .duration % 60))",
- views: "\(.views)",
- date: .publishedAt
- }
- ]' <"$_tmp_json" >>"$output_json_file"
-
-}
-scrape_P() { scrape_peertube "$@"; }
-## }}}
-
-## Odysee {{{
-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}" -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
-
- 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="" ;;
- esac
- case "$search_upload_date" in
- 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 ;;
- 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 "$?"
- 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 "$?"
-
- fi
- # select(.duration != null) selects videos that aren't live, there is no .is_live key
- jq '
- def pad_left(n; num):
- num | tostring |
- if (n > length) then ((n - length) * "0") + (.) else . end
- ;
- [ .[] |select(.duration != null) |
- {
- scraper: "odysee_search",
- ID: .claimId,
- title: .title,
- url: "https://www.odysee.com/\(.channel)/\(.name)",
- channel: .channel,
- thumbs: .thumbnail_url,
- duration: "\(.duration / 60 | floor):\(pad_left(2; .duration % 60))",
- views: "\(.view_cnt)",
- date: .cq_created_at
- }
- ]' <"$_tmp_json" >>"$output_json_file"
-
-}
-scrape_O() { scrape_odysee "$@"; }
-## }}}
-
-# ytfzf json format{{{
-
-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 "$@"; }
-#}}}
-
-# Comments{{{
-scrape_comments() {
- video_id="$1"
- [ "$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##*=}" ;;
- 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}))"
- 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))
- done
- printf "%s\n" "$i" >"${session_temp_dir}/comments-current-page"
-}
-
-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"; }
-#}}}
-
-# }}}
-
-# Sorting {{{
-
-############################
-# SORTING #
-############################
-
-# There is a 2 step soring process.
-# 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
-
-# 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() {
- _video_json_line="$1"
- date="${_video_json_line##*'"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"
-}
-
-# This function sorts the data being piped into it.
-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() {
- if [ $is_sort -eq 1 ]; then
- 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"
- done | data_sort_fn | cut -f2-
- else
- cat
- fi
-}
-#}}}
-
-# History Management {{{
-
-#}}}
-
-# User Interface {{{
-
-############################
-# INTERFACES #
-############################
-
-# 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
-# 2: The url file to write to
-# each url should be seperated by a new line when written to the url file.
-
-# Interfaces are responsible for the following:
-
-# $ytfzf_video_json_file contains a file with the raw search result json
-# 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.
-
-# * 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
-
-# * 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
-
-# Keypresses {{{
-set_keypress() {
- # this function uses echo to keep new lines
- read -r keypress
- while read -r line; do
- input="${input}${new_line}${line}"
- 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
- echo "$input" | sed -n '2,$p'
- else
- # there was no key press, remove all blank lines
- echo "${keypress}${new_line}${input}" | grep -Ev '^[[:space:]]*$'
- fi
- unset keypress
-}
-
-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
- ;;
- esac
- unset keypress
-
-}
-
-handle_keypress() {
- read -r keypress <"$1"
-
- 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="$?"
- ;;
- esac
- unset keypress
- return "${rv:-0}"
-}
-
-#}}}
-
-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 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 ;;
- *) 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() {
- 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
- [ -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
-
- [ "$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; }
-
- video_json_file=$1
- selected_id_file=$2
-
- 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)"
-
- # shellcheck disable=2015
- 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' 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
- )"
-}
-#}}}
-
-# External interface {{{
-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))
- dur_len=7
- date_len=14
-
- create_sorted_video_data |
- video_info_text |
- external_menu 'Select Video:' | # MODIFIED
- trim_url >"$selected_id_file"
-}
-#}}}
-
-# Thumbnail Interface {{{
-
-_get_video_json_attr() {
- sed -n 's/^[[:space:]]*"'"$1"'":[[:space:]]*"\([^\n]*\)",*/\1/p' <<-EOF | sed 's/\\\([\\"]\)/\1/g'
- $_correct_json
- EOF
-}
-
-# Image preview {{{
-preview_start() {
- thumbnail_viewer=$1
- case $thumbnail_viewer in
- 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() {
- thumbnail_viewer=$1
- case $thumbnail_viewer in
- 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
- ;;
- 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"
-}
-
-preview_no_img() {
- thumbnail_viewer="$1"
- case $thumbnail_viewer in
- 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" ;;
- esac && do_an_event_function "on_no_thumbnail"
-
-}
-# ueberzug positioning{{{
-command_exists "get_ueberzug_positioning_left" || get_ueberzug_positioning_left() {
- width=$1
- height=$(($2 - __text_line_count + 2))
- x=2
- y=$((__text_line_count + 2))
-}
-command_exists "get_ueberzug_positioning_right" || get_ueberzug_positioning_right() {
- get_ueberzug_positioning_left "$@"
- width=$1
- x=$(($1 + 6))
-}
-command_exists "get_ueberzug_positioning_up" || get_ueberzug_positioning_up() {
- width=$1
- height=$(($2 - __text_line_count))
- x=2
- y=9
-}
-command_exists "get_ueberzug_positioning_down" || get_ueberzug_positioning_down() {
- width=$1
- height=$(($2 - __text_line_count))
- #$2*2 goes to the bottom subtracts height, adds padding
- y=$(($2 * 2 - height + 2))
- x=2
-}
-
-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))
-
- # 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_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=$((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))
-}
-
-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
-
- 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_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"
- case $thumbnail_viewer in
- ueberzug | sixel | kitty | iterm2)
- {
- 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)
- 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
- ;;
- *)
- 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"
- ;;
-
- esac
-}
-#}}}
-
-preview_img() {
- # This function is common to every thumbnail viewer
- thumbnail_viewer=$1
- line=$2
- video_json_file=$3
- url=${line##*"|"}
-
- # make sure all variables are set{{{
- _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")"
- views="$(_get_video_json_attr "views")"
- date="$(_get_video_json_attr "date")"
- 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')"
- #}}}
-
- _const_text=$(if command_exists "thumbnail_video_info_text${scraper:+_$scraper}"; then
- thumbnail_video_info_text${scraper:+_$scraper}
- else
- thumbnail_video_info_text
- fi)
-
- __text_line_count=$(
- wc -l <<-EOF
- $_const_text
- EOF
- )
-
- echo "$_const_text"
-
- preview_display_image "$thumbnail_viewer" "$id"
-}
-
-interface_thumbnails() {
- # Takes video json file and downloads the thumnails as ${ID}.png to thumb_dir
- video_json_file=$1
- selected_id_file=$2
-
- preview_start "$thumbnail_viewer"
-
- unset IFS
-
- # 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)"
-
- 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' 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
- )"
-}
-#}}}
-
-#}}}
-
-# 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() {
- 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 ;;
- esac
- return 0
-
-}
-
-submenu_handler() {
- # eat stdin and close it
- 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"
- 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"
- }
-
- _getopts "$@"
-
- source_scrapers
-
- search_again=0
-
- 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
-
- something_was_scraped || exit 4
-
- 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() {
- 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() {
- # isaudio, isdownload, ytdl_pref
- 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
-
- 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"
-
- # 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
- printf "%s\t" "$ytdl_pref" "$is_audio_only" "$is_detach" "$video_pref" "$audio_pref" "$url_handler_opts" | session_temp_dir="${session_temp_dir}" session_cache_dir="${session_cache_dir}" "$url_handler" "$@"
- end_of_set_args
-}
-#}}}
-
-# Format selection {{{
-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() {
- 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
-}
-
-#}}}
-
-# Internal Actions {{{
-#
-# 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...]
-An action followed by \\003 (ascii: EOT) followed by args which are seperated by \\003.
-Actions:
-help:
- Prints usage on -W
-preview_img <ytfzf-line-format>:
- ytfzf-line-format:
- path to file where items seperated by \t| where the last item is the url of the item
- The following variables must be set
- session_temp_dir set to the ytfzf process instance's temp dir
- session_cache_dir set to the ytfzf process instance's cache dir"
-}
-
-internal_action_next_page() {
- shift
-
- read -r line <"$*"
- url="${line##*"|"}"
-
- video_json_file="$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")"
-
- 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)"'
-
-}
-
-internal_action_preview_img() {
- shift
-
- thumb_dir="${session_cache_dir}/thumbnails"
-
- 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"
-}
-# }}}
-
-# Options {{{
-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"
- # 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 ] && 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
-
- 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
-}
-
-_getopts "$@"
-# shellcheck disable=SC2031
-shift $((OPTIND - 1))
-#}}}
-
-do_an_event_function "on_post_set_vars"
-
-# Get search{{{
-#$initial_search should be used before make_search is called
-#$_search should be used in make_search or after it's called and outisde of any scrapers themselves
-#$search should be used in a scraper: eg scrape_json_file
-unset IFS
-: "${initial_search:=$*}"
-#}}}
-
-# files {{{
-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")
- 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"
-
- #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"
-
- thumb_dir="${session_cache_dir}/thumbnails"
-
- ytfzf_selected_urls=$session_cache_dir/ids
- ytfzf_video_json_file=$session_cache_dir/videos_json
-
- mkdir -p "$session_temp_dir" "$thumb_dir"
-
- keypress_file="${session_temp_dir}/menu_keypress"
-
- : >"$ytfzf_video_json_file" 2>"$ytfzf_selected_urls" 3>"$keypress_file"
-
- [ "$1" ] && printf "%s\n" "$1" >"${session_cache_dir}/searches.list"
-
- unset _session_cache_dir_prefix
-}
-
-# }}}
-
-# actions {{{
-
-############################
-# ACTIONS #
-############################
-
-# Actions happen after a video is selected, and after the keypresses are handled
-
-# Actions are formatted in the following way
-# 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
-
-# actions are attached to videos/items in the menu
-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')
- while read -r action; do
- 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 $?
- ;;
- 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
-}
-
-#}}}
-
-# scraping wrappers {{{
-
-set_scrape_count() {
- prepare_for_set_args ","
- # shellcheck disable=SC2086
- set -- $scrape
- end_of_set_args
- __total_scrape_count="$#"
-}
-
-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" ;;
- 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
-}
-
-# 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
-}
-#}}}
-
-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() {
- _search="$1"
- _search_source="$2"
-
- 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"; }
- init_files "$_search"
- set_scrape_count
-
- # shellcheck disable=SC2031
-
- do_an_event_function "on_init_search" "$_search"
-}
-
-init_and_make_search() {
- _search=$1
- _search_source=$2
- init_search "$_search" "$_search_source"
- make_search "$_search"
-}
-
-make_search() {
- _search="$1"
- handle_scraping "$_search"
- do_an_event_function post_scrape
-}
-#}}}
-
-# Main {{{
-
-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
-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
-
- 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
- handle_info
- case "$?" in
- 2) break ;; 3) continue ;; esac
- fi
-
- open_format_selection_if_requested "$ytfzf_selected_urls"
-
- open_url_handler "$ytfzf_selected_urls"
- close_url_handler "$url_handler"
-
- [ "$is_loop" -eq 0 ] && break
- 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
diff --git a/lcl-edit b/lcl-edit
index 5757625..16b3937 100755
--- a/lcl-edit
+++ b/lcl-edit
@@ -1,180 +1,113 @@
-#! /bin/bash
-
-source ./util/menu/deasy-core
-source ./util/notify
-source ./util/dpath
-
-run() {
- cd "$HOME/Desktop"
- filepath="$(find -L "$DPATH" \
- . \
- -type f | dmenu -b \
- -i \
- -l 25 \
- -p 'Search File:' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
- [ -f "$filepath" ] || exit 0
-
- CHOICES=('[Video]Strip' \
- '[Video]Transcode' \
- '[Video]Scale' \
- '[Video]Clip' \
- '[Video]Extract-Chapters' \
- '[Video]Insert-Chapters' \
- '[Video]Split-Chapters' \
- '[Image]Strip' \
- '[Image]Convert' \
- '[Image]Resize'
- '[Image]Trim' \
- '[Image]Border-Radius')
-
- [ ! -z "$1" ] && selected="$1" \
- || selected="$(echo "${CHOICES[@]}" | tr ' ' '\n' \
- | dmenu -i \
- -l 20 \
- -p 'Select Edit:' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
-
- basedir="$(dirname "$filepath")";
- filename="$(basename "$filepath")"
- savepath="$DPATH/$filename"
- basename="${filename%.*}"
-
- case "$selected" in
- "${CHOICES[0]}")
- ffmpeg -n \
- -sn \
- -i "$filepath" \
- -map_metadata -1 \
- -map_chapters -1 \
- -c:v copy \
- -c:a copy \
- "$DPATH/[STRIP] $filename"
- ;;
-
- "${CHOICES[1]}")
- fileext="$(deasy_core 'File Extension (mp3)')"
- [ -z "$fileext" ] && exit 0
-
- ffmpeg -n -i "$filepath" "${savepath%.*}.$fileext"
- ;;
-
- "${CHOICES[2]}")
- scale="$(deasy_core 'Scale (854:480)')"
- [ -z "$scale" ] && exit 0
-
- ffmpeg -y \
- -i "$filepath" \
- -vf scale="$scale" \
- "$DPATH/[$scale] $filename"
- ;;
-
- "${CHOICES[3]}")
- from="$(deasy_core 'From (00:00/n)')"
- [ -z "$from" ] && exit 0
-
- to="$(deasy_core 'To (00:00/n)')"
- [ -z "$to" ] && exit 0
-
- ffmpeg -y \
- -ss "$from" \
- -to "$to" \
- -i "$filepath" \
- "$DPATH/[CLIP] $filename"
- ;;
-
- "${CHOICES[4]}")
- ffmpeg -n \
- -i "$filepath" \
- -f ffmetadata \
- "$DPATH/[CHAPTERS] $basename"
- ;;
-
- "${CHOICES[5]}")
- chapterpath="$(deasy_core 'Chapter Filepath')"
- [ -z "$chapterpath" ] && exit 0
-
- ffmpeg -n \
- -i "$filepath" \
- -i "$chapterpath" \
- -map_metadata 1 \
- -codec copy \
- "$DPATH/[WITH-CHAPTERS] $filename"
- ;;
-
- "${CHOICES[6]}")
- mkdir -p "$DPATH/$basename"
- ffprobe -print_format csv \
- -show_chapters \
- "$filepath" \
- | cut -d ',' -f '5,7,8' \
- | while IFS=, read start end chapter
- do
- ffmpeg -n \
- -nostdin \
- -i "$filepath" \
- -ss "$start" \
- -to "$end" \
- -c copy \
- -map 0 \
- -map_chapters -1 \
- "$DPATH/$basename/$chapter.${filepath##*.}"
- done
- ;;
-
- "${CHOICES[7]}")
- cp "$filepath" "$DPATH/[STRIP] $filename"
- exiftool -overwrite_original -all= "$DPATH/[STRIP] $filename"
- ;;
-
- "${CHOICES[8]}")
- fileext="$(deasy_core 'File Extension (png)')"
- [ -z "$fileext" ] && exit 0
-
- convert "$filepath" "${savepath%.*}.$fileext"
- ;;
-
- "${CHOICES[9]}")
- size="$(deasy_core 'Size (75x75)')"
- [ -z "$size" ] && exit 0
-
- convert "$filepath" -resize "$size" "$DPATH/[$size] $filename"
- ;;
-
- "${CHOICES[10]}")
- convert "$filepath" -trim "$DPATH/[TRIM] $filename"
- ;;
-
- "${CHOICES[11]}")
- n="$(deasy_core 'Border Radius (n)')"
- [ -z "$n" ] && exit 0
-
- convert "$filepath" \
- \( +clone -alpha extract \
- -draw "fill black polygon 0,0 0,$n $n,0 fill white circle $n,$n $n,0" \
- \( +clone -flip \) -compose Multiply -composite \
- \( +clone -flop \) -compose Multiply -composite \
- \) -alpha off -compose CopyOpacity -composite "$DPATH/[RADIUS] $filename"
- ;;
-
- '')
- exit 0
- ;;
-
- *)
- notify 'Invalid Entry'
- ;;
- esac
-}
-
-run "$@"; status=$?
-
-[ ! $status -eq 0 ] && [ ! $status -eq 143 ] \
- && notify 'Something Went Wrong'
-
+#! /bin/zsh
+
+filepath="$(find ~/Archive -type d \( -name node_modules -o -name .git \) -prune -o \
+ -type f -name '*' -print | dmenu -i -l 15 -p 'Search File')"
+[ ! -f "$filepath" ] && exit 1
+
+CHOICES=('[Video]Strip' \
+ '[Video]Transcode' \
+ '[Video]Scale' \
+ '[Video]Clip' \
+ '[Video]Extract-Chapters' \
+ '[Video]Insert-Chapters' \
+ '[Video]Split-Chapters' \
+ '[Image]Strip' \
+ '[Image]Convert' \
+ '[Image]Resize'
+ '[Image]Trim' \
+ '[Image]Border-Radius')
+
+[ ! -z "$1" ] && selected="$1" \
+ || selected="$(echo "${CHOICES[@]}" | tr ' ' '\n' \
+ | dmenu -i -l 15 -p 'Select Edit')"
+
+filename="$(basename "$filepath")"
+
+case "$selected" in
+ "${CHOICES[1]}")
+ ffmpeg -n \
+ -sn \
+ -i "$filepath" \
+ -map_metadata -1 \
+ -map_chapters -1 \
+ -c:v copy \
+ -c:a copy \
+ "$HOME/[STRIP] $filename" ;;
+ "${CHOICES[2]}")
+ fileext="$(print | dmenu -p 'File Extension (mp3)')"
+ [ ! -z "$fileext" ] && ffmpeg -n \
+ -i "$filepath" \
+ "${filepath%.*}.$fileext" ;;
+ "${CHOICES[3]}")
+ scale="$(print | dmenu -p 'Scale (854:480)')"
+ [ ! -z "$scale" ] && ffmpeg -y \
+ -i "$filepath" \
+ -vf scale="$scale" \
+ "$HOME/[$scale] $filename" ;;
+ "${CHOICES[4]}")
+ from="$(print | dmenu -p 'From (00:00/n)')"
+ [ -z "$from" ] && exit 0
+
+ to="$(print | dmenu -p 'To (00:00/n)')"
+ [ -z "$to" ] && exit 0
+
+ ffmpeg -y \
+ -ss "$from" \
+ -to "$to" \
+ -i "$filepath" \
+ "$HOME/[CLIP] $filename" ;;
+ "${CHOICES[5]}")
+ ffmpeg -n \
+ -i "$filepath" \
+ -f ffmetadata \
+ "$HOME/[CHAPTERS] ${filename%.*}" ;;
+ "${CHOICES[6]}")
+ chapterpath="$(print | dmenu -p 'Chapter Filepath')"
+ [ -z "$chapterpath" ] && exit 0
+
+ ffmpeg -n \
+ -i "$filepath" \
+ -i "$chapterpath" \
+ -map_metadata 1 \
+ -codec copy \
+ "$HOME/[WITH-CHAPTERS] $filename" ;;
+ "${CHOICES[7]}")
+ mkdir -p "$HOME/${filename%.*}"
+ ffprobe -print_format csv \
+ -show_chapters \
+ "$filepath" \
+ | cut -d ',' -f '5,7,8' \
+ | while IFS=, read start end chapter
+ do
+ ffmpeg -n \
+ -nostdin \
+ -i "$filepath" \
+ -ss "$start" \
+ -to "$end" \
+ -c copy \
+ -map 0 \
+ -map_chapters -1 \
+ "$HOME/${filename%.*}/$chapter.${filepath##*.}"
+ done ;;
+ "${CHOICES[8]}")
+ cp "$filepath" "$HOME/[STRIP] $filename"
+ exiftool -overwrite_original -all= "$HOME/[STRIP] $filename" ;;
+ "${CHOICES[9]}")
+ fileext="$(print | dmenu -p 'File Extension (png)')"
+ [ ! -z "$fileext" ] && convert "$filepath" "$HOME/${filename%.*}.$fileext" ;;
+ "${CHOICES[10]}")
+ size="$(print | dmenu -p 'Size (75x75)')"
+ [ ! -z "$size" ] && convert "$filepath" -resize "$size" "$HOME/[$size] $filename" ;;
+ "${CHOICES[11]}")
+ convert "$filepath" -trim "$HOME/[TRIM] $filename" ;;
+ "${CHOICES[12]}")
+ n="$(print | dmenu -p 'Border Radius (n)')"
+ [ ! -z "$n" ] && convert "$filepath" \
+ \( +clone -alpha extract \
+ -draw "fill black polygon 0,0 0,$n $n,0 fill white circle $n,$n $n,0" \
+ \( +clone -flip \) -compose Multiply -composite \
+ \( +clone -flop \) -compose Multiply -composite \
+ \) -alpha off -compose CopyOpacity -composite "$HOME/[RADIUS] $filename" ;;
+ '') exit 0 ;;
+ *) exit 1 ;;
+esac
diff --git a/lcl-find b/lcl-find
index 1a1fd42..1bd073e 100755
--- a/lcl-find
+++ b/lcl-find
@@ -1,17 +1,5 @@
-#! /bin/bash
-
-source ./util/dpath
-
-cd "$HOME/Desktop"
-file="$(find -L "$DPATH" \
- . \
- -type f | dmenu -b \
- -i \
- -l 25 \
- -p 'Search File:' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
-[ -f "$file" ] && xdg-open "$file" || exit 0
+#! /bin/sh
+selected="$(find ~/Archive -type d \( -name node_modules -o -name .git \) -prune -o \
+ -type f -name '*' -print | dmenu -i -l 15 -p 'Search File')"
+[ -f "$selected" ] && setsid -f xdg-open "$selected" > /dev/null 2>&1
diff --git a/lcl-kill b/lcl-kill
index f8861fa..a9d649e 100755
--- a/lcl-kill
+++ b/lcl-kill
@@ -1,15 +1,5 @@
-#! /bin/bash
+#! /bin/sh
plist="$(ps -u "$USER" -o pid,%mem,%cpu,comm --no-headers | sort -b -k2 -r)"
-proc="$(echo "$plist" | dmenu -b \
- -i \
- -l 25 \
- -p 'Select Process:' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
-[ -z "$proc" ] && exit 0
-
-kill -15 "$(echo "$proc" | awk '{print $1}')"
-
+proc="$(echo "$plist" | dmenu -i -l 15 -p 'Select Process')"
+[ ! -z "$proc" ] && kill -15 "$(echo "$proc" | awk '{print $1}')"
diff --git a/lcl-playlist b/lcl-playlist
index 1ed5695..fbf3603 100755
--- a/lcl-playlist
+++ b/lcl-playlist
@@ -1,46 +1,19 @@
-#! /bin/bash
+#! /bin/zsh
-source ./util/dpath
+files="$(find ~/Archive -type f -name '*.mp4' \
+ -o -name '*.mkv' \
+ -o -name '*.avi' \
+ -o -name '*.webm' \
+ -o -name '*.flac' \
+ -o -name '*.ogg' \
+ -o -name '*.mp3' | dmenu -i -l 15 -p 'Select Files')"
+[ -z "$files" ] && exit 1
playlist=()
-cd "$HOME/Desktop"
-
-function data() {
- echo "$(printf '%s\n' "${playlist[@]}")\n"
-}
-
-function action() {
- [ ! -z "$playlist" ] && echo -e "$(data)" \
- && mpv "${playlist[@]}"
-}
-
-function main() {
- file="$(find -L "$DPATH" \
- . \
- -type f -name '*.mp4' \
- -o -name '*.mkv' \
- -o -name '*.avi' \
- -o -name '*.webm' \
- -o -name '*.mp3' \
- -o -name '*.ogg' \
- | dmenu -b \
- -i \
- -l 25 \
- -p 'Select Files:' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
-
- if [ -f "$file" ]
- then
- [[ "$file" = './'* ]] && playlist+=("$(readlink -f "$file")") \
- || playlist+=("$file")
- main
- else
- action
- fi
-}
-
-main
+while read -r file
+ do
+ playlist+="$(printf '%s' "$file")"
+done < <(print "$files")
+[ "$1" = '-o' ] && echo "${playlist[@]}" \
+ setsid -f mpv "${playlist[@]}" > /dev/null 2>&1
diff --git a/lcl-start b/lcl-start
deleted file mode 100755
index 2fb64f7..0000000
--- a/lcl-start
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /bin/bash
-
-dmenu_run -i \
- -p 'Start' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff'
-
diff --git a/lcl-system b/lcl-system
index 7dc932c..ea4e136 100755
--- a/lcl-system
+++ b/lcl-system
@@ -1,36 +1,10 @@
-#! /bin/bash
+#! /bin/sh
-source ./util/notify
-
-CHOICES=('Shutdown' 'Hibernate' 'Restart')
-
-[ ! -z "$1" ] && selected="$1" \
- || selected="$(echo "${CHOICES[@]}" | tr ' ' '\n' \
- | dmenu -i \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
+selected="$(printf "Lock\nSleep\nShutdown\nReboot" | dmenu -i)"
case "$selected" in
- "${CHOICES[0]}")
- shutdown -P 0
- ;;
-
- "${CHOICES[1]}")
- systemctl suspend
- ;;
-
- "${CHOICES[2]}")
- shutdown -r 0
- ;;
-
- '')
- exit 0
- ;;
-
- *)
- notify 'Invalid Entry'
- ;;
+ Lock) slock ;;
+ Sleep) systemctl suspend ;;
+ Shutdown) sudo shutdown -P 0 ;;
+ Reboot) sudo reboot ;;
esac
-
diff --git a/lib/action/Clip b/lib/action/Clip
deleted file mode 100755
index d98cf0e..0000000
--- a/lib/action/Clip
+++ /dev/null
@@ -1,23 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-https
-source ./util/menu/deasy-core
-source ./util/dpath
-
-url="$(is_https_url_cbd "$1")"
-
-from="$(deasy_core 'From (00:00/n)')"
-[ -z "$from" ] && exit 0
-
-to="$(deasy_core 'To (00:00/n)')"
-[ -z "$to" ] && exit 0
-
-filename="$(deasy_core 'Filename')"
-[ -z "$filename" ] && exit 0
-
-ffmpeg -n \
- -ss "$from" \
- -to "$to" \
- -i "$(yt-dlp -f b --get-url "$url")" \
- "$DPATH/$filename.mp4"
-
diff --git a/lib/action/Clipboard b/lib/action/Clipboard
deleted file mode 100755
index b8e42e1..0000000
--- a/lib/action/Clipboard
+++ /dev/null
@@ -1,12 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-core
-
-[ -z "$1" ] && url="$(get_cbd)" \
- && [ -z "$url" ] \
- && exit 0 \
- || url="$1"
-
-printf "$url" | xclip -sel c
-printf "$url" | xclip
-
diff --git a/lib/action/Download b/lib/action/Download
deleted file mode 100755
index 01e8eb5..0000000
--- a/lib/action/Download
+++ /dev/null
@@ -1,10 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-https
-source ./util/dpath
-
-yt-dlp -f 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' \
- -o "$DPATH/%(title)s.%(ext)s" \
- --embed-chapters \
- "$(is_https_url_cbd "$1")"
-
diff --git a/lib/action/Open b/lib/action/Open
deleted file mode 100755
index 76f1246..0000000
--- a/lib/action/Open
+++ /dev/null
@@ -1,6 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-https
-
-xdg-open "$(is_https_url_cbd "$1")"
-
diff --git a/lib/action/Play b/lib/action/Play
deleted file mode 100755
index fa56bac..0000000
--- a/lib/action/Play
+++ /dev/null
@@ -1,11 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-https
-
-url="$(is_https_url_cbd "$1")"
-
-[[ "$url" = *"&list="* ]] && playlist="$(echo "$url" | awk -F '&list=' '{print $2}')" \
- && url="https://www.youtube.com/playlist?list=$playlist"
-
-mpv "$url"
-
diff --git a/lib/action/Record b/lib/action/Record
deleted file mode 100755
index ac6c182..0000000
--- a/lib/action/Record
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-https
-source ./util/dpath
-
-mpv --stream-record="$DPATH/$(date +'%Y-%m-%d—%T.mkv')" "$(is_https_url_cbd "$1")"
-
diff --git a/lib/action/Torrent b/lib/action/Torrent
deleted file mode 100755
index 4309da8..0000000
--- a/lib/action/Torrent
+++ /dev/null
@@ -1,6 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-magnet
-
-qbittorrent "$(is_magnet_url_cbd "$1")"
-
diff --git a/lib/action/aDownload b/lib/action/aDownload
deleted file mode 100755
index 56fe212..0000000
--- a/lib/action/aDownload
+++ /dev/null
@@ -1,13 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-https
-source ./util/dpath
-
-yt-dlp -f 'bestaudio/best' \
- -o "$DPATH/%(title)s.%(ext)s" \
- --embed-thumbnail \
- --extract-audio \
- --audio-format mp3 \
- --embed-chapters \
- "$(is_https_url_cbd "$1")"
-
diff --git a/lib/search/1337x b/lib/search/1337x
deleted file mode 100755
index 7464fcd..0000000
--- a/lib/search/1337x
+++ /dev/null
@@ -1,67 +0,0 @@
-#! /bin/bash
-
-source ./util/menu/deasy
-source ./util/notify
-
-CACHEDIR="$HOME/.cache/notflix-mod"
-[ -d "$CACHEDIR" ] || mkdir -p "$CACHEDIR"
-
-BASEURL='https://1337x.to'
-
-query="$(echo $(deasy "$1" 'Search 1337x') | sed 's/ /+/g')"
-curl -s "$BASEURL/search/$query/1/" > "$CACHEDIR/tmp.html"
-
-# Titles
-grep -o '<a href="/torrent/.*</a>' "$CACHEDIR/tmp.html" \
- | sed 's/<[^>]*>//g' > "$CACHEDIR/titles.bw"
-
-# Void Check
-count=$(wc -l "$CACHEDIR/titles.bw" | awk '{print $1}')
-[ $count -lt 1 ] && notify 'Empty Response' && exit 0
-
-# Seeders & Leechers
-grep -o '<td class="coll-2 seeds.*</td>\|<td class="coll-3 leeches.*</td>' "$CACHEDIR/tmp.html" \
- | sed 's/<[^>]*>//g' \
- | sed 'N;s/\n/ /' > "$CACHEDIR/seedleech.bw"
-
-# Size
-grep -o '<td class="coll-4 size.*</td>' "$CACHEDIR/tmp.html" \
- | sed 's/<span class="seeds">.*<\/span>//g' \
- | sed -e 's/<[^>]*>//g' > "$CACHEDIR/size.bw"
-
-# Links
-grep -E '/torrent/' "$CACHEDIR/tmp.html" \
- | sed -E 's#.*(/torrent/.*)/">.*/#\1#' \
- | sed 's/td>//g' > "$CACHEDIR/links.bw"
-
-# Cleanup
-sed 's/\./ /g; s/\-/ /g' "$CACHEDIR/titles.bw" \
- | sed 's/[^A-Za-z0-9 ]//g' \
- | tr -s ' ' > "$CACHEDIR/tmp" && mv "$CACHEDIR/tmp" "$CACHEDIR/titles.bw"
-
-awk '{print NR " - ["$0"]"}' "$CACHEDIR/size.bw" > "$CACHEDIR/tmp" && mv "$CACHEDIR/tmp" "$CACHEDIR/size.bw"
-awk '{print "[S:"$1 ", L:"$2"]" }' "$CACHEDIR/seedleech.bw" > "$CACHEDIR/tmp" && mv "$CACHEDIR/tmp" "$CACHEDIR/seedleech.bw"
-
-# Line
-line="$(paste -d\ "$CACHEDIR/size.bw" "$CACHEDIR/seedleech.bw" "$CACHEDIR/titles.bw" \
- | dmenu -i \
- -l 20 \
- -p 'Select Torrent:' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff' \
- | cut -d\- -f1 \
- | awk '{$1=$1; print}')"
-[ -z "$line" ] && exit 0
-
-# 1. Magnet Page
-tailurl="$(head -n $line "$CACHEDIR/links.bw" | tail -n +$line)"
-curl -s "${BASEURL}${tailurl}/" > "$CACHEDIR/tmp.html"
-
-# 2. Magnet Link
-magnet="$(grep -Po 'magnet:\?xt=urn:btih:[a-zA-Z0-9]*' "$CACHEDIR/tmp.html" | head -n 1)"
-
-# 3. Magnet Action
-./rmt-action 'Torrent' "$magnet"
-
diff --git a/lib/search/Odysee b/lib/search/Odysee
deleted file mode 100755
index 58c900f..0000000
--- a/lib/search/Odysee
+++ /dev/null
@@ -1,10 +0,0 @@
-#! /bin/bash
-
-source ./util/menu/deasy
-
-query="$(deasy "$1" 'Search Odysee')"
-selected="$(./ext/ytfzf -D -L -cO "$query")"
-[ -z "$selected" ] && exit 0
-
-./rmt-action "$2" "$selected"
-
diff --git a/lib/search/PeerTube b/lib/search/PeerTube
deleted file mode 100755
index ce083f2..0000000
--- a/lib/search/PeerTube
+++ /dev/null
@@ -1,10 +0,0 @@
-#! /bin/bash
-
-source ./util/menu/deasy
-
-query="$(deasy "$1" 'Search PeerTube')"
-selected="$(./ext/ytfzf -D -L -cP "$query")"
-[ -z "$selected" ] && exit 0
-
-./rmt-action "$2" "$selected"
-
diff --git a/lib/search/Wallhaven b/lib/search/Wallhaven
deleted file mode 100755
index 458e0ef..0000000
--- a/lib/search/Wallhaven
+++ /dev/null
@@ -1,8 +0,0 @@
-#! /bin/bash
-
-source ./util/menu/deasy
-source ./util/dpath
-
-query="$(deasy "$1" 'Search Wallhaven')"
-./ext/waldl "$query" "$DPATH"
-
diff --git a/lib/search/YouTube b/lib/search/YouTube
deleted file mode 100755
index 43e3905..0000000
--- a/lib/search/YouTube
+++ /dev/null
@@ -1,19 +0,0 @@
-#! /bin/bash
-
-source ./util/menu/deasy
-
-query="$(deasy "$1" 'Search YouTube')"
-query_type='video'; CH_SUFFIX='\C'
-
-if [[ "$query" == *"$CH_SUFFIX" ]]
- then
- query=${query%"$CH_SUFFIX"}
- query_type='channel'
-fi
-
-[ -z "$query" ] && exit 0
-selected="$(./ext/ytfzf -D -L --type="$query_type" --ii='https://invidious.flokinet.to' "$query")"
-[ -z "$selected" ] && exit 0
-
-./rmt-action "$2" "$selected"
-
diff --git a/rmt-action b/rmt-action
index e3ec2ae..13924fb 100755
--- a/rmt-action
+++ b/rmt-action
@@ -1,70 +1,55 @@
-#! /bin/bash
-
-source ./util/notify
-
-run() {
- CHOICES=('Play' \
- 'Record' \
- 'Clip' \
- 'Download' \
- 'aDownload' \
- 'Torrent' \
- 'Open' \
- 'Clipboard')
-
- [ ! -z "$1" ] && selected="$1" \
- || selected="$(echo "${CHOICES[@]}" | tr ' ' '\n' \
- | dmenu -i \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
-
- case "$selected" in
- "${CHOICES[0]}")
- ./lib/action/"${CHOICES[0]}" "$2"
- ;;
-
- "${CHOICES[1]}")
- ./lib/action/"${CHOICES[1]}" "$2"
- ;;
-
- "${CHOICES[2]}")
- ./lib/action/"${CHOICES[2]}" "$2"
- ;;
-
- "${CHOICES[3]}")
- ./lib/action/"${CHOICES[3]}" "$2"
- ;;
-
- "${CHOICES[4]}")
- ./lib/action/"${CHOICES[4]}" "$2"
- ;;
-
- "${CHOICES[5]}")
- ./lib/action/"${CHOICES[5]}" "$2"
- ;;
-
- "${CHOICES[6]}")
- ./lib/action/"${CHOICES[6]}" "$2"
- ;;
-
- "${CHOICES[7]}")
- ./lib/action/"${CHOICES[7]}" "$2"
- ;;
-
- '')
- exit 0
- ;;
-
- *)
- notify 'Invalid Entry'
- ;;
- esac
-}
-
-run "$@"; status=$?
-
-[ ! $status -eq 0 ] && [ ! $status -eq 143 ] \
- && notify 'Something Went Wrong'
-
+#! /bin/zsh
+
+[ ! -z "$2" ] && url="$2" \
+ || url=$(printf '%s' "$(xclip -o -sel c)")
+
+[[ "$url" != *'http://'* ]] && [[ "$url" != 'https://'* ]] && exit 1
+
+CHOICES=('Play' 'Record' 'Clip' 'Download' 'aDownload' 'Open' 'Clipboard')
+
+[ ! -z "$1" ] && selected="$1" \
+ || selected="$(echo "${CHOICES[@]}" | tr ' ' '\n' | dmenu -i)"
+
+case "$selected" in
+ "${CHOICES[1]}")
+ [[ "$url" = *"&list="* ]] && playlist="$(echo "$url" | awk -F '&list=' '{print $2}')" \
+ && url="https://www.youtube.com/playlist?list=$playlist"
+ setsid -f mpv "$url" > /dev/null 2>&1 ;;
+ "${CHOICES[2]}")
+ setsid -f mpv --stream-record="$HOME/$(date '+%Y-%m-%d—%T.mkv')" "$url" > /dev/null 2>&1 ;;
+ "${CHOICES[3]}")
+ from="$(print | dmenu -p 'From (00:00/n)')"
+ [ -z "$from" ] && exit 1
+
+ to="$(print | dmenu -p 'To (00:00/n)')"
+ [ -z "$to" ] && exit 1
+
+ filename="$(print | dmenu -p 'Filename')"
+ [ -z "$filename" ] && exit 1
+
+ ffmpeg -n \
+ -ss "$from" \
+ -to "$to" \
+ -i "$(yt-dlp -f b --get-url "$url")" \
+ "$HOME/$filename.mp4" ;;
+ "${CHOICES[4]}")
+ yt-dlp -f 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' \
+ -o "$HOME/%(title)s.%(ext)s" \
+ --embed-chapters \
+ "$url" ;;
+ "${CHOICES[5]}")
+ yt-dlp -f 'bestaudio/best' \
+ -o "$HOME/%(title)s.%(ext)s" \
+ --embed-thumbnail \
+ --extract-audio \
+ --audio-format mp3 \
+ --embed-chapters \
+ "$url" ;;
+ "${CHOICES[6]}")
+ setsid -f xdg-open "$url" > /dev/null 2>&1 ;;
+ "${CHOICES[7]}")
+ print "$url" | xclip -r -sel c
+ print "$url" | xclip -r ;;
+ '') exit 0 ;;
+ *) exit 1 ;;
+esac
diff --git a/rmt-history b/rmt-history
deleted file mode 100755
index dd6880f..0000000
--- a/rmt-history
+++ /dev/null
@@ -1,15 +0,0 @@
-#! /bin/bash
-
-source ./util/hpath
-
-selected="$(echo "$(cat "$HPATH" | tac)" | dmenu -i \
- -l 20 \
- -p 'Select Link:' \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
-[ -z "$selected" ] && exit 0
-
-./rmt-action "$1" "$selected"
-
diff --git a/rmt-search b/rmt-search
index ecf3594..b5db078 100755
--- a/rmt-search
+++ b/rmt-search
@@ -1,46 +1,19 @@
-#! /bin/bash
+#! /bin/zsh
-CHOICES=('YouTube' \
- 'Odysee' \
- 'PeerTube' \
- '1337x' \
- 'Wallhaven')
+query_type='video'
+CH_SUFFIX='\C'
-[ ! -z "$1" ] && selected="$1" \
- || selected="$(echo "${CHOICES[@]}" | tr ' ' '\n' \
- | dmenu -i \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
+[ ! -z "$1" ] && query="$1" \
+ || query="$(print | dmenu -p 'Search Query')"
-case "$selected" in
- "${CHOICES[0]}")
- ./lib/search/"${CHOICES[0]}" "$2" "$3"
- ;;
+if [[ "$query" == *"$CH_SUFFIX" ]]
+ then
+ query=${query%"$CH_SUFFIX"}
+ query_type='channel'
+fi
- "${CHOICES[1]}")
- ./lib/search/"${CHOICES[1]}" "$2" "$3"
- ;;
-
- "${CHOICES[2]}")
- ./lib/search/"${CHOICES[2]}" "$2" "$3"
- ;;
-
- "${CHOICES[3]}")
- ./lib/search/"${CHOICES[3]}" "$2" "$3"
- ;;
-
- "${CHOICES[4]}")
- ./lib/search/"${CHOICES[4]}" "$2" "$3"
- ;;
-
- '')
- exit 0
- ;;
-
- *)
- ./lib/search/"${CHOICES[0]}" "$selected" "$2"
- ;;
-esac
+[ -z "$query" ] && exit 0
+selected="$(ytfzf -D -L --type="$query_type" \
+ --ii='https://invidious.flokinet.to' "$query")"
+[ ! -z "$selected" ] && rmt-action "$2" "$selected"
diff --git a/util/dpath b/util/dpath
deleted file mode 100755
index e981068..0000000
--- a/util/dpath
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /bin/bash
-
-DPATH="$HOME/Downloads"
-[ -d "$DPATH" ] || mkdir -p "$DPATH"
-
diff --git a/util/hpath b/util/hpath
deleted file mode 100755
index c8362aa..0000000
--- a/util/hpath
+++ /dev/null
@@ -1,11 +0,0 @@
-#! /bin/bash
-
-histdir() {
- CACHEDIR="$HOME/.cache/EZown"
- [ -d "$CACHEDIR" ] || mkdir -p "$CACHEDIR"
-
- echo "$CACHEDIR"
-}
-
-HPATH="$(histdir)/History"
-
diff --git a/util/menu/deasy b/util/menu/deasy
deleted file mode 100755
index a63fa74..0000000
--- a/util/menu/deasy
+++ /dev/null
@@ -1,12 +0,0 @@
-#! /bin/bash
-
-deasy() {
- source ./util/menu/deasy-core
-
- [ ! -z "$1" ] && query="$1" \
- || query="$(deasy_core "$2")"
- [ -z "$query" ] && kill -15 $$
-
- echo "$query"
-}
-
diff --git a/util/menu/deasy-core b/util/menu/deasy-core
deleted file mode 100755
index 36e88d3..0000000
--- a/util/menu/deasy-core
+++ /dev/null
@@ -1,10 +0,0 @@
-#! /bin/bash
-
-deasy_core() {
- echo "$(: | dmenu -p "$1:" \
- -nb '#ffffff' \
- -nf '#000080' \
- -sb '#000080' \
- -sf '#ffffff')"
-}
-
diff --git a/util/notify b/util/notify
deleted file mode 100755
index 81639de..0000000
--- a/util/notify
+++ /dev/null
@@ -1,6 +0,0 @@
-#! /bin/bash
-
-notify() {
- notify-send -t 4000 -i 'dialog-error' "$1 — Try Again!"
-}
-
diff --git a/util/verify/cbd-core b/util/verify/cbd-core
deleted file mode 100755
index 99dcbde..0000000
--- a/util/verify/cbd-core
+++ /dev/null
@@ -1,25 +0,0 @@
-#! /bin/bash
-
-source ./util/notify
-source ./util/hpath
-
-get_cbd() {
- echo "$(xclip -o)"
-}
-
-end_script() {
- notify "$1"
- kill -15 $$
-}
-
-url_pattern_match() {
- [[ "$1" != "$2"* ]] && end_script "$3" \
- || echo "$1" >> "$HPATH"
-}
-
-url_pattern_match_cbd() {
- [ -z "$3" ] && cbd="$(get_cbd)" || cbd="$3"
- url_pattern_match "$cbd" "$1" "$2"
- echo "$cbd"
-}
-
diff --git a/util/verify/cbd-https b/util/verify/cbd-https
deleted file mode 100755
index 4eefc8b..0000000
--- a/util/verify/cbd-https
+++ /dev/null
@@ -1,8 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-core
-
-is_https_url_cbd() {
- echo "$(url_pattern_match_cbd 'https://' 'Invalid URL' "$1")"
-}
-
diff --git a/util/verify/cbd-magnet b/util/verify/cbd-magnet
deleted file mode 100755
index 837bf87..0000000
--- a/util/verify/cbd-magnet
+++ /dev/null
@@ -1,8 +0,0 @@
-#! /bin/bash
-
-source ./util/verify/cbd-core
-
-is_magnet_url_cbd() {
- echo "$(url_pattern_match_cbd 'magnet:?xt=urn:btih:' 'Invalid Magnet URL' "$1")"
-}
-