+#!/bin/bash
+
+f=/usr/local/lib/err;test -r $f || { echo "error: $0 no $f" >&2;exit 1;}; . $f
+
+
+plist_only=false
+dry_run=false
+while (( $# )); do
+ case $1 in
+ plist)
+ plist_only=true
+ ;;
+ dry)
+ dry_run=true
+ ;;
+ esac
+done
+
+m ()
+{
+ printf "%s\n" "$*" >&2
+ if $dry_run && [[ $1 == beet && $2 == modify ]]; then
+ echo "dry run: $*"
+ else
+ "$@"
+ fi
+}
+
+tmpf=$(mktemp)
+
+
+if ! $plist_only; then
+ echo begin star rating import from navidrome to beets
+
+ declare -A navirating
+ tmpdir=$(mktemp -d)
+ cd $tmpdir
+ if [[ $HOSTNAME != kd ]]; then
+ ssh_prefix="ssh b8.nz"
+ ssh b8.nz '
+install -m 700 -d /tmp/nav2beet
+for r in 1 2 3 4 5; do
+ sqlite3 /i/navidrome/navidrome.db ".output /tmp/nav2beet/$r" "select path from annotation inner join media_file on item_id = id where rating = $r;"
+done
+tar cz -C /tmp nav2beet
+' | tar xz
+ cd nav2beet
+ else
+ for r in 1 2 3 4 5; do
+ sqlite3 /i/navidrome/navidrome.db ".output $r" "select path from annotation inner join media_file on item_id = id where rating = $r;"
+ done
+ fi
+ declare -A flacs
+
+ # todo: consider if this is a problem: file removed/renamed in main
+ # collection, but not yet updated navidrome, we want to skip it not
+ # die.
+
+ while read -r l; do
+ flacs[$l]=t
+ done < <($ssh_prefix find /i/m -type f -name '*.flac')
+ for r in 1 2 3 4 5; do
+ while read -r path; do
+ beetpath="/i/m${path#/i/converted}"
+ flac="${beetpath%.mp3}.flac"
+ if [[ ${flacs[$flac]} ]]; then
+ beetpath="$flac"
+ fi
+ navirating[$beetpath]=$r
+ done <$r
+ done
+ cd
+ rm -rf $tmpdir
+ declare -A beetrating
+ for r in 1 2 3 4 5; do
+ m beet ls -f '$path' rating:$r >$tmpf
+ while read -r path; do
+ beetrating[$path]=$r
+ done <$tmpf
+ done
+
+ for path in "${!navirating[@]}"; do
+ r="${navirating[$path]}"
+ if [[ $r != "${beetrating[$path]}" ]]; then
+ # note: this assumes there are no cases like filea.mp3 filea.mp3.mp3, which would affect both files.
+ echo "$r != ${beetrating[$path]}, beet modify -y path:$path rating=$r"
+ beet modify -y "path:$path" "rating=$r"
+ fi
+ done
+ # end star rating import from navidrome to beets:
+fi
+
+echo begin import navidrome playlists as flexible attribute with value t.
+# These are only the playlists listed in the beets config.yaml
+# "subsonicplaylist:" and then duplicated here:
+
+
+plists=(
+ # these are useful tags
+ expl
+ gimicky
+ sad
+ # these are normal playlists
+ love
+ pump1
+ pumprap
+ rend
+ run
+)
+# this puts the navidrome playlists into a smart attribute
+# subsonic_playlist
+m beet subsonicplaylist
+
+beet ls -f '$id $subsonic_playlist' subsonic_playlist::. | sed 's/;/ /g' >$tmpf
+
+# for debugging
+#m head $tmpf
+
+navlists=()
+while read -r id id_plists; do
+ navlists[id]="$id_plists"
+done <$tmpf
+
+for plist in ${plists[@]}; do
+ echo "processing $plist"
+ for id in $(beet ls -f '$id' $plist:t ^genre:spoken-w ^genre:skit ^rating:1); do
+ found=false
+ newnavlist=()
+ for navlist in ${navlists[id]}; do
+ if [[ $navlist == "$plist" ]]; then
+ found=true
+ else
+ newnavlist+=($navlist)
+ fi
+ done
+ if $found; then
+ navlists[id]="${newnavlist[*]}"
+ else
+ # exists in beets, but not navidrome so we must have removed
+ # it from the playlist in navidrome.
+ m beet modify -y "id:$id" "$plist!"
+ fi
+ done
+done
+# The ones we didnt find and remove are ones we added
+# in navidrome.
+for id in ${!navlists[@]}; do
+ [[ ${navlists[id]} ]] || continue
+ m beet modify -y "id:$id" ${navlists[id]// /=t }=t
+done
+
+# old way of doing it which sets everything. this doesnt
+# account for removed tracks in navidrome, and it will
+# be slower for handling many playlists.
+# while read -r id plists; do
+# plists="${plists#;}"
+# m beet modify -y "id:$id" ${plists//;/=t }
+# done < <(beet ls -f '$id $subsonic_playlist' subsonic_playlist::.)
+
+# if we remove a track from a playlist in navidrome,
+# beet subsonicplaylist won't remove corresponding beet
+# smart attribute, so clear them all here.
+
+m beet modify -y subsonic_playlist::. 'subsonic_playlist!'