improved speed, added local data lists, some cleaning

This commit is contained in:
2025-09-10 22:50:41 +02:00
parent f35461cc50
commit 56b8c73297
9 changed files with 336 additions and 280 deletions

View File

@@ -3,10 +3,10 @@
set -eu set -eu
# The user interface of this application is composed out of the following # The user interface of this application is composed out of the following
# views. # views:
# - VIEW_ARTIST: Show all release group of an artist # - VIEW_ARTIST: Show all release group of an artist
# - VIEW_RELEASEGROUP: Show all releases within a release group # - 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_ARTIST: Interface to search artists on MusicBrainz
# - VIEW_SEARCH_ALBUM: Interface to search albums (release groups) on MusicBrainz # - VIEW_SEARCH_ALBUM: Interface to search albums (release groups) on MusicBrainz
# - VIEW_LIST_ARTISTS: Presentation of all artists in the local database # - 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 # - VIEW_QUIT: Exiting view, to terminate the application
# #
# All views but the last three are handled within a single fzf instance. The # 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 # instance. The last view (VIEW_QUIT) does nothing but terminate the
# application. # application.
# #
@@ -26,7 +26,7 @@ set -eu
# the query string can be written. # the query string can be written.
# #
# All views and modes are referred to by the following constants. The values # 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_ARTIST="artist"
VIEW_RELEASEGROUP="rg" VIEW_RELEASEGROUP="rg"
VIEW_RELEASE="release" VIEW_RELEASE="release"
@@ -56,7 +56,7 @@ MODE_INSERT="show"
# Load playlist tools # Load playlist tools
. "sh/playlist.sh" . "sh/playlist.sh"
# Load MusicBrainz and Discogs methods # Load MusicBrainz, Discogs, and wiki methods
. "sh/api.sh" . "sh/api.sh"
# Load mpv methods # Load mpv methods
@@ -313,6 +313,18 @@ case "${1:-}" in
;; ;;
esac 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 # Non-interactive user commands intended to the user. These commands do not
# require temporary files, fzf, nor the mpv instance. # require temporary files, fzf, nor the mpv instance.
case "${1:-}" in case "${1:-}" in
@@ -332,7 +344,7 @@ case "${1:-}" in
fi fi
exit 0 exit 0
;; ;;
"--reload") "--reload-database")
# Reload database of local music # Reload database of local music
# #
# @argument $2: path # @argument $2: path
@@ -343,7 +355,7 @@ case "${1:-}" in
[ ! "${2:-}" ] && err "Path to decorated music is missing." && exit 1 [ ! "${2:-}" ] && err "Path to decorated music is missing." && exit 1
[ ! -d "$2" ] && err "Path does not point to a directory." && exit 1 [ ! -d "$2" ] && err "Path does not point to a directory." && exit 1
info "Reloading information of local music directory $2" 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" info "Done"
exit 0 exit 0
;; ;;
@@ -356,27 +368,18 @@ GENERAL OPTIONS:
--help Show this help and exit. --help Show this help and exit.
--artists Default options, list artists of local music --artists Default options, list artists of local music
--albums List albums of local music --albums List albums of local music
--search-artist [<query>] Search artist on MusicBrainz --search-artist Search artist on MusicBrainz
--search-album [<query>] Search album on MusicBrainz --search-album Search album on MusicBrainz
--artist <mbid> List release groups of given artist <mbid> --artist <mbid> List release groups of given artist <mbid>
--releasegroup <mbid> List releases in given release group <mbid> --releasegroup <mbid> List releases in given release group <mbid>
--release <mbid> Show release given by <mbid> --release <mbid> Show release given by <mbid>
--ni-search-artist [<query>] Non-interactive search on MusicBrainz
--ni-search-album [<query>] Non-interactive search on MusicBrainz
MANAGE LOCAL MUSIC: MANAGE LOCAL MUSIC:
--decorate <path> Decorate directory containing a tagged release --decorate <path> Decorate directory containing a tagged release
--reload <path> Populate database with decorated local music from <path> --reload-database <path> Populate database with decorated local music from <path>
EOF EOF
exit 0 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 esac
# Interactive user commands # Interactive user commands
@@ -428,38 +431,29 @@ case "${1:-}" in
esac esac
# Start application: # Start application:
# - load configuration
# - load and export theme
# - load and export preset filters # - load and export preset filters
# - load and export keys # - load and export keys
# - load and export tools
# - load and export awk scripts
# - set title # - set title
# - check for missing data from MusicBrainz
# - precompute main views
# - get temporary directory for temporary files # - get temporary directory for temporary files
# - start mpv daemon # - start mpv daemon
# - enter main loop and start fzf # - enter main loop and start fzf
# Load configuration
. "sh/config.sh"
# Load theme
. "sh/theme.sh"
# Load filters # Load filters
. "sh/filter.sh" . "sh/filter.sh"
# Load keys # Load keys
. "sh/keys.sh" . "sh/keys.sh"
# Load tools
. "sh/tools.sh"
# Load AWK scripts
. "sh/awk.sh"
# Set window title # Set window title
printf '\033]0;%s\007' "$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 # Generate filenames for temporary files
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
LOCKFILE="$tmpdir/lock" LOCKFILE="$tmpdir/lock"

View File

@@ -1,7 +1,12 @@
if [ ! "${API_LOADED:-}" ]; then
MB_MAX_RETRIES=10 MB_MAX_RETRIES=10
MB_BROWSE_STEPS=100 MB_BROWSE_STEPS=100
USER_AGENT="$APP_NAME/$APP_VERSION ($APP_WEBSITE)" USER_AGENT="$APP_NAME/$APP_VERSION ($APP_WEBSITE)"
SLEEP_ON_ERROR=1 SLEEP_ON_ERROR=1
export MB_MAX_RETRIES MB_BROWSE_STEPS USER_AGENT SLEEP_ON_ERROR
export API_LOADED=1
fi
__api_mb() { __api_mb() {
tmpout=$(mktemp) tmpout=$(mktemp)
@@ -78,16 +83,18 @@ __api_mb() {
"https://musicbrainz.org/ws/2/release-group" "https://musicbrainz.org/ws/2/release-group"
;; ;;
esac 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" cat "$tmpout"
rm -f "$tmpout" rm -f "$tmpout"
return 0 return 0
else
sleep "$SLEEP_ON_ERROR"
fi fi
done done
rm -f "$tmpout" 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 return 1
} }

View File

@@ -6,25 +6,38 @@
# ./releasegroup/radix(uuid)/musicbrainz.json # Release group information # ./releasegroup/radix(uuid)/musicbrainz.json # Release group information
# ./releasegroup/radix(uuid)/releases.json # List of all releases in release group # ./releasegroup/radix(uuid)/releases.json # List of all releases in release group
# ./release/radix(uuid)/musicbrainz.json # Release information with tracklist etc. # ./release/radix(uuid)/musicbrainz.json # Release information with tracklist etc.
if [ ! "${CACHE_LOADED:-}" ]; then
CACHEDIR="$HOME/.cache/$APP_NAME" CACHEDIR="$HOME/.cache/$APP_NAME"
TYPE_ARTIST="artist" TYPE_ARTIST="artist"
TYPE_RELEASEGROUP="releasegroup" TYPE_RELEASEGROUP="releasegroup"
TYPE_RELEASE="release" 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
artist_filename="musicbrainz.json" export CACHE_LOADED=1
artist_releasegroups_filename="releasegroups.json" fi
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"
# Radix transform directory name # Radix transform directory name
__radix() { __radix() {
echo "$1" | awk -F "" '{ print $1$2$3$4"/"$5$6$7$8"/"$0 }' 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 # Super wrapper
# argument $1: type # argument $1: type
# argument $2: MusicBrainz ID # argument $2: MusicBrainz ID
@@ -50,87 +63,87 @@ __put_json() {
## Artist ## Artist
cache_get_artist() { cache_get_artist() {
__get_json "$TYPE_ARTIST" "$1" "$artist_filename" __get_json "$TYPE_ARTIST" "$1" "$ARTIST_FILENAME"
} }
cache_get_artist_releasegroups() { cache_get_artist_releasegroups() {
__get_json "$TYPE_ARTIST" "$1" "$artist_releasegroups_filename" __get_json "$TYPE_ARTIST" "$1" "$ARTIST_RELEASEROUPS_FILENAME"
} }
cache_get_artist_discogs() { cache_get_artist_discogs() {
__get_json "$TYPE_ARTIST" "$1" "$artist_discogs_filename" __get_json "$TYPE_ARTIST" "$1" "$ARTIST_DISCOGS_FILENAME"
} }
cache_get_artist_enwikipedia() { cache_get_artist_enwikipedia() {
__get_json "$TYPE_ARTIST" "$1" "$artist_enwikipedia_filename" __get_json "$TYPE_ARTIST" "$1" "$ARTIST_ENWIKIPEDIA_FILENAME"
} }
cache_get_artist_wikidata() { cache_get_artist_wikidata() {
__get_json "$TYPE_ARTIST" "$1" "$artist_wikidata_filename" __get_json "$TYPE_ARTIST" "$1" "$ARTIST_WIKIDATA_FILENAME"
} }
cache_put_artist() { cache_put_artist() {
cat | __put_json "$TYPE_ARTIST" "$1" "$artist_filename" cat | __put_json "$TYPE_ARTIST" "$1" "$ARTIST_FILENAME"
} }
cache_put_artist_releasegroups() { 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() { cache_append_artist_releasegroups() {
tmpf=$(mktemp) tmpf=$(mktemp)
cat >"$tmpf" cat >"$tmpf"
updated=$(mktemp) 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" $JQ --slurpfile n "$tmpf" '."release-groups" += ($n[0]|."release-groups")' "$f" >"$updated" && mv "$updated" "$f"
rm -f "$tmpf" rm -f "$tmpf"
} }
cache_put_artist_discogs() { 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() { 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() { cache_put_artist_wikidata() {
cat | __put_json "$TYPE_ARTIST" "$1" "$artist_wikidata_filename" cat | __put_json "$TYPE_ARTIST" "$1" "$ARTIST_WIKIDATA_FILENAME"
} }
## Release group ## Release group
cache_get_releasegroup() { cache_get_releasegroup() {
__get_json "$TYPE_RELEASEGROUP" "$1" "$releasegroup_filename" __get_json "$TYPE_RELEASEGROUP" "$1" "$RELEASEGROUP_FILENAME"
} }
cache_get_releasegroup_releases() { cache_get_releasegroup_releases() {
__get_json "$TYPE_RELEASEGROUP" "$1" "$releasegroup_releases_filename" __get_json "$TYPE_RELEASEGROUP" "$1" "$RELEASEGROUP_RELEASES_FILENAME"
} }
cache_put_releasegroup() { cache_put_releasegroup() {
cat | __put_json "$TYPE_RELEASEGROUP" "$1" "$releasegroup_filename" cat | __put_json "$TYPE_RELEASEGROUP" "$1" "$RELEASEGROUP_FILENAME"
} }
cache_put_releasegroup_releases() { 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() { cache_append_releasegroup_releases() {
tmpf=$(mktemp) tmpf=$(mktemp)
cat >"$tmpf" cat >"$tmpf"
updated=$(mktemp) 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" $JQ --slurpfile n "$tmpf" '."releases" += ($n[0]|."releases")' "$f" >"$updated" && mv "$updated" "$f"
rm -f "$tmpf" rm -f "$tmpf"
} }
## Release ## Release
cache_get_release() { cache_get_release() {
__get_json "$TYPE_RELEASE" "$1" "$release_filename" __get_json "$TYPE_RELEASE" "$1" "$RELEASE_FILENAME"
} }
cache_put_release() { cache_put_release() {
cat | __put_json "$TYPE_RELEASE" "$1" "$release_filename" cat | __put_json "$TYPE_RELEASE" "$1" "$RELEASE_FILENAME"
} }
## Cache deletion ## Cache deletion
@@ -144,18 +157,35 @@ cache_delete_artist() {
# argument $2: MusicBrainz ID # argument $2: MusicBrainz ID
in_cache() { in_cache() {
case "$1" in case "$1" in
"$TYPE_ARTIST") "$TYPE_ARTIST") fn="$ARTIST_FILENAME" ;;
fn="$artist_filename" "$TYPE_RELEASEGROUP") fn="$RELEASEGROUP_FILENAME" ;;
;; "$TYPE_RELEASE") fn="$RELEASE_FILENAME" ;;
"$TYPE_RELEASEGROUP") *) return 1 ;;
fn="$releasegroup_filename"
;;
"$TYPE_RELEASE")
fn="$release_filename"
;;
*)
return 1
;;
esac esac
[ "$(__get_json "$1" "$2" "$fn")" ] && return 0 || return 1 [ "$(__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) }'
}

View File

@@ -1,5 +1,10 @@
# Application information # Application information
if [ ! "${INFO_LOADED:-}" ]; then
APP_NAME="fuzic" APP_NAME="fuzic"
APP_VERSION="0.1" APP_VERSION="0.1"
APP_WEBSITE="https://git.indyfac.ch/amin/fuzic" APP_WEBSITE="https://git.indyfac.ch/amin/fuzic"
WINDOW_TITLE="🔎🎶 $APP_NAME | a simple music finder and player" WINDOW_TITLE="🔎🎶 $APP_NAME | a simple music finder and player"
export APP_NAME APP_VERSION APP_WEBSITE WINDOW_TITLE
export INFO_LOADED=1
fi

View File

@@ -158,8 +158,7 @@ list_artists_from_json() {
-v format_disambiguation="$AV_DISAMBIGUATION" \ -v format_disambiguation="$AV_DISAMBIGUATION" \
-v format_local="$FORMAT_LOCAL" \ -v format_local="$FORMAT_LOCAL" \
"$AWK_ARTISTS" | "$AWK_ARTISTS" |
column -t -s "$(printf '\t')" | column -t -s "$(printf '\t')" -l 2
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|'
} }
# Generate playlist view # Generate playlist view

View File

@@ -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() { gettags() {
ffprobe -v error -show_entries format_tags -print_format json "$1" | ffprobe -v error -show_entries format_tags -print_format json "$1" |
$JQ '.format.tags | { $JQ '.format.tags | {
@@ -47,75 +68,81 @@ decorate() {
} }
# Load missing cache entries (batch mode) # Load missing cache entries (batch mode)
#
# argument $1: type # 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() { __batch_load_missing() {
tmpf=$(mktemp) tmpf=$(mktemp)
while IFS= read -r mbid; do cat |
if ! in_cache "$1" "$mbid"; then cache_get_file_batch "$1" |
echo "$mbid" >>"$tmpf" xargs \
fi sh -c 'for f; do [ -e "$f" ] || echo "$f"; done' _ |
done <"$2" cache_mbid_from_path_batch >"$tmpf"
if [ -s "$tmpf" ]; then
lines=$(wc -l "$tmpf" | cut -d ' ' -f 1) lines=$(wc -l "$tmpf" | cut -d ' ' -f 1)
if [ "$lines" -gt 0 ]; then if [ "$lines" -gt 0 ]; then
case "$1" in case "$1" in
"$TYPE_ARTIST") "$TYPE_ARTIST") tt="artists" ;;
tt="artists" "$TYPE_RELEASEGROUP") tt="release groups" ;;
;; "$TYPE_RELEASE") tt="releases" ;;
"$TYPE_RELEASEGROUP")
tt="release groups"
;;
"$TYPE_RELEASE")
tt="releases"
;;
esac esac
info "Fetching $lines missing $tt" info "Fetching missing $tt"
cnt=0 cnt=0
while IFS= read -r mbid; do while IFS= read -r mbid; do
case "$1" in case "$1" in
"$TYPE_ARTIST") "$TYPE_ARTIST")
name=$(mb_artist "$mbid" | $JQ ".name") name=$(mb_artist "$mbid" | $JQ '.name')
;;
"$TYPE_RELEASEGROUP")
name=$(mb_releasegroup "$mbid" | $JQ ".title")
;;
"$TYPE_RELEASE")
name=$(mb_release "$mbid" | $JQ ".title")
;; ;;
"$TYPE_RELEASEGROUP") name=$(mb_releasegroup "$mbid" | $JQ '.title') ;;
"$TYPE_RELEASE") name=$(mb_release "$mbid" | $JQ '.title') ;;
esac esac
cnt=$((cnt + 1)) cnt=$((cnt + 1))
printf "\033[2K\r%d/%d (%s: %s)" "$cnt" "$lines" "$mbid" "$name" info "$(printf "%d/%d (%s: %s)" "$cnt" "$lines" "$mbid" "$name")"
sleep 1 sleep 1
done <"$tmpf" done <"$tmpf"
printf "\n"
fi
fi fi
rm -f "$tmpf" 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 views
precompute_view() { __precompute_lists() {
info "Precomputing artist view" cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs \
while IFS= read -r aid; do $JQ '[
mb_artist "$aid" | $JQ '[
.id, .id,
.type, .type,
.name, .name,
.disambiguation, .disambiguation,
.["life-span"].begin, .["life-span"].begin,
.["life-span"].end .["life-span"].end
] | join("\t")' ] | join("\t")' >"$LOCALDATA_ARTISTS_LIST"
done <"$LOCALDATA_ARTISTS" | 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")' >"$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 \ awk \
-F "\t" \ -F "\t" \
-v file_local_artists="${LOCALDATA_ARTISTS:-}" \ -v file_local_artists="${LOCALDATA_ARTISTS:-}" \
@@ -123,21 +150,11 @@ precompute_view() {
-v format_group="$AV_GROUP" \ -v format_group="$AV_GROUP" \
-v format_disambiguation="$AV_DISAMBIGUATION" \ -v format_disambiguation="$AV_DISAMBIGUATION" \
-v format_local="$FORMAT_LOCAL" \ -v format_local="$FORMAT_LOCAL" \
"$AWK_ARTISTS" | "$AWK_ARTISTS" "$LOCALDATA_ARTISTS_LIST" |
sort | sort |
column -t -s "$(printf '\t')" -E 0 | column -t -s "$(printf '\t')" -l 2 >"$LOCALDATA_ARTISTS_VIEW"
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_ARTISTS_VIEW" #column -t -s "$(printf '\t')" |
info "Precomputing releasegroup view" #sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_ARTISTS_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 \ awk \
-F "\t" \ -F "\t" \
-v file_local_releasegroups="${LOCALDATA_RELEASEGROUPS:-}" \ -v file_local_releasegroups="${LOCALDATA_RELEASEGROUPS:-}" \
@@ -164,75 +181,60 @@ precompute_view() {
-v format_demo="$FORMAT_TYPE_SECONDARY_DEMO" \ -v format_demo="$FORMAT_TYPE_SECONDARY_DEMO" \
-v format_fieldrec="$FORMAT_TYPE_SECONDARY_FIELDREC" \ -v format_fieldrec="$FORMAT_TYPE_SECONDARY_FIELDREC" \
-v format_local="$FORMAT_LOCAL" \ -v format_local="$FORMAT_LOCAL" \
"$AWK_RELEASEGROUPS" | "$AWK_RELEASEGROUPS" "$LOCALDATA_RELEASEGROUPS_LIST" | sort -n -r |
sort -n -r |
cut -d "$(printf '\t')" -f 2- | cut -d "$(printf '\t')" -f 2- |
column -t -s "$(printf '\t')" -E 0 | column -t -s "$(printf '\t')" -l 5 >"$LOCALDATA_RELEASEGROUPS_VIEW"
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_RELEASEGROUPS_VIEW" #column -t -s "$(printf '\t')" |
info "Precomputing release view" #sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_RELEASEGROUPS_VIEW"
cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" |
while IFS= read -r rid; do
mb_release "$rid" | $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")'
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 |
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"
} }
# Load local music # Load local music
#
# argument $1: path to decorated music files # argument $1: path to decorated music files
load_local() { #
[ -d "$LOCALDATADIR" ] || mkdir -p "$LOCALDATADIR" # This method parses all decorations and generates a line-by-line database of
tmpreleases=$(mktemp) # locally available artists, releases, and release groups.
[ -f "$tmpreleases" ] || exit 1 reloaddb() {
info "Locating and parsing decoration files ($DECORATION_FILENAME)" rm -rf "$LOCALDATADIR"
mkdir -p "$LOCALDATADIR"
find "$1" -type f -name "$DECORATION_FILENAME" -print0 | find "$1" -type f -name "$DECORATION_FILENAME" -print0 |
xargs -0 -P 4 $JQ '.releaseid+"\t"+input_filename' | xargs -0 $JQ '.releaseid+"\t"+input_filename' >"$LOCALDATA_RELEASES"
tee "$LOCALDATA_RELEASES" | # Get necessary metadata and setup lists
cut -d "$(printf '\t')" -f 1 >"$tmpreleases" tmpreleases=$(mktemp)
__batch_load_missing "$TYPE_RELEASE" "$tmpreleases" cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" |
# Get release groups and album artists tee "$tmpreleases" |
while IFS= read -r rid; do __batch_load_missing "$TYPE_RELEASE"
mb=$(mb_release "$rid") tmpreleasefiles=$(mktemp)
echo "$mb" | $JQ '."release-group".id' >>"$LOCALDATA_RELEASEGROUPS" cache_get_file_batch "$TYPE_RELEASE" <"$tmpreleases" >"$tmpreleasefiles"
echo "$mb" | $JQ '."release-group"."artist-credit" | map(.artist.id) | join("\n")' >>"$LOCALDATA_ARTISTS" xargs \
done <"$tmpreleases" $JQ '."release-group".id' \
tf=$(mktemp) <"$tmpreleasefiles" >"$LOCALDATA_RELEASEGROUPS"
sort "$LOCALDATA_RELEASEGROUPS" | uniq >"$tf" && mv "$tf" "$LOCALDATA_RELEASEGROUPS" xargs \
sort "$LOCALDATA_ARTISTS" | uniq >"$tf" && mv "$tf" "$LOCALDATA_ARTISTS" $JQ '."release-group"."artist-credit" | map(.artist.id) | join("\n")' \
# Populate cache with missing data <"$tmpreleasefiles" >"$LOCALDATA_ARTISTS"
__batch_load_missing "$TYPE_RELEASEGROUP" "$LOCALDATA_RELEASEGROUPS" rm -f "$tmpreleases" "$tmpreleasefiles"
__batch_load_missing "$TYPE_ARTIST" "$LOCALDATA_ARTISTS" tf1=$(mktemp)
rm -f "$tmpreleases" tf2=$(mktemp)
info "Resetting views" sort "$LOCALDATA_RELEASEGROUPS" | uniq >"$tf1"
rm -f "$LOCALDATA_ARTISTS_VIEW" "$LOCALDATA_RELEASEGROUPS_VIEW" "$LOCALDATA_RELEASES_VIEW" mv "$tf1" "$LOCALDATA_RELEASEGROUPS"
precompute_view 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"
} }

View File

@@ -1,10 +1,15 @@
# Logging methods # Logging methods
if [ ! "${LOG_LOADED:-}" ]; then
ERR="\033[38;5;196m" ERR="\033[38;5;196m"
INFO="\033[38;5;75m" INFO="\033[38;5;75m"
OFF="\033[m" OFF="\033[m"
LOGDIR="$HOME/.local/state/$APP_NAME" LOGDIR="$HOME/.local/state/$APP_NAME"
[ -d "$LOGDIR" ] || mkdir -p "$LOGDIR" [ -d "$LOGDIR" ] || mkdir -p "$LOGDIR"
LOGFILE="$LOGDIR/log" LOGFILE="$LOGDIR/log"
export ERR INFO OFF LOGFILE
export LOG_LOADED=1
fi
err() { err() {
echo "$(date) [$$]>${ERR}ERROR:${OFF} ${1:-}" | tee -a "$LOGFILE" | cut -d ">" -f 2- >/dev/stderr echo "$(date) [$$]>${ERR}ERROR:${OFF} ${1:-}" | tee -a "$LOGFILE" | cut -d ">" -f 2- >/dev/stderr

View File

@@ -4,6 +4,7 @@
# a higher-level playback functionality is provided. # a higher-level playback functionality is provided.
# Available playback commands # Available playback commands
if [ ! "${PLAYBACK_LOADED:-}" ]; then
PLAYBACK_CMD_PLAY="play" PLAYBACK_CMD_PLAY="play"
PLAYBACK_CMD_QUEUE="queue" PLAYBACK_CMD_QUEUE="queue"
PLAYBACK_CMD_QUEUE_NEXT="queue-next" PLAYBACK_CMD_QUEUE_NEXT="queue-next"
@@ -12,6 +13,12 @@ PLAYBACK_CMD_PLAY_NEXT="next"
PLAYBACK_CMD_PLAY_PREV="prev" PLAYBACK_CMD_PLAY_PREV="prev"
PLAYBACK_CMD_SEEK_FORWARD="seekf" PLAYBACK_CMD_SEEK_FORWARD="seekf"
PLAYBACK_CMD_SEEK_BACKWARD="seekb" 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 # Obtain playback command from key press
# @argument $1: key # @argument $1: key

View File

@@ -1,3 +1,4 @@
if [ ! "${PLAYLIST_LOADED:-}" ]; then
PLAYLIST_CMD_REMOVE="rm" PLAYLIST_CMD_REMOVE="rm"
PLAYLIST_CMD_UP="up" PLAYLIST_CMD_UP="up"
PLAYLIST_CMD_DOWN="down" PLAYLIST_CMD_DOWN="down"
@@ -6,6 +7,12 @@ PLAYLIST_CMD_CLEAR_ABOVE="clear-above"
PLAYLIST_CMD_CLEAR_BELOW="clear-below" PLAYLIST_CMD_CLEAR_BELOW="clear-below"
PLAYLIST_CMD_SHUFFLE="shuffle" PLAYLIST_CMD_SHUFFLE="shuffle"
PLAYLIST_CMD_UNSHUFFLE="unshuffle" 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 # Run playback commands
# #