Compare commits

...

21 Commits

Author SHA1 Message Date
Emre AKYÜZ
3e51924cac
Merge ae85e2223642cfaec66ae6c69753fd11cb036a4c into c43f390f07098c42db5efce654b07870951b512a 2024-10-22 14:50:54 -04:00
appeasementPolitik
c43f390f07
Fix sb-price after bash -> dash change (#1426) 2024-10-22 18:47:41 +00:00
appeasementPolitik
6f67789b8b
Clean up pipewire conf (#1427)
* Clean up pipewire.conf

* Needs extra newline
2024-10-22 18:46:41 +00:00
Rokosun
61026e18de
Fix dangerous copy/move script in lf (#1437)
Context: I accidentally pressed the C key on my .config directory and was presented with a list of directories to copy it to, then I pressed escape to quit the fzf menu without choosing anything - instead of doing nothing the script copied all of the contents inside my .config directory into my home directory. After dealing with that mess I decided to make this PR which does the following:

- Allow users to escape out of the fzf menu without unexpected copies
- Asks the user for confirmation before copying/moving files
- Some improvements in the UI
2024-10-22 18:34:33 +00:00
Emre AKYÜZ
ae85e22236
Update ybrowser 2024-07-02 01:54:11 +03:00
Emre AKYÜZ
37ffc69ce5
Update ybrowser 2024-07-02 01:53:57 +03:00
Emre AKYÜZ
a43657a52a
Update channelrefresh 2024-07-02 01:52:53 +03:00
Emre AKYÜZ
d5f31b599d
Small fix 2023-12-04 11:05:51 +03:00
Emre AKYÜZ
aa74c7a8cd
Minor improvements 2023-12-02 19:50:56 +03:00
Emre AKYÜZ
ce577053f4
Improve further. 2023-12-02 18:59:04 +03:00
Emre AKYÜZ
a7061f79bf
add some comments 2023-11-19 16:05:00 +03:00
Emre AKYÜZ
60866cad50
extra error handling 2023-11-19 15:55:28 +03:00
Emre AKYÜZ
996af9904e
Check for dependencies && Install them if needed 2023-11-19 13:50:01 +03:00
Emre AKYÜZ
3ba7e6da11
Add robust error handling. 2023-11-19 02:37:42 +03:00
Emre AKYÜZ
e15f5ee6df
Add extra error handling 2023-11-19 02:15:32 +03:00
Emre AKYÜZ
2734857902
Use a better way to wait for the network. 2023-11-19 02:09:54 +03:00
Emre AKYÜZ
96b997e8cf
improve further 2023-10-17 23:34:06 +03:00
Emre AKYÜZ
4fc941b9f1
Update Channels & Notify 2023-09-29 11:53:53 +03:00
Emre AKYÜZ
9e2bf97977
Remove IFs & Nested Loops | Use DASH | Add Custom Lists 2023-09-29 10:58:29 +03:00
Emre AKYÜZ
395a277120
Add Approximate Date as Default 2023-05-30 10:59:59 +03:00
Emre AKYÜZ
8e7c55f586
The Ultimate Way of Browsing Channels
The script works extremely fast except the first time to update the whole data. It takes about 2 minutes to update the whole database with 80 different channels. You can set a cronjob for this. It's not a heavy work for the PC. It justs fetch the text data with yt-dlp. Video example below:

This script is a sophisticated and ingenious tool designed to streamline your YouTube experience by organizing and managing your favorite YouTube channels, allowing you to browse and watch videos directly within the script without ever visiting its website. You can assign the channels inside various categories such as "Tech", "Science", "Sports", etc. The videos can be played using the 'mpv' media player. Moreover, the script allows you to sort videos based on view count or duration; download videos; and even maintain a "Watch Later" list. If you combine this script with "SponsorBlock" lua script created for "mpv", then you will have the ultimate experience. SponsorBlock removes all sponsored segments in a video including intros, outros or similar unnecessary parts. It's normally a browser extension but is also available for "mpv".

No browsers, accounts, distractions, crappy algorithm and recommendations, advertisements, sponsors, intros, outros, fillers or empty spaces. We eliminate them all.

Required Programs: dmenu | mpv | jq | yt-dlp

FEATURES
1. Browse all videos from all channels you set at the same time. You can filter titles through dmenu.
2. Browse a channel's videos.
3. Select a channel either from the main menu or inside a Category.
4. Watch, Download or Put videos in a "Watch Later List".
5. Sort videos by view or duration. The default sort is upload date. The only problem is, we can't have the exact upload date, so we can't apply much more advanced filtering. It can be done but it makes fetching the data for the first time too slow.
6. The menus have a complex loop system. It always continues where you left off. The script doesn't close itself when you make a selection. So you don't have to run the script over and over again and get to where you left off. You can also press Escape to return to a prior menu.
7. You won't see the URLs or any unnecessary things inside dmenu. Just the titles.



JUSTIFICATION
This script is incredibly beneficial for those who seek a minimalist and focused approach to consuming content on YouTube. By providing a CLI-based interface (dmenu), the script reduces distractions and clutter that are commonly encountered on the Youtube website. It allows users to personalize their content consumption and manage channels more effectively. The script is also remarkably efficient and easy to navigate, providing a user-friendly experience that saves time and promotes productivity.

The script is organized into functions that each perform a specific task, such as updating channel data, retrieving video titles, playing videos, downloading videos, adding videos to the watch later list, and browsing all channels. These functions are called by the main script to provide the user with various options for navigating and interacting with the videos.

The script makes use of various Bash features such as associative arrays, shell redirection and piping, to simplify and streamline the code. It also uses conditionals and loops to handle different user input and error cases. Overall, this script is a powerful and flexible tool for browsing, watching, organizing YouTube channels, and it provides a great example of Bash usage to automate and streamline complex tasks.

DETAILED EXPLANATION
- The script begins by defining two associative arrays, CHANNELS and CATEGORIES, which store the YouTube channel names along with their respective URLs and categories. It then sets the directories for storing data and videos, and creates them if they do not already exist.
- The 'update_data' function updates the metadata for a given channel, while the 'update_all_channels' function updates metadata for all channels. The metadata includes the video title, URL, view count, and duration, which are extracted using 'yt-dlp' and 'jq' utilities.
- The 'get_videos' function retrieves the video titles from the metadata of a given channel, sorted by the specified criteria (if any). The 'video_url' function returns the URL of a video based on its title and channel name. The 'play_video' and 'download_video' functions use 'mpv' and 'yt-dlp', respectively, to play or download a video given its title and channel name.
- The 'add_to_watch_later' function appends the video title and channel name to a watch later list, while the 'play_watch_later' and 'delete_from_watch_later' functions play a video from the list or remove it, respectively.
- The 'get_all_videos' function retrieves all video titles from the metadata of all channels, sorted by the specified criteria. The 'browse_all_channels' function lets you browse through all channels and select a video to watch, download, or add to the watch later list.
- The main part of the script first prompts the user to update the database of channels. If the user chooses to do so, the 'update_all_channels' function is called. The script then presents the user with options to browse all channels, browse channels by category, or browse the watch later list. The script loops through these options until the user decides to exit.
2023-05-02 21:49:46 +00:00
6 changed files with 276 additions and 258 deletions

View File

@ -92,25 +92,37 @@ cmd delete ${{
}}
cmd moveto ${{
clear; tput cup $(($(tput lines)/3)); tput bold
set -f
clear; echo "Move to where?"
dest="$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs | fzf | sed 's|~|$HOME|')" &&
dest=$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' "${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs" | fzf --prompt 'Move to where? ' | sed 's|~|$HOME|')
[ -z "$dest" ] && exit
destpath=$(eval printf '%s' \"$dest\")
clear; tput cup $(($(tput lines)/3)); tput bold
echo "From:"
echo "$fx" | sed 's/^/ /'
printf "To:\n %s\n\n\tmove?[y/N]" "$destpath"
read -r ans
[ "$ans" != "y" ] && exit
for x in $fx; do
eval mv -iv \"$x\" \"$dest\"
mv -iv "$x" "$destpath"
done &&
notify-send "🚚 File(s) moved." "File(s) moved to $dest."
notify-send "🚚 File(s) moved." "File(s) moved to $destpath."
}}
cmd copyto ${{
clear; tput cup $(($(tput lines)/3)); tput bold
set -f
clear; echo "Copy to where?"
dest="$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs | fzf | sed 's|~|$HOME|')" &&
dest=$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' "${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs" | fzf --prompt 'Copy to where? ' | sed 's|~|$HOME|')
[ -z "$dest" ] && exit
destpath=$(eval printf '%s' \"$dest\")
clear; tput cup $(($(tput lines)/3)); tput bold
echo "From:"
echo "$fx" | sed 's/^/ /'
printf "To:\n %s\n\n\tcopy?[y/N]" "$destpath"
read -r ans
[ "$ans" != "y" ] && exit
for x in $fx; do
eval cp -ivr \"$x\" \"$dest\"
cp -ivr "$x" "$destpath"
done &&
notify-send "📋 File(s) copied." "File(s) copies to $dest."
notify-send "📋 File(s) copied." "File(s) copied to $destpath."
}}
cmd setbg "$1"

View File

@ -1,247 +0,0 @@
# Daemon config file for PipeWire version "0.3.40" #
#
# Copy and edit this file in /etc/pipewire for system-wide changes
# or in ~/.config/pipewire for local changes.
context.properties = {
## Configure properties in the system.
#library.name.system = support/libspa-support
#context.data-loop.library.name.system = support/libspa-support
#support.dbus = true
#link.max-buffers = 64
link.max-buffers = 16 # version < 3 clients can't handle more
#mem.warn-mlock = false
#mem.allow-mlock = true
#mem.mlock-all = false
#clock.power-of-two-quantum = true
#log.level = 2
#cpu.zero.denormals = true
core.daemon = true # listening for socket connections
core.name = pipewire-0 # core name and socket name
## Properties for the DSP configuration.
#default.clock.rate = 48000
#default.clock.allowed-rates = [ 48000 ]
#default.clock.quantum = 1024
#default.clock.min-quantum = 32
#default.clock.max-quantum = 8192
#default.video.width = 640
#default.video.height = 480
#default.video.rate.num = 25
#default.video.rate.denom = 1
#
# These overrides are only applied when running in a vm.
vm.overrides = {
default.clock.min-quantum = 1024
}
}
context.spa-libs = {
#<factory-name regex> = <library-name>
#
# Used to find spa factory names. It maps an spa factory name
# regular expression to a library name that should contain
# that factory.
#
audio.convert.* = audioconvert/libspa-audioconvert
api.alsa.* = alsa/libspa-alsa
api.v4l2.* = v4l2/libspa-v4l2
api.libcamera.* = libcamera/libspa-libcamera
api.bluez5.* = bluez5/libspa-bluez5
api.vulkan.* = vulkan/libspa-vulkan
api.jack.* = jack/libspa-jack
support.* = support/libspa-support
#videotestsrc = videotestsrc/libspa-videotestsrc
#audiotestsrc = audiotestsrc/libspa-audiotestsrc
}
context.modules = [
#{ name = <module-name>
# [ args = { <key> = <value> ... } ]
# [ flags = [ [ ifexists ] [ nofail ] ]
#}
#
# Loads a module with the given parameters.
# If ifexists is given, the module is ignored when it is not found.
# If nofail is given, module initialization failures are ignored.
#
# Uses RTKit to boost the data thread priority.
{ name = libpipewire-module-rtkit
args = {
#nice.level = -11
#rt.prio = 88
#rt.time.soft = 2000000
#rt.time.hard = 2000000
}
flags = [ ifexists nofail ]
}
# Set thread priorities without using RTKit.
#{ name = libpipewire-module-rt
# args = {
# nice.level = -11
# rt.prio = 88
# rt.time.soft = 2000000
# rt.time.hard = 2000000
# }
# flags = [ ifexists nofail ]
#}
# The native communication protocol.
{ name = libpipewire-module-protocol-native }
# The profile module. Allows application to access profiler
# and performance data. It provides an interface that is used
# by pw-top and pw-profiler.
{ name = libpipewire-module-profiler }
# Allows applications to create metadata objects. It creates
# a factory for Metadata objects.
{ name = libpipewire-module-metadata }
# Creates a factory for making devices that run in the
# context of the PipeWire server.
{ name = libpipewire-module-spa-device-factory }
# Creates a factory for making nodes that run in the
# context of the PipeWire server.
{ name = libpipewire-module-spa-node-factory }
# Allows creating nodes that run in the context of the
# client. Is used by all clients that want to provide
# data to PipeWire.
{ name = libpipewire-module-client-node }
# Allows creating devices that run in the context of the
# client. Is used by the session manager.
{ name = libpipewire-module-client-device }
# The portal module monitors the PID of the portal process
# and tags connections with the same PID as portal
# connections.
{ name = libpipewire-module-portal
flags = [ ifexists nofail ]
}
# The access module can perform access checks and block
# new clients.
{ name = libpipewire-module-access
args = {
# access.allowed to list an array of paths of allowed
# apps.
#access.allowed = [
# /usr/bin/pipewire-media-session
#]
# An array of rejected paths.
#access.rejected = [ ]
# An array of paths with restricted access.
#access.restricted = [ ]
# Anything not in the above lists gets assigned the
# access.force permission.
#access.force = flatpak
}
}
# Makes a factory for wrapping nodes in an adapter with a
# converter and resampler.
{ name = libpipewire-module-adapter }
# Makes a factory for creating links between ports.
{ name = libpipewire-module-link-factory }
# Provides factories to make session manager objects.
{ name = libpipewire-module-session-manager }
]
context.objects = [
#{ factory = <factory-name>
# [ args = { <key> = <value> ... } ]
# [ flags = [ [ nofail ] ]
#}
#
# Creates an object from a PipeWire factory with the given parameters.
# If nofail is given, errors are ignored (and no object is created).
#
#{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } }
#{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] }
#{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } }
#{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } }
#{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test } }
#{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } }
# A default dummy driver. This handles nodes marked with the "node.always-driver"
# property when no other driver is currently active. JACK clients need this.
{ factory = spa-node-factory
args = {
factory.name = support.node.driver
node.name = Dummy-Driver
node.group = pipewire.dummy
priority.driver = 20000
}
}
{ factory = spa-node-factory
args = {
factory.name = support.node.driver
node.name = Freewheel-Driver
priority.driver = 19000
node.group = pipewire.freewheel
node.freewheel = true
}
}
# This creates a new Source node. It will have input ports
# that you can link, to provide audio for this source.
#{ factory = adapter
# args = {
# factory.name = support.null-audio-sink
# node.name = "my-mic"
# node.description = "Microphone"
# media.class = "Audio/Source/Virtual"
# audio.position = "FL,FR"
# }
#}
# This creates a single PCM source device for the given
# alsa device path hw:0. You can change source to sink
# to make a sink in the same way.
#{ factory = adapter
# args = {
# factory.name = api.alsa.pcm.source
# node.name = "alsa-source"
# node.description = "PCM Source"
# media.class = "Audio/Source"
# api.alsa.path = "hw:0"
# api.alsa.period-size = 1024
# api.alsa.headroom = 0
# api.alsa.disable-mmap = false
# api.alsa.disable-batch = false
# audio.format = "S16LE"
# audio.rate = 48000
# audio.channels = 2
# audio.position = "FL,FR"
# }
#}
]
context.exec = [
#{ path = <program-name> [ args = "<arguments>" ] }
#
# Execute the given program with arguments.
#
# You can optionally start the session manager here,
# but it is better to start it as a systemd service.
# Run the session manager with -h for options.
#
{ path = "/usr/bin/wireplumber" args = "" }
#
# You can optionally start the pulseaudio-server here as well
# but it is better to start it as a systemd service.
# It can be interesting to start another daemon here that listens
# on another address with the -a option (eg. -a tcp:4713).
#
{ path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf" }
]

View File

@ -0,0 +1,4 @@
context.exec = [
{ path = "/usr/bin/wireplumber" args = "" condition = [ { exec.session-manager = null } { exec.session-manager = true } ] }
{ path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf" condition = [ { exec.pipewire-pulse = null } { exec.pipewire-pulse = true } ] }
]

61
.local/bin/channelrefresh Normal file
View File

@ -0,0 +1,61 @@
#!/bin/dash
while ! ping -c 1 "9.9.9.9"; do sleep "0.5"; done
dd="${XDG_CACHE_HOME}/ychannels"
chl="${XDG_DATA_HOME}/channels"
ctg="${XDG_DATA_HOME}/categories"
n() { notify-send -i "${XDG_CONFIG_HOME}/dunst/yt.png" -u "critical" "${1}"; }
eh() {
[ ! -s "${chl}" ] && [ ! -s "${ctg}" ] && {
printf "%s\n" \
"Luke Smith=https://www.youtube.com/@LukeSmithxyz/videos" \
"Mental Outlaw=https://www.youtube.com/@MentalOutlaw/videos" \
> "${chl}"
printf "%s\n" "Tech=Luke Smith|Mental Outlaw" > "${ctg}"
sudo pacman -S --needed --noconfirm mpv yt-dlp jq || n "Failed to install mpv jq yt-dlp"
}
}
mkdir -p "${dd}" && touch "${chl}"
comd() {
chn="${1}"
df="${dd}/${chn}.tsv"
odf="${dd}/${chn}_old.tsv"
[ -f "${odf}" ] && {
ou="$(cut -f2 "${odf}")"
nu="$(cut -f2 "${df}")"
printf "%s\n" "${ou}" | sort > "t1"
printf "%s\n" "${nu}" | sort > "t2"
nv="$(comm -13 "t1" "t2" | wc -l)"
rm -f "t1" "t2"
[ "${nv}" -gt "0" ] && n "${chn} | ${nv} videos."
}
}
ud() {
chn="${1}"
cu="${2}"
df="${dd}/${chn}.tsv"
odf="${dd}/${chn}_old.tsv"
mv -f "${df}" "${odf}" 2> "/dev/null"
yt-dlp -j --flat-playlist --skip-download --extractor-args \
"youtubetab:approximate_date" "${cu}" |
jq -r '[.title, .url, .view_count, .duration, .upload_date] | @tsv' > "${df}"
}
uac() {
while IFS="=" read -r chn cu; do
ud "${chn}" "${cu}" &
done < "${chl}"
wait
while IFS="=" read -r chn cu; do
comd "${chn}"
done < "${chl}"
avf="${dd}/all_videos.tsv"
rm -f "${avf}"
while IFS= read -r line; do
cn="${line%%=*}"
cat "${dd}/${cn}.tsv" >> "${avf}"
done < "${chl}"
}
eh
uac

View File

@ -28,7 +28,9 @@ filestat="$(stat -c %x "$pricefile" 2>/dev/null)"
[ -d "$dir" ] || mkdir -p "$dir"
updateprice() { curl -sf -m 1 --fail-early $denom.$url/{1$target,$target$interval} --output "$pricefile" --output "$chartfile" ||
updateprice() { curl -sf \
--fail-early "${denom}.${url}/1${target}" "${denom}.${url}/${target}${interval}" \
--output "$pricefile" --output "$chartfile" ||
rm -f "$pricefile" "$chartfile" ;}
[ "${filestat%% *}" != "$(date '+%Y-%m-%d')" ] &&

186
.local/bin/ybrowser Normal file
View File

@ -0,0 +1,186 @@
#!/bin/sh
dd="${XDG_CACHE_HOME}/ychannels"
yd="${HOME}/ytvideos"
cl="${XDG_DATA_HOME}/categories"
chl="${XDG_DATA_HOME}/channels"
cld="${XDG_DATA_HOME}/clists"
mkdir -p "${dd}" "${yd}" "${cld}"
pr() { printf "%s\n" "${@}"; }
d() {
i="$(cat)"
l="$(pr "${i}" | wc -l)"
[ "${l}" -gt "21" ] && l="21"
[ "${i}" ] || l="0"
pr "${i}" | dmenu -i -l "${l}" -p "${1}"
}
n() { notify-send -i "${XDG_CONFIG_HOME}/dunst/yt.png" "${1}"; }
sv() {
df="${1}"
so="${2}"
case "${so}" in
"@@sv") sort -nr -t" " -k3 "${df}" ;;
"@@sd") sort -nr -t" " -k4 "${df}" ;;
*) sort -nr -t" " -k5 "${df}" ;;
esac | cut -f1
}
gv() {
cn="${1}"
so="${2}"
df="${dd}/${cn}.tsv"
sv "${df}" "${so}"
}
vu() {
cn="${1}"
vt="${2}"
df="${dd}/${cn}.tsv"
grep -F "${vt}" "${df}" | cut -f2
}
ma() {
pr "WATCH" "DOWNLOAD" "SEND TO A LIST" | d "Actions"
}
ca() {
lsts="$(pr "${cld}"/* | sed 's|.*/||')"
[ "${lsts}" = "*" ] && lsts=""
pr "${lsts}" "## CREATE LIST ##" "## DELETE LIST ##" | d "Lists"
}
lva() {
pr "WATCH" "DOWNLOAD" "DELETE" | d "Actions"
}
atl() {
vt="${1}"
cn="${2}"
ln="${3}"
pr "${cn}: ${vt}" >> "${cld}/${ln}"
}
clm() {
while true; do
lst="$(ca)"
case "${lst}" in
"## CREATE LIST ##")
nlst="$(pr | d "Name")"
[ "${nlst}" ] && touch "${cld}/${nlst}"
;;
"## DELETE LIST ##")
dlst="$(find "${cld}" -mindepth "1" -exec basename {} \; | d "Delete List")"
[ "${dlst}" ] && rm -f "${cld}/${dlst}"
;;
"") return ;;
*) cvm "${lst}" ;;
esac
done
}
cvm() {
ln="${1}"
while true; do
vi=$(d "Videos" < "${cld}/${ln}")
[ "${vi}" ] || return
cn="${vi%%: *}"
vt="${vi##*: }"
clvm "${vt}" "${cn}" "${ln}"
done
}
clvm() {
vt="${1}"
cn="${2}"
ln="${3}"
ac="$(lva)"
case "${ac}" in
"WATCH") vp "${ac}" "${vt}" "${cn}" ;;
"DOWNLOAD") vp "${ac}" "${vt}" "${cn}" && n "Download finished" ;;
"DELETE") sed -i "/${vt}/d" "${cld}/${ln}" ;;
*) return ;;
esac
}
vp() {
ac="${1}"
vt="${2}"
cn="${3}"
vu="$(vu "${cn}" "${vt}")"
case "${ac}" in
"WATCH") mpv "${vu}" ;;
"DOWNLOAD")
cdd="${yd}/${cn}"
mkdir -p "${cdd}"
yt-dlp -o "${cdd}/%(title)s.%(ext)s" "${vu}"
n "Download finished"
;;
esac
}
gav() {
so="${1}"
avf="${dd}/all_videos.tsv"
sv "${avf}" "${so}"
}
bac() {
while vt="$(gav | d "Videos | Sort: @@s{v,d}")"; do
[ "${vt}" ] || break
[ "${vt}" = "@@sv" ] || [ "${vt}" = "@@sd" ] && {
vt=$(gav "${vt}" | d "Videos")
[ "${vt}" ] || continue
}
grep -lF "${vt}" "${dd}"/*.tsv | head -n "1" | while read -r "vf"; do
vam "${vt}" "$(basename "${vf}" .tsv)"
break
done
done
}
cm() {
while true; do
c="$(cut -d= -f1 "${cl}" | d "Categories")"
[ "${c}" ] || return
chm "${c}"
done
}
chm() {
c="${1}"
IFS="|"
ch="$(sed -n "s/^${c}=\(.*\)$/\1/p" "${cl}")"
set -- ${ch}
while true; do
cn="$(pr "${@}" | d "Channels")"
[ "${cn}" ] || return
vm "${cn}"
done
}
vm() {
cn="${1}"
while true; do
vt=$(gv "${cn}" | d "Videos")
[ "${vt}" ] || return
[ "${vt}" = "@@sv" ] || [ "${vt}" = "@@sd" ] && {
so="${vt}"
vt="$(gv "${cn}" "${so}" | d "Videos")"
}
vam "${vt}" "${cn}"
done
}
vam() {
vt="${1}"
cn="${2}"
while [ "${vt}" ] && [ "${vt}" != "@@sv" ] && [ "${vt}" != "@@sd" ]; do
ac="$(ma)"
case "${ac}" in
"WATCH") vp "${ac}" "${vt}" "${cn}" ;;
"DOWNLOAD") vp "${ac}" "${vt}" "${cn}" && n "Download finished" ;;
"SEND TO A LIST")
ln="$(find "${cld}" -mindepth "1" -exec basename {} \; | d "Lists")"
[ "${ln}" ] && atl "${vt}" "${cn}" "${ln}" && n "${vt} > list: ${ln}"
;;
*) return ;;
esac
done
}
mm() {
pr " ## ALL CHANNELS ##" " ## CATEGORIES ##" " ## CUSTOM LISTS ##" \
"$(cut -d= -f1 "${chl}")" | d "YouTube"
}
while true; do
mc="$(mm)"
case "${mc}" in
" ## ALL CHANNELS ##") bac ;;
" ## CATEGORIES ##") cm ;;
" ## CUSTOM LISTS ##") clm ;;
"") exit ;;
*) vm "${mc}" ;;
esac
done