diff --git a/src/main.sh b/src/main.sh index 9c754f3..a7c37da 100755 --- a/src/main.sh +++ b/src/main.sh @@ -360,6 +360,21 @@ case "${1:-}" in fi exit 0 ;; +"--decorate-as") + # Decorate the specified directory as given MusicBrainz release + # + # @argument $2: path + # @argument $3: MusicBrainz release ID + [ ! "${2:-}" ] && err "You did not specify a directory." && exit 1 + [ ! -d "$2" ] && err "Path $2 does not point to a directory." && exit 1 + [ ! "${3:-}" ] && err "You did not specify a MusicBrainz release ID." && exit 1 + [ ! "$(mb_release "$3" | $JQ '.title // ""')" ] && err "Did you specify a correct MusicBrainz release ID?" && exit 1 + if ! decorate_as "$2" "$3"; then + err "Something went wrong." + exit 1 + fi + exit 0 + ;; "--reload-database") # Reload database of local music # @@ -392,6 +407,7 @@ GENERAL OPTIONS: MANAGE LOCAL MUSIC: --decorate Decorate directory containing a tagged release + --decorate-as Decorate directory as the relase --reload-database Populate database with decorated local music from EOF exit 0 diff --git a/src/sh/api.sh b/src/sh/api.sh index 76bce13..9282960 100644 --- a/src/sh/api.sh +++ b/src/sh/api.sh @@ -103,7 +103,8 @@ __api_mb() { errormsg=$($JQ -e '.error // ""' "$tmpout") if [ "$errormsg" ]; then err "Failed to fetch MusicBrainz data for $1 $2: $errormsg" - [ "$errormsg" = "Not Found" ] && break + echo "$errormsg" | grep -q -i "not found" && break + echo "$errormsg" | grep -q -i "invalid" && break sleep "$SLEEP_ON_ERROR" else cat "$tmpout" diff --git a/src/sh/local.sh b/src/sh/local.sh index 8061beb..71e323a 100644 --- a/src/sh/local.sh +++ b/src/sh/local.sh @@ -83,6 +83,97 @@ decorate() { fi } +# Decorate locally available music with specified MusicBrainz release +# +# @input $1: Path to directory with album +# @input $2: MusicBrainz release ID +# +# Similar as `decorate`, but the MusicBrainz IDs are not inferred from the +# tags, but passed as argument. +decorate_as() { + if [ -f "$1/$DECORATION_FILENAME" ]; then + rid="$($JQ '.releaseid' "$1/$DECORATION_FILENAME")" + title="$(mb_release "$rid" | $JQ '.title // ""')" + artist="$(mb_release "$rid" | $JQ '."artist-credit" | map([.name, .joinphrase] | join("")) | join("")')" + [ "$rid" = "$2" ] && + info "Directory $1 has already been decorated as the release '$title' - '$artist' with the identical MusicBrainz release ID." || + info "Directory $1 has already been decorated as the release '$title' - '$artist' with the MusicBrainz release ID $rid." + while true; do + infonn "Do you want to redecorate $1? (yes/no)" + read -r yn + case $yn in + "yes") break ;; + "no") return 0 ;; + *) info "Please answer \"yes\" or \"no\"." ;; + esac + done + fi + # Print info + title="$(mb_release "$2" | $JQ '.title // ""')" + artist="$(mb_release "$2" | $JQ '."artist-credit" | map([.name, .joinphrase] | join("")) | join("")')" + info "Decorating $1 as the release $title by $artist" + # Start decoration + decoration=$($JQ -n '.tracks = {}') + tmpf=$(mktemp) + (cd "$1" && find . -type f -iname '*.mp3' -o -iname '*.mp4' -o -iname '*.flac' -o -iname '*.m4a' | sort) >"$tmpf" + # Compare number of tracks with release + rcnt="$(mb_release "$2" | $JQ '.media | map(."track-count") | add')" + dcnt="$(wc -l "$tmpf" | cut -d ' ' -f 1)" + if [ ! "$rcnt" -eq "$dcnt" ]; then + err "Number of tracks in directory ($dcnt) does not match number of tracks in release ($rcnt)." + return 1 + fi + # + tmpj=$(mktemp) + mb_release "$2" | + $JQ '.media[] | + .position as $pos | + .tracks | + map({ + $pos, + "id": .id, + "n": .number, + "t": .title + }) | + map(if(.n | type == "string" and test("^[0-9]+$")) then .n |= tonumber else . end) | + sort_by([.pos, .n])[] | + [.t, .id] | + join("\t")' >"$tmpj" + assocfile=$(mktemp) + awk -F '\t' ' +BEGIN { OFS = "\t" } +FNR == NR { title[FNR] = $1; id[FNR] = $2 } +FNR != NR { fname[FNR] = $1 } +END { for (i in id) print title[i], id[i], fname[i] } +' "$tmpj" "$tmpf" >"$assocfile" + rm -f "$tmpj" "$tmpf" + # Ask user if this is ok + info "We discovered the following associatoin:" + while IFS= read -r line; do + t="$(echo "$line" | cut -d "$(printf '\t')" -f 1)" + f="$(echo "$line" | cut -d "$(printf '\t')" -f 3)" + printf "Track '%s'\tFile '%s'\n" "$t" "$f" + done <"$assocfile" | column -t -s "$(printf '\t')" + while true; do + infonn "Are the track correctly associated to the audio files? (yes/no)" + read -r yn + case $yn in + "yes") break ;; + "no") return 0 ;; + *) info "Please answer \"yes\" or \"no\"." ;; + esac + done + # Construct decoration + decoration=$($JQ -n '.tracks = {}') + while IFS= read -r line; do + i="$(echo "$line" | cut -d "$(printf '\t')" -f 2)" + f="$(echo "$line" | cut -d "$(printf '\t')" -f 3)" + decoration=$(echo "$decoration" | $JQ ".tracks += {\"$i\": \"$f\"}") + done <"$assocfile" + echo "$decoration" | $JQ ".releaseid = \"$2\"" >"$1/$DECORATION_FILENAME" + return 0 +} + # Load missing cache entries (batch mode) # # argument $1: type diff --git a/src/sh/log.sh b/src/sh/log.sh index cddf059..650d307 100644 --- a/src/sh/log.sh +++ b/src/sh/log.sh @@ -25,3 +25,8 @@ err() { info() { echo "$(date) [$$]>${INFO}Info:${OFF} ${1:-}" | tee -a "$LOGFILE" | cut -d ">" -f 2- >/dev/stderr } + +# Like `info` but without newlnes on stderr. +infonn() { + echo "$(date) [$$]>${INFO}Info:${OFF} ${1:-}" | tee -a "$LOGFILE" | cut -d ">" -f 2- | tr '\n' ' ' >/dev/stderr +}