+
+iki() {
+ local url path
+ if [[ $1 ]]; then
+ path="$*"
+ else
+ read -r -p "enter path" path
+ fi
+ url=$(readlink -f "$path")
+ url="https://brains.fsf.org/wiki/${url#*brains/}"
+ url="${url%.mdwn}"
+ echo "$url"
+ # /f/brains/sysadmin/interns/2022/nick_shrader/intro_blog_post.mdwn
+ # becomes
+ # https://brains.fsf.org/wiki/sysadmin/interns/2022/nick_shrader/intro_blog_post
+
+}
+
+# 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
+}
+
+# Export beets ratings into navidrome
+beetrating() {
+ local tmp tmpfile myuser userid rating path cpath sqlpath
+ # plucked this from the db. im the only user.
+ userid=23cc2eb9-e35e-4811-a0f0-d5f0dd6eb634
+ tmpfile=$(mktemp)
+ beet ls -f '$rating $path' ^genre:spoken-w ^genre:skit rating:2..5 >$tmpfile
+ while read -r rating path; do
+ tmp="/i/converted${path#/i/m}"
+ cpath="${tmp%.*}.mp3" # converted path
+ 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
+ # https://stackoverflow.com/a/50317320
+ m sqlite3 /i/navidrome/navidrome.db "
+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
+ # /a/opt/navidrome/persistence/sql_annotations.go v0.48.0
+ # https://www.sqlite.org/lang_insert.html
+ m sqlite3 /i/navidrome/navidrome.db "insert into annotation select '$(uuidgen)', '$userid', id, 'media_file', 0, NULL, $rating, 0, NULL from media_file where path = '$sqlpath';"
+ fi
+ #sqlite3 /i/navidrome/navidrome.db "select path from annotation inner join media_file on item_id = id where rating = $r;"
+ done <$tmpfile
+}
+
+# Do transcoding and hardlinking of audio files for navidrome.
+#
+# Deletes files in the converted directory which should no longer
+# be there due to a rename of the unconverted file.
+beetconvert() {
+ # directs to avoid printing every file
+ beet convert -y ^genre:spoken-w ^genre:skit ^rating:1 >/dev/null 2> >(grep -v '^convert: Skipping' ||:)
+ local l
+ local -A paths
+ while read -r l; do
+ convertedpath="/i/converted${l#/i/m}"
+ case $convertedpath in
+ *.flac) convertedpath="${convertedpath%.flac}.mp3" ;;
+ esac
+ paths[$convertedpath]=t
+ done < <(beet ls -f '$path' ^genre:spoken-w ^genre:skit ^rating:1)
+ while read -r l; do
+ if [[ ! ${paths[$l]} ]]; then
+ rm -v "$l"
+ fi
+ done < <(find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \))
+}
+
+# tag with beets.
+# usage: beetag QUERY
+# it lists the query, reads an input char for tagging one by one
+# 1-5 = set rating
+# a-z+ = set genre/playlist.
+# enter = next song
+# , = play song
+beetag() {
+ if (( ! $# )); then
+ echo beetag: error expected a query arg >&2
+ return 1
+ fi
+ local last_genre_i fstring tag id char new_item char_i genre tag remove
+ local -a genres pl_tags buttons button_map ids tags
+ local -A button_i
+ genres=(
+ ambient
+ avant
+ blues
+ classical
+ country
+ # like power glove
+ dark-wave
+ hardcore
+ instrumental
+ jazz
+ latin
+ metal
+ musical
+ # mq = mac quale. similar to the mr robot soundtracks.
+ # slow, foreboding. usually electronic.
+ mq
+ noise
+ pop
+ rap
+ rock
+ skit
+ spoken-w
+ techno
+ world
+ )
+ pl_tags=(
+ expl
+ love
+ pump1
+ pumprap
+ rend
+ run
+ sad
+ )
+ last_genre_i=$(( ${#genres[@]} - 1 ))
+ buttons=( {a..z} 0 {6..9} )
+ button_map=(${genres[@]} ${pl_tags[@]})
+ fstring=
+ for tag in "${pl_tags[@]}"; do
+ fstring+="%ifdef{$tag,$tag }"
+ done
+
+ for (( i=0; i<${#buttons[@]}; i++ )); do
+ button_i[${buttons[i]}]=$i
+ done
+ beet ls -f '%ifdef{rating,$rating }'"$fstring"', $genre $artist - $album - $title' "$@"
+ hr
+ mapfile -t ids < <(beet ls -f '$id' "$@")
+ for id in "${ids[@]}"; do
+ lsout="$(beet ls -f '%ifdef{rating,$rating }'"$fstring"', $genre $id $artist - $album - $title' "id:$id")"
+ tags=( ${lsout%%,*} )
+ printf "%s\n" "$lsout"
+ for (( i=0; i<${#button_map[@]}; i++ )); do
+ echo ${buttons[i]} ${button_map[i]}
+ done
+ while true; do
+ read -r -N 1 -s char
+ if [[ $char == $'\n' ]]; then
+ break
+ fi
+ case $char in
+ ,)
+ beet play "id:$id"
+ continue
+ ;;
+ [1-5])
+ beet modify -y "id:$id" rating=$char
+ continue
+ ;;
+ esac
+ char_i=${button_i[$char]}
+ new_item=${button_map[$char_i]}
+ if [[ ! $char_i || ! $new_item ]]; then
+ echo "error: no mapping of input found, try again"
+ continue
+ fi
+ if (( char_i <= last_genre_i )); then
+ m beet modify -y "id:$id" genre=$new_item
+ else
+ remove=false
+ for tag in ${tags[@]}; do
+ if [[ $new_item == "$tag" ]]; then
+ remove=true
+ break
+ fi
+ done
+ if $remove; then
+ m beet modify -y "id:$id" "$new_item!"
+ else
+ m beet modify -y "id:$id" $new_item=t
+ fi
+ fi
+ done
+ done
+
+ # sadpop
+ #
+ # rending:
+ # two dollar guitar: speed
+ # black heard procession
+ # strong enough sheryl crow
+ #
+ #
+}
+
+# escape regex.
+#
+# This is not perfect but generally good enough. It escapes all
+# metachars listed man 3 pcrepattern.
+er() {
+ sed 's/[]\\^$.[|()?*+{}]/[&]/g; s/\^/\\^/g' <<<"$*"
+}
+
+# usage beegenre QUERY
+#
+# beet set genre for QUERY based on existing artist most used genre on
+#
+# inverse of query for each artist found in QUERY. If query starts with
+# "artist:" it is used as the artist instead of each artist in QUERY.
+#
+beegenre() {
+ local artist artregex genre term singleartist
+ local -a artists genres terms
+ singleartist=false
+ case $1 in
+ artist:*)
+ singleartist=true
+ artist="$term"
+ ;;
+ esac
+ if $singleartist; then
+ read count genre < <(beet ls -f '$genre' "$artist" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1) ||:
+ beet modify "$artist" "$@" genre=$genre
+ else
+ while read -r artist; do
+ artregex=$(er "$artist")
+ read count genre < <(beet ls -f '$genre' "artist::^$artregex$" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1) || continue
+ if [[ $count ]]; then
+ artists+=("$artregex")
+ genres+=("$genre")
+ echo "beet modify -y $@ \"artist::^$artist$\" genre=$genre # $count"
+ fi
+ done < <(beet ls -f '$artist' "$@" | sort -u)
+ read -r -N 1 -s -p "Y/n " char
+ case $char in
+ [Yy$'\n'])
+ for (( i=0; i<${#artists[@]}; i++ )); do
+ beet modify -y "$@" "artist::^${artists[i]}$" genre=${genre[i]}
+ done
+ ;;
+ esac
+ fi
+}
+