esac
}
-source /a/bin/ds/beet-data
-
-
-# Generate beet smartplaylists for navidrome.
-# for going in the reverse direction, run
-# /b/ds/navidrome-playlist-export
-beetsmartplaylists() {
- install -m 0700 -d /tmp/ianbeetstmp
- beet splupdate
- # kill off any playlists we deleted. they will still need manual
- # killing from a navidrome client.
- rm -rf /i/converted/beetsmartplaylists
- mkdir -p /i/converted/beetsmartplaylists
- for f in /tmp/ianbeetstmp/*; do
- sed 's,^/i/m,/i/converted,;s,\.flac$,.mp3,' "$f" >"/i/converted/beetsmartplaylists/${f##*/}"
- rm "$f"
- done
- rmdir /tmp/ianbeetstmp
-}
-# internal function for beetrating, in case we need to ssh
-beetrating-stdin() {
- local tmp rating path cpath sqlpath userid
- # plucked this from the db. im the only user.
- userid=23cc2eb9-e35e-4811-a0f0-d5f0dd6eb634
- while read -r rating path; do
- cpath="/i/converted${path#/i/m}" # converted path
- case $cpath in
- *.flac)
- cpath="${cpath%.*}.mp3"
- ;;
- esac
- if [[ ! -e $cpath ]]; then
- echo "beetraing: error: this should not happen, path does not exist: $cpath"
- return 1
- fi
- sqlpath="${cpath//\'/\'\'}"
- old_rating=$(sqlite3 /i/navidrome/navidrome.db "select rating from annotation inner join media_file on item_id = id where path = '$sqlpath' and item_type = 'media_file';")
- if [[ $old_rating ]]; then
- if [[ $old_rating != "$rating" ]]; then
- echo "setting rating $old_rating -> $rating $cpath"
- # https://stackoverflow.com/a/50317320
- # we got a timeout error once. arbitrarily chose 15 seconds.
- sqlite3 /i/navidrome/navidrome.db ".timeout 15000" "
-update annotation set rating = $rating
- where item_id in (
- select media_file.id from annotation inner join media_file on annotation.item_id = media_file.id
- where media_file.path = '$sqlpath' and annotation.item_type = 'media_file' );"
- fi
- else
- echo "setting rating $rating $cpath"
- # /a/opt/navidrome/persistence/sql_annotations.go v0.48.0
- # https://www.sqlite.org/lang_insert.html
- sqlite3 /i/navidrome/navidrome.db ".timeout 15000" "insert into annotation select '$(uuidgen)', '$userid', id, 'media_file', 0, NULL, $rating, 0, NULL from media_file where path = '$sqlpath';"
- fi
+# debug variables
+dv() {
+ for arg; do
+ printf "%s=%s " "$arg" "${!arg}"
done
+ echo
}
-nav_convert_query="^genre:spoken-w ^genre:skit ^lesser_version:t rating:3..5"
-
-# Export beets ratings into navidrome
-beetrating() {
- local ssh_prefix
- source /p/c/domain-info
- if [[ $HOSTNAME != "$d_host" ]]; then
- ssh_prefix="ssh b8.nz"
- fi
- # shellcheck disable=SC2016 # obvious reason
- beet ls -f '$rating $path' $nav_convert_query | $ssh_prefix beetrating-stdin
-}
-
-# Do transcoding and hardlinking of audio files for navidrome.
-beetconvert() {
- 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 convert -y $nav_convert_query > >(grep -vFx 'ignore_this' ||:) 2> >(grep -v '^convert: Skipping' ||:)
- rm "$tmpf"
-}
-# This deletes files in the converted directory which should no longer
-# be there due to a rename of the unconverted file.
-beetconvert-rm-extras() {
- local l tmpf
- local -A paths
- tmpf="$(mktemp)"
- # shellcheck disable=SC2016 # obvious reason
- beet ls -f '$path' $nav_convert_query >"$tmpf"
- ## begin removal of files that are leftover from previous conversion,
- # eg, previously rated > 1, now rated 1.
- while read -r l; do
- convertedpath="/i/converted${l#/i/m}"
- case $convertedpath in
- *.flac) convertedpath="${convertedpath%.flac}.mp3" ;;
- esac
- paths[$convertedpath]=t
- done <"$tmpf"
+#### begin beet related functions ####
+source /a/bin/ds/beet-data
- find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \) -name '*.mp3' -o -name '*.m4a' >"$tmpf"
- while read -r l; do
- if [[ ! ${paths[$l]} ]]; then
- rm -v "$l"
- fi
- # note: the pruning is duplicative of filtering on name, but whatever.
- done <"$tmpf"
- rm "$tmpf"
-}
+# 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.
+source /a/c/beetag
+# For adding or removing a smart playlist, copy output to
+# /p/c/subdir_files/.config/beets/config.yaml
beets-gen-playlists() {
local i str
local -a query_array query_str
complete -W "${!bpla[*]}" bpl
-
-# debug variables
-dv() {
- for arg; do
- printf "%s=%s " "$arg" "${!arg}"
- done
- echo
-}
-
-# 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]
+# usage: FILE|ALBUM_DIR [GENRE] [RATING]
beetadd() {
- local import_path genre_arg single_track_arg
+ local import_path genre_arg single_track_arg rating_arg
import_path="$1"
if [[ ! -e $import_path ]]; then
echo "beetadd error: path does not exist"
if [[ $2 ]]; then
genre_arg="--set genre=$2"
fi
+ if [[ $3 ]]; then
+ rating_arg="--set rating=$3"
+ fi
if [[ -f $import_path ]]; then
single_track_arg=-s
fi
- beet import --set totag=t $single_track_arg $genre_arg "$import_path"
+ beet import --set totag=t $single_track_arg $genre_arg $rating_arg "$import_path"
+ function err-cleanup() {
+ if beet ls -a totag:t | grep -q .; then
+ beet modify -ay totag:t 'totag!'
+ fi
+ if beet ls totag:t | grep -q .; then
+ beet modify -y totag:t 'totag!'
+ fi
+ }
beetag totag:t
- beet modify -y totag:t "totag!"
-}
-
+ err-cleanup
+ unset -f err-cleanup
+}
+
+# beetag: error: no result from beet ls totag:t
+# [1] 711197
+# █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+# a arp i nv s sleep 8 pump1
+# b avant l latin t techno , pumprap
+# c blues m metal u world . rend
+# d chill n mq v expl / run
+# e country o pop w gimicky - tiredof
+# f dance p rap 6 sad = worm
+# g darkwave r rock 7 love \ lesser_version
+# h hardcore
+
+# y other genres z fg player ' = toggle play 1-5 rate ] repeat1
+# ; previous _ delete j/k skip mpv_keys vol,pause,seek
+# █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+# ██
+# [file] Cannot open file '': No such file or directory
+# Failed to open .
+# bash: ( j + 1 ) % id_count : division by 0 (error token is "id_count ")
+
+
+##### begin beet2nav functions #####
# update navidrome music data after doing beets tagging
beet2nav() {
m beetpull
fi
}
+# Do transcoding and hardlinking of audio files for navidrome.
+beetconvert() {
+ 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 convert -y $nav_convert_query > >(grep -vFx 'ignore_this' ||:) 2> >(grep -v '^convert: Skipping' ||:)
+ rm "$tmpf"
+}
+
+nav_convert_query="^genre:spoken-w ^genre:skit ^lesser_version:t rating:3..5"
+
+# Export beets ratings into navidrome
+beetrating() {
+ local ssh_prefix
+ source /p/c/domain-info
+ if [[ $HOSTNAME != "$d_host" ]]; then
+ ssh_prefix="ssh b8.nz"
+ fi
+ # shellcheck disable=SC2016 # obvious reason
+ beet ls -f '$rating $path' $nav_convert_query | $ssh_prefix beetrating-stdin
+}
+
+
+# This deletes files in the converted directory which should no longer
+# be there due to a rename of the unconverted file.
+beetconvert-rm-extras() {
+ local l tmpf
+ local -A paths
+ tmpf="$(mktemp)"
+ # shellcheck disable=SC2016 # obvious reason
+ beet ls -f '$path' $nav_convert_query >"$tmpf"
+ ## begin removal of files that are leftover from previous conversion,
+ # eg, previously rated > 1, now rated 1.
+ while read -r l; do
+ convertedpath="/i/converted${l#/i/m}"
+ case $convertedpath in
+ *.flac) convertedpath="${convertedpath%.flac}.mp3" ;;
+ esac
+ paths[$convertedpath]=t
+ done <"$tmpf"
+
+ find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \) -name '*.mp3' -o -name '*.m4a' >"$tmpf"
+ while read -r l; do
+ if [[ ! ${paths[$l]} ]]; then
+ rm -v "$l"
+ fi
+ # note: the pruning is duplicative of filtering on name, but whatever.
+ done <"$tmpf"
+ rm "$tmpf"
+}
+
+# Generate beet smartplaylists for navidrome.
+# for going in the reverse direction, run
+# /b/ds/navidrome-playlist-export
+beetsmartplaylists() {
+ install -m 0700 -d /tmp/ianbeetstmp
+ beet splupdate
+ # kill off any playlists we deleted. they will still need manual
+ # killing from a navidrome client.
+ rm -rf /i/converted/beetsmartplaylists
+ mkdir -p /i/converted/beetsmartplaylists
+ for f in /tmp/ianbeetstmp/*; do
+ sed 's,^/i/m,/i/converted,;s,\.flac$,.mp3,' "$f" >"/i/converted/beetsmartplaylists/${f##*/}"
+ rm "$f"
+ done
+ rmdir /tmp/ianbeetstmp
+}
+
+# internal function for beetrating, in case we need to ssh
+beetrating-stdin() {
+ local tmp rating path cpath sqlpath userid
+ # plucked this from the db. im the only user.
+ userid=23cc2eb9-e35e-4811-a0f0-d5f0dd6eb634
+ while read -r rating path; do
+ cpath="/i/converted${path#/i/m}" # converted path
+ case $cpath in
+ *.flac)
+ cpath="${cpath%.*}.mp3"
+ ;;
+ esac
+ if [[ ! -e $cpath ]]; then
+ echo "beetraing: error: this should not happen, path does not exist: $cpath"
+ return 1
+ fi
+ sqlpath="${cpath//\'/\'\'}"
+ old_rating=$(sqlite3 /i/navidrome/navidrome.db "select rating from annotation inner join media_file on item_id = id where path = '$sqlpath' and item_type = 'media_file';")
+ if [[ $old_rating ]]; then
+ if [[ $old_rating != "$rating" ]]; then
+ echo "setting rating $old_rating -> $rating $cpath"
+ # https://stackoverflow.com/a/50317320
+ # we got a timeout error once. arbitrarily chose 15 seconds.
+ sqlite3 /i/navidrome/navidrome.db ".timeout 15000" "
+update annotation set rating = $rating
+ where item_id in (
+ select media_file.id from annotation inner join media_file on annotation.item_id = media_file.id
+ where media_file.path = '$sqlpath' and annotation.item_type = 'media_file' );"
+ fi
+ else
+ echo "setting rating $rating $cpath"
+ # /a/opt/navidrome/persistence/sql_annotations.go v0.48.0
+ # https://www.sqlite.org/lang_insert.html
+ sqlite3 /i/navidrome/navidrome.db ".timeout 15000" "insert into annotation select '$(uuidgen)', '$userid', id, 'media_file', 0, NULL, $rating, 0, NULL from media_file where path = '$sqlpath';"
+ fi
+ done
+}
+##### end beet2nav functions #####
+
# remove all playlists in navidrome, for when I make big
# playlist name changes and just want to scrap everything.
nav-rm-plists() {
rm $tmpf
}
+#### end beet related functions ####
+
+
# prettify the date
btrbk-date() {
local indate
# programs. If a small program grows beyond 300 lines, I plan to change
# to a recommended GPL license.
-# Copyright 2024 Ian Kelling
+# Copyright 2025 Ian Kelling
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
rep() {
local paths
- paths="/p/c /p/profanity-config"
+ local -a opts
+ if [[ ! $1 ]]; then
+ echo rem: missing argument >&2
+ return 1
+ fi
+ for arg; do
+ if [[ $arg == -* ]]; then
+ opts+=("$1")
+ shift
+ else
+ break
+ fi
+ done
+ paths="/p/c /p/profanity-config /b/bash_unpublished"
find $paths -not \( -name .svn -prune -o -name .git -prune \
-o -name .hg -prune -o -name .editor-backups -prune \
-o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto -- "$*" ||:
- rgv $local_rgv_args -- "$*" $paths /a/t.org /p/w.org ||:
+ rgv $local_rgv_args "${opts[@]}" -- "$*" $paths /a/t.org /p/w.org ||:
}
repl() { # rem with limit to 5 matches per file
local local_rgv_args="-m 5"
esac
-## begin beets
-# soo, apt install beets fails due to wanting a pip package,
-# we find out why it wants this through
-# apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances beets | less
-# python-mediafile requires tox, which requires virtualenv, which requires pip.
-# but, python-mediafile doesn't really require tox, it is specified in
-# ./usr/lib/python3/dist-packages/mediafile-0.9.0.dist-info/METADATA
-# as being required only for testing, but the debian package
-# included it anyways, due to a mistake or bad tooling or something.
-# I don't plan to use tox, so, according to https://serverfault.com/a/251091,
-# we can create and install a dummy package by:
-#
-# "equivs-control <name>, edit the file produced to provide the right
-# dependency and have a nice name, then run equivs-build <name> and
-# finally dpkg -i the resulting .deb file"
-# as of 2023-02, the tox dependency was removed in debian unstable, so
-# this hack will probably go away in t12.
+##### begin beets #####
+
+# ## apt install method, disabled because it is 2025 & packaged version is 2021.
+# # apt install beets fails due to wanting a pip package,
+# # we find out why it wants this through
+# # apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances beets | less
+# # python-mediafile requires tox, which requires virtualenv, which requires pip.
+# # but, python-mediafile doesn't really require tox, it is specified in
+# # ./usr/lib/python3/dist-packages/mediafile-0.9.0.dist-info/METADATA
+# # as being required only for testing, but the debian package
+# # included it anyways, due to a mistake or bad tooling or something.
+# # I don't plan to use tox, so, according to https://serverfault.com/a/251091,
+# # we can create and install a dummy package by:
+# #
+# # "equivs-control <name>, edit the file produced to provide the right
+# # dependency and have a nice name, then run equivs-build <name> and
+# # finally dpkg -i the resulting .deb file"
+# # as of 2023-02, the tox dependency was removed in debian unstable, so
+# # this hack will probably go away in t12.
+
+# case $(debian-codename) in
+# aramo)
+# if pcheck tox; then
+# tmpdir="$(mktemp -d)"
+# cd "$tmpdir"
+# # edited from output of equivs-control tox
+# cat >tox <<'EOF'
+# Section: python
+# Priority: optional
+# Standards-Version: 3.9.2
+# Package: tox
+# Description: tox-dummy
+# EOF
+# equivs-build tox
+# sudo dpkg -i tox_1.0_all.deb
+# rm -rf ./tox*
+# cd
+# rm -r "$tmpdir"
+# fi
+# ;;
+# esac
+# pi beets beets-doc
+# # get rid of annoying message
+# s sed -ri "s/^([[:space:]]*ui.print_\('Playing)/#\1/" /usr/share/beets/beetsplug/play.py
-case $(debian-codename) in
- aramo)
- if pcheck tox; then
- tmpdir="$(mktemp -d)"
- cd "$tmpdir"
- # edited from output of equivs-control tox
- cat >tox <<'EOF'
-Section: python
-Priority: optional
-Standards-Version: 3.9.2
-Package: tox
-Description: tox-dummy
-EOF
- equivs-build tox
- sudo dpkg -i tox_1.0_all.deb
- rm -rf ./tox*
- cd
- rm -r "$tmpdir"
- fi
- ;;
-esac
-pi beets beets-doc
+pipx ensurepath -v
+# in brackets are nondefault plugins
+pipx install 'beets[lyrics,discogs,mbsync]'
-# get rid of annoying message
-s sed -ri "s/^([[:space:]]*ui.print_\('Playing)/#\1/" /usr/share/beets/beetsplug/play.py
+# note: when i first setup beets, i found installing some plugins
+# useful, but now it is bundled with enough good ones that i found no
+# urgent need.
-## end beets
+##### end beets #####
# notes about barrier
# from https://raw.githubusercontent.com/cli/cli/trunk/docs/install_linux.md
# One time setup afterwards:
+# pass github-gh-token | gh auth login --with-token
+#
+# Originally,
# gh auth login
#
# When it gets to the page where it asks to authorize github, the button
# is grayed out. You can just open browser dev tools, inspect the
# button, remove disabled="", then click it and it works.
#
-# Auth token gets saved into /p/c/subdir_files/.local/share/keyrings/
+# Auth token gets saved into /p/c/subdir_files/.local/share/keyrings/ ,
+# but I found a solution for backing it up and importing it to other
+# comps: I saved the output of `gh auth token` to pass, and it can be
+# imported with the above command.
+#
#
# initial config goes to /home/iank/.config/gh
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \