diff --git a/src/main.sh b/src/main.sh index 7d95cf0..0021866 100755 --- a/src/main.sh +++ b/src/main.sh @@ -3,10 +3,10 @@ set -eu # The user interface of this application is composed out of the following -# views. +# views: # - VIEW_ARTIST: Show all release group of an artist # - VIEW_RELEASEGROUP: Show all releases within a release group -# - VIEW_RELEASE: Show track list of release +# - VIEW_RELEASE: Show track list of a release # - VIEW_SEARCH_ARTIST: Interface to search artists on MusicBrainz # - VIEW_SEARCH_ALBUM: Interface to search albums (release groups) on MusicBrainz # - VIEW_LIST_ARTISTS: Presentation of all artists in the local database @@ -16,7 +16,7 @@ set -eu # - VIEW_QUIT: Exiting view, to terminate the application # # All views but the last three are handled within a single fzf instance. The -# views VIEW_SELECT_ARTIST, and VIEW_PLAYLIST run each in a separate fzf +# views VIEW_SELECT_ARTIST and VIEW_PLAYLIST run each in a separate fzf # instance. The last view (VIEW_QUIT) does nothing but terminate the # application. # @@ -26,7 +26,7 @@ set -eu # the query string can be written. # # All views and modes are referred to by the following constants. The values -# are arbitrary but distinct. +# are arbitrary but must be distinct. VIEW_ARTIST="artist" VIEW_RELEASEGROUP="rg" VIEW_RELEASE="release" @@ -56,7 +56,7 @@ MODE_INSERT="show" # Load playlist tools . "sh/playlist.sh" -# Load MusicBrainz and Discogs methods +# Load MusicBrainz, Discogs, and wiki methods . "sh/api.sh" # Load mpv methods @@ -313,6 +313,18 @@ case "${1:-}" in ;; esac +# Load configuration +. "sh/config.sh" + +# Load theme +. "sh/theme.sh" + +# Load tools +. "sh/tools.sh" + +# Load AWK scripts +. "sh/awk.sh" + # Non-interactive user commands intended to the user. These commands do not # require temporary files, fzf, nor the mpv instance. case "${1:-}" in @@ -332,7 +344,7 @@ case "${1:-}" in fi exit 0 ;; -"--reload") +"--reload-database") # Reload database of local music # # @argument $2: path @@ -343,7 +355,7 @@ case "${1:-}" in [ ! "${2:-}" ] && err "Path to decorated music is missing." && exit 1 [ ! -d "$2" ] && err "Path does not point to a directory." && exit 1 info "Reloading information of local music directory $2" - load_local "$2" || err "Failed to load local data" + reloaddb "$2" || err "Failed to load local data" info "Done" exit 0 ;; @@ -356,27 +368,18 @@ GENERAL OPTIONS: --help Show this help and exit. --artists Default options, list artists of local music --albums List albums of local music - --search-artist [] Search artist on MusicBrainz - --search-album [] Search album on MusicBrainz + --search-artist Search artist on MusicBrainz + --search-album Search album on MusicBrainz --artist List release groups of given artist --releasegroup List releases in given release group --release Show release given by - --ni-search-artist [] Non-interactive search on MusicBrainz - --ni-search-album [] Non-interactive search on MusicBrainz MANAGE LOCAL MUSIC: --decorate Decorate directory containing a tagged release - --reload Populate database with decorated local music from + --reload-database Populate database with decorated local music from EOF exit 0 ;; -"--refresh-view") - # Recompute main views - # - # With this method, the content for the views VIEW_LIST_ARTISTS and VIEW_LIST_ALBUMS are recomputed. - precompute_view - exit 0 - ;; esac # Interactive user commands @@ -428,38 +431,29 @@ case "${1:-}" in esac # Start application: -# - load configuration -# - load and export theme # - load and export preset filters # - load and export keys -# - load and export tools -# - load and export awk scripts # - set title +# - check for missing data from MusicBrainz +# - precompute main views # - get temporary directory for temporary files # - start mpv daemon # - enter main loop and start fzf -# Load configuration -. "sh/config.sh" - -# Load theme -. "sh/theme.sh" - # Load filters . "sh/filter.sh" # Load keys . "sh/keys.sh" -# Load tools -. "sh/tools.sh" - -# Load AWK scripts -. "sh/awk.sh" - # Set window title printf '\033]0;%s\007' "$WINDOW_TITLE" +# Check if the required json files are present +local_files_present || load_missing_files +# Generate views +precompute_views + # Generate filenames for temporary files tmpdir=$(mktemp -d) LOCKFILE="$tmpdir/lock" diff --git a/src/sh/api.sh b/src/sh/api.sh index fb00114..cf3ecea 100644 --- a/src/sh/api.sh +++ b/src/sh/api.sh @@ -1,7 +1,12 @@ -MB_MAX_RETRIES=10 -MB_BROWSE_STEPS=100 -USER_AGENT="$APP_NAME/$APP_VERSION ($APP_WEBSITE)" -SLEEP_ON_ERROR=1 +if [ ! "${API_LOADED:-}" ]; then + MB_MAX_RETRIES=10 + MB_BROWSE_STEPS=100 + USER_AGENT="$APP_NAME/$APP_VERSION ($APP_WEBSITE)" + SLEEP_ON_ERROR=1 + export MB_MAX_RETRIES MB_BROWSE_STEPS USER_AGENT SLEEP_ON_ERROR + + export API_LOADED=1 +fi __api_mb() { tmpout=$(mktemp) @@ -78,16 +83,18 @@ __api_mb() { "https://musicbrainz.org/ws/2/release-group" ;; esac - if ! $JQ -e '.error' "$tmpout" >/dev/null 2>/dev/stdout; then + errormsg=$($JQ -e '.error // ""' "$tmpout") + if [ "$errormsg" ]; then + err "Failed to fetch MusicBrainz data for $1 $2: $errormsg" + sleep "$SLEEP_ON_ERROR" + else cat "$tmpout" rm -f "$tmpout" return 0 - else - sleep "$SLEEP_ON_ERROR" fi done rm -f "$tmpout" - err "Failed to fetch MusicBrainz data for $1 $2" + err "Failed to fetch MusicBrainz data for $1 $2 (not retrying anymore...)" return 1 } diff --git a/src/sh/cache.sh b/src/sh/cache.sh index ba5b4ee..01b26e6 100644 --- a/src/sh/cache.sh +++ b/src/sh/cache.sh @@ -6,25 +6,38 @@ # ./releasegroup/radix(uuid)/musicbrainz.json # Release group information # ./releasegroup/radix(uuid)/releases.json # List of all releases in release group # ./release/radix(uuid)/musicbrainz.json # Release information with tracklist etc. -CACHEDIR="$HOME/.cache/$APP_NAME" -TYPE_ARTIST="artist" -TYPE_RELEASEGROUP="releasegroup" -TYPE_RELEASE="release" -artist_filename="musicbrainz.json" -artist_releasegroups_filename="releasegroups.json" -artist_discogs_filename="discogs.json" -artist_wikidata_filename="wikidata.json" -artist_enwikipedia_filename="enwikipedia.json" -releasegroup_filename="musicbrainz.json" -releasegroup_releases_filename="releases.json" -release_filename="musicbrainz.json" +if [ ! "${CACHE_LOADED:-}" ]; then + CACHEDIR="$HOME/.cache/$APP_NAME" + TYPE_ARTIST="artist" + TYPE_RELEASEGROUP="releasegroup" + TYPE_RELEASE="release" + ARTIST_FILENAME="musicbrainz.json" + ARTIST_RELEASEROUPS_FILENAME="releasegroups.json" + ARTIST_DISCOGS_FILENAME="discogs.json" + ARTIST_WIKIDATA_FILENAME="wikidata.json" + ARTIST_ENWIKIPEDIA_FILENAME="enwikipedia.json" + RELEASEGROUP_FILENAME="musicbrainz.json" + RELEASEGROUP_RELEASES_FILENAME="releases.json" + RELEASE_FILENAME="musicbrainz.json" + export CACHEDIR TYPE_ARTIST TYPE_RELEASEGROUP TYPE_RELEASE ARTIST_FILENAME \ + ARTIST_RELEASEROUPS_FILENAME ARTIST_DISCOGS_FILENAME \ + ARTIST_WIKIDATA_FILENAME ARTIST_ENWIKIPEDIA_FILENAME \ + RELEASEGROUP_FILENAME RELEASEGROUP_RELEASES_FILENAME RELEASE_FILENAME + + export CACHE_LOADED=1 +fi # Radix transform directory name __radix() { echo "$1" | awk -F "" '{ print $1$2$3$4"/"$5$6$7$8"/"$0 }' } +# Radix transform directory names from stdin +__radix_batch() { + cat | awk -F "" '{ print $1$2$3$4"/"$5$6$7$8"/"$0 }' +} + # Super wrapper # argument $1: type # argument $2: MusicBrainz ID @@ -50,87 +63,87 @@ __put_json() { ## Artist cache_get_artist() { - __get_json "$TYPE_ARTIST" "$1" "$artist_filename" + __get_json "$TYPE_ARTIST" "$1" "$ARTIST_FILENAME" } cache_get_artist_releasegroups() { - __get_json "$TYPE_ARTIST" "$1" "$artist_releasegroups_filename" + __get_json "$TYPE_ARTIST" "$1" "$ARTIST_RELEASEROUPS_FILENAME" } cache_get_artist_discogs() { - __get_json "$TYPE_ARTIST" "$1" "$artist_discogs_filename" + __get_json "$TYPE_ARTIST" "$1" "$ARTIST_DISCOGS_FILENAME" } cache_get_artist_enwikipedia() { - __get_json "$TYPE_ARTIST" "$1" "$artist_enwikipedia_filename" + __get_json "$TYPE_ARTIST" "$1" "$ARTIST_ENWIKIPEDIA_FILENAME" } cache_get_artist_wikidata() { - __get_json "$TYPE_ARTIST" "$1" "$artist_wikidata_filename" + __get_json "$TYPE_ARTIST" "$1" "$ARTIST_WIKIDATA_FILENAME" } cache_put_artist() { - cat | __put_json "$TYPE_ARTIST" "$1" "$artist_filename" + cat | __put_json "$TYPE_ARTIST" "$1" "$ARTIST_FILENAME" } cache_put_artist_releasegroups() { - cat | __put_json "$TYPE_ARTIST" "$1" "$artist_releasegroups_filename" + cat | __put_json "$TYPE_ARTIST" "$1" "$ARTIST_RELEASEROUPS_FILENAME" } cache_append_artist_releasegroups() { tmpf=$(mktemp) cat >"$tmpf" updated=$(mktemp) - f="$CACHEDIR/$TYPE_ARTIST/$(__radix "$1")/$artist_releasegroups_filename" + f="$CACHEDIR/$TYPE_ARTIST/$(__radix "$1")/$ARTIST_RELEASEROUPS_FILENAME" $JQ --slurpfile n "$tmpf" '."release-groups" += ($n[0]|."release-groups")' "$f" >"$updated" && mv "$updated" "$f" rm -f "$tmpf" } cache_put_artist_discogs() { - cat | __put_json "$TYPE_ARTIST" "$1" "$artist_discogs_filename" + cat | __put_json "$TYPE_ARTIST" "$1" "$ARTIST_DISCOGS_FILENAME" } cache_put_artist_enwikipedia() { - cat | __put_json "$TYPE_ARTIST" "$1" "$artist_enwikipedia_filename" + cat | __put_json "$TYPE_ARTIST" "$1" "$ARTIST_ENWIKIPEDIA_FILENAME" } cache_put_artist_wikidata() { - cat | __put_json "$TYPE_ARTIST" "$1" "$artist_wikidata_filename" + cat | __put_json "$TYPE_ARTIST" "$1" "$ARTIST_WIKIDATA_FILENAME" } ## Release group cache_get_releasegroup() { - __get_json "$TYPE_RELEASEGROUP" "$1" "$releasegroup_filename" + __get_json "$TYPE_RELEASEGROUP" "$1" "$RELEASEGROUP_FILENAME" } cache_get_releasegroup_releases() { - __get_json "$TYPE_RELEASEGROUP" "$1" "$releasegroup_releases_filename" + __get_json "$TYPE_RELEASEGROUP" "$1" "$RELEASEGROUP_RELEASES_FILENAME" } cache_put_releasegroup() { - cat | __put_json "$TYPE_RELEASEGROUP" "$1" "$releasegroup_filename" + cat | __put_json "$TYPE_RELEASEGROUP" "$1" "$RELEASEGROUP_FILENAME" } cache_put_releasegroup_releases() { - cat | __put_json "$TYPE_RELEASEGROUP" "$1" "$releasegroup_releases_filename" + cat | __put_json "$TYPE_RELEASEGROUP" "$1" "$RELEASEGROUP_RELEASES_FILENAME" } cache_append_releasegroup_releases() { tmpf=$(mktemp) cat >"$tmpf" updated=$(mktemp) - f="$CACHEDIR/$TYPE_RELEASEGROUP/$(__radix "$1")/$releasegroup_releases_filename" + f="$CACHEDIR/$TYPE_RELEASEGROUP/$(__radix "$1")/$RELEASEGROUP_RELEASES_FILENAME" $JQ --slurpfile n "$tmpf" '."releases" += ($n[0]|."releases")' "$f" >"$updated" && mv "$updated" "$f" rm -f "$tmpf" } ## Release cache_get_release() { - __get_json "$TYPE_RELEASE" "$1" "$release_filename" + __get_json "$TYPE_RELEASE" "$1" "$RELEASE_FILENAME" } cache_put_release() { - cat | __put_json "$TYPE_RELEASE" "$1" "$release_filename" + cat | __put_json "$TYPE_RELEASE" "$1" "$RELEASE_FILENAME" } ## Cache deletion @@ -144,18 +157,35 @@ cache_delete_artist() { # argument $2: MusicBrainz ID in_cache() { case "$1" in - "$TYPE_ARTIST") - fn="$artist_filename" - ;; - "$TYPE_RELEASEGROUP") - fn="$releasegroup_filename" - ;; - "$TYPE_RELEASE") - fn="$release_filename" - ;; - *) - return 1 - ;; + "$TYPE_ARTIST") fn="$ARTIST_FILENAME" ;; + "$TYPE_RELEASEGROUP") fn="$RELEASEGROUP_FILENAME" ;; + "$TYPE_RELEASE") fn="$RELEASE_FILENAME" ;; + *) return 1 ;; esac [ "$(__get_json "$1" "$2" "$fn")" ] && return 0 || return 1 } + +# Print all cache paths to the files specified by their IDs +# +# @argument $1: type +# +# This method reads from stdin any number of MusicBrainz IDs of objects of the +# specified type, and prints the file pahts. +cache_get_file_batch() { + case "$1" in + "$TYPE_ARTIST") fn="$ARTIST_FILENAME" ;; + "$TYPE_RELEASEGROUP") fn="$RELEASEGROUP_FILENAME" ;; + "$TYPE_RELEASE") fn="$RELEASE_FILENAME" ;; + *) return 1 ;; + esac + cat | + __radix_batch | + awk -v dir="$CACHEDIR/$1/" -v f="/$fn" '{ print dir $0 f }' +} + +# Print MusicBrainz ID associated to the file paths +# +# This reads from stdin any number of paths (one per line) +cache_mbid_from_path_batch() { + cat | awk -F "/" '{ print $(NF-1) }' +} diff --git a/src/sh/info.sh b/src/sh/info.sh index c8ca3a4..e316917 100644 --- a/src/sh/info.sh +++ b/src/sh/info.sh @@ -1,5 +1,10 @@ # Application information -APP_NAME="fuzic" -APP_VERSION="0.1" -APP_WEBSITE="https://git.indyfac.ch/amin/fuzic" -WINDOW_TITLE="🔎🎶 $APP_NAME | a simple music finder and player" +if [ ! "${INFO_LOADED:-}" ]; then + APP_NAME="fuzic" + APP_VERSION="0.1" + APP_WEBSITE="https://git.indyfac.ch/amin/fuzic" + WINDOW_TITLE="🔎🎶 $APP_NAME | a simple music finder and player" + export APP_NAME APP_VERSION APP_WEBSITE WINDOW_TITLE + + export INFO_LOADED=1 +fi diff --git a/src/sh/lists.sh b/src/sh/lists.sh index c94936d..7b5d350 100644 --- a/src/sh/lists.sh +++ b/src/sh/lists.sh @@ -158,8 +158,7 @@ list_artists_from_json() { -v format_disambiguation="$AV_DISAMBIGUATION" \ -v format_local="$FORMAT_LOCAL" \ "$AWK_ARTISTS" | - column -t -s "$(printf '\t')" | - sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' + column -t -s "$(printf '\t')" -l 2 } # Generate playlist view diff --git a/src/sh/local.sh b/src/sh/local.sh index 81a6fbd..8e2db6e 100644 --- a/src/sh/local.sh +++ b/src/sh/local.sh @@ -1,3 +1,24 @@ +if [ ! "${LOCAL_LOADED:-}" ]; then + LOCALDATADIR="$HOME/.cache/$APP_NAME/local" + LOCALDATA_ARTISTS="$LOCALDATADIR/artists" + LOCALDATA_RELEASEGROUPS="$LOCALDATADIR/releasegroups" + LOCALDATA_RELEASES="$LOCALDATADIR/releases" + LOCALDATA_ARTISTS_VIEW="$LOCALDATADIR/artists_view" + LOCALDATA_RELEASEGROUPS_VIEW="$LOCALDATADIR/releasegroups_view" + LOCALDATA_RELEASES_VIEW="$LOCALDATADIR/releases_view" + LOCALDATA_ARTISTS_LIST="$LOCALDATADIR/artists_list" + LOCALDATA_RELEASEGROUPS_LIST="$LOCALDATADIR/releasegroups_list" + LOCALDATA_RELEASES_LIST="$LOCALDATADIR/releases_list" + DECORATION_FILENAME=${DECORATION_FILENAME:-"mbid.json"} + + export LOCALDATADIR LOCALDATA_ARTISTS LOCALDATA_RELEASEGROUPS \ + LOCALDATA_RELEASES LOCALDATA_ARTISTS_VIEW LOCALDATA_RELEASEGROUPS_VIEW \ + LOCALDATA_RELEASES_VIEW LOCALDATA_ARTISTS_LIST LOCALDATA_RELEASEGROUPS_LIST \ + LOCALDATA_RELEASES_LIST DECORATION_FILENAME + + export LOCAL_LOADED=1 +fi + gettags() { ffprobe -v error -show_entries format_tags -print_format json "$1" | $JQ '.format.tags | { @@ -47,192 +68,173 @@ decorate() { } # Load missing cache entries (batch mode) +# # argument $1: type -# argument $2: File with one ID per line +# +# This method reads one MusicBrainz IDs of the specified type from stdin (one +# per line), and fetches the missing items. __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 + cat | + cache_get_file_batch "$1" | + xargs \ + sh -c 'for f; do [ -e "$f" ] || echo "$f"; done' _ | + cache_mbid_from_path_batch >"$tmpf" + 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 missing $tt" + cnt=0 + while IFS= read -r mbid; do case "$1" in "$TYPE_ARTIST") - tt="artists" - ;; - "$TYPE_RELEASEGROUP") - tt="release groups" - ;; - "$TYPE_RELEASE") - tt="releases" + name=$(mb_artist "$mbid" | $JQ '.name') ;; + "$TYPE_RELEASEGROUP") name=$(mb_releasegroup "$mbid" | $JQ '.title') ;; + "$TYPE_RELEASE") name=$(mb_release "$mbid" | $JQ '.title') ;; esac - info "Fetching $lines missing $tt" - cnt=0 - while IFS= read -r mbid; do - case "$1" in - "$TYPE_ARTIST") - name=$(mb_artist "$mbid" | $JQ ".name") - ;; - "$TYPE_RELEASEGROUP") - name=$(mb_releasegroup "$mbid" | $JQ ".title") - ;; - "$TYPE_RELEASE") - name=$(mb_release "$mbid" | $JQ ".title") - ;; - esac - cnt=$((cnt + 1)) - printf "\033[2K\r%d/%d (%s: %s)" "$cnt" "$lines" "$mbid" "$name" - sleep 1 - done <"$tmpf" - printf "\n" - fi + cnt=$((cnt + 1)) + info "$(printf "%d/%d (%s: %s)" "$cnt" "$lines" "$mbid" "$name")" + sleep 1 + done <"$tmpf" fi rm -f "$tmpf" } -LOCALDATADIR="$HOME/.cache/$APP_NAME/local" -LOCALDATA_ARTISTS="$LOCALDATADIR/artists" -LOCALDATA_RELEASEGROUPS="$LOCALDATADIR/releasegroups" -LOCALDATA_RELEASES="$LOCALDATADIR/releases" -LOCALDATA_ARTISTS_VIEW="$LOCALDATADIR/artists_view" -LOCALDATA_RELEASEGROUPS_VIEW="$LOCALDATADIR/releasegroups_view" -LOCALDATA_RELEASES_VIEW="$LOCALDATADIR/releases_view" -DECORATION_FILENAME=${DECORATION_FILENAME:-"mbid.json"} - # Precompute views -precompute_view() { - info "Precomputing artist view" - while IFS= read -r aid; do - mb_artist "$aid" | $JQ '[ - .id, - .type, - .name, - .disambiguation, - .["life-span"].begin, - .["life-span"].end - ] | join("\t")' - done <"$LOCALDATA_ARTISTS" | - awk \ - -F "\t" \ - -v file_local_artists="${LOCALDATA_ARTISTS:-}" \ - -v format_person="$AV_PERSON" \ - -v format_group="$AV_GROUP" \ - -v format_disambiguation="$AV_DISAMBIGUATION" \ - -v format_local="$FORMAT_LOCAL" \ - "$AWK_ARTISTS" | - sort | - column -t -s "$(printf '\t')" -E 0 | - sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_ARTISTS_VIEW" - info "Precomputing releasegroup view" - while IFS= read -r rgid; do - mb_releasegroup "$rgid" | $JQ '[ - .id, - ."primary-type", - (."secondary-types" // []|join(";")), - ."first-release-date", - .title, - (."artist-credit" | map(([.name, .joinphrase]|join(""))) | join("")) - ] | join("\t")' - done <"$LOCALDATA_RELEASEGROUPS" | - awk \ - -F "\t" \ - -v file_local_releasegroups="${LOCALDATA_RELEASEGROUPS:-}" \ - -v format_release="$RGV_RELEASE" \ - -v format_release_w_artist="$RGV_RELEASE_W_ARTIST" \ - -v format_year="$RGV_YEAR" \ - -v format_album="$FORMAT_TYPE_ALBUM" \ - -v format_single="$FORMAT_TYPE_SINGLE" \ - -v format_ep="$FORMAT_TYPE_EP" \ - -v format_broadcast="$FORMAT_TYPE_BROADCAST" \ - -v format_other="$FORMAT_TYPE_OTHER" \ - -v format_has_secondary="$FORMAT_TYPE_HAS_SECONDARY" \ - -v format_secondary="$FORMAT_TYPE_SECONDARY" \ - -v format_compilation="$FORMAT_TYPE_SECONDARY_COMPILATION" \ - -v format_soundtrack="$FORMAT_TYPE_SECONDARY_SOUNDTRACK" \ - -v format_spokenword="$FORMAT_TYPE_SECONDARY_SPOKENWORD" \ - -v format_interview="$FORMAT_TYPE_SECONDARY_INTERVIEW" \ - -v format_audiobook="$FORMAT_TYPE_SECONDARY_AUDIOBOOK" \ - -v format_audiodrama="$FORMAT_TYPE_SECONDARY_AUDIODRAMA" \ - -v format_live="$FORMAT_TYPE_SECONDARY_LIVE" \ - -v format_remix="$FORMAT_TYPE_SECONDARY_REMIX" \ - -v format_djmix="$FORMAT_TYPE_SECONDARY_DJMIX" \ - -v format_mixtape="$FORMAT_TYPE_SECONDARY_MIXTAPE" \ - -v format_demo="$FORMAT_TYPE_SECONDARY_DEMO" \ - -v format_fieldrec="$FORMAT_TYPE_SECONDARY_FIELDREC" \ - -v format_local="$FORMAT_LOCAL" \ - "$AWK_RELEASEGROUPS" | - sort -n -r | - cut -d "$(printf '\t')" -f 2- | - column -t -s "$(printf '\t')" -E 0 | - sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_RELEASEGROUPS_VIEW" - info "Precomputing release view" - cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" | - while IFS= read -r rid; do - mb_release "$rid" | $JQ '[ +__precompute_lists() { + cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs \ + $JQ '[ .id, - .status, - .date, - ."cover-art-archive".count, - (."label-info" | map(.label.name) | unique | join(", ")), - (.media | map(."track-count") | add), - (.media | map(.format) | unique | join(", ")), - .country, + .type, + .name, + .disambiguation, + .["life-span"].begin, + .["life-span"].end + ] | join("\t")' >"$LOCALDATA_ARTISTS_LIST" + cache_get_file_batch "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" | xargs \ + $JQ '[ + .id, + ."primary-type", + (."secondary-types" // []|join(";")), + ."first-release-date", .title, (."artist-credit" | map(([.name, .joinphrase]|join(""))) | join("")) - ] | join("\t")' - done | - awk \ - -F "\t" \ - -v file_local_releases="${LOCALDATA_RELEASES:-}" \ - -v release_official="$FORMAT_STATUS_OFFICIAL" \ - -v release_promotion="$FORMAT_STATUS_PROMO" \ - -v release_bootleg="$FORMAT_STATUS_BOOTLEG" \ - -v release_pseudo="$FORMAT_STATUS_PSEUDO" \ - -v release_withdrawn="$FORMAT_STATUS_WITHDRAWN" \ - -v release_expunged="$FORMAT_STATUS_EXPUNGED" \ - -v release_cancelled="$FORMAT_STATUS_CANCELLED" \ - -v release_format="$RV_FORMAT" \ - -v release_format_title_artist="$RV_TITLE_ARTIST" \ - -v release_format_title="$RV_TITLE" \ - -v release_format_artist="$RV_ARTIST" \ - -v format_local="$FORMAT_LOCAL" \ - "$AWK_RELEASES" | - sort -n -r | + ] | join("\t")' >"$LOCALDATA_RELEASEGROUPS_LIST" + # cache_get_file_batch "$TYPE_RELEASE" <"$LOCALDATA_RELEASES" | xargs \ + # $JQ '[ + # .id, + # .status, + # .date, + # ."cover-art-archive".count, + # (."label-info" | map(.label.name) | unique | join(", ")), + # (.media | map(."track-count") | add), + # (.media | map(.format) | unique | join(", ")), + # .country, + # .title, + # (."artist-credit" | map(([.name, .joinphrase]|join(""))) | join("")) + # ] | join("\t")' >"$LOCALDATA_RELEASES_LIST" & +} + +# Precompute views +# TODO: The sed opperations take too long, improve! +precompute_views() { + awk \ + -F "\t" \ + -v file_local_artists="${LOCALDATA_ARTISTS:-}" \ + -v format_person="$AV_PERSON" \ + -v format_group="$AV_GROUP" \ + -v format_disambiguation="$AV_DISAMBIGUATION" \ + -v format_local="$FORMAT_LOCAL" \ + "$AWK_ARTISTS" "$LOCALDATA_ARTISTS_LIST" | + sort | + column -t -s "$(printf '\t')" -l 2 >"$LOCALDATA_ARTISTS_VIEW" + #column -t -s "$(printf '\t')" | + #sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_ARTISTS_VIEW" + awk \ + -F "\t" \ + -v file_local_releasegroups="${LOCALDATA_RELEASEGROUPS:-}" \ + -v format_release="$RGV_RELEASE" \ + -v format_release_w_artist="$RGV_RELEASE_W_ARTIST" \ + -v format_year="$RGV_YEAR" \ + -v format_album="$FORMAT_TYPE_ALBUM" \ + -v format_single="$FORMAT_TYPE_SINGLE" \ + -v format_ep="$FORMAT_TYPE_EP" \ + -v format_broadcast="$FORMAT_TYPE_BROADCAST" \ + -v format_other="$FORMAT_TYPE_OTHER" \ + -v format_has_secondary="$FORMAT_TYPE_HAS_SECONDARY" \ + -v format_secondary="$FORMAT_TYPE_SECONDARY" \ + -v format_compilation="$FORMAT_TYPE_SECONDARY_COMPILATION" \ + -v format_soundtrack="$FORMAT_TYPE_SECONDARY_SOUNDTRACK" \ + -v format_spokenword="$FORMAT_TYPE_SECONDARY_SPOKENWORD" \ + -v format_interview="$FORMAT_TYPE_SECONDARY_INTERVIEW" \ + -v format_audiobook="$FORMAT_TYPE_SECONDARY_AUDIOBOOK" \ + -v format_audiodrama="$FORMAT_TYPE_SECONDARY_AUDIODRAMA" \ + -v format_live="$FORMAT_TYPE_SECONDARY_LIVE" \ + -v format_remix="$FORMAT_TYPE_SECONDARY_REMIX" \ + -v format_djmix="$FORMAT_TYPE_SECONDARY_DJMIX" \ + -v format_mixtape="$FORMAT_TYPE_SECONDARY_MIXTAPE" \ + -v format_demo="$FORMAT_TYPE_SECONDARY_DEMO" \ + -v format_fieldrec="$FORMAT_TYPE_SECONDARY_FIELDREC" \ + -v format_local="$FORMAT_LOCAL" \ + "$AWK_RELEASEGROUPS" "$LOCALDATA_RELEASEGROUPS_LIST" | sort -n -r | cut -d "$(printf '\t')" -f 2- | - column -t -s "$(printf '\t')" -E 0 | - sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\):\(.*$\)$|\t\1\t\2\t\3|' >"$LOCALDATA_RELEASES_VIEW" + column -t -s "$(printf '\t')" -l 5 >"$LOCALDATA_RELEASEGROUPS_VIEW" + #column -t -s "$(printf '\t')" | + #sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_RELEASEGROUPS_VIEW" } # Load local music +# # argument $1: path to decorated music files -load_local() { - [ -d "$LOCALDATADIR" ] || mkdir -p "$LOCALDATADIR" - tmpreleases=$(mktemp) - [ -f "$tmpreleases" ] || exit 1 - info "Locating and parsing decoration files ($DECORATION_FILENAME)" +# +# This method parses all decorations and generates a line-by-line database of +# locally available artists, releases, and release groups. +reloaddb() { + rm -rf "$LOCALDATADIR" + mkdir -p "$LOCALDATADIR" find "$1" -type f -name "$DECORATION_FILENAME" -print0 | - xargs -0 -P 4 $JQ '.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 '."release-group".id' >>"$LOCALDATA_RELEASEGROUPS" - echo "$mb" | $JQ '."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" - info "Resetting views" - rm -f "$LOCALDATA_ARTISTS_VIEW" "$LOCALDATA_RELEASEGROUPS_VIEW" "$LOCALDATA_RELEASES_VIEW" - precompute_view + xargs -0 $JQ '.releaseid+"\t"+input_filename' >"$LOCALDATA_RELEASES" + # Get necessary metadata and setup lists + tmpreleases=$(mktemp) + cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" | + tee "$tmpreleases" | + __batch_load_missing "$TYPE_RELEASE" + tmpreleasefiles=$(mktemp) + cache_get_file_batch "$TYPE_RELEASE" <"$tmpreleases" >"$tmpreleasefiles" + xargs \ + $JQ '."release-group".id' \ + <"$tmpreleasefiles" >"$LOCALDATA_RELEASEGROUPS" + xargs \ + $JQ '."release-group"."artist-credit" | map(.artist.id) | join("\n")' \ + <"$tmpreleasefiles" >"$LOCALDATA_ARTISTS" + rm -f "$tmpreleases" "$tmpreleasefiles" + tf1=$(mktemp) + tf2=$(mktemp) + sort "$LOCALDATA_RELEASEGROUPS" | uniq >"$tf1" + mv "$tf1" "$LOCALDATA_RELEASEGROUPS" + sort "$LOCALDATA_ARTISTS" | uniq >"$tf2" + mv "$tf2" "$LOCALDATA_ARTISTS" + __batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" + __batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" + __precompute_lists +} + +# Check if necessary cache files are present or not +local_files_present() { + cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs ls >/dev/null 2>&1 || return 1 + cache_get_file_batch "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" | xargs ls >/dev/null 2>&1 || return 1 + cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" | cache_get_file_batch "$TYPE_RELEASEGROUP" | xargs ls >/dev/null 2>&1 || return 1 + return 0 +} + +# Load missing files +load_missing_files() { + __batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" + __batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" + cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" | __batch_load_missing "$TYPE_RELEASE" } diff --git a/src/sh/log.sh b/src/sh/log.sh index 7334daf..29df336 100644 --- a/src/sh/log.sh +++ b/src/sh/log.sh @@ -1,10 +1,15 @@ # Logging methods -ERR="\033[38;5;196m" -INFO="\033[38;5;75m" -OFF="\033[m" -LOGDIR="$HOME/.local/state/$APP_NAME" -[ -d "$LOGDIR" ] || mkdir -p "$LOGDIR" -LOGFILE="$LOGDIR/log" +if [ ! "${LOG_LOADED:-}" ]; then + ERR="\033[38;5;196m" + INFO="\033[38;5;75m" + OFF="\033[m" + LOGDIR="$HOME/.local/state/$APP_NAME" + [ -d "$LOGDIR" ] || mkdir -p "$LOGDIR" + LOGFILE="$LOGDIR/log" + export ERR INFO OFF LOGFILE + + export LOG_LOADED=1 +fi err() { echo "$(date) [$$]>${ERR}ERROR:${OFF} ${1:-}" | tee -a "$LOGFILE" | cut -d ">" -f 2- >/dev/stderr diff --git a/src/sh/playback.sh b/src/sh/playback.sh index 39cd6de..eac9ec3 100644 --- a/src/sh/playback.sh +++ b/src/sh/playback.sh @@ -4,14 +4,21 @@ # a higher-level playback functionality is provided. # Available playback commands -PLAYBACK_CMD_PLAY="play" -PLAYBACK_CMD_QUEUE="queue" -PLAYBACK_CMD_QUEUE_NEXT="queue-next" -PLAYBACK_CMD_TOGGLE_PLAYBACK="toggle" -PLAYBACK_CMD_PLAY_NEXT="next" -PLAYBACK_CMD_PLAY_PREV="prev" -PLAYBACK_CMD_SEEK_FORWARD="seekf" -PLAYBACK_CMD_SEEK_BACKWARD="seekb" +if [ ! "${PLAYBACK_LOADED:-}" ]; then + PLAYBACK_CMD_PLAY="play" + PLAYBACK_CMD_QUEUE="queue" + PLAYBACK_CMD_QUEUE_NEXT="queue-next" + PLAYBACK_CMD_TOGGLE_PLAYBACK="toggle" + PLAYBACK_CMD_PLAY_NEXT="next" + PLAYBACK_CMD_PLAY_PREV="prev" + PLAYBACK_CMD_SEEK_FORWARD="seekf" + PLAYBACK_CMD_SEEK_BACKWARD="seekb" + export PLAYBACK_CMD_PLAY PLAYBACK_CMD_QUEUE PLAYBACK_CMD_QUEUE_NEXT \ + PLAYBACK_CMD_TOGGLE_PLAYBACK PLAYBACK_CMD_PLAY_NEXT \ + PLAYBACK_CMD_PLAY_PREV PLAYBACK_CMD_SEEK_FORWARD PLAYBACK_CMD_SEEK_BACKWARD + + export PLAYBACK_LOADED=1 +fi # Obtain playback command from key press # @argument $1: key diff --git a/src/sh/playlist.sh b/src/sh/playlist.sh index 5d8ce94..4e0d7c5 100644 --- a/src/sh/playlist.sh +++ b/src/sh/playlist.sh @@ -1,11 +1,18 @@ -PLAYLIST_CMD_REMOVE="rm" -PLAYLIST_CMD_UP="up" -PLAYLIST_CMD_DOWN="down" -PLAYLIST_CMD_CLEAR="clear" -PLAYLIST_CMD_CLEAR_ABOVE="clear-above" -PLAYLIST_CMD_CLEAR_BELOW="clear-below" -PLAYLIST_CMD_SHUFFLE="shuffle" -PLAYLIST_CMD_UNSHUFFLE="unshuffle" +if [ ! "${PLAYLIST_LOADED:-}" ]; then + PLAYLIST_CMD_REMOVE="rm" + PLAYLIST_CMD_UP="up" + PLAYLIST_CMD_DOWN="down" + PLAYLIST_CMD_CLEAR="clear" + PLAYLIST_CMD_CLEAR_ABOVE="clear-above" + PLAYLIST_CMD_CLEAR_BELOW="clear-below" + PLAYLIST_CMD_SHUFFLE="shuffle" + PLAYLIST_CMD_UNSHUFFLE="unshuffle" + export PLAYLIST_CMD_REMOVE PLAYLIST_CMD_UP PLAYLIST_CMD_DOWN \ + PLAYLIST_CMD_CLEAR PLAYLIST_CMD_CLEAR_ABOVE PLAYLIST_CMD_CLEAR_BELOW \ + PLAYLIST_CMD_SHUFFLE PLAYLIST_CMD_UNSHUFFLE + + export PLAYLIST_LOADED=1 +fi # Run playback commands #