From 6bd7c20f25dbee51d61ef22b21e3831d64e82a05 Mon Sep 17 00:00:00 2001 From: TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> Date: Sat, 25 Apr 2026 20:51:27 +0900 Subject: modified playlists/entire.m3u, modified bin/dmenudelmusic, modified bin/qndl, modified Music/.music.txt --- ar/.local/bin/dmenudelmusic | 7 ++++-- ar/.local/bin/qndl | 56 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 15 deletions(-) (limited to 'ar/.local/bin') diff --git a/ar/.local/bin/dmenudelmusic b/ar/.local/bin/dmenudelmusic index 91beeac..91994fb 100755 --- a/ar/.local/bin/dmenudelmusic +++ b/ar/.local/bin/dmenudelmusic @@ -4,8 +4,11 @@ music_dir="${XDG_MUSIC_DIR:-${HOME}/Music}" music_txt="${music_dir}/.music.txt" playlist_dir="${XDG_CONFIG_HOME:-${HOME}/.config}/mpd/playlists" -# Select the relative path (so we can find the actual file in subdirectories) -selected_relpath=$(cd "$music_dir" && find . -type f | sed 's|^\./||' | dmenu -i -l 20 -p "Select a file to delete:") || exit 1 +# Select the relative path. Sort the list so "first match" semantics in dmenu +# are deterministic — otherwise filesystem order is unstable and typing a +# substring (e.g. "성시경") can return whichever file find walked first. +# `-not -path '*/.*'` skips hidden files (.music.txt, etc.); name filter limits to audio. +selected_relpath=$(cd "$music_dir" && find . -type f -not -path '*/.*' \( -name '*.mp3' -o -name '*.m4a' -o -name '*.opus' -o -name '*.flac' -o -name '*.wav' -o -name '*.webm' \) | sed 's|^\./||' | LC_ALL=C.UTF-8 sort | dmenu -i -l 20 -p "Select a file to delete:") || exit 1 [ -n "$selected_relpath" ] || exit 1 selected_file="$music_dir/$selected_relpath" diff --git a/ar/.local/bin/qndl b/ar/.local/bin/qndl index cc61690..6cf3053 100755 --- a/ar/.local/bin/qndl +++ b/ar/.local/bin/qndl @@ -7,7 +7,9 @@ # --------------------------------------------------------------------------- notify() { - notify-send "$1" "$2" + # `--` ends option parsing so titles/bodies that look like flags (e.g. URLs + # containing "-A...") aren't misread as notify-send options. + notify-send -- "$1" "$2" } die() { @@ -16,8 +18,10 @@ die() { } get_cookies() { + # yt-dlp doesn't have a native 'librewolf' option but reads its cookies as + # a Firefox-style profile when given --cookies-from-browser firefox. case "${BROWSER:-}" in - *firefox*) printf 'firefox' ;; + *firefox* | *librewolf*) printf 'firefox' ;; esac } @@ -110,17 +114,18 @@ enqueue() { _quoted_args="$_quoted_args '${_escaped}'" done - _ytdl_cmd="yt-dlp${_quoted_args} '$(printf '%s' "$_url" | sed "s/'/'\\\\''/g")'" + # `--` ends option parsing so URLs whose video ID starts with '-' aren't treated as flags. + _ytdl_cmd="yt-dlp${_quoted_args} -- '$(printf '%s' "$_url" | sed "s/'/'\\\\''/g")'" _idnum="$(tsp bash -c "$_ytdl_cmd")" pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}" # tsp -D runs next job only if dependency succeeded (exit 0) # Success notification — runs only if download succeeds - tsp -D "$_idnum" notify-send "✅ ${_dl_type} download complete:" "$_url" + tsp -D "$_idnum" notify-send -- "✅ ${_dl_type} download complete:" "$_url" # Failure notification — waits for job, checks its exit status - tsp bash -c 'tsp -w "$1"; _exit=$(tsp -i "$1" | sed -n "s/.*exit code //p"); [ -n "$_exit" ] && [ "$_exit" != "0" ] && notify-send "❌ Failed to download:" "$2"' -- "$_idnum" "$_url" + tsp bash -c 'tsp -w "$1"; _exit=$(tsp -i "$1" | sed -n "s/.*exit code //p"); [ -n "$_exit" ] && [ "$_exit" != "0" ] && notify-send -- "❌ Failed to download:" "$2"' -- "$_idnum" "$_url" if [ "$_dl_type" = "music" ]; then tsp -D "$_idnum" bash -c "mpc update --wait && find '$_music_dir' -name '*.mp3' | sed 's|$_music_dir/||' | sort > '$HOME/.config/mpd/playlists/entire.m3u'" @@ -177,7 +182,9 @@ download_music() { _url="$1" _output_dir="${XDG_MUSIC_DIR:-$HOME/Music}" _archive="${XDG_DOTFILES_DIR:-$HOME/.dotfiles}/global/Music/.music.txt" + _titles="${XDG_DOTFILES_DIR:-$HOME/.dotfiles}/global/Music/.music_titles.txt" _format="${_output_dir}/%(artists.0|Unknown Artist)s/%(album|Unknown Album)s/%(title)s.%(ext)s" + _title_fmt="$(printf '%%(id)s\t%%(artists.0|Unknown Artist)s - %%(title)s')" _pl_result="$(handle_playlist "$_url" "music" "$_output_dir" "$_format")" _pl_flag="$(printf '%s' "$_pl_result" | head -n 1)" @@ -192,6 +199,7 @@ download_music() { --audio-format mp3 \ --audio-quality 0 \ --download-archive "$_archive" \ + --print-to-file "$_title_fmt" "$_titles" \ --output "$_fmt" } @@ -251,30 +259,51 @@ download_video() { restore_archive() { _output_dir="${XDG_MUSIC_DIR:-$HOME/Music}" _archive="${XDG_DOTFILES_DIR:-$HOME/.dotfiles}/global/Music/.music.txt" + _titles="${XDG_DOTFILES_DIR:-$HOME/.dotfiles}/global/Music/.music_titles.txt" _format="${_output_dir}/%(artists.0|Unknown Artist)s/%(album|Unknown Album)s/%(title)s.%(ext)s" [ ! -f "$_archive" ] && die "⛔ Archive not found" "$_archive" - _ids="$(awk '{print $2}' "$_archive")" - _total="$(printf '%s\n' "$_ids" | wc -l | tr -d ' ')" + # Build dmenu lines: "Artist - Title [VIDEOID]" if title is cached, else "[VIDEOID]" + # Archive uses space ("youtube ID"); titles file uses tab ("IDtitle"). + if [ -f "$_titles" ]; then + _display="$(awk ' + NR==FNR { + tab = index($0, "\t") + if (tab > 0) titles[substr($0, 1, tab-1)] = substr($0, tab+1) + next + } + { + sp = index($0, " ") + if (sp == 0) next + id = substr($0, sp+1) + if (id == "") next + if (id in titles) print titles[id] " [" id "]" + else print "[" id "]" + } + ' "$_titles" "$_archive")" + else + _display="$(awk '$2 != "" { print "[" $2 "]" }' "$_archive")" + fi + _total="$(printf '%s\n' "$_display" | grep -c .)" - _choice="$(printf 'all\n%s' "$_ids" | dmenu -i -l 20 -p 'Restore which video ID?')" + _choice="$(printf 'all\n%s' "$_display" | dmenu -i -l 20 -p "Restore which song? ($_total total)")" [ -z "$_choice" ] && return 0 if [ "$_choice" = "all" ]; then - _selected="$_ids" + _selected="$(awk '$2 != "" { print $2 }' "$_archive")" else - _selected="$_choice" + # Extract video ID from trailing "[VIDEOID]" (11-char YouTube id, anchored at end) + _selected="$(printf '%s' "$_choice" | sed -n 's/.*\[\([A-Za-z0-9_-]\{11\}\)\][[:space:]]*$/\1/p')" + [ -z "$_selected" ] && die "⛔ Could not parse video ID from selection" "$_choice" fi _sel_total="$(printf '%s\n' "$_selected" | grep -c .)" + notify "⏳ Queueing $_sel_total restore(s)" "via tsp" _tmpfile="$(mktemp)" printf '%s\n' "$_selected" >"$_tmpfile" - _n=0 while IFS= read -r _id; do [ -z "$_id" ] && continue - _n=$((_n + 1)) - notify "⏳ Restoring ($_n/$_sel_total)" "$_id" enqueue "music" "https://www.youtube.com/watch?v=$_id" \ --no-playlist \ --extract-audio \ @@ -283,6 +312,7 @@ restore_archive() { --output "$_format" done <"$_tmpfile" rm -f "$_tmpfile" + notify "✅ All $_sel_total job(s) queued" "tsp will process them sequentially" } # --------------------------------------------------------------------------- -- cgit v1.2.3