gettags() { ffprobe -v error -show_entries format_tags -print_format json "$1" | $JQ -r --compact-output '.format.tags | { trackid: (."MusicBrainz Release Track Id" // ."MUSICBRAINZ_RELEASETRACKID" // ."MusicBrainz/Release Track Id" // ""), releaseid: (."MusicBrainz Album Id" // ."MUSICBRAINZ_ALBUMID" // ."MusicBrainz/Album Id" // "") }' } # Read music files in specified directory and create json file that points to # all relevant MusicBrainz IDs. # @input $1: Path to directory with album decorate() { if [ -f "$1/$DECORATION_FILENAME" ]; then info "Directory $1 has already been decorated (skipping)" return 0 fi decoration=$($JQ -n '.tracks = {}') tmpf=$(mktemp) (cd "$1" && find . -type f -iname '*.mp3' -o -iname '*.mp4' -o -iname '*.flac' -o -iname '*.m4a') >"$tmpf" while IFS= read -r f; do mbid=$(gettags "$1/$f") rid=$(echo "$mbid" | $JQ -r '.releaseid') tid=$(echo "$mbid" | $JQ -r '.trackid') if [ ! "$rid" ] || [ ! "$tid" ]; then err "File $f: Seems not tagged" releaseid="" break fi if [ "${releaseid:-}" ]; then if [ "$releaseid" != "$rid" ]; then err "Directory $1 contains files of different releases" releaseid="" break fi else info "Decorating $1 as release $rid" releaseid="$rid" fi decoration=$(echo "$decoration" | $JQ ".tracks += {\"$tid\": \"$f\"}") done <"$tmpf" rm -f "$tmpf" if [ "$releaseid" ]; then echo "$decoration" | $JQ --compact-output ".releaseid = \"$releaseid\"" >"$1/$DECORATION_FILENAME" else return 1 fi } # Load missing cache entries (batch mode) # argument $1: type # argument $2: File with one ID per line __batch_load_missing() { tmpf=$(mktemp) while IFS= read -r mbid; do if ! in_cache "$1" "$mbid"; then echo "$mbid" >>"$tmpf" fi done <"$2" if [ -s "$tmpf" ]; then lines=$(wc -l "$tmpf" | cut -d ' ' -f 1) if [ "$lines" -gt 0 ]; then case "$1" in "$TYPE_ARTIST") tt="artists" ;; "$TYPE_RELEASEGROUP") tt="release groups" ;; "$TYPE_RELEASE") tt="releases" ;; esac info "Fetching $lines missing $tt" cnt=0 while IFS= read -r mbid; do case "$1" in "$TYPE_ARTIST") mb_artist "$mbid" >/dev/null ;; "$TYPE_RELEASEGROUP") mb_releasegroup "$mbid" >/dev/null ;; "$TYPE_RELEASE") mb_release "$mbid" >/dev/null ;; esac cnt=$((cnt + 1)) printf "\033[K\r%d/%d (%s: %s)" "$cnt" "$lines" "$mbid" "$($JQ -r ".name // .title")" sleep 1 done <"$tmpf" printf "\n" fi fi rm -f "$tmpf" } LOCALDATADIR="$HOME/.cache/$APP_NAME/local" LOCALDATA_ARTISTS="$LOCALDATADIR/artists" LOCALDATA_RELEASEGROUPS="$LOCALDATADIR/releasegroups" LOCALDATA_RELEASES="$LOCALDATADIR/releases" # Load local music load_local() { [ -d "$LOCALDATADIR" ] || mkdir -p "$LOCALDATADIR" tmpreleases=$(mktemp) [ -f "$tmpreleases" ] || exit 1 find "$MUSICDIR" -type f -name "$DECORATION_FILENAME" -print0 | xargs -0 -P 4 $JQ -r '.releaseid+"\t"+input_filename' | tee "$LOCALDATA_RELEASES" | cut -d "$(printf '\t')" -f 1 >"$tmpreleases" __batch_load_missing "$TYPE_RELEASE" "$tmpreleases" # Get release groups and album artists while IFS= read -r rid; do mb=$(mb_release "$rid") echo "$mb" | $JQ -r '."release-group".id' >>"$LOCALDATA_RELEASEGROUPS" echo "$mb" | $JQ -r '."release-group"."artist-credit" | map(.artist.id) | join("\n")' >>"$LOCALDATA_ARTISTS" done <"$tmpreleases" tf=$(mktemp) sort "$LOCALDATA_RELEASEGROUPS" | uniq >"$tf" && mv "$tf" "$LOCALDATA_RELEASEGROUPS" sort "$LOCALDATA_ARTISTS" | uniq >"$tf" && mv "$tf" "$LOCALDATA_ARTISTS" # Populate cache with missing data __batch_load_missing "$TYPE_RELEASEGROUP" "$LOCALDATA_RELEASEGROUPS" __batch_load_missing "$TYPE_ARTIST" "$LOCALDATA_ARTISTS" rm -f "$tmpreleases" } # List all releases list_releases() { cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" } # List all release groups list_releasegroups() { cat "$LOCALDATA_RELEASEGROUPS" } # List all album artists list_artists() { cat "$LOCALDATA_ARTISTS" }