#!/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!'