From 8e7c55f58611e5152771729d427223744a01a213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Tue, 2 May 2023 21:49:46 +0000 Subject: [PATCH 01/17] 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. --- .local/bin/yt-browser | 290 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 .local/bin/yt-browser diff --git a/.local/bin/yt-browser b/.local/bin/yt-browser new file mode 100644 index 0000000000..b588a29a95 --- /dev/null +++ b/.local/bin/yt-browser @@ -0,0 +1,290 @@ +#!/bin/bash + +declare -A CHANNELS +CHANNELS=( + ["Luke Smith"]="https://www.youtube.com/@LukeSmithxyz/videos" + ["Mental Outlaw"]="https://www.youtube.com/@MentalOutlaw/videos" + ["Sabine Hossenfelder"]="https://www.youtube.com/@SabineHossenfelder/videos" + ["Veritasium"]="https://www.youtube.com/@veritasium/videos" + # Add More Channels +) + +declare -A CATEGORIES +CATEGORIES=( + ["Tech"]="Luke Smith|Mental Outlaw" + ["Science"]="Sabine Hossenfelder|Veritasium" + # Add More Categories and Channels +) + +DATA_DIR="$HOME/.cache/youtube_channels" +DOWNLOAD_DIR="$HOME/ytvideos" +WATCH_LATER_LIST="${DATA_DIR}/watch_later.list" +mkdir -p "$DATA_DIR" "$DOWNLOAD_DIR" + +update_data() { + local channel_name="$1" + local channel_url="$2" + local data_file="${DATA_DIR}/${channel_name}.tsv" + + yt-dlp -j --flat-playlist --skip-download "$channel_url" | jq -r '[.title, .url, .view_count, .duration] | @tsv' > "$data_file" & +} + +update_all_channels() { + for channel_name in "${!CHANNELS[@]}"; do + update_data "$channel_name" "${CHANNELS[$channel_name]}" + done + wait +} + +get_videos() { + local channel_name="$1" + local sort_option="$2" + local data_file="${DATA_DIR}/${channel_name}.tsv" + + case $sort_option in + "@@sv") + sort -nr -t$'\t' -k3 "$data_file" | cut -f1 + ;; + "@@sd") + sort -nr -t$'\t' -k4 "$data_file" | cut -f1 + ;; + *) + cut -f1 "$data_file" + ;; + esac +} + +video_url() { + local channel_name="$1" + local video_title="$2" + local data_file="${DATA_DIR}/${channel_name}.tsv" + grep -F "$video_title" "$data_file" | cut -f2 +} + +play_video() { + local video_title="$1" + local channel_name="$2" + local video_url=$(video_url "$channel_name" "$video_title") + mpv "$video_url" +} + +download_video() { + local video_title="$1" + local channel_name="$2" + local video_url=$(video_url "$channel_name" "$video_title") + local channel_download_dir="${DOWNLOAD_DIR}/${channel_name}" + mkdir -p "$channel_download_dir" + yt-dlp -o "${channel_download_dir}/%(title)s.%(ext)s" "$video_url" +} + +add_to_watch_later() { + local video_title="$1" + local channel_name="$2" + echo "${channel_name}: ${video_title}" >> "$WATCH_LATER_LIST" +} + +play_watch_later() { + local line="$1" + local channel_name="${line%%: *}" + local video_title="${line#*: }" + play_video "$video_title" "$channel_name" +} + +delete_from_watch_later() { + local video_line="$1" + local tmp_list="${DATA_DIR}/watch_later_tmp.list" + grep -vF "$video_line" "$WATCH_LATER_LIST" > "$tmp_list" + mv "$tmp_list" "$WATCH_LATER_LIST" +} + +get_all_videos() { + local sort_option="$1" + local all_videos_file="${DATA_DIR}/all_videos.tsv" + + # Combine all channel video data into a single file + rm -f "$all_videos_file" + for channel_name in "${!CHANNELS[@]}"; do + cat "${DATA_DIR}/${channel_name}.tsv" >> "$all_videos_file" + done + + # Sort videos based on the specified criteria + case $sort_option in + "@@sv") + sort -nr -t$'\t' -k3 "$all_videos_file" | cut -f1 + ;; + "@@sd") + sort -nr -t$'\t' -k4 "$all_videos_file" | cut -f1 + ;; + *) + cut -f1 "$all_videos_file" + ;; + esac +} + +browse_all_channels() { + while true; do + video_title=$(get_all_videos | dmenu -i -l 20 -p "Choose a video or enter @@sv or @@sd to sort by view count or duration") + + if [[ -z "$video_title" ]]; then + break + elif [[ $video_title == "@@sv" ]] || [[ $video_title == "@@sd" ]]; then + sort_option="$video_title" + video_title=$(get_all_videos "$sort_option" | l 20 -dmenu -i -p "Choose a video") + fi + + if [[ -n "$video_title" ]] && [[ $video_title != "@@sv" ]] && [[ $video_title != "@@sd" ]]; then + for channel_name in "${!CHANNELS[@]}"; do + if grep -qF "$video_title" "${DATA_DIR}/${channel_name}.tsv"; then + break + fi + done + + action=$(echo -e "Watch\nDownload\nAdd to Watch Later" | dmenu -i -l 3 -p "Choose an action for the video") + + case $action in + Watch) + play_video "$video_title" "$channel_name" + ;; + Download) + download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." + ;; + "Add to Watch Later") + add_to_watch_later "$video_title" "$channel_name" + ;; + *) + echo "Invalid action" + ;; + esac + fi + done +} + +# Main script +update_choice=$(echo -e "No\nYes" | dmenu -i -l 2 -p "Do you want to update the database? (Yes/No)") +case $update_choice in + Yes) + update_all_channels + ;; + No) + ;; + *) + echo "Invalid option" + exit 1 + ;; +esac + +while true; do + main_choice=$(echo -e "All Channels\nCategories\nWatch Later List\n$(printf '%s\n' "${!CHANNELS[@]}")" | dmenu -i -l 20 -p "Choose an Option or a Channel") + + if [[ -z "$main_choice" ]]; then + break + elif [[ $main_choice == "All Channels" ]]; then + browse_all_channels + elif [[ $main_choice == "Categories" ]]; then + while true; do + category=$(echo -e "$(printf '%s\n' "${!CATEGORIES[@]}")" | dmenu -i -l 12 -p "Choose a category") + + if [[ -z "$category" ]]; then + break + fi + + while true; do + IFS="|" read -ra channels <<< "${CATEGORIES[$category]}" + channel_name=$(printf '%s\n' "${channels[@]}" | dmenu -i -l 20 -p "Choose a channel") + + if [[ -z "$channel_name" ]]; then + break + fi + + while true; do + video_title=$(get_videos "$channel_name" | dmenu -i -l 20 -p "Choose a video") + + if [[ -z "$video_title" ]]; then + break + elif [[ $video_title == "@@sv" ]] || [[ $video_title == "@@sd" ]]; then + sort_option="$video_title" + video_title=$(get_videos "$channel_name" "$sort_option" | dmenu -i -l 20 -p "Choose a video") + fi + + if [[ -n "$video_title" ]] && [[ $video_title != "@@sv" ]] && [[ $video_title != "@@sd" ]]; then + action=$(echo -e "Watch\nDownload\nAdd to Watch Later" | dmenu -i -l 3 -p "Choose an action for the video") + + case $action in + Watch) + play_video "$video_title" "$channel_name" + ;; + Download) + download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." + ;; + "Add to Watch Later") + add_to_watch_later "$video_title" "$channel_name" + ;; + *) + echo "Invalid action" + ;; + esac + fi + done + done + done + elif [[ $main_choice == "Watch Later List" ]]; then + while true; do + video_line=$(cat "$WATCH_LATER_LIST" | dmenu -i -l 5 -p "Choose a video from Watch Later List") + + if [[ -z "$video_line" ]]; then + break + fi + + action=$(echo -e "Watch\nDownload\nDelete" | dmenu -i -l 3 -p "Choose an action for the video") + + case $action in + Watch) + play_watch_later "$video_line" + ;; + Download) + channel_name="${video_line%%: *}" + video_title="${video_line#*: }" + download_video "$video_title" "$channel_name" + ;; + Delete) + delete_from_watch_later "$video_line" + ;; + *) + echo "Invalid action" + ;; + esac + done + else + channel_name="$main_choice" + + while true; do + video_title=$(get_videos "$channel_name" | dmenu -i -l 20 -p "Choose a video or enter @@sv or @@sd to sort by view count or duration") + + if [[ -z "$video_title" ]]; then + break + elif [[ $video_title == "@@sv" ]] || [[ $video_title == "@@sd" ]]; then + sort_option="$video_title" + video_title=$(get_videos "$channel_name" "$sort_option" | l 20 -dmenu -i -p "Choose a video") + fi + + if [[ -n "$video_title" ]] && [[ $video_title != "@@sv" ]] && [[ $video_title != "@@sd" ]]; then + action=$(echo -e "Watch\nDownload\nAdd to Watch Later" | dmenu -i -l 3 -p "Choose an action for the video") + + case $action in + Watch) + play_video "$video_title" "$channel_name" + ;; + Download) + download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." + ;; + "Add to Watch Later") + add_to_watch_later "$video_title" "$channel_name" + ;; + *) + echo "Invalid action" + ;; + esac + fi + done + fi +done From 395a277120fbbd148bf74ec650c5574aed48e93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Tue, 30 May 2023 10:59:59 +0300 Subject: [PATCH 02/17] Add Approximate Date as Default --- .local/bin/yt-browser | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.local/bin/yt-browser b/.local/bin/yt-browser index b588a29a95..ca3a648d30 100644 --- a/.local/bin/yt-browser +++ b/.local/bin/yt-browser @@ -26,7 +26,7 @@ update_data() { local channel_url="$2" local data_file="${DATA_DIR}/${channel_name}.tsv" - yt-dlp -j --flat-playlist --skip-download "$channel_url" | jq -r '[.title, .url, .view_count, .duration] | @tsv' > "$data_file" & +yt-dlp -j --flat-playlist --skip-download --extractor-args youtubetab:approximate_date "$channel_url" | jq -r '[.title, .url, .view_count, .duration, .upload_date] | @tsv' > "$data_file" & } update_all_channels() { @@ -49,7 +49,7 @@ get_videos() { sort -nr -t$'\t' -k4 "$data_file" | cut -f1 ;; *) - cut -f1 "$data_file" + sort -nr -t$'\t' -k5 "$data_file" | cut -f1 ;; esac } @@ -116,20 +116,20 @@ get_all_videos() { sort -nr -t$'\t' -k4 "$all_videos_file" | cut -f1 ;; *) - cut -f1 "$all_videos_file" + sort -nr -t$'\t' -k5 "$all_videos_file" | cut -f1 ;; esac } browse_all_channels() { while true; do - video_title=$(get_all_videos | dmenu -i -l 20 -p "Choose a video or enter @@sv or @@sd to sort by view count or duration") + video_title=$(get_all_videos | dmenu -i -l 20 -p "Choose a video or enter @@sv or @@sd") if [[ -z "$video_title" ]]; then break elif [[ $video_title == "@@sv" ]] || [[ $video_title == "@@sd" ]]; then sort_option="$video_title" - video_title=$(get_all_videos "$sort_option" | l 20 -dmenu -i -p "Choose a video") + video_title=$(get_all_videos "$sort_option" | dmenu -i -l 20 -dmenu -i -p "Choose a video") fi if [[ -n "$video_title" ]] && [[ $video_title != "@@sv" ]] && [[ $video_title != "@@sd" ]]; then @@ -152,7 +152,7 @@ browse_all_channels() { add_to_watch_later "$video_title" "$channel_name" ;; *) - echo "Invalid action" + continue ;; esac fi From 9e2bf97977caee3c29f6e282e98cb6bc9521a31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Fri, 29 Sep 2023 10:58:29 +0300 Subject: [PATCH 03/17] Remove IFs & Nested Loops | Use DASH | Add Custom Lists --- .local/bin/yt-browser | 433 +++++++++++++++++++----------------------- 1 file changed, 193 insertions(+), 240 deletions(-) diff --git a/.local/bin/yt-browser b/.local/bin/yt-browser index ca3a648d30..165cd69125 100644 --- a/.local/bin/yt-browser +++ b/.local/bin/yt-browser @@ -1,290 +1,243 @@ -#!/bin/bash - -declare -A CHANNELS -CHANNELS=( - ["Luke Smith"]="https://www.youtube.com/@LukeSmithxyz/videos" - ["Mental Outlaw"]="https://www.youtube.com/@MentalOutlaw/videos" - ["Sabine Hossenfelder"]="https://www.youtube.com/@SabineHossenfelder/videos" - ["Veritasium"]="https://www.youtube.com/@veritasium/videos" - # Add More Channels -) - -declare -A CATEGORIES -CATEGORIES=( - ["Tech"]="Luke Smith|Mental Outlaw" - ["Science"]="Sabine Hossenfelder|Veritasium" - # Add More Categories and Channels -) +#!/bin/dash DATA_DIR="$HOME/.cache/youtube_channels" DOWNLOAD_DIR="$HOME/ytvideos" -WATCH_LATER_LIST="${DATA_DIR}/watch_later.list" -mkdir -p "$DATA_DIR" "$DOWNLOAD_DIR" +CATEGORY_LIST="$HOME/.local/share/categories.txt" +CHANNEL_LIST="$HOME/.local/share/channels.txt" +CUSTOM_LIST_DIR="$DATA_DIR/custom_lists" -update_data() { - local channel_name="$1" - local channel_url="$2" - local data_file="${DATA_DIR}/${channel_name}.tsv" +mkdir -p "$DATA_DIR" "$DOWNLOAD_DIR" "$CUSTOM_LIST_DIR" -yt-dlp -j --flat-playlist --skip-download --extractor-args youtubetab:approximate_date "$channel_url" | jq -r '[.title, .url, .view_count, .duration, .upload_date] | @tsv' > "$data_file" & -} +sort_videos() { + local data_file="$1" + local sort_option="$2" -update_all_channels() { - for channel_name in "${!CHANNELS[@]}"; do - update_data "$channel_name" "${CHANNELS[$channel_name]}" - done - wait + case $sort_option in + "@@sv") sort -nr -t" " -k3 "$data_file" ;; + "@@sd") sort -nr -t" " -k4 "$data_file" ;; + *) sort -nr -t" " -k5 "$data_file" ;; + esac | cut -f1 } get_videos() { local channel_name="$1" local sort_option="$2" - local data_file="${DATA_DIR}/${channel_name}.tsv" - - case $sort_option in - "@@sv") - sort -nr -t$'\t' -k3 "$data_file" | cut -f1 - ;; - "@@sd") - sort -nr -t$'\t' -k4 "$data_file" | cut -f1 - ;; - *) - sort -nr -t$'\t' -k5 "$data_file" | cut -f1 - ;; - esac + local data_file="$DATA_DIR/$channel_name.tsv" + sort_videos "$data_file" "$sort_option" } video_url() { local channel_name="$1" local video_title="$2" - local data_file="${DATA_DIR}/${channel_name}.tsv" - grep -F "$video_title" "$data_file" | cut -f2 + local data_file="$DATA_DIR/$channel_name.tsv" + sed -n "s/$video_title\t\([^\t]*\)\t.*$/\1/p" "$data_file" } -play_video() { - local video_title="$1" - local channel_name="$2" - local video_url=$(video_url "$channel_name" "$video_title") - mpv "$video_url" +rofi_action() { + echo "Watch\nDownload\nSend To a List" | dmenu -i -l 3 -p "Choose an action for the video" } -download_video() { - local video_title="$1" - local channel_name="$2" - local video_url=$(video_url "$channel_name" "$video_title") - local channel_download_dir="${DOWNLOAD_DIR}/${channel_name}" - mkdir -p "$channel_download_dir" - yt-dlp -o "${channel_download_dir}/%(title)s.%(ext)s" "$video_url" +rofi_custom_list_action() { + echo "$(ls "$CUSTOM_LIST_DIR")\nCreate a List\nDelete a List" | dmenu -i -l 10 -p "Choose an action or list" } -add_to_watch_later() { +list_video_action() { + echo "Watch\nDownload\nDelete" | dmenu -i -l 3 -p "Choose an action for the video" +} + +add_to_list() { local video_title="$1" local channel_name="$2" - echo "${channel_name}: ${video_title}" >> "$WATCH_LATER_LIST" + local list_name="$3" + echo "$channel_name: $video_title" >> "$CUSTOM_LIST_DIR/$list_name" } -play_watch_later() { - local line="$1" - local channel_name="${line%%: *}" - local video_title="${line#*: }" - play_video "$video_title" "$channel_name" +custom_list_menu() { + while true; do + local list="$(rofi_custom_list_action)" + [ -z "$list" ] && return + case "$list" in + "Create a List") + local new_list=$(dmenu -i -l 0 -p "Enter the name of the new list") + [ -n "$new_list" ] && touch "$CUSTOM_LIST_DIR/$new_list" + ;; + "Delete a List") + local delete_list=$(ls "$CUSTOM_LIST_DIR" | dmenu -i -l 10 -p "Choose a list to delete") + [ -n "$delete_list" ] && rm "$CUSTOM_LIST_DIR/$delete_list" + ;; + *) + custom_list_video_menu "$list" + ;; + esac + done } -delete_from_watch_later() { - local video_line="$1" - local tmp_list="${DATA_DIR}/watch_later_tmp.list" - grep -vF "$video_line" "$WATCH_LATER_LIST" > "$tmp_list" - mv "$tmp_list" "$WATCH_LATER_LIST" +custom_list_video_menu() { + local list_name="$1" + local video_info + local video_title + local channel_name + while true; do + video_info=$(cat "$CUSTOM_LIST_DIR/$list_name" | dmenu -i -l 20 -p "Choose a video") + [ -z "$video_info" ] && return + channel_name="${video_info%%: *}" + video_title="${video_info##*: }" + custom_list_video_action_menu "$video_title" "$channel_name" "$list_name" + done } -get_all_videos() { - local sort_option="$1" - local all_videos_file="${DATA_DIR}/all_videos.tsv" +custom_list_video_action_menu() { + local video_title="$1" + local channel_name="$2" + local list_name="$3" - # Combine all channel video data into a single file - rm -f "$all_videos_file" - for channel_name in "${!CHANNELS[@]}"; do - cat "${DATA_DIR}/${channel_name}.tsv" >> "$all_videos_file" - done + local action + action=$(list_video_action) - # Sort videos based on the specified criteria - case $sort_option in - "@@sv") - sort -nr -t$'\t' -k3 "$all_videos_file" | cut -f1 + case $action in + Watch) + play_video "$video_title" "$channel_name" + ;; + Download) + download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." ;; - "@@sd") - sort -nr -t$'\t' -k4 "$all_videos_file" | cut -f1 + Delete) + escaped_video_title="$(echo "$video_title" | tr -d '|')" + sed -i "/$escaped_video_title/d" "$CUSTOM_LIST_DIR/$list_name" ;; *) - sort -nr -t$'\t' -k5 "$all_videos_file" | cut -f1 + return ;; esac } +play_video() { + local video_title="$1" + local channel_name="$2" + local video_url="$(video_url "$channel_name" "$video_title")" + mpv "$video_url" +} + +download_video() { + local video_title="$1" + local channel_name="$2" + local video_url=$(video_url "$channel_name" "$video_title") + local channel_download_dir="$DOWNLOAD_DIR/$channel_name" + mkdir -p "$channel_download_dir" + yt-dlp -o "$channel_download_dir/%(title)s.%(ext)s" "$video_url" +} + +get_all_videos() { + local sort_option="$1" + local all_videos_file="$DATA_DIR/all_videos.tsv" + + rm -f "$all_videos_file" + while IFS= read -r line + do + local channel_name="${line%%=*}" + cat "$DATA_DIR/$channel_name.tsv" >> "$all_videos_file" + done < "$CHANNEL_LIST" + + sort_videos "$all_videos_file" "$sort_option" +} + browse_all_channels() { while true; do video_title=$(get_all_videos | dmenu -i -l 20 -p "Choose a video or enter @@sv or @@sd") - if [[ -z "$video_title" ]]; then - break - elif [[ $video_title == "@@sv" ]] || [[ $video_title == "@@sd" ]]; then + [ -z "$video_title" ] && break || { + [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && sort_option="$video_title" && video_title=$(get_all_videos "$sort_option" | i -l 20 -dmenu -i -p "Choose a video") + } + + [ -n "$video_title" -a "$video_title" != "@@sv" -a "$video_title" != "@@sd" ] && { + while read -r line; do + channel_name="${line%%=*}" + grep -qF "$video_title" "${DATA_DIR}/${channel_name}.tsv" && break + done < "$CHANNEL_LIST" + + video_action_menu "$video_title" "$channel_name" + } + done +} + +category_menu() { + while true; do + local category=$(echo "$(cut -d= -f1 "$CATEGORY_LIST")" | dmenu -i -l 12 -p "Choose a category") + [ -z "$category" ] && return + + channel_menu "$category" + done +} + +channel_menu() { + local category="$1" + local channels + local channel_name + local IFS="|" + + channels=$(sed -n "s/^${category}=\(.*\)$/\1/p" "$CATEGORY_LIST") + set -- $channels + + while true; do + channel_name=$(printf '%s\n' "$@" | dmenu -i -l 20 -p "Choose a channel") + [ -z "$channel_name" ] && return + + video_menu "$channel_name" + done +} + +video_menu() { + local channel_name="$1" + local video_title + local sort_option + + while true; do + video_title=$(get_videos "$channel_name" | dmenu -i -l 20 -p "Choose a video") + [ -z "$video_title" ] && return + [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && { sort_option="$video_title" - video_title=$(get_all_videos "$sort_option" | dmenu -i -l 20 -dmenu -i -p "Choose a video") - fi - - if [[ -n "$video_title" ]] && [[ $video_title != "@@sv" ]] && [[ $video_title != "@@sd" ]]; then - for channel_name in "${!CHANNELS[@]}"; do - if grep -qF "$video_title" "${DATA_DIR}/${channel_name}.tsv"; then - break - fi - done - - action=$(echo -e "Watch\nDownload\nAdd to Watch Later" | dmenu -i -l 3 -p "Choose an action for the video") - - case $action in - Watch) - play_video "$video_title" "$channel_name" - ;; - Download) - download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." - ;; - "Add to Watch Later") - add_to_watch_later "$video_title" "$channel_name" - ;; - *) - continue - ;; - esac - fi + video_title=$(get_videos "$channel_name" "$sort_option" | dmenu -i -l 20 -p "Choose a video") + } + + video_action_menu "$video_title" "$channel_name" done } -# Main script -update_choice=$(echo -e "No\nYes" | dmenu -i -l 2 -p "Do you want to update the database? (Yes/No)") -case $update_choice in - Yes) - update_all_channels - ;; - No) - ;; - *) - echo "Invalid option" - exit 1 - ;; -esac +video_action_menu() { + local video_title="$1" + local channel_name="$2" + + while [ -n "$video_title" ] && [ "$video_title" != "@@sv" ] && [ "$video_title" != "@@sd" ]; do + local action + action=$(rofi_action) + + case $action in + Watch) + play_video "$video_title" "$channel_name" + ;; + Download) + download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." + ;; + "Send To a List") + list_name=$(ls "$CUSTOM_LIST_DIR" | dmenu -i -l 10 -p "Choose a list") + [ -n "$list_name" ] && add_to_list "$video_title" "$channel_name" "$list_name" + ;; + *) + return + ;; + esac + done +} while true; do - main_choice=$(echo -e "All Channels\nCategories\nWatch Later List\n$(printf '%s\n' "${!CHANNELS[@]}")" | dmenu -i -l 20 -p "Choose an Option or a Channel") - - if [[ -z "$main_choice" ]]; then - break - elif [[ $main_choice == "All Channels" ]]; then - browse_all_channels - elif [[ $main_choice == "Categories" ]]; then - while true; do - category=$(echo -e "$(printf '%s\n' "${!CATEGORIES[@]}")" | dmenu -i -l 12 -p "Choose a category") - - if [[ -z "$category" ]]; then - break - fi - - while true; do - IFS="|" read -ra channels <<< "${CATEGORIES[$category]}" - channel_name=$(printf '%s\n' "${channels[@]}" | dmenu -i -l 20 -p "Choose a channel") - - if [[ -z "$channel_name" ]]; then - break - fi - - while true; do - video_title=$(get_videos "$channel_name" | dmenu -i -l 20 -p "Choose a video") - - if [[ -z "$video_title" ]]; then - break - elif [[ $video_title == "@@sv" ]] || [[ $video_title == "@@sd" ]]; then - sort_option="$video_title" - video_title=$(get_videos "$channel_name" "$sort_option" | dmenu -i -l 20 -p "Choose a video") - fi - - if [[ -n "$video_title" ]] && [[ $video_title != "@@sv" ]] && [[ $video_title != "@@sd" ]]; then - action=$(echo -e "Watch\nDownload\nAdd to Watch Later" | dmenu -i -l 3 -p "Choose an action for the video") - - case $action in - Watch) - play_video "$video_title" "$channel_name" - ;; - Download) - download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." - ;; - "Add to Watch Later") - add_to_watch_later "$video_title" "$channel_name" - ;; - *) - echo "Invalid action" - ;; - esac - fi - done - done - done - elif [[ $main_choice == "Watch Later List" ]]; then - while true; do - video_line=$(cat "$WATCH_LATER_LIST" | dmenu -i -l 5 -p "Choose a video from Watch Later List") - - if [[ -z "$video_line" ]]; then - break - fi - - action=$(echo -e "Watch\nDownload\nDelete" | dmenu -i -l 3 -p "Choose an action for the video") - - case $action in - Watch) - play_watch_later "$video_line" - ;; - Download) - channel_name="${video_line%%: *}" - video_title="${video_line#*: }" - download_video "$video_title" "$channel_name" - ;; - Delete) - delete_from_watch_later "$video_line" - ;; - *) - echo "Invalid action" - ;; - esac - done - else - channel_name="$main_choice" - - while true; do - video_title=$(get_videos "$channel_name" | dmenu -i -l 20 -p "Choose a video or enter @@sv or @@sd to sort by view count or duration") - - if [[ -z "$video_title" ]]; then - break - elif [[ $video_title == "@@sv" ]] || [[ $video_title == "@@sd" ]]; then - sort_option="$video_title" - video_title=$(get_videos "$channel_name" "$sort_option" | l 20 -dmenu -i -p "Choose a video") - fi - - if [[ -n "$video_title" ]] && [[ $video_title != "@@sv" ]] && [[ $video_title != "@@sd" ]]; then - action=$(echo -e "Watch\nDownload\nAdd to Watch Later" | dmenu -i -l 3 -p "Choose an action for the video") - - case $action in - Watch) - play_video "$video_title" "$channel_name" - ;; - Download) - download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." - ;; - "Add to Watch Later") - add_to_watch_later "$video_title" "$channel_name" - ;; - *) - echo "Invalid action" - ;; - esac - fi - done - fi + main_choice=$(echo "All Channels\nCategories\nCustom Lists\n$(cut -d= -f1 "$CHANNEL_LIST")" | dmenu -i -l 20 -p "Choose an Option or a Category") + + [ -z "$main_choice" ] && exit + + case "$main_choice" in + "All Channels") browse_all_channels ;; + "Categories") category_menu ;; + "Custom Lists") custom_list_menu ;; + *) video_menu "$main_choice" ;; + esac done From 4fc941b9f12e297909f08c2fe33db0932de3d287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Fri, 29 Sep 2023 11:53:53 +0300 Subject: [PATCH 04/17] Update Channels & Notify --- .local/bin/channelrefresh | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .local/bin/channelrefresh diff --git a/.local/bin/channelrefresh b/.local/bin/channelrefresh new file mode 100644 index 0000000000..8e4786e327 --- /dev/null +++ b/.local/bin/channelrefresh @@ -0,0 +1,50 @@ +#!/bin/dash + +sleep 16 + +DATA_DIR="$HOME/.cache/youtube_channels" +CHANNEL_LIST="$HOME/.local/share/channels.txt" +mkdir -p "$DATA_DIR" + +compare_data() { + local channel_name="$1" + local data_file="${DATA_DIR}/${channel_name}.tsv" + local old_data_file="${DATA_DIR}/${channel_name}_old.tsv" + + [ -e "$old_data_file" ] && { + old_urls=$(cut -f2 "$old_data_file") + new_urls=$(cut -f2 "$data_file") + + echo "$old_urls" | sort > temp1 + echo "$new_urls" | sort > temp2 + new_videos=$(comm -13 temp1 temp2 | wc -l) + rm temp1 temp2 + + [ "$new_videos" -gt 0 ] && notify-send -u critical "$channel_name | $new_videos videos" + } +} + +update_data() { + local channel_name="$1" + local channel_url="$2" + local data_file="${DATA_DIR}/${channel_name}.tsv" + local old_data_file="${DATA_DIR}/${channel_name}_old.tsv" + + mv "$data_file" "$old_data_file" 2>/dev/null + + yt-dlp -j --flat-playlist --skip-download --extractor-args youtubetab:approximate_date "$channel_url" | jq -r '[.title, .url, .view_count, .duration, .upload_date] | @tsv' > "$data_file" +} + +update_all_channels() { + while IFS="=" read -r channel_name channel_url; do + update_data "$channel_name" "$channel_url" & + done < "$CHANNEL_LIST" + + wait + + while IFS="=" read -r channel_name channel_url; do + compare_data "$channel_name" + done < "$CHANNEL_LIST" +} + +update_all_channels From 96b997e8cf92134e5f8f7188a3b96ec300523b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Tue, 17 Oct 2023 23:34:06 +0300 Subject: [PATCH 05/17] improve further --- .local/bin/yt-browser | 174 +++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 80 deletions(-) diff --git a/.local/bin/yt-browser b/.local/bin/yt-browser index 165cd69125..e7cf932adc 100644 --- a/.local/bin/yt-browser +++ b/.local/bin/yt-browser @@ -8,9 +8,13 @@ CUSTOM_LIST_DIR="$DATA_DIR/custom_lists" mkdir -p "$DATA_DIR" "$DOWNLOAD_DIR" "$CUSTOM_LIST_DIR" +DMENU() { + dmenu -i -l $1 -p "$2" +} + sort_videos() { - local data_file="$1" - local sort_option="$2" + data_file="$1" + sort_option="$2" case $sort_option in "@@sv") sort -nr -t" " -k3 "$data_file" ;; @@ -20,49 +24,52 @@ sort_videos() { } get_videos() { - local channel_name="$1" - local sort_option="$2" - local data_file="$DATA_DIR/$channel_name.tsv" + channel_name="$1" + sort_option="$2" + data_file="$DATA_DIR/$channel_name.tsv" sort_videos "$data_file" "$sort_option" } video_url() { - local channel_name="$1" - local video_title="$2" - local data_file="$DATA_DIR/$channel_name.tsv" + channel_name="$1" + video_title="$2" + data_file="$DATA_DIR/$channel_name.tsv" sed -n "s/$video_title\t\([^\t]*\)\t.*$/\1/p" "$data_file" } rofi_action() { - echo "Watch\nDownload\nSend To a List" | dmenu -i -l 3 -p "Choose an action for the video" + echo "WATCH\nDOWNLOAD\nSEND TO A LIST" | DMENU 3 "Choose an action for the video" } rofi_custom_list_action() { - echo "$(ls "$CUSTOM_LIST_DIR")\nCreate a List\nDelete a List" | dmenu -i -l 10 -p "Choose an action or list" + echo "$(find "$CUSTOM_LIST_DIR" -maxdepth 1 -type f -exec basename {} \;) +#### CREATE A LIST #### +#### DELETE A LIST ####" | DMENU 10 "Choose an action or list" } list_video_action() { - echo "Watch\nDownload\nDelete" | dmenu -i -l 3 -p "Choose an action for the video" + echo "WATCH\nDOWNLOAD\nDELETE" | DMENU 3 "Choose an action for the video" } add_to_list() { - local video_title="$1" - local channel_name="$2" - local list_name="$3" + video_title="$1" + channel_name="$2" + list_name="$3" echo "$channel_name: $video_title" >> "$CUSTOM_LIST_DIR/$list_name" } custom_list_menu() { while true; do - local list="$(rofi_custom_list_action)" + list="$(rofi_custom_list_action)" [ -z "$list" ] && return case "$list" in - "Create a List") - local new_list=$(dmenu -i -l 0 -p "Enter the name of the new list") + *CREATE*) + new_list=$(DMENU 0 "Enter the name of the new list") [ -n "$new_list" ] && touch "$CUSTOM_LIST_DIR/$new_list" ;; - "Delete a List") - local delete_list=$(ls "$CUSTOM_LIST_DIR" | dmenu -i -l 10 -p "Choose a list to delete") + *DELETE*) + delete_list=$(find "$CUSTOM_LIST_DIR" -maxdepth 1 -type f -exec basename {} \; | + DMENU 10 "Choose a list to delete") [ -n "$delete_list" ] && rm "$CUSTOM_LIST_DIR/$delete_list" ;; *) @@ -73,12 +80,9 @@ custom_list_menu() { } custom_list_video_menu() { - local list_name="$1" - local video_info - local video_title - local channel_name + list_name="$1" while true; do - video_info=$(cat "$CUSTOM_LIST_DIR/$list_name" | dmenu -i -l 20 -p "Choose a video") + video_info=$(cat "$CUSTOM_LIST_DIR/$list_name" | DMENU 20 "Choose a video") [ -z "$video_info" ] && return channel_name="${video_info%%: *}" video_title="${video_info##*: }" @@ -87,23 +91,23 @@ custom_list_video_menu() { } custom_list_video_action_menu() { - local video_title="$1" - local channel_name="$2" - local list_name="$3" + video_title="$1" + channel_name="$2" + list_name="$3" - local action action=$(list_video_action) case $action in - Watch) - play_video "$video_title" "$channel_name" + WATCH) + video_process WATCH "$video_title" "$channel_name" ;; - Download) - download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." + DOWNLOAD) + video_process DOWNLOAD "$video_title" "$channel_name" && notify-send "Downloading has finished." ;; - Delete) - escaped_video_title="$(echo "$video_title" | tr -d '|')" - sed -i "/$escaped_video_title/d" "$CUSTOM_LIST_DIR/$list_name" + DELETE) + escaped_video_title="$(echo "$video_title" | sed 's/[][\^$.\/|*+?(){}#]/\\&/g')" + sed "/$escaped_video_title/d" "$CUSTOM_LIST_DIR/$list_name" > "$CUSTOM_LIST_DIR/${list_name}.tmp" && + mv "$CUSTOM_LIST_DIR/${list_name}.tmp" "$CUSTOM_LIST_DIR/$list_name" ;; *) return @@ -111,30 +115,33 @@ custom_list_video_action_menu() { esac } -play_video() { - local video_title="$1" - local channel_name="$2" - local video_url="$(video_url "$channel_name" "$video_title")" - mpv "$video_url" -} - -download_video() { - local video_title="$1" - local channel_name="$2" - local video_url=$(video_url "$channel_name" "$video_title") - local channel_download_dir="$DOWNLOAD_DIR/$channel_name" - mkdir -p "$channel_download_dir" - yt-dlp -o "$channel_download_dir/%(title)s.%(ext)s" "$video_url" +video_process() { + action="$1" + video_title="$2" + channel_name="$3" + video_url=$(video_url "$channel_name" "$video_title") + + case "$action" in + WATCH) + mpv "$video_url" + ;; + DOWNLOAD) + local channel_download_dir="$DOWNLOAD_DIR/$channel_name" + mkdir -p "$channel_download_dir" + yt-dlp -o "$channel_download_dir/%(title)s.%(ext)s" "$video_url" + notify-send "Downloading has finished." + ;; + esac } get_all_videos() { - local sort_option="$1" - local all_videos_file="$DATA_DIR/all_videos.tsv" + sort_option="$1" + all_videos_file="$DATA_DIR/all_videos.tsv" rm -f "$all_videos_file" while IFS= read -r line do - local channel_name="${line%%=*}" + channel_name="${line%%=*}" cat "$DATA_DIR/$channel_name.tsv" >> "$all_videos_file" done < "$CHANNEL_LIST" @@ -143,10 +150,11 @@ get_all_videos() { browse_all_channels() { while true; do - video_title=$(get_all_videos | dmenu -i -l 20 -p "Choose a video or enter @@sv or @@sd") + video_title=$(get_all_videos | DMENU 20 "Choose a video or enter @@sv or @@sd") [ -z "$video_title" ] && break || { - [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && sort_option="$video_title" && video_title=$(get_all_videos "$sort_option" | i -l 20 -dmenu -i -p "Choose a video") + [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && + sort_option="$video_title" && video_title=$(get_all_videos "$sort_option" | DMENU 20 "Choose a video") } [ -n "$video_title" -a "$video_title" != "@@sv" -a "$video_title" != "@@sd" ] && { @@ -162,7 +170,7 @@ browse_all_channels() { category_menu() { while true; do - local category=$(echo "$(cut -d= -f1 "$CATEGORY_LIST")" | dmenu -i -l 12 -p "Choose a category") + category=$(echo "$(cut -d= -f1 "$CATEGORY_LIST")" | DMENU 12 "Choose a category") [ -z "$category" ] && return channel_menu "$category" @@ -170,16 +178,14 @@ category_menu() { } channel_menu() { - local category="$1" - local channels - local channel_name - local IFS="|" + category="$1" + IFS="|" channels=$(sed -n "s/^${category}=\(.*\)$/\1/p" "$CATEGORY_LIST") set -- $channels while true; do - channel_name=$(printf '%s\n' "$@" | dmenu -i -l 20 -p "Choose a channel") + channel_name=$(printf '%s\n' "$@" | DMENU 20 "Choose a channel") [ -z "$channel_name" ] && return video_menu "$channel_name" @@ -187,16 +193,14 @@ channel_menu() { } video_menu() { - local channel_name="$1" - local video_title - local sort_option + channel_name="$1" while true; do - video_title=$(get_videos "$channel_name" | dmenu -i -l 20 -p "Choose a video") + video_title=$(get_videos "$channel_name" | DMENU 20 "Choose a video") [ -z "$video_title" ] && return [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && { sort_option="$video_title" - video_title=$(get_videos "$channel_name" "$sort_option" | dmenu -i -l 20 -p "Choose a video") + video_title=$(get_videos "$channel_name" "$sort_option" | DMENU 20 "Choose a video") } video_action_menu "$video_title" "$channel_name" @@ -204,23 +208,23 @@ video_menu() { } video_action_menu() { - local video_title="$1" - local channel_name="$2" + video_title="$1" + channel_name="$2" while [ -n "$video_title" ] && [ "$video_title" != "@@sv" ] && [ "$video_title" != "@@sd" ]; do - local action action=$(rofi_action) case $action in - Watch) - play_video "$video_title" "$channel_name" + WATCH) + video_process WATCH "$video_title" "$channel_name" ;; - Download) - download_video "$video_title" "$channel_name" && notify-send "Downloading has finished." + DOWNLOAD) + video_process DOWNLOAD "$video_title" "$channel_name" && notify-send "Downloading has finished." ;; - "Send To a List") - list_name=$(ls "$CUSTOM_LIST_DIR" | dmenu -i -l 10 -p "Choose a list") - [ -n "$list_name" ] && add_to_list "$video_title" "$channel_name" "$list_name" + "SEND TO A LIST") + list_name=$(find "$CUSTOM_LIST_DIR" -maxdepth 1 -type f -exec basename {} \; | DMENU 10 "Choose a list") + [ -n "$list_name" ] && add_to_list "$video_title" "$channel_name" "$list_name" && + notify-send ""$video_title" is added to the list: "$list_name"" ;; *) return @@ -229,15 +233,25 @@ video_action_menu() { done } +main_menu() { + options=" #### ALL CHANNELS #### + #### CATEGORIES #### + #### CUSTOM LISTS #### +$(cut -d= -f1 "$CHANNEL_LIST")" + + echo "$options" | DMENU 20 "Choose an Option or a Category" +} + while true; do - main_choice=$(echo "All Channels\nCategories\nCustom Lists\n$(cut -d= -f1 "$CHANNEL_LIST")" | dmenu -i -l 20 -p "Choose an Option or a Category") + + main_choice=$(main_menu) [ -z "$main_choice" ] && exit case "$main_choice" in - "All Channels") browse_all_channels ;; - "Categories") category_menu ;; - "Custom Lists") custom_list_menu ;; - *) video_menu "$main_choice" ;; + *ALL\ CHANNELS*) browse_all_channels ;; + *CATEGORIES* ) category_menu ;; + *CUSTOM\ LISTS*) custom_list_menu ;; + *) video_menu "$main_choice" ;; esac done From 273485790205921223007f81e7b87e31f6ad4b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sun, 19 Nov 2023 02:09:54 +0300 Subject: [PATCH 06/17] Use a better way to wait for the network. --- .local/bin/channelrefresh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.local/bin/channelrefresh b/.local/bin/channelrefresh index 8e4786e327..d5de819025 100644 --- a/.local/bin/channelrefresh +++ b/.local/bin/channelrefresh @@ -1,6 +1,6 @@ #!/bin/dash -sleep 16 +while ! ping -c 1 9.9.9.9 > /dev/null 2>&1; do sleep 0.5; done DATA_DIR="$HOME/.cache/youtube_channels" CHANNEL_LIST="$HOME/.local/share/channels.txt" From e15f5ee6df720d5c1bfb4326bd360979a301b176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sun, 19 Nov 2023 02:15:32 +0300 Subject: [PATCH 07/17] Add extra error handling --- .local/bin/channelrefresh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.local/bin/channelrefresh b/.local/bin/channelrefresh index d5de819025..7b494770ed 100644 --- a/.local/bin/channelrefresh +++ b/.local/bin/channelrefresh @@ -4,7 +4,9 @@ while ! ping -c 1 9.9.9.9 > /dev/null 2>&1; do sleep 0.5; done DATA_DIR="$HOME/.cache/youtube_channels" CHANNEL_LIST="$HOME/.local/share/channels.txt" -mkdir -p "$DATA_DIR" +mkdir -p "$DATA_DIR"; touch $CHANNEL_LIST + +[ ! -s $CHANNEL_LIST ] && notify-send "You don't have any channels in "channels.txt". Refer to documentation." compare_data() { local channel_name="$1" From 3ba7e6da1119dbd0c2466d40c96c9f9d0189d3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sun, 19 Nov 2023 02:37:42 +0300 Subject: [PATCH 08/17] Add robust error handling. --- .local/bin/channelrefresh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.local/bin/channelrefresh b/.local/bin/channelrefresh index 7b494770ed..5a449c7730 100644 --- a/.local/bin/channelrefresh +++ b/.local/bin/channelrefresh @@ -4,9 +4,18 @@ while ! ping -c 1 9.9.9.9 > /dev/null 2>&1; do sleep 0.5; done DATA_DIR="$HOME/.cache/youtube_channels" CHANNEL_LIST="$HOME/.local/share/channels.txt" -mkdir -p "$DATA_DIR"; touch $CHANNEL_LIST - -[ ! -s $CHANNEL_LIST ] && notify-send "You don't have any channels in "channels.txt". Refer to documentation." +mkdir -p "$DATA_DIR" && touch "$CHANNEL_LIST" + +error_handling() { + [ -s "$CHANNEL_LIST" ] || { + notify-send "You don't have any channels in 'channels.txt'." + exit 1 + } + grep -q "^.*=https://www.youtube.com/@[[:alnum:]]*/videos$" "$CHANNEL_LIST" || { + notify-send "'channels.txt' formatting is wrong." + exit 1 + } +} compare_data() { local channel_name="$1" @@ -49,4 +58,5 @@ update_all_channels() { done < "$CHANNEL_LIST" } +error_handling update_all_channels From 996af9904ef8b89d4c66cc91401afbe709e8307e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sun, 19 Nov 2023 13:50:01 +0300 Subject: [PATCH 09/17] Check for dependencies && Install them if needed --- .local/bin/channelrefresh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.local/bin/channelrefresh b/.local/bin/channelrefresh index 5a449c7730..2521562de6 100644 --- a/.local/bin/channelrefresh +++ b/.local/bin/channelrefresh @@ -2,6 +2,7 @@ while ! ping -c 1 9.9.9.9 > /dev/null 2>&1; do sleep 0.5; done +INSTALLER="sudo pacman -S --noconfirm" DATA_DIR="$HOME/.cache/youtube_channels" CHANNEL_LIST="$HOME/.local/share/channels.txt" mkdir -p "$DATA_DIR" && touch "$CHANNEL_LIST" @@ -15,7 +16,16 @@ error_handling() { notify-send "'channels.txt' formatting is wrong." exit 1 } -} + for pkg in yt-dlp mpv jq; do + command -v "$pkg" >/dev/null || { + notify-send "$pkg is not installed. Installing..." + $INSTALLER "$pkg" || { + notify-send "Failed to install $pkg." + exit 1 + } + } + done +} compare_data() { local channel_name="$1" From 60866cad501fa03c0df93df40da52ac1e706b70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sun, 19 Nov 2023 15:55:28 +0300 Subject: [PATCH 10/17] extra error handling --- .local/bin/{yt-browser => ybrowser} | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) rename .local/bin/{yt-browser => ybrowser} (96%) diff --git a/.local/bin/yt-browser b/.local/bin/ybrowser similarity index 96% rename from .local/bin/yt-browser rename to .local/bin/ybrowser index e7cf932adc..43538369f6 100644 --- a/.local/bin/yt-browser +++ b/.local/bin/ybrowser @@ -8,6 +8,11 @@ CUSTOM_LIST_DIR="$DATA_DIR/custom_lists" mkdir -p "$DATA_DIR" "$DOWNLOAD_DIR" "$CUSTOM_LIST_DIR" +[ "$(find "$DATA_DIR" -maxdepth 1 -type f -name "*.tsv" 2>/dev/null)" ] || { + notify-send "You need to run "channelrefresh" script first." + exit 1 +} + DMENU() { dmenu -i -l $1 -p "$2" } @@ -64,7 +69,7 @@ custom_list_menu() { [ -z "$list" ] && return case "$list" in *CREATE*) - new_list=$(DMENU 0 "Enter the name of the new list") + new_list=$(echo "" | DMENU 0 "Enter the name of the new list") [ -n "$new_list" ] && touch "$CUSTOM_LIST_DIR/$new_list" ;; *DELETE*) From a7061f79bf27c44416350bdc06237d160c564f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sun, 19 Nov 2023 16:05:00 +0300 Subject: [PATCH 11/17] add some comments --- .local/bin/channelrefresh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.local/bin/channelrefresh b/.local/bin/channelrefresh index 2521562de6..113671940d 100644 --- a/.local/bin/channelrefresh +++ b/.local/bin/channelrefresh @@ -2,6 +2,13 @@ while ! ping -c 1 9.9.9.9 > /dev/null 2>&1; do sleep 0.5; done +# "~/.local/share/channels.txt" looks like below: +# Luke Smith=https://www.youtube.com/@LukeSmithxyz/videos +# Mental Outlaw=https://www.youtube.com/@MentalOutlaw/videos + +# ~/.local/share/categories.txt looks like below: +# Tech=Luke Smith|Mental Outlaw + INSTALLER="sudo pacman -S --noconfirm" DATA_DIR="$HOME/.cache/youtube_channels" CHANNEL_LIST="$HOME/.local/share/channels.txt" From ce577053f448bf264d1c5a3b3e23eec50be1fa3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sat, 2 Dec 2023 18:59:04 +0300 Subject: [PATCH 12/17] Improve further. --- .local/bin/ybrowser | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.local/bin/ybrowser b/.local/bin/ybrowser index 43538369f6..0ba6e33144 100644 --- a/.local/bin/ybrowser +++ b/.local/bin/ybrowser @@ -87,7 +87,7 @@ custom_list_menu() { custom_list_video_menu() { list_name="$1" while true; do - video_info=$(cat "$CUSTOM_LIST_DIR/$list_name" | DMENU 20 "Choose a video") + video_info=$(DMENU 20 "Choose a video" < "$CUSTOM_LIST_DIR/$list_name") [ -z "$video_info" ] && return channel_name="${video_info%%: *}" video_title="${video_info##*: }" @@ -154,28 +154,21 @@ get_all_videos() { } browse_all_channels() { - while true; do - video_title=$(get_all_videos | DMENU 20 "Choose a video or enter @@sv or @@sd") - - [ -z "$video_title" ] && break || { - [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && - sort_option="$video_title" && video_title=$(get_all_videos "$sort_option" | DMENU 20 "Choose a video") - } - - [ -n "$video_title" -a "$video_title" != "@@sv" -a "$video_title" != "@@sd" ] && { - while read -r line; do - channel_name="${line%%=*}" - grep -qF "$video_title" "${DATA_DIR}/${channel_name}.tsv" && break - done < "$CHANNEL_LIST" + while video_title=$(get_all_videos | DMENU 20 "Choose a video or enter @@sv or @@sd"); do + [ -z "$video_title" ] && break + [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && video_title=$(get_all_videos "$video_title" | DMENU 20 "Choose a video") + grep -lF "$video_title" "${DATA_DIR}"/*.tsv | head -n 1 | while read -r video_file; do + channel_name=$(basename "$video_file" .tsv) video_action_menu "$video_title" "$channel_name" - } + break + done done } category_menu() { while true; do - category=$(echo "$(cut -d= -f1 "$CATEGORY_LIST")" | DMENU 12 "Choose a category") + category="$(cut -d= -f1 "$CATEGORY_LIST" | DMENU 12 "Choose a category")" [ -z "$category" ] && return channel_menu "$category" @@ -187,7 +180,7 @@ channel_menu() { IFS="|" channels=$(sed -n "s/^${category}=\(.*\)$/\1/p" "$CATEGORY_LIST") - set -- $channels + set -- "$channels" while true; do channel_name=$(printf '%s\n' "$@" | DMENU 20 "Choose a channel") @@ -203,7 +196,7 @@ video_menu() { while true; do video_title=$(get_videos "$channel_name" | DMENU 20 "Choose a video") [ -z "$video_title" ] && return - [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && { + [ "$video_title" = "@@sv" ] || [ "$video_title" = "@@sd" ] && { sort_option="$video_title" video_title=$(get_videos "$channel_name" "$sort_option" | DMENU 20 "Choose a video") } @@ -229,7 +222,7 @@ video_action_menu() { "SEND TO A LIST") list_name=$(find "$CUSTOM_LIST_DIR" -maxdepth 1 -type f -exec basename {} \; | DMENU 10 "Choose a list") [ -n "$list_name" ] && add_to_list "$video_title" "$channel_name" "$list_name" && - notify-send ""$video_title" is added to the list: "$list_name"" + notify-send "$video_title is added to the list: $list_name" ;; *) return From aa74c7a8cdbeff12cf591659f432d89b8a5bd678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Sat, 2 Dec 2023 19:50:56 +0300 Subject: [PATCH 13/17] Minor improvements --- .local/bin/ybrowser | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.local/bin/ybrowser b/.local/bin/ybrowser index 0ba6e33144..c3dec98a4f 100644 --- a/.local/bin/ybrowser +++ b/.local/bin/ybrowser @@ -14,7 +14,7 @@ mkdir -p "$DATA_DIR" "$DOWNLOAD_DIR" "$CUSTOM_LIST_DIR" } DMENU() { - dmenu -i -l $1 -p "$2" + dmenu -i -l "$1" -p "$2" } sort_videos() { @@ -39,7 +39,7 @@ video_url() { channel_name="$1" video_title="$2" data_file="$DATA_DIR/$channel_name.tsv" - sed -n "s/$video_title\t\([^\t]*\)\t.*$/\1/p" "$data_file" + grep -F "$video_title" "$data_file" | cut -f2 } rofi_action() { @@ -156,7 +156,7 @@ get_all_videos() { browse_all_channels() { while video_title=$(get_all_videos | DMENU 20 "Choose a video or enter @@sv or @@sd"); do [ -z "$video_title" ] && break - [ "$video_title" = "@@sv" -o "$video_title" = "@@sd" ] && video_title=$(get_all_videos "$video_title" | DMENU 20 "Choose a video") + [ "$video_title" = "@@sv" ] || [ "$video_title" = "@@sd" ] && video_title=$(get_all_videos "$video_title" | DMENU 20 "Choose a video") grep -lF "$video_title" "${DATA_DIR}"/*.tsv | head -n 1 | while read -r video_file; do channel_name=$(basename "$video_file" .tsv) From d5f31b599dcbec75ce373af11ab88de0685c4f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Mon, 4 Dec 2023 11:05:51 +0300 Subject: [PATCH 14/17] Small fix --- .local/bin/ybrowser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.local/bin/ybrowser b/.local/bin/ybrowser index c3dec98a4f..b6e91a1dae 100644 --- a/.local/bin/ybrowser +++ b/.local/bin/ybrowser @@ -180,7 +180,7 @@ channel_menu() { IFS="|" channels=$(sed -n "s/^${category}=\(.*\)$/\1/p" "$CATEGORY_LIST") - set -- "$channels" + set -- $channels while true; do channel_name=$(printf '%s\n' "$@" | DMENU 20 "Choose a channel") From a43657a52a581fa055efc0d32d64badd7b2a356d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Tue, 2 Jul 2024 01:52:53 +0300 Subject: [PATCH 15/17] Update channelrefresh --- .local/bin/channelrefresh | 130 ++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 74 deletions(-) diff --git a/.local/bin/channelrefresh b/.local/bin/channelrefresh index 113671940d..a5831bb456 100644 --- a/.local/bin/channelrefresh +++ b/.local/bin/channelrefresh @@ -1,79 +1,61 @@ #!/bin/dash - -while ! ping -c 1 9.9.9.9 > /dev/null 2>&1; do sleep 0.5; done - -# "~/.local/share/channels.txt" looks like below: -# Luke Smith=https://www.youtube.com/@LukeSmithxyz/videos -# Mental Outlaw=https://www.youtube.com/@MentalOutlaw/videos - -# ~/.local/share/categories.txt looks like below: -# Tech=Luke Smith|Mental Outlaw - -INSTALLER="sudo pacman -S --noconfirm" -DATA_DIR="$HOME/.cache/youtube_channels" -CHANNEL_LIST="$HOME/.local/share/channels.txt" -mkdir -p "$DATA_DIR" && touch "$CHANNEL_LIST" - -error_handling() { - [ -s "$CHANNEL_LIST" ] || { - notify-send "You don't have any channels in 'channels.txt'." - exit 1 - } - grep -q "^.*=https://www.youtube.com/@[[:alnum:]]*/videos$" "$CHANNEL_LIST" || { - notify-send "'channels.txt' formatting is wrong." - exit 1 - } - for pkg in yt-dlp mpv jq; do - command -v "$pkg" >/dev/null || { - notify-send "$pkg is not installed. Installing..." - $INSTALLER "$pkg" || { - notify-send "Failed to install $pkg." - exit 1 - } - } - done -} - -compare_data() { - local channel_name="$1" - local data_file="${DATA_DIR}/${channel_name}.tsv" - local old_data_file="${DATA_DIR}/${channel_name}_old.tsv" - - [ -e "$old_data_file" ] && { - old_urls=$(cut -f2 "$old_data_file") - new_urls=$(cut -f2 "$data_file") - - echo "$old_urls" | sort > temp1 - echo "$new_urls" | sort > temp2 - new_videos=$(comm -13 temp1 temp2 | wc -l) - rm temp1 temp2 - - [ "$new_videos" -gt 0 ] && notify-send -u critical "$channel_name | $new_videos videos" - } +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" + } } -update_data() { - local channel_name="$1" - local channel_url="$2" - local data_file="${DATA_DIR}/${channel_name}.tsv" - local old_data_file="${DATA_DIR}/${channel_name}_old.tsv" - - mv "$data_file" "$old_data_file" 2>/dev/null - - yt-dlp -j --flat-playlist --skip-download --extractor-args youtubetab:approximate_date "$channel_url" | jq -r '[.title, .url, .view_count, .duration, .upload_date] | @tsv' > "$data_file" +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." + } } - -update_all_channels() { - while IFS="=" read -r channel_name channel_url; do - update_data "$channel_name" "$channel_url" & - done < "$CHANNEL_LIST" - - wait - - while IFS="=" read -r channel_name channel_url; do - compare_data "$channel_name" - done < "$CHANNEL_LIST" +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}" } - -error_handling -update_all_channels +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 From 37ffc69ce585aa5fa6cf39bad969f6640edf9776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Tue, 2 Jul 2024 01:53:57 +0300 Subject: [PATCH 16/17] Update ybrowser --- .local/bin/ybrowser | 437 +++++++++++++++++++------------------------- 1 file changed, 184 insertions(+), 253 deletions(-) diff --git a/.local/bin/ybrowser b/.local/bin/ybrowser index b6e91a1dae..c74fdbb521 100644 --- a/.local/bin/ybrowser +++ b/.local/bin/ybrowser @@ -1,255 +1,186 @@ -#!/bin/dash - -DATA_DIR="$HOME/.cache/youtube_channels" -DOWNLOAD_DIR="$HOME/ytvideos" -CATEGORY_LIST="$HOME/.local/share/categories.txt" -CHANNEL_LIST="$HOME/.local/share/channels.txt" -CUSTOM_LIST_DIR="$DATA_DIR/custom_lists" - -mkdir -p "$DATA_DIR" "$DOWNLOAD_DIR" "$CUSTOM_LIST_DIR" - -[ "$(find "$DATA_DIR" -maxdepth 1 -type f -name "*.tsv" 2>/dev/null)" ] || { - notify-send "You need to run "channelrefresh" script first." - exit 1 -} - -DMENU() { - dmenu -i -l "$1" -p "$2" -} - -sort_videos() { - data_file="$1" - sort_option="$2" - - case $sort_option in - "@@sv") sort -nr -t" " -k3 "$data_file" ;; - "@@sd") sort -nr -t" " -k4 "$data_file" ;; - *) sort -nr -t" " -k5 "$data_file" ;; - esac | cut -f1 -} - -get_videos() { - channel_name="$1" - sort_option="$2" - data_file="$DATA_DIR/$channel_name.tsv" - sort_videos "$data_file" "$sort_option" -} - -video_url() { - channel_name="$1" - video_title="$2" - data_file="$DATA_DIR/$channel_name.tsv" - grep -F "$video_title" "$data_file" | cut -f2 -} - -rofi_action() { - echo "WATCH\nDOWNLOAD\nSEND TO A LIST" | DMENU 3 "Choose an action for the video" -} - -rofi_custom_list_action() { - echo "$(find "$CUSTOM_LIST_DIR" -maxdepth 1 -type f -exec basename {} \;) -#### CREATE A LIST #### -#### DELETE A LIST ####" | DMENU 10 "Choose an action or list" -} - -list_video_action() { - echo "WATCH\nDOWNLOAD\nDELETE" | DMENU 3 "Choose an action for the video" -} - -add_to_list() { - video_title="$1" - channel_name="$2" - list_name="$3" - echo "$channel_name: $video_title" >> "$CUSTOM_LIST_DIR/$list_name" -} - -custom_list_menu() { - while true; do - list="$(rofi_custom_list_action)" - [ -z "$list" ] && return - case "$list" in - *CREATE*) - new_list=$(echo "" | DMENU 0 "Enter the name of the new list") - [ -n "$new_list" ] && touch "$CUSTOM_LIST_DIR/$new_list" - ;; - *DELETE*) - delete_list=$(find "$CUSTOM_LIST_DIR" -maxdepth 1 -type f -exec basename {} \; | - DMENU 10 "Choose a list to delete") - [ -n "$delete_list" ] && rm "$CUSTOM_LIST_DIR/$delete_list" - ;; - *) - custom_list_video_menu "$list" - ;; - esac - done -} - -custom_list_video_menu() { - list_name="$1" - while true; do - video_info=$(DMENU 20 "Choose a video" < "$CUSTOM_LIST_DIR/$list_name") - [ -z "$video_info" ] && return - channel_name="${video_info%%: *}" - video_title="${video_info##*: }" - custom_list_video_action_menu "$video_title" "$channel_name" "$list_name" - done -} - -custom_list_video_action_menu() { - video_title="$1" - channel_name="$2" - list_name="$3" - - action=$(list_video_action) - - case $action in - WATCH) - video_process WATCH "$video_title" "$channel_name" - ;; - DOWNLOAD) - video_process DOWNLOAD "$video_title" "$channel_name" && notify-send "Downloading has finished." - ;; - DELETE) - escaped_video_title="$(echo "$video_title" | sed 's/[][\^$.\/|*+?(){}#]/\\&/g')" - sed "/$escaped_video_title/d" "$CUSTOM_LIST_DIR/$list_name" > "$CUSTOM_LIST_DIR/${list_name}.tmp" && - mv "$CUSTOM_LIST_DIR/${list_name}.tmp" "$CUSTOM_LIST_DIR/$list_name" - ;; - *) - return - ;; - esac -} - -video_process() { - action="$1" - video_title="$2" - channel_name="$3" - video_url=$(video_url "$channel_name" "$video_title") - - case "$action" in - WATCH) - mpv "$video_url" - ;; - DOWNLOAD) - local channel_download_dir="$DOWNLOAD_DIR/$channel_name" - mkdir -p "$channel_download_dir" - yt-dlp -o "$channel_download_dir/%(title)s.%(ext)s" "$video_url" - notify-send "Downloading has finished." - ;; - esac -} - -get_all_videos() { - sort_option="$1" - all_videos_file="$DATA_DIR/all_videos.tsv" - - rm -f "$all_videos_file" - while IFS= read -r line - do - channel_name="${line%%=*}" - cat "$DATA_DIR/$channel_name.tsv" >> "$all_videos_file" - done < "$CHANNEL_LIST" - - sort_videos "$all_videos_file" "$sort_option" -} - -browse_all_channels() { - while video_title=$(get_all_videos | DMENU 20 "Choose a video or enter @@sv or @@sd"); do - [ -z "$video_title" ] && break - [ "$video_title" = "@@sv" ] || [ "$video_title" = "@@sd" ] && video_title=$(get_all_videos "$video_title" | DMENU 20 "Choose a video") - - grep -lF "$video_title" "${DATA_DIR}"/*.tsv | head -n 1 | while read -r video_file; do - channel_name=$(basename "$video_file" .tsv) - video_action_menu "$video_title" "$channel_name" - break - done - done -} - -category_menu() { - while true; do - category="$(cut -d= -f1 "$CATEGORY_LIST" | DMENU 12 "Choose a category")" - [ -z "$category" ] && return - - channel_menu "$category" - done -} - -channel_menu() { - category="$1" - IFS="|" - - channels=$(sed -n "s/^${category}=\(.*\)$/\1/p" "$CATEGORY_LIST") - set -- $channels - - while true; do - channel_name=$(printf '%s\n' "$@" | DMENU 20 "Choose a channel") - [ -z "$channel_name" ] && return - - video_menu "$channel_name" - done -} - -video_menu() { - channel_name="$1" - - while true; do - video_title=$(get_videos "$channel_name" | DMENU 20 "Choose a video") - [ -z "$video_title" ] && return - [ "$video_title" = "@@sv" ] || [ "$video_title" = "@@sd" ] && { - sort_option="$video_title" - video_title=$(get_videos "$channel_name" "$sort_option" | DMENU 20 "Choose a video") - } - - video_action_menu "$video_title" "$channel_name" - done -} - -video_action_menu() { - video_title="$1" - channel_name="$2" - - while [ -n "$video_title" ] && [ "$video_title" != "@@sv" ] && [ "$video_title" != "@@sd" ]; do - action=$(rofi_action) - - case $action in - WATCH) - video_process WATCH "$video_title" "$channel_name" - ;; - DOWNLOAD) - video_process DOWNLOAD "$video_title" "$channel_name" && notify-send "Downloading has finished." - ;; - "SEND TO A LIST") - list_name=$(find "$CUSTOM_LIST_DIR" -maxdepth 1 -type f -exec basename {} \; | DMENU 10 "Choose a list") - [ -n "$list_name" ] && add_to_list "$video_title" "$channel_name" "$list_name" && - notify-send "$video_title is added to the list: $list_name" - ;; - *) - return - ;; - esac - done -} - -main_menu() { - options=" #### ALL CHANNELS #### - #### CATEGORIES #### - #### CUSTOM LISTS #### -$(cut -d= -f1 "$CHANNEL_LIST")" - - echo "$options" | DMENU 20 "Choose an Option or a Category" -} - +#!/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}" | rofi -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 - - main_choice=$(main_menu) - - [ -z "$main_choice" ] && exit - - case "$main_choice" in - *ALL\ CHANNELS*) browse_all_channels ;; - *CATEGORIES* ) category_menu ;; - *CUSTOM\ LISTS*) custom_list_menu ;; - *) video_menu "$main_choice" ;; - esac + mc="$(mm)" + case "${mc}" in + " ## ALL CHANNELS ##") bac ;; + " ## CATEGORIES ##") cm ;; + " ## CUSTOM LISTS ##") clm ;; + "") exit ;; + *) vm "${mc}" ;; + esac done From ae85e2223642cfaec66ae6c69753fd11cb036a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20AKY=C3=9CZ?= Date: Tue, 2 Jul 2024 01:54:11 +0300 Subject: [PATCH 17/17] Update ybrowser --- .local/bin/ybrowser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.local/bin/ybrowser b/.local/bin/ybrowser index c74fdbb521..4ec5124df7 100644 --- a/.local/bin/ybrowser +++ b/.local/bin/ybrowser @@ -11,7 +11,7 @@ d() { l="$(pr "${i}" | wc -l)" [ "${l}" -gt "21" ] && l="21" [ "${i}" ] || l="0" - pr "${i}" | rofi -dmenu -i -l "${l}" -p "${1}" + pr "${i}" | dmenu -i -l "${l}" -p "${1}" } n() { notify-send -i "${XDG_CONFIG_HOME}/dunst/yt.png" "${1}"; } sv() {