# See the License for the specific language governing permissions and
# limitations under the License.
+# Uncomment this when developing this file for quick shellcheck results,
+# otherwise it is redundant.
. /a/c/fsf-script-lib
-
# Must be called from beetag for variables to be setup
beetag-help() {
source /a/bin/ds/beet-data
fi
}
+# beet modify quietly
+beetmq() {
+ local tmpf
+ tmpf="$(mktemp)"
+ # a bunch of effort to ignore output we dont care about...
+ sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
+ beet -c $tmpf modify -y "$@" > >(grep -vFx -e 'ignore_this' -e 'Modifying 1 items.' ||:)
+ rm "$tmpf"
+ beetag-nostatus 1
+}
+
+kill-bg-quiet() {
+ # https://stackoverflow.com/a/5722874
+ kill %% 2>/dev/null ||:; wait %% 2>/dev/null ||:
+}
+
+
+# run if not running.
+#
+# Note: this does not work with shell scripts as they are normally
+# invoked, because the ps output has the interpreter at the start.
+# A workaround is to invoke the command in that format, or we could
+# do various other workarounds.
+#
+# background, this relies on how ps converts newlines in arguments to spaces, and
+# assumes we won't be searching for a command with spaces in its arguments
+rinr() {
+ # shellcheck disable=SC2009 # pgrep has no fixed string option, plus see above.
+ if ps h -o args -C "${1##*/}" | grep -Fxqv "$*" &>/dev/null || [[ $? == 141 ]]; then
+ "$@"
+ fi
+}
+# variation of above: run or wait if running
+rowir() {
+ local pid
+ pid=$(ps h -o 'pid,args' -C "${1##*/}" | sed -r 's/^[[:space:]]*([0-9]+)[[:space:]](.*)/\1\n\2/' | grep -B1 -Fx "$*" | head -n1 ||: )
+ if [[ $pid ]]; then
+ # https://unix.stackexchange.com/questions/427115/listen-for-exit-of-process-given-pid
+ tail --pid="$pid" -f /dev/null
+ else
+ "$@"
+ fi
+}
+
+
+mpvrpc-loadfile() {
+ local path nextpath cachedir finalpath nextpath count
+ cachedir=$HOME/.iank-music-cache
+ path="$1"
+ nextpath="$2"
+
+ # note: logic duplicated in beetpull
+ local remote_p=true
+ source /p/c/domain-info
+ if [[ $HOSTNAME == "$d_host" ]]; then
+ remote_p=false
+ fi
+
+ if $remote_p; then
+ finalpath="$cachedir${path#/i/m}"
+ rowir rsync --partial -a --inplace --mkpath "b8.nz:$path" "$finalpath"
+ finalnextpath="$cachedir${nextpath#/i/m}"
+ count=$(pgrep -a -f "^rsync --partial -a --inplace --mkpath $cachedir" || [[ $? == 1 ]] )
+ # allow us to start 2 rsyncs in the background
+ if [[ $count == [01] ]]; then
+ rinr rsync --partial -a --inplace --mkpath "b8.nz:$nextpath" "$finalnextpath" &
+ fi
+ else
+ finalpath="$path"
+ fi
+ mpvrpc '{ "command": ["loadfile", "'"$finalpath"'"] }'
+}
+
+
# Must be called from beetag for variables to be setup
beetag-nostatus() {
if (( $# )); then
#
# Sets variables: pl_state_path pl_seed_path. Does mkdir of their directory.
#
+# Creates/recreates $pl_seed_path if needed.
+#
# Input vars: random
-beetag-pl-state-init() {
+pl-state-init() {
local seed_num seed_file pl_state_dir pl_state_file
# note: this structure of files is rather haphazard.
seed_num=1 # later we might want a few
pl_state_path=$pl_state_dir/$pl_state_file
pl_seed_path=$pl_state_dir/$seed_file
+ if $new_random || [[ ! -r $pl_seed_path ]]; then
+ { base64 < /dev/urandom | head -c 200 ||:; echo; } > $pl_seed_path
+ fi
+
}
+# Call from beetag.
+#
+# Sets do_rare_genres, button_map, last_genre_i, button_i. Toggles those
+# between rare and common genres.
+#
+# Input vars: buttons, common_genres, pl_tags
+toggle-rare-genres() {
+
+ # starts as off if not yet set.
+ if $do_rare_genres || [[ ! $do_rare_genres ]]; then
+ do_rare_genres=false
+ button_map=(${common_genres[@]} ${pl_tags[@]})
+ last_genre_i=$(( ${#rare_genres[@]} - 1 ))
+ else
+ do_rare_genres=true
+ button_map=(${rare_genres[@]} ${pl_tags[@]})
+ last_genre_i=$(( ${#rare_genres[@]} - 1 ))
+ fi
+ for (( i=0; i<${#buttons[@]}; i++ )); do
+ button_i[${buttons[i]}]=$i
+ done
+
+ }
+
+# Call from beetag. Queries our songlist and parses it into variables.
+#
+# Sets ls_lines, id_count, paths, ids
+#
+# Input vars: pl_tags, pl_seed_path, random, beet_query
+beetag-ls-setup() {
+ local fmt tag_query tmpstr line tag ls_line line_no_path right_pad
+ local -a ls_out
+
+ # note: PijokVipiotOzeph is just a random string for a delimiter
+ # shellcheck disable=SC2016 # false positive
+ fmt='%ifdef{rating,$rating }'"$tag_query"'$genre | $title - $artist - $album $length $id PijokVipiotOzeph $path'
+
+
+ for tag in "${pl_tags[@]}"; do
+ tag_query+="%ifdef{$tag,$tag }"
+ done
+
+ # shellcheck disable=SC2016 # obvious reason
+ tmpstr=$(beet ls -f "$fmt" "${beet_query[@]}" | { if $random; then sort -R --random-source=$pl_seed_path; else cat; fi; } )
+ mapfile -t ls_out <<<"$tmpstr"
+ if [[ ! ${ls_out[0]} ]]; then
+ echo "beetag: error: no result from beet ls ${beet_query[*]}"
+ return 1
+ fi
+ id_count=${#ls_out[@]}
+
+ for line in "${ls_out[@]}"; do
+ paths+=("${line#*PijokVipiotOzeph }")
+ line_no_path="${line% PijokVipiotOzeph*}"
+ ids+=("${line_no_path##* }")
+ right_pad="${line_no_path%% |*}"
+ ls_line="$(printf %-11s "$right_pad")${line_no_path#"$right_pad"}"
+ ls_lines+=("$ls_line")
+ done
+
+ }
+
# tag with beets.
# usage: beetag [-r] [-s] QUERY
# it lists the query, reads an input char for tagging one by one.
# todo: enter should also unpause
beetag() {
source /a/bin/ds/beet-data
- local last_genre_i tag_query tag id char new_item char_i tag remove doplay i j random path
- local do_rare_genres read_wait line lsout ls_line skip_lookback
- local escape_char escaped_input expected_input skip_input_regex right_pad erasable_line seek_sec
- local pl_state_path tmpstr
- local new_random pl_seed_path fmt first_play repeat1
- local -a buttons button_map ids tags tmp_tags initial_ls ls_lines paths
+ local last_genre_i tag id char new_item char_i tag remove doplay i j random path
+ local read_wait line lsout ls_line skip_lookback do_rare_genres pl_state_path
+ local escape_char escaped_input expected_input skip_input_regex erasable_line seek_sec
+ local new_random pl_seed_path first_play repeat1
+ local -a buttons button_map ids tags tmp_tags ls_lines paths beet_query
local -A button_i
local -i i j volume scrolled id_count line_int skip_start pre_j_count head_count skip_lookback
local -i overflow_lines overflow
echo beetag: error expected a query arg >&2
return 1
fi
+ beet_query=("$@")
### end arg processing ###
readonly -a buttons=( {a..p} {r..w} {6..8} , . / - "=")
- # note: I used to do beetpull here, but mpv + ssfs on slowish
- # connection leads to bad/buggy result.
-
- do_rare_genres=false
+ ### begin control knob vars ###
volume=70
- read_wait=2
doplay=true
+ ### end control knob vars ###
- last_genre_i=$(( ${#common_genres[@]} - 1 ))
-
- button_map=(${common_genres[@]} ${pl_tags[@]})
-
- for (( i=0; i<${#buttons[@]}; i++ )); do
- button_i[${buttons[i]}]=$i
- done
-
- beetag-pl-state-init
-
- if $new_random || [[ ! -r $pl_seed_path ]]; then
- { base64 < /dev/urandom | head -c 200 ||:; echo; } > $pl_seed_path
- fi
-
- tag_query=
- for tag in "${pl_tags[@]}"; do
- tag_query+="%ifdef{$tag,$tag }"
- done
- # note: PijokVipiotOzeph is just a random string for a delimiter
- # shellcheck disable=SC2016 # false positive
- fmt='%ifdef{rating,$rating }'"$tag_query"'$genre | $title - $artist - $album $length $id PijokVipiotOzeph $path'
- # shellcheck disable=SC2016 # obvious reason
- tmpstr=$(beet ls -f "$fmt" "$@" | { if $random; then sort -R --random-source=$pl_seed_path; else cat; fi; } )
- mapfile -t initial_ls <<<"$tmpstr"
- if [[ ! ${initial_ls[0]} ]]; then
- echo "beetag: error: no result from beet ls $*"
- return 1
- fi
- id_count=${#initial_ls[@]}
- for line in "${initial_ls[@]}"; do
- path="${line#*PijokVipiotOzeph }"
- # https://github.com/koalaman/shellcheck/issues/2171
- # shellcheck disable=SC2190 # bug in shellcheck, looking at paths from an earlier function
- paths+=("$path")
- line_no_path="${line% PijokVipiotOzeph*}"
- id="${line_no_path##* }"
- ids+=("$id")
- right_pad="${line_no_path%% |*}"
- ls_line="$(printf %-11s "$right_pad")${line_no_path#"$right_pad"}"
- ls_lines+=("$ls_line")
- i=$(( i+1 ))
- done
+ read_wait=2
+ toggle-rare-genres
+ pl-state-init
+ beetag-ls-setup
j=0
if [[ $playlist ]]; then
return
;;
y)
- if $do_rare_genres; then
- do_rare_genres=false
- button_map=(${common_genres[@]} ${pl_tags[@]})
- last_genre_i=$(( ${#rare_genres[@]} - 1 ))
- else
- do_rare_genres=true
- button_map=(${rare_genres[@]} ${pl_tags[@]})
- last_genre_i=$(( ${#rare_genres[@]} - 1 ))
- fi
- local -A button_i
- for (( i=0; i<${#buttons[@]}; i++ )); do
- button_i[${buttons[i]}]=$i
- done
+ toggle-rare-genres
for (( i=0; i<${#button_map[@]}; i++ )); do
echo ${buttons[i]} ${button_map[i]}
done
fi
done
}
-
-beetag "$@"
rm "$tmpf"
}
-declare -A bpla # beet playlist associative array
-beetapl() { # beet add playlist
- local name
- name="$1"
- shift
- bpla[$name]="${*@Q}"
-}
-
-
beets-gen-playlists() {
local i str
local -a query_array query_str
complete -W "${!bpla[*]}" bpl
-# beet modify quietly
-beetmq() {
- local tmpf
- tmpf="$(mktemp)"
- # a bunch of effort to ignore output we dont care about...
- sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
- beet -c $tmpf modify -y "$@" > >(grep -vFx -e 'ignore_this' -e 'Modifying 1 items.' ||:)
- rm "$tmpf"
- beetag-nostatus 1
-}
-
-kill-bg-quiet() {
- # https://stackoverflow.com/a/5722874
- kill %% 2>/dev/null ||:; wait %% 2>/dev/null ||:
-}
# debug variables
dv() {
echo
}
-
-# run if not running.
-#
-# Note: this does not work with shell scripts as they are normally
-# invoked, because the ps output has the interpreter at the start.
-# A workaround is to invoke the command in that format, or we could
-# do various other workarounds.
-#
-# background, this relies on how ps converts newlines in arguments to spaces, and
-# assumes we won't be searching for a command with spaces in its arguments
-rinr() {
- # shellcheck disable=SC2009 # pgrep has no fixed string option, plus see above.
- if ps h -o args -C "${1##*/}" | grep -Fxqv "$*" &>/dev/null || [[ $? == 141 ]]; then
- "$@"
- fi
-}
-# variation of above: run or wait if running
-rowir() {
- local pid
- pid=$(ps h -o 'pid,args' -C "${1##*/}" | sed -r 's/^[[:space:]]*([0-9]+)[[:space:]](.*)/\1\n\2/' | grep -B1 -Fx "$*" | head -n1 ||: )
- if [[ $pid ]]; then
- # https://unix.stackexchange.com/questions/427115/listen-for-exit-of-process-given-pid
- tail --pid="$pid" -f /dev/null
- else
- "$@"
- fi
-}
-
-mpvrpc-loadfile() {
- local path nextpath cachedir finalpath nextpath count
- cachedir=$HOME/.iank-music-cache
- path="$1"
- nextpath="$2"
-
- # note: logic duplicated in beetpull
- local remote_p=true
- source /p/c/domain-info
- if [[ $HOSTNAME == "$d_host" ]]; then
- remote_p=false
- fi
-
- if $remote_p; then
- finalpath="$cachedir${path#/i/m}"
- rowir rsync --partial -a --inplace --mkpath "b8.nz:$path" "$finalpath"
- finalnextpath="$cachedir${nextpath#/i/m}"
- count=$(pgrep -a -f "^rsync --partial -a --inplace --mkpath $cachedir" || [[ $? == 1 ]] )
- # allow us to start 2 rsyncs in the background
- if [[ $count == [01] ]]; then
- rinr rsync --partial -a --inplace --mkpath "b8.nz:$nextpath" "$finalnextpath" &
- fi
- else
- finalpath="$path"
- fi
- mpvrpc '{ "command": ["loadfile", "'"$finalpath"'"] }'
-}
+# I tried making it a separate script, but the background process
+# management stuff doesn't work that way. There might be a fix, but I
+# didn't try to find one.
+. /a/c/beetag
# usage: FILE|ALBUM_DIR [GENRE]
beetadd() {