Compare commits

...

9 Commits

13 changed files with 453 additions and 91 deletions

View File

@@ -12,6 +12,7 @@ Control playback from your terminal while browsing the vast world of music relea
- Handle metadata without touching your audio files - Handle metadata without touching your audio files
- Fast and hackable application with [vim](https://www.vim.org)-like keybindings - Fast and hackable application with [vim](https://www.vim.org)-like keybindings
- Save and load playlists - Save and load playlists
- Lyrics support
🎞️ Screenshot 🎞️ Screenshot
------------- -------------
@@ -19,7 +20,7 @@ Control playback from your terminal while browsing the vast world of music relea
The MusicBrainz database is automatically searched after entering a query. The MusicBrainz database is automatically searched after entering a query.
In the screenshot below, the emoji 🔆 indicates that some tracks of _Mc Solaar_ are available in the local music library. In the screenshot below, the emoji 🔆 indicates that some tracks of _Mc Solaar_ are available in the local music library.
They are played by hitting `<enter>`. They are played by hitting `<enter>`.
![MusicBrainz artist search](//screenshots/search.png) ![MusicBrainz artist search](./screenshots/search.png)
### Browse Local Music Library ### Browse Local Music Library
Here, we fuzzily search an album of _Daft Punk._ Here, we fuzzily search an album of _Daft Punk._
@@ -119,7 +120,7 @@ $ fuzic --decorate ~/Music/Daft\ Punk/Random\ Access\ Memories
Info: Decorating /home/amin/Music/Daft Punk/Random Access Memories as release 5000a285-b67e-4cfc-b54b-2b98f1810d2e Info: Decorating /home/amin/Music/Daft Punk/Random Access Memories as release 5000a285-b67e-4cfc-b54b-2b98f1810d2e
$ fuzic --decorate-as ~/Music/Mc\ Solaar/Prose\ Combat 69e5cf67-7cea-4fe8-9129-9779f0a93d69 $ fuzic --decorate-as ~/Music/Mc\ Solaar/Prose\ Combat 69e5cf67-7cea-4fe8-9129-9779f0a93d69
Info: Decorating /home/amin/Music/Mc Solaar/Prose Combat as the release Prose Combat by MC Solaar Info: Decorating /home/amin/Music/Mc Solaar/Prose Combat as the release Prose Combat by MC Solaar
Info: We discovered the following associatoin: Info: We discovered the following association:
Track 'Aubade' File './01 Aubade.m4a' Track 'Aubade' File './01 Aubade.m4a'
Track 'Obsolète' File './02 Obsolète.m4a' Track 'Obsolète' File './02 Obsolète.m4a'
Track 'Nouveau western' File './03 Nouveau western.m4a' Track 'Nouveau western' File './03 Nouveau western.m4a'
@@ -171,6 +172,33 @@ This means that `fuzic` users may share their playlists irrespective of the audi
Possibly even more importantly, this means that you can preserve your playlist even if you reorganize your local music library: Possibly even more importantly, this means that you can preserve your playlist even if you reorganize your local music library:
You can safely share your playlists with your future self. You can safely share your playlists with your future self.
Playlists are stored under `XDG_DATA_HOME/fuzic/playlists`, which defaults to `~/.local/share/fuzic/playlists`.
You may also define the playlist directory using the `PLAYLIST_DIRECTORY` environment variable.
In `share/playlists` you find example playlists.
Thus, the command `PLAYLIST_DIRECTORY=share/playlists fuzic` launches this application with access to these example playlists.
📜 Lyrics
---------
By using the key `L` (normal mode), the lyrics of the selected track are displayed.
The lyrics are taken from (in that order)
1. the lyrics stored using `fuzic`, or
2. from the `.lrc` file with the same name as the audio file, or
3. from the audio-file tags (requires `ffprobe`), or
4. from a custom script.
To specify the custom script, you can set the `LYRICS_FETCH_CUSTOM` environment variable to point to an executable.
The executable reads a JSON string on the standard input, and is supposed to print the lyrics (see `share/lyrics/example.sh`).
You may also skip the first three sources and directly make a call to your custom script by using the key `Y` (normal mode).
The key `alt-L` opens the lyrics in a new window using the `EDITOR`.
This requires one of the following terminal emulators (`kitty`, `x-terminal-emulator`, `gnome-terminal`, `xterm`).
If you use another terminal emulator, you may specify the environment variable `EXTERNALEDIT` to hold the string such that
```sh
$ExTERNALEDIT "$file"
```
spawns a text editor with `$file` loaded inside a terminal emulator.
🧭 Planned Features 🧭 Planned Features
------------------- -------------------
The following features are planned: The following features are planned:

25
share/lyrics/example.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
# Example script for `fuzic` to display the lyrics of a track. This script
# reads from stdin a JSON string and stores it in the variable `j`. The
# variable `tj` contains the JSON string of the track.
j="$(cat)"
tj="$(echo "$j" | jq -r '.trackid as $tid | .release.media[].tracks[] | select(.id == $tid)')"
# The following four variables are self-explanatory:
track_name="$(echo "$tj" | jq -r '.title')"
artist_name="$(echo "$tj" | jq -r '."artist-credit" | map([.name, .joinphrase] | join("")) | join("")')"
album_name="$(echo "$j" | jq -r '.release.title')"
duration="$(echo "$tj" | jq -r '.length / 1000 | round')"
# Now, you may call an API to fetch the lyrics for this track,
#
# curl \
# --get \
# --silent \
# --data-urlencode "track_name=$track_name" \
# --data-urlencode "artist_name=$artist_name" \
# --data-urlencode "album_name=$album_name" \
# --data-urlencode "duration=$duration" \
# "$URL"
#
# or simply print a template to write the lyrics yourself:
printf "Lyrics '%s' by '%s' (album: %s, duration: %s seconds)\n" "$track_name" "$artist_name" "$album_name" "$duration"

7
share/playlists/example Normal file
View File

@@ -0,0 +1,7 @@
e2276744-afe0-4ecd-9340-9e34f1526cc5 56e2c4d2-01b2-4bc6-a11f-ee6a50be9fa6
a75bb4c3-d1ce-4d35-aac5-2068f7c32805 8def9034-1cd7-4fe3-88c6-5c83fdc9537a
60a90232-228c-4ee2-af2f-07706fa13bd8 f9f5c049-b2f2-3ce7-93e3-956ce2c77d2c
13027e7b-340c-4478-8dda-df5fa4af3564 b2f18f14-7918-4856-bce3-b936fd628763
ca2e4dca-0ec3-411a-b2a0-60bb97107c2c d0c5cb23-ec7a-41ce-8a1c-3498aafdf527
486239ef-0be2-435b-963a-b9451f8c8c57 c6c3b393-fede-489e-bb77-23e32951ebc9
b3cf4db5-deb4-40b8-88bc-e016cca1104f c90ec13c-b406-3c15-83d3-8443304ccc6f

View File

@@ -103,6 +103,9 @@ MODE_INSERT="show"
# Load filters # Load filters
. "sh/filter.sh" . "sh/filter.sh"
# Load lyrics support
. "sh/lyrics.sh"
# Command-line options that may only be used internally. # Command-line options that may only be used internally.
# --lines # --lines
# --playback # --playback
@@ -115,6 +118,8 @@ MODE_INSERT="show"
# --preview # --preview
# --show-keybindings # --show-keybindings
# --remove-from-cache # --remove-from-cache
# --edit-lyrics
# --lyrics-custom
case "${1:-}" in case "${1:-}" in
"--lines") "--lines")
# Print lines that are fed into fzf. # Print lines that are fed into fzf.
@@ -286,6 +291,7 @@ case "${1:-}" in
"$VIEW_SEARCH_ARTIST" | "$VIEW_LIST_ARTISTS") view="$VIEW_ARTIST" ;; "$VIEW_SEARCH_ARTIST" | "$VIEW_LIST_ARTISTS") view="$VIEW_ARTIST" ;;
"$VIEW_ARTIST" | "$VIEW_SEARCH_ALBUM" | "$VIEW_LIST_ALBUMS") view="$VIEW_RELEASEGROUP" ;; "$VIEW_ARTIST" | "$VIEW_SEARCH_ALBUM" | "$VIEW_LIST_ALBUMS") view="$VIEW_RELEASEGROUP" ;;
"$VIEW_RELEASEGROUP") view="$VIEW_RELEASE" ;; "$VIEW_RELEASEGROUP") view="$VIEW_RELEASE" ;;
"$VIEW_RELEASE") exit 0 ;;
esac esac
;; ;;
*) ;; *) ;;
@@ -369,6 +375,20 @@ case "${1:-}" in
esac esac
exit 0 exit 0
;; ;;
"--edit-lyrics")
# Edit lyrics file in an external window
info "Call to $*"
mbid="${2:-}"
file="$(lyrics_file "$mbid")"
[ -f "$file" ] || echo "No lyrics" | store_lyrics "$mbid"
[ "$EXTERNALEDIT" ] && $EXTERNALEDIT "$file" || err "Failed to externally edit the file $file"
exit 0
;;
"--lyrics-custom")
# Use custom command to (re-)fetch the lyrics
store_lyrics_custom "$2" "$3"
exit 0
;;
esac esac
# Non-interactive user commands intended to the user. These commands do not # Non-interactive user commands intended to the user. These commands do not
@@ -423,7 +443,18 @@ case "${1:-}" in
;; ;;
"--playlists") "--playlists")
# List available playlists # List available playlists
stored_playlists stored_playlists | cut -d "$(printf '\t')" -f 1
exit 0
;;
"--print-playlist")
# Pretty print playlist
list_playlist_stored "${2:-}" |
cut -d "$(printf '\t')" -f 1
exit 0
;;
"--lyrics")
shift
lyrics "$@"
exit 0 exit 0
;; ;;
"--help") "--help")
@@ -442,6 +473,8 @@ GENERAL OPTIONS:
--release <mbid> Show release given by <mbid> --release <mbid> Show release given by <mbid>
--playlists List stored playlists and exit --playlists List stored playlists and exit
--load-playlist <playlist> Load specified playlist --load-playlist <playlist> Load specified playlist
--print-playlist <playlist> Print specified playlist and exit
--lyrics <relid> <mbid> Show lyrics of track <mbid> in release <relid> and exit
MANAGE LOCAL MUSIC: MANAGE LOCAL MUSIC:
--decorate <path> Decorate directory containing a tagged release --decorate <path> Decorate directory containing a tagged release
@@ -541,7 +574,10 @@ export LOCKFILE RESULTS PIDFILE
mpv_start mpv_start
# Playback possible now # Playback possible now
[ "${1:-}" = "--load-playlist" ] && $0 --playlist "$PLAYLIST_CMD_LOAD" "${2:-}" if [ "${1:-}" = "--load-playlist" ]; then
sleep 1
$0 --playlist "$PLAYLIST_CMD_LOAD" "${2:-}"
fi
# main loop # main loop
# states are stored in (in)visible labels # states are stored in (in)visible labels
@@ -561,6 +597,7 @@ mpv_start
IN_NORMAL_MODE="[ \$FZF_INPUT_STATE = hidden ]" IN_NORMAL_MODE="[ \$FZF_INPUT_STATE = hidden ]"
IN_VIEW_PATTERN="[ \$FZF_LIST_LABEL = %s ]" IN_VIEW_PATTERN="[ \$FZF_LIST_LABEL = %s ]"
IN_LIST_ARTISTS_VIEW="$(printf "$IN_VIEW_PATTERN" "$VIEW_LIST_ARTISTS")" IN_LIST_ARTISTS_VIEW="$(printf "$IN_VIEW_PATTERN" "$VIEW_LIST_ARTISTS")"
IN_RELEASE_VIEW="$(printf "$IN_VIEW_PATTERN" "$VIEW_RELEASE")"
FZF_CURRENT_MODE="\$FZF_INPUT_STATE" FZF_CURRENT_MODE="\$FZF_INPUT_STATE"
FZF_CURRENT_VIEW="\$FZF_LIST_LABEL" FZF_CURRENT_VIEW="\$FZF_LIST_LABEL"
FZF_CURRENT_MBID="\$FZF_BORDER_LABEL" FZF_CURRENT_MBID="\$FZF_BORDER_LABEL"
@@ -655,7 +692,7 @@ while true; do
sel=$( sel=$(
stored_playlists | $FZF \ stored_playlists | $FZF \
--border=double \ --border=double \
--border-label="$TITLE_PLYLST_LIST" \ --border-label="$TITLE_PLYLST_STORE" \
--margin="2%,10%" \ --margin="2%,10%" \
--bind="$KEYS_I_NORMAL:" \ --bind="$KEYS_I_NORMAL:" \
--bind="$KEYS_DOWN:down" \ --bind="$KEYS_DOWN:down" \
@@ -666,9 +703,16 @@ while true; do
--bind="$KEYS_KEYBINDINGS:preview:$0 --show-keybindings $VIEW_PLAYLIST_PLAYLISTSTORE" \ --bind="$KEYS_KEYBINDINGS:preview:$0 --show-keybindings $VIEW_PLAYLIST_PLAYLISTSTORE" \
--bind="$KEYS_SCROLL_PREVIEW_DOWN:preview-down" \ --bind="$KEYS_SCROLL_PREVIEW_DOWN:preview-down" \
--bind="$KEYS_SCROLL_PREVIEW_UP:preview-up" \ --bind="$KEYS_SCROLL_PREVIEW_UP:preview-up" \
--bind="$KEYS_PREVIEW_TOGGLE_WRAP:toggle-preview-wrap" \
--bind="$KEYS_PREVIEW_TOGGLE_SIZE:change-preview-window(right,90%,border-line,nowrap|$FZF_DEFAULT_PREVIEW_WINDOW)" \
--bind="$KEYS_PREVIEW_OPEN:show-preview" \
--bind="$KEYS_PREVIEW_CLOSE:hide-preview" \ --bind="$KEYS_PREVIEW_CLOSE:hide-preview" \
--bind="$KEYS_PLAYLISTSTORE_SELECT:transform:[ {1} ] && $0 --playlist $PLAYLIST_CMD_LOAD {1} && echo accept" \ --bind="$KEYS_PLAYLISTSTORE_SELECT:transform:[ {1} ] && $0 --playlist $PLAYLIST_CMD_LOAD {2} && echo accept" \
--bind="$KEYS_PLAYLISTSTORE_DELETE:transform:[ {1} ] && rm \"$PLAYLIST_DIRECTORY/{r1}\" && echo \"reload($0 --playlists\)\"" \ --bind="$KEYS_PLAYLISTSTORE_DELETE:transform:[ {1} ] && rm \"$PLAYLIST_DIRECTORY/{r2}\" && echo \"reload($0 --playlists\)\"" \
--preview="$0 --print-playlist {2}" \
--preview-window="$FZF_DEFAULT_PREVIEW_WINDOW" \
--with-nth="{1}" \
--delimiter="\t" \
--wrap-sign="" || true --wrap-sign="" || true
) )
VIEW="$VIEW_PLAYLIST" VIEW="$VIEW_PLAYLIST"
@@ -718,6 +762,9 @@ while true; do
--bind="$KEYS_PLAYLIST_GOTO_RELEASE:print($VIEW_RELEASE)+accept" \ --bind="$KEYS_PLAYLIST_GOTO_RELEASE:print($VIEW_RELEASE)+accept" \
--bind="$KEYS_PLAYLIST_STORE:print($VIEW_PLAYLIST_STORE)+print("")+print($LASTVIEW)+print($LASTARG)+accept" \ --bind="$KEYS_PLAYLIST_STORE:print($VIEW_PLAYLIST_STORE)+print("")+print($LASTVIEW)+print($LASTARG)+accept" \
--bind="$KEYS_PLAYLIST_OPEN_STORE:print($VIEW_PLAYLIST_PLAYLISTSTORE)+print("")+print($LASTVIEW)+print($LASTARG)+accept" \ --bind="$KEYS_PLAYLIST_OPEN_STORE:print($VIEW_PLAYLIST_PLAYLISTSTORE)+print("")+print($LASTVIEW)+print($LASTARG)+accept" \
--bind="$KEYS_N_LYRICS:show-preview+preview:$0 --lyrics {3} {4}" \
--bind="$KEYS_LYRICS_EDIT:execute-silent:$0 --edit-lyrics {4}" \
--bind="$KEYS_N_LYRICS_FETCH_CUSTOM:execute-silent($0 --lyrics-custom {3} {4})+show-preview+preview:$0 --lyrics {3} {4}" \
--preview-window="hidden" \ --preview-window="hidden" \
--wrap-sign="" \ --wrap-sign="" \
--delimiter="\t" \ --delimiter="\t" \
@@ -813,6 +860,9 @@ open \"\$(dirname {5})\"" \
--bind="$KEYS_PREVIEW_CLOSE:hide-preview" \ --bind="$KEYS_PREVIEW_CLOSE:hide-preview" \
--bind="$KEYS_PLAYBACK:transform:$0 --playback $FZF_CURRENT_VIEW \"$FZF_CURRENT_MBID\" {4} {5}" \ --bind="$KEYS_PLAYBACK:transform:$0 --playback $FZF_CURRENT_VIEW \"$FZF_CURRENT_MBID\" {4} {5}" \
--bind="$KEYS_N_PLAYBACK:transform:$IN_NORMAL_MODE && $0 --playback $FZF_CURRENT_VIEW \"$FZF_CURRENT_MBID\" {4} {5} || $PUT_FZF_KEY_LOGIC" \ --bind="$KEYS_N_PLAYBACK:transform:$IN_NORMAL_MODE && $0 --playback $FZF_CURRENT_VIEW \"$FZF_CURRENT_MBID\" {4} {5} || $PUT_FZF_KEY_LOGIC" \
--bind="$KEYS_N_LYRICS:transform:$IN_NORMAL_MODE && $IN_RELEASE_VIEW && printf \"show-preview+preview:%s --lyrics %s %s\" \"$0\" {3} {4}" \
--bind="$KEYS_LYRICS_EDIT:transform:$IN_RELEASE_VIEW && printf \"execute-silent:%s --edit-lyrics %s\" \"$0\" {4}" \
--bind="$KEYS_N_LYRICS_FETCH_CUSTOM:transform:$IN_NORMAL_MODE && $IN_RELEASE_VIEW && {$0 --lyrics-custom {3} {4};printf \"show-preview+preview:%s --lyrics %s %s\" \"$0\" {3} {4}}" \
--bind="change:execute-silent($0 --mbsearch $FZF_CURRENT_VIEW &)+reload:$0 --lines $FZF_CURRENT_VIEW" \ --bind="change:execute-silent($0 --mbsearch $FZF_CURRENT_VIEW &)+reload:$0 --lines $FZF_CURRENT_VIEW" \
--preview-window="$FZF_DEFAULT_PREVIEW_WINDOW" \ --preview-window="$FZF_DEFAULT_PREVIEW_WINDOW" \
--wrap-sign="" \ --wrap-sign="" \

View File

@@ -115,6 +115,7 @@ awk_releasegroups() {
-v hassecondary_yes="$RGV_FMT_HASSECONDARY_YES" \ -v hassecondary_yes="$RGV_FMT_HASSECONDARY_YES" \
-v hassecondary_no="$RGV_FMT_HASSECONDARY_NO" \ -v hassecondary_no="$RGV_FMT_HASSECONDARY_NO" \
-v fmtsecondary="$RGV_FMT_SECONDARY" \ -v fmtsecondary="$RGV_FMT_SECONDARY" \
-v secondary_compilation="$RGV_FMT_SECONDARY_COMPILATION" \
-v secondary_soundtrack="$RGV_FMT_SECONDARY_SOUNDTRACK" \ -v secondary_soundtrack="$RGV_FMT_SECONDARY_SOUNDTRACK" \
-v secondary_spokenword="$RGV_FMT_SECONDARY_SPOKENWORD" \ -v secondary_spokenword="$RGV_FMT_SECONDARY_SPOKENWORD" \
-v secondary_interview="$RGV_FMT_SECONDARY_INTERVIEW" \ -v secondary_interview="$RGV_FMT_SECONDARY_INTERVIEW" \

View File

@@ -238,6 +238,18 @@ cache_get_file_batch() {
awk -v dir="$CACHEDIR/$1/" -v f="/$fn" '{ print dir $0 f }' awk -v dir="$CACHEDIR/$1/" -v f="/$fn" '{ print dir $0 f }'
} }
# Detect missing cache files
#
# @argument $1: type
# @argument $2: path to list with MusicBrainz IDs
#
# This method returns a nonzero value if some MusicBrainz objects listed in $2
# are not cached.
cached() {
cache_get_file_batch "$1" <"$2" |
xargs -d '\n' ls >/dev/null 2>&1 || return 1
}
# Print MusicBrainz ID associated to the file paths # Print MusicBrainz ID associated to the file paths
# #
# This reads from stdin any number of paths (one per line) # This reads from stdin any number of paths (one per line)
@@ -295,3 +307,41 @@ cache_rm_release() {
info "Removing $d from cache" info "Removing $d from cache"
rm -rf "$d" rm -rf "$d"
} }
# Load missing cache entries (batch mode)
#
# argument $1: type
#
# 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)
cat |
cache_get_file_batch "$1" |
xargs -d '\n' \
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")
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))
info "$(printf "%d/%d (%s: %s)" "$cnt" "$lines" "$mbid" "$name")"
sleep 1
done <"$tmpf"
fi
rm -f "$tmpf"
}

View File

@@ -63,6 +63,11 @@
# - KEYS_PREVIEW_TOGGLE_SIZE: Toggle size (small, large) of preview window # - KEYS_PREVIEW_TOGGLE_SIZE: Toggle size (small, large) of preview window
# - KEYS_REFRESH: Refresh current entry # - KEYS_REFRESH: Refresh current entry
# #
# Lyrics:
# - KEYS_N_LYRICS: Show lyrics of selected track
# - KEYS_LYRICS_EDIT: Edit lyrics of selected track
# - KEYS_N_LYRICS_FETCH_CUSTOM: Fetch lyrics using custom command
#
# Playback: # Playback:
# - KEYS_PLAY: Play selected release or selected track # - KEYS_PLAY: Play selected release or selected track
# - KEYS_QUEUE: Queue selected release or selected track # - KEYS_QUEUE: Queue selected release or selected track
@@ -165,6 +170,12 @@ if [ ! "${KEYS_LOADED:-}" ]; then
KEYS_PREVIEW_OPEN KEYS_PREVIEW_TOGGLE_WRAP KEYS_PREVIEW_TOGGLE_SIZE \ KEYS_PREVIEW_OPEN KEYS_PREVIEW_TOGGLE_WRAP KEYS_PREVIEW_TOGGLE_SIZE \
KEYS_REFRESH KEYS_REFRESH
# Lyrics:
KEYS_N_LYRICS="${KEYS_N_LYRICS:-"L"}"
KEYS_LYRICS_EDIT="${KEYS_LYRICS_EDIT:-"alt-L"}"
KEYS_N_LYRICS_FETCH_CUSTOM="${KEYS_N_LYRICS_FETCH_CUSTOM:-"Y"}"
export KEYS_N_LYRICS KEYS_LYRICS_EDIT KEYS_N_LYRICS_FETCH_CUSTOM
# Playback: # Playback:
KEYS_PLAY="${KEYS_PLAY:-"enter"}" KEYS_PLAY="${KEYS_PLAY:-"enter"}"
KEYS_QUEUE="${KEYS_QUEUE:-"ctrl-alt-m"}" # That's actually alt-enter KEYS_QUEUE="${KEYS_QUEUE:-"ctrl-alt-m"}" # That's actually alt-enter
@@ -317,6 +328,10 @@ print_keybindings() {
"$KEYS_PLAY_PREV,$KEYS_N_PLAY_PREV" "Play previous track" \ "$KEYS_PLAY_PREV,$KEYS_N_PLAY_PREV" "Play previous track" \
"$KEYS_SEEK_FORWARD,$KEYS_N_SEEK_FORWARD" "Seek forward" \ "$KEYS_SEEK_FORWARD,$KEYS_N_SEEK_FORWARD" "Seek forward" \
"$KEYS_SEEK_BACKWARD,$KEYS_N_SEEK_BACKWARD" "Seek backward" "$KEYS_SEEK_BACKWARD,$KEYS_N_SEEK_BACKWARD" "Seek backward"
__keybindinggroup_from_args "Lyrics" \
"$KEYS_N_LYRICS" "Show lyrics" \
"$KEYS_LYRICS_EDIT" "Edit lyrics" \
"$KEYS_N_LYRICS_FETCH_CUSTOM" "Fetch lyrics using custom command"
__keybindinggroup_from_args "Special operations" \ __keybindinggroup_from_args "Special operations" \
"$KEYS_BROWSE" "Open selected item in browser" \ "$KEYS_BROWSE" "Open selected item in browser" \
"$KEYS_OPEN" "Open selected item in file manager" \ "$KEYS_OPEN" "Open selected item in file manager" \
@@ -328,6 +343,9 @@ print_keybindings() {
"$KEYS_SCROLL_PREVIEW_DOWN" "Scroll preview down" \ "$KEYS_SCROLL_PREVIEW_DOWN" "Scroll preview down" \
"$KEYS_SCROLL_PREVIEW_UP" "Scroll preview up" \ "$KEYS_SCROLL_PREVIEW_UP" "Scroll preview up" \
"$KEYS_KEYBINDINGS" "Show these keybindings" \ "$KEYS_KEYBINDINGS" "Show these keybindings" \
"$KEYS_PREVIEW_TOGGLE_WRAP" "Toggle preview wrapping" \
"$KEYS_PREVIEW_TOGGLE_SIZE" "Toggle preview size" \
"$KEYS_PREVIEW_OPEN" "Open preview window" \
"$KEYS_PREVIEW_CLOSE" "Close preview window" "$KEYS_PREVIEW_CLOSE" "Close preview window"
__keybindinggroup_from_args "Navigation" \ __keybindinggroup_from_args "Navigation" \
"$KEYS_DOWN" "Down" \ "$KEYS_DOWN" "Down" \
@@ -402,6 +420,10 @@ print_keybindings() {
"$KEYS_N_PLAY_PREV" "Play previous track" \ "$KEYS_N_PLAY_PREV" "Play previous track" \
"$KEYS_N_SEEK_FORWARD" "Seek forward" \ "$KEYS_N_SEEK_FORWARD" "Seek forward" \
"$KEYS_N_SEEK_BACKWARD" "Seek backward" "$KEYS_N_SEEK_BACKWARD" "Seek backward"
__keybindinggroup_from_args "Lyrics" \
"$KEYS_N_LYRICS" "Show lyrics (normal mode)" \
"$KEYS_LYRICS_EDIT" "Edit lyrics" \
"$KEYS_N_LYRICS_FETCH_CUSTOM" "Fetch lyrics using custom command (normal)"
__keybindinggroup_from_args "Special operations" \ __keybindinggroup_from_args "Special operations" \
"$KEYS_SHOW_PLAYLIST" "Show playlist" \ "$KEYS_SHOW_PLAYLIST" "Show playlist" \
"$KEYS_BROWSE" "Open selected item in browser" \ "$KEYS_BROWSE" "Open selected item in browser" \

View File

@@ -98,6 +98,7 @@ list_artists_from_json() {
# Print playlist currently loaded # Print playlist currently loaded
list_playlist() { list_playlist() {
count=$(mpv_playlist_count) count=$(mpv_playlist_count)
[ "$count" ] || return 0
[ "$count" -eq 0 ] && return 0 [ "$count" -eq 0 ] && return 0
mpvquery="" mpvquery=""
for i in $(seq 0 $((count - 1))); do for i in $(seq 0 $((count - 1))); do
@@ -105,3 +106,17 @@ list_playlist() {
done done
__mpv_get "$mpvquery" | grep '.' | awk_playlist __mpv_get "$mpvquery" | grep '.' | awk_playlist
} }
# List stored playlist
#
# @argument $1: paylist name
list_playlist_stored() {
t=$(mktemp)
r=$(mktemp)
generate_playlist_stored "$PLAYLIST_DIRECTORY/$1" |
grep "$(printf '\t')" |
cut -d "," -f 2- >"$t"
grep -v "/dev/null$" "$t" | cut -d "$(printf '\t')" -f 2 >"$r"
awk_recordings "$r" <"$t"
rm -f "$t" "$r"
}

View File

@@ -157,7 +157,7 @@ END { for (i in id) print title[i], id[i], fname[i] }
' "$tmpj" "$tmpf" >"$assocfile" ' "$tmpj" "$tmpf" >"$assocfile"
rm -f "$tmpj" "$tmpf" rm -f "$tmpj" "$tmpf"
# Ask user if this is ok # Ask user if this is ok
info "We discovered the following associatoin:" info "We discovered the following association:"
while IFS= read -r line; do while IFS= read -r line; do
t="$(echo "$line" | cut -d "$(printf '\t')" -f 1)" t="$(echo "$line" | cut -d "$(printf '\t')" -f 1)"
f="$(echo "$line" | cut -d "$(printf '\t')" -f 3)" f="$(echo "$line" | cut -d "$(printf '\t')" -f 3)"
@@ -183,44 +183,6 @@ END { for (i in id) print title[i], id[i], fname[i] }
return 0 return 0
} }
# Load missing cache entries (batch mode)
#
# argument $1: type
#
# 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)
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")
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))
info "$(printf "%d/%d (%s: %s)" "$cnt" "$lines" "$mbid" "$name")"
sleep 1
done <"$tmpf"
fi
rm -f "$tmpf"
}
# Precompute lists # Precompute lists
# #
# The main views (VIEW_ARTIST and TYPE_RELEASEGROUP) for locally available # The main views (VIEW_ARTIST and TYPE_RELEASEGROUP) for locally available
@@ -229,7 +191,7 @@ __batch_load_missing() {
# theme-independent fashion. The lists are stored in the files # theme-independent fashion. The lists are stored in the files
# `LOCALDATA_ARTISTS_LIST` and `LOCALDATA_RELEASEGROUPS_LIST`. # `LOCALDATA_ARTISTS_LIST` and `LOCALDATA_RELEASEGROUPS_LIST`.
__precompute_lists() { __precompute_lists() {
cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs \ cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs -d "\n" \
$JQ '[ $JQ '[
.id, .id,
.type, .type,
@@ -239,7 +201,7 @@ __precompute_lists() {
.["life-span"].begin, .["life-span"].begin,
.["life-span"].end .["life-span"].end
] | join("\t")' >"$LOCALDATA_ARTISTS_LIST" & ] | join("\t")' >"$LOCALDATA_ARTISTS_LIST" &
cache_get_file_batch "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" | xargs \ cache_get_file_batch "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" | xargs -d "\n" \
$JQ '[ $JQ '[
.id, .id,
."primary-type", ."primary-type",
@@ -278,11 +240,11 @@ reloaddb() {
tmpreleases=$(mktemp) tmpreleases=$(mktemp)
cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" | cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" |
tee "$tmpreleases" | tee "$tmpreleases" |
__batch_load_missing "$TYPE_RELEASE" batch_load_missing "$TYPE_RELEASE"
tmpreleasefiles=$(mktemp) tmpreleasefiles=$(mktemp)
cache_get_file_batch "$TYPE_RELEASE" <"$tmpreleases" >"$tmpreleasefiles" cache_get_file_batch "$TYPE_RELEASE" <"$tmpreleases" >"$tmpreleasefiles"
( (
xargs \ xargs -d "\n" \
$JQ '."release-group".id' \ $JQ '."release-group".id' \
<"$tmpreleasefiles" >"$LOCALDATA_RELEASEGROUPS" <"$tmpreleasefiles" >"$LOCALDATA_RELEASEGROUPS"
tf1=$(mktemp) tf1=$(mktemp)
@@ -290,7 +252,7 @@ reloaddb() {
mv "$tf1" "$LOCALDATA_RELEASEGROUPS" mv "$tf1" "$LOCALDATA_RELEASEGROUPS"
) & ) &
( (
xargs \ xargs -d "\n" \
$JQ '."release-group"."artist-credit" | map(.artist.id) | join("\n")' \ $JQ '."release-group"."artist-credit" | map(.artist.id) | join("\n")' \
<"$tmpreleasefiles" >"$LOCALDATA_ARTISTS" <"$tmpreleasefiles" >"$LOCALDATA_ARTISTS"
tf2=$(mktemp) tf2=$(mktemp)
@@ -299,8 +261,8 @@ reloaddb() {
) & ) &
wait wait
rm -f "$tmpreleases" "$tmpreleasefiles" rm -f "$tmpreleases" "$tmpreleasefiles"
__batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS"
__batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS"
__precompute_lists __precompute_lists
} }
@@ -311,8 +273,8 @@ reloaddb() {
# derivation of the MusicBrainz artist IDs and MusicBrainz release-group IDs # derivation of the MusicBrainz artist IDs and MusicBrainz release-group IDs
# from the MusicBrainz releases (see the `reloaddb` method above). # from the MusicBrainz releases (see the `reloaddb` method above).
local_files_present() { local_files_present() {
cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs ls >/dev/null 2>&1 || return 1 cached "$TYPE_ARTIST" "$LOCALDATA_ARTISTS" || return 1
cache_get_file_batch "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" | xargs ls >/dev/null 2>&1 || return 1 cached "$TYPE_RELEASEGROUP" "$LOCALDATA_RELEASEGROUPS" || return 1
} }
# Load missing files # Load missing files
@@ -320,6 +282,6 @@ local_files_present() {
# If missing files were detected with `local_files_present`, then these missing # If missing files were detected with `local_files_present`, then these missing
# files may be cached using the present method. # files may be cached using the present method.
load_missing_files() { load_missing_files() {
__batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS"
__batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS" batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS"
} }

121
src/sh/lyrics.sh Normal file
View File

@@ -0,0 +1,121 @@
# Methods and constants for lyrics handling
#
# Lyrics are retrieved as following:
# 1. Check if the lyrics are already stored in this store
# 2. If the track is playable, check if an accompanying `.lrc` file is present.
# 3. If the track is playable, read lyrics from the tags
# 4. Call custom fetch command
#
# The path to the lyrics is `__radix(mbid)/mbid.lrc` where `mbid` is the
# MusicBrainz ID of the track.
if [ ! "${LYRICS_LOADED:-}" ]; then
# Folder to store lyrics
LYRICS_DIRECTORY="${LYRICS_DIRECTORY:-"$LOCALDATADIR/lyrics"}"
[ -d "$LYRICS_DIRECTORY" ] || mkdir -p "$LYRICS_DIRECTORY"
export LYRICS_DIRECTORY
# Custom command to fetch lyrics
#
# This command reads from stdin the json object of the release and prints the
# lyrics of the track.
LYRICS_FETCH_CUSTOM="${LYRICS_FETCH_CUSTOM:-""}"
export LYRICS_FETCH_CUSTOM
export LYRICS_LOADED=1
fi
# File path for lyrics file
#
# @argument $1: MusicBrainz track ID
lyrics_file() {
mbid="${1:-}"
echo "$LYRICS_DIRECTORY/$(__radix "$mbid").lrc"
}
# Store lyrics
#
# @argument $1: MusicBrainz track ID
#
# This methods reads from stdin and stores it.
store_lyrics() {
mbid="${1:-}"
file="$(lyrics_file "$mbid")"
dir="$(dirname "$file")"
[ -d "$dir" ] || mkdir -p "$dir"
cat >"$file"
}
# Fetch lyrics using custom command and store them
#
# @argument $1: MusicBrainz release ID
# @argument $2: MusicBrainz track ID
#
# The custom script is executed only if the environment variable is set. Else
# the message stored in `$LYRICS_NO_LYRICS` is saved.
store_lyrics_custom() {
rlid="${1:-}"
mbid="${2:-}"
if [ "$LYRICS_FETCH_CUSTOM" ]; then
mb_release "$rlid" |
$JQ --arg mbid "$mbid" '{release: ., trackid: $mbid}' |
sh -c "$LYRICS_FETCH_CUSTOM" |
store_lyrics "$mbid"
else
echo "$LYRICS_NO_LYRICS" |
store_lyrics "$mbid"
fi
}
# Print lyrics
#
# @argument $1: MusicBrainz release ID
# @argument $2: MusicBrainz track ID
lyrics() {
rlid="${1:-}"
mbid="${2:-}"
# 1. Check if lyrics has already been stored
file="$(lyrics_file "$mbid")"
if [ -f "$file" ]; then
cat "$file"
return
fi
# 2. & 3.: For playable tracks only
decoration="$(grep "^$rlid" "$LOCALDATA_RELEASES" | cut -d "$(printf '\t')" -f 2)"
if [ "$decoration" ] && [ -f "$decoration" ]; then
afname="$($JQ --arg mbid "$mbid" '.tracks | to_entries[] | select(.key == $mbid) | .value' "$decoration")"
af="$(dirname "$decoration")/$afname"
# Check if `.lrc` file exists
lf="$(echo "$af" | rev | cut -d "." -f 2- | rev).lrc"
if [ -f "$lf" ]; then
store_lyrics "$mbid" <"$lf"
cat "$file"
return
fi
# Read lyrics from tag
if [ "$FFPROBE" ]; then
lyrics="$($FFPROBE -v error -show_entries format_tags -print_format json "$af" |
$JQ '.format.tags | ."USLT:description" // ."LYRICS" // ."Lyrics" // ."©lyr" // ."WM/Lyrics" // ""')"
if [ "$lyrics" ]; then
echo "$lyrics" | store_lyrics "$mbid"
cat "$file"
return
fi
fi
fi
# Make call to external command
store_lyrics_custom "$rlid" "$mbid"
cat "$file"
}
# Reload lyrics file
#
# @argument $1: MusicBrainz release ID
# @argument $2: MusicBrainz track ID
reload_lyrics() {
rlid="${1:-}"
mbid="${2:-}"
file="$(lyrics_file "$mbid")"
rm -f "$file"
lyrics "$rlid" "$mbid"
}

View File

@@ -18,7 +18,7 @@ if [ ! "${PLAYLIST_LOADED:-}" ]; then
PLAYLIST_CMD_SHUFFLE PLAYLIST_CMD_UNSHUFFLE PLAYLIST_CMD_LOAD PLAYLIST_CMD_SHUFFLE PLAYLIST_CMD_UNSHUFFLE PLAYLIST_CMD_LOAD
# Storage and loading of playlists # Storage and loading of playlists
PLAYLIST_DIRECTORY="$LOCALDATADIR/playlists" PLAYLIST_DIRECTORY="${PLAYLIST_DIRECTORY:-"$LOCALDATADIR/playlists"}"
[ -d "$PLAYLIST_DIRECTORY" ] || mkdir -p "$PLAYLIST_DIRECTORY" [ -d "$PLAYLIST_DIRECTORY" ] || mkdir -p "$PLAYLIST_DIRECTORY"
export PLAYLIST_DIRECTORY export PLAYLIST_DIRECTORY
@@ -29,7 +29,7 @@ fi
# #
# This prints the names of the stored playlists. # This prints the names of the stored playlists.
stored_playlists() { stored_playlists() {
find "$PLAYLIST_DIRECTORY" -mindepth 1 -maxdepth 1 -type f -printf '%f\n' | find "$PLAYLIST_DIRECTORY" -mindepth 1 -maxdepth 1 -type f -printf "$PLYSTORE_PLAYLIST\t%f\n" |
sort sort
} }
@@ -78,15 +78,23 @@ generate_playlist() {
map("#EXTINF:" + .length + "," + .t + "\n" + $base + "/" + .file)[]' map("#EXTINF:" + .length + "," + .t + "\n" + $base + "/" + .file)[]'
} }
# Generate playlist content from stored playlist # Generate m3u playlist from stored playlist
# #
# @argument $1: Playlist file # @argument $1: Playlist file
generate_playlist_stored() { generate_playlist_stored() {
f="${1:-}" f="${1:-}"
[ -s "$f" ] || return [ -s "$f" ] || return
tab="$(printf '\t')" # Check that we have all releases cached, else fetch missing ones
PLAYLISTSTART="#EXTM3U" relf=$(mktemp)
printf "%s\n" "$PLAYLISTSTART" cut -d "$(printf '\t')" -f 1 "$f" >"$relf"
cached "$TYPE_RELEASE" "$relf" || batch_load_missing "$TYPE_RELEASE" <"$relf"
jrelf=$(mktemp)
# Write json file with all releases
cache_get_file_batch "$TYPE_RELEASE" <"$relf" |
xargs -d '\n' cat >"$jrelf"
# Get associated decorations and write json file with all decorations
jpf=$(mktemp)
jdecf=$(mktemp)
awk -F '\t' \ awk -F '\t' \
-v rfile="$LOCALDATA_RELEASES" \ -v rfile="$LOCALDATA_RELEASES" \
'BEGIN { 'BEGIN {
@@ -94,17 +102,59 @@ generate_playlist_stored() {
while ((getline < rfile) == 1) while ((getline < rfile) == 1)
release[$1] = $2 release[$1] = $2
close(rfile) close(rfile)
print "["
} }
{ NR > 1 { print "," }
if (release[$1]) { print "{\"rid\":\"" $1 "\",\"tid\":\"" $2 "\",\"deco\":\"" (release[$1] ? release[$1] : "") "\"}" }
print $1, release[$1], $2 END {print "]"}' <"$f" >"$jpf"
}' <"$f" | $JQ 'map(.deco) | join("\n")' "$jpf" |
while IFS= read -r line; do grep '.' |
rid=$(echo "$line" | cut -d "$tab" -f 1) xargs -d '\n' cat >"$jdecf"
path=$(echo "$line" | cut -d "$tab" -f 2) # Merge all data using jq and print playlist
tid=$(echo "$line" | cut -d "$tab" -f 3) printf "#EXTM3U\n"
generate_playlist "$rid" "$path" "$tid" $JQ \
done | grep -v "^$PLAYLISTSTART$" --slurpfile deco "$jdecf" \
--slurpfile mb "$jrelf" \
'$deco as $decorations |
$mb as $releases |
map(
. as $item |
first(
if ($item.deco | length) > 0 then
($item.deco | sub("/[^/]+$"; "")) as $base |
first($deco[] | select(.releaseid == $item.rid).tracks | to_entries[] | select(.key == $item.tid).value) as $fn |
$base + "/" + $fn
else
"/dev/null"
end
) as $p |
first(
$mb[] | select(.id == $item.rid).media[].tracks[] | select(.id == $item.tid)
) as $track |
(
$track.length // 0 / 1000 | round | tostring
) as $length |
( if ($item.deco | length) > 0 then $item.deco else "/dev/null" end) as $d |
$item + {
path: $p,
length: $length,
t: [
$item.rid,
$item.tid,
"",
"",
"",
$length,
$track.title,
($track."artist-credit" | map([.name, .joinphrase] | join("")) | join("")),
$d
] | join("\t")
}
) |
map("#EXTINF:" + .length + "," + .t + "\n" + .path)[]' \
"$jpf"
# Clean up
rm -f "$relf" "$jrelf" "$jpf" "$jdecf"
} }
# Run playback commands # Run playback commands

View File

@@ -19,6 +19,7 @@ if [ ! "${THEME_LOADED:-}" ]; then
CNOTE="${ESC}[38;5;242m" CNOTE="${ESC}[38;5;242m"
CXXX="${ESC}[38;5;109m" CXXX="${ESC}[38;5;109m"
CDESC="${ESC}[38;5;254m" CDESC="${ESC}[38;5;254m"
CPURPLE="${ESC}[38;5;213m"
CKB="${ESC}[38;5;224m" CKB="${ESC}[38;5;224m"
OFF="${ESC}[m" OFF="${ESC}[m"
@@ -99,6 +100,7 @@ if [ ! "${THEME_LOADED:-}" ]; then
RGV_FMT_HASSECONDARY_YES="${RGV_FMT_HASSECONDARY_YES:-"☼"}" RGV_FMT_HASSECONDARY_YES="${RGV_FMT_HASSECONDARY_YES:-"☼"}"
RGV_FMT_HASSECONDARY_NO="${RGV_FMT_HASSECONDARY_NO:-""}" RGV_FMT_HASSECONDARY_NO="${RGV_FMT_HASSECONDARY_NO:-""}"
RGV_FMT_SECONDARY="${RGV_FMT_SECONDARY:-"${CNOTE}[☼: %s]${OFF}"}" RGV_FMT_SECONDARY="${RGV_FMT_SECONDARY:-"${CNOTE}[☼: %s]${OFF}"}"
RGV_FMT_SECONDARY_COMPILATION="${RGV_FMT_SECONDARY_COMPILATION:-"🧩 compilation"}"
RGV_FMT_SECONDARY_SOUNDTRACK="${RGV_FMT_SECONDARY_SOUNDTRACK:-"🎬 soundtrack"}" RGV_FMT_SECONDARY_SOUNDTRACK="${RGV_FMT_SECONDARY_SOUNDTRACK:-"🎬 soundtrack"}"
RGV_FMT_SECONDARY_SPOKENWORD="${RGV_FMT_SECONDARY_SPOKENWORD:-"📖 spokenword"}" RGV_FMT_SECONDARY_SPOKENWORD="${RGV_FMT_SECONDARY_SPOKENWORD:-"📖 spokenword"}"
RGV_FMT_SECONDARY_INTERVIEW="${RGV_FMT_SECONDARY_INTERVIEW:-"💬 interview"}" RGV_FMT_SECONDARY_INTERVIEW="${RGV_FMT_SECONDARY_INTERVIEW:-"💬 interview"}"
@@ -117,12 +119,13 @@ if [ ! "${THEME_LOADED:-}" ]; then
RGV_FMT_FLAG_NO_LOCAL RGV_FMT_TYPE_SINGLE RGV_FMT_TYPE_ALBUM \ RGV_FMT_FLAG_NO_LOCAL RGV_FMT_TYPE_SINGLE RGV_FMT_TYPE_ALBUM \
RGV_FMT_TYPE_EP RGV_FMT_TYPE_BROADCAST RGV_FMT_TYPE_OTHER \ RGV_FMT_TYPE_EP RGV_FMT_TYPE_BROADCAST RGV_FMT_TYPE_OTHER \
RGV_FMT_TYPE_UNKNOWN RGV_FMT_HASSECONDARY_YES RGV_FMT_HASSECONDARY_NO \ RGV_FMT_TYPE_UNKNOWN RGV_FMT_HASSECONDARY_YES RGV_FMT_HASSECONDARY_NO \
RGV_FMT_SECONDARY RGV_FMT_SECONDARY_SOUNDTRACK \ RGV_FMT_SECONDARY RGV_FMT_SECONDARY_COMPILATION \
RGV_FMT_SECONDARY_SPOKENWORD RGV_FMT_SECONDARY_INTERVIEW \ RGV_FMT_SECONDARY_SOUNDTRACK RGV_FMT_SECONDARY_SPOKENWORD \
RGV_FMT_SECONDARY_AUDIOBOOK RGV_FMT_SECONDARY_AUDIODRAMA \ RGV_FMT_SECONDARY_INTERVIEW RGV_FMT_SECONDARY_AUDIOBOOK \
RGV_FMT_SECONDARY_LIVE RGV_FMT_SECONDARY_REMIX RGV_FMT_SECONDARY_DJMIX \ RGV_FMT_SECONDARY_AUDIODRAMA RGV_FMT_SECONDARY_LIVE \
RGV_FMT_SECONDARY_MIXTAPE RGV_FMT_SECONDARY_DEMO \ RGV_FMT_SECONDARY_REMIX RGV_FMT_SECONDARY_DJMIX \
RGV_FMT_SECONDARY_FIELDREC RGV_FMT_TITLE RGV_FMT_ARTIST RGV_FMT_YEAR RGV_FMT_SECONDARY_MIXTAPE RGV_FMT_SECONDARY_DEMO RGV_FMT_SECONDARY_FIELDREC \
RGV_FMT_TITLE RGV_FMT_ARTIST RGV_FMT_YEAR
# Release view # Release view
# ============ # ============
@@ -172,7 +175,7 @@ if [ ! "${THEME_LOADED:-}" ]; then
# <<duration>> string Track duration # <<duration>> string Track duration
REC_FMT="${REC_FMT:-"<<flag>>\t<<media>>\t<<nr>>\t<<title>>\t<<artist>>\t<<duration>>"}" REC_FMT="${REC_FMT:-"<<flag>>\t<<media>>\t<<nr>>\t<<title>>\t<<artist>>\t<<duration>>"}"
REC_FMT_CNT=$(($(printf "$REC_FMT" | tr -cd '\t' | wc -c) + 1)) REC_FMT_CNT=$(($(printf "$REC_FMT" | tr -cd '\t' | wc -c) + 1))
REC_FMT_RIGHTALIGN="${REC_FMT_RIGHTALIGN:-"1,2,3,6"}" REC_FMT_RIGHTALIGN="${REC_FMT_RIGHTALIGN:-"1,2,3"}"
REC_FMT_FLAG_LOCAL="${REC_FMT_FLAG_LOCAL:-"$FORMAT_LOCAL"}" REC_FMT_FLAG_LOCAL="${REC_FMT_FLAG_LOCAL:-"$FORMAT_LOCAL"}"
REC_FMT_FLAG_NO_LOCAL="${REC_FMT_FLAG_NO_LOCAL:-""}" REC_FMT_FLAG_NO_LOCAL="${REC_FMT_FLAG_NO_LOCAL:-""}"
REC_FMT_MEDIA="${REC_FMT_MEDIA:-"${CNOTE}${FAINT}%s${OFF}"}" REC_FMT_MEDIA="${REC_FMT_MEDIA:-"${CNOTE}${FAINT}%s${OFF}"}"
@@ -237,6 +240,7 @@ if [ ! "${THEME_LOADED:-}" ]; then
HEADER_RG_FMT_HASSECONDARY_YES="${HEADER_RG_FMT_HASSECONDARY_YES:-"$RGV_FMT_HASSECONDARY_YES"}" HEADER_RG_FMT_HASSECONDARY_YES="${HEADER_RG_FMT_HASSECONDARY_YES:-"$RGV_FMT_HASSECONDARY_YES"}"
HEADER_RG_FMT_HASSECONDARY_NO="${HEADER_RG_FMT_HASSECONDARY_NO:-"$RGV_FMT_HASSECONDARY_NO"}" HEADER_RG_FMT_HASSECONDARY_NO="${HEADER_RG_FMT_HASSECONDARY_NO:-"$RGV_FMT_HASSECONDARY_NO"}"
HEADER_RG_FMT_SECONDARY="${HEADER_RG_FMT_SECONDARY:-"$RGV_FMT_SECONDARY"}" HEADER_RG_FMT_SECONDARY="${HEADER_RG_FMT_SECONDARY:-"$RGV_FMT_SECONDARY"}"
HEADER_RG_FMT_SECONDARY_COMPILATION="${HEADER_RG_FMT_SECONDARY_COMPILATION:-"$RGV_FMT_SECONDARY_COMPILATION"}"
HEADER_RG_FMT_SECONDARY_SOUNDTRACK="${HEADER_RG_FMT_SECONDARY_SOUNDTRACK:-"$RGV_FMT_SECONDARY_SOUNDTRACK"}" HEADER_RG_FMT_SECONDARY_SOUNDTRACK="${HEADER_RG_FMT_SECONDARY_SOUNDTRACK:-"$RGV_FMT_SECONDARY_SOUNDTRACK"}"
HEADER_RG_FMT_SECONDARY_SPOKENWORD="${HEADER_RG_FMT_SECONDARY_SPOKENWORD:-"$RGV_FMT_SECONDARY_SPOKENWORD"}" HEADER_RG_FMT_SECONDARY_SPOKENWORD="${HEADER_RG_FMT_SECONDARY_SPOKENWORD:-"$RGV_FMT_SECONDARY_SPOKENWORD"}"
HEADER_RG_FMT_SECONDARY_INTERVIEW="${HEADER_RG_FMT_SECONDARY_INTERVIEW:-"$RGV_FMT_SECONDARY_INTERVIEW"}" HEADER_RG_FMT_SECONDARY_INTERVIEW="${HEADER_RG_FMT_SECONDARY_INTERVIEW:-"$RGV_FMT_SECONDARY_INTERVIEW"}"
@@ -257,13 +261,13 @@ if [ ! "${THEME_LOADED:-}" ]; then
HEADER_RG_FMT_TYPE_BROADCAST HEADER_RG_FMT_TYPE_OTHER \ HEADER_RG_FMT_TYPE_BROADCAST HEADER_RG_FMT_TYPE_OTHER \
HEADER_RG_FMT_TYPE_UNKNOWN HEADER_RG_FMT_HASSECONDARY_YES \ HEADER_RG_FMT_TYPE_UNKNOWN HEADER_RG_FMT_HASSECONDARY_YES \
HEADER_RG_FMT_HASSECONDARY_NO HEADER_RG_FMT_SECONDARY \ HEADER_RG_FMT_HASSECONDARY_NO HEADER_RG_FMT_SECONDARY \
HEADER_RG_FMT_SECONDARY_SOUNDTRACK HEADER_RG_FMT_SECONDARY_SPOKENWORD \ HEADER_RG_FMT_SECONDARY_COMPILATION HEADER_RG_FMT_SECONDARY_SOUNDTRACK \
HEADER_RG_FMT_SECONDARY_INTERVIEW HEADER_RG_FMT_SECONDARY_AUDIOBOOK \ HEADER_RG_FMT_SECONDARY_SPOKENWORD HEADER_RG_FMT_SECONDARY_INTERVIEW \
HEADER_RG_FMT_SECONDARY_AUDIODRAMA HEADER_RG_FMT_SECONDARY_LIVE \ HEADER_RG_FMT_SECONDARY_AUDIOBOOK HEADER_RG_FMT_SECONDARY_AUDIODRAMA \
HEADER_RG_FMT_SECONDARY_REMIX HEADER_RG_FMT_SECONDARY_DJMIX \ HEADER_RG_FMT_SECONDARY_LIVE HEADER_RG_FMT_SECONDARY_REMIX \
HEADER_RG_FMT_SECONDARY_MIXTAPE HEADER_RG_FMT_SECONDARY_DEMO \ HEADER_RG_FMT_SECONDARY_DJMIX HEADER_RG_FMT_SECONDARY_MIXTAPE \
HEADER_RG_FMT_SECONDARY_FIELDREC HEADER_RG_FMT_TITLE HEADER_RG_FMT_ARTIST \ HEADER_RG_FMT_SECONDARY_DEMO HEADER_RG_FMT_SECONDARY_FIELDREC \
HEADER_RG_FMT_YEAR HEADER_RG_FMT_TITLE HEADER_RG_FMT_ARTIST HEADER_RG_FMT_YEAR
# Header that displays the release (based on release view) # Header that displays the release (based on release view)
HEADER_R_FMT="${HEADER_R_FMT:-"🎤\t<<artist>> 》<<title>> 〉<<tracks>> ${CRELINFO}tx${OFF} <<media>> | <<label>> <<country>> <<year>>"}" HEADER_R_FMT="${HEADER_R_FMT:-"🎤\t<<artist>> 》<<title>> 〉<<tracks>> ${CRELINFO}tx${OFF} <<media>> | <<label>> <<country>> <<year>>"}"
@@ -387,11 +391,18 @@ if [ ! "${THEME_LOADED:-}" ]; then
KBF_DESC="${KBF_DESC:-"${CKB}%s${OFF}"}" KBF_DESC="${KBF_DESC:-"${CKB}%s${OFF}"}"
export KBF_GROUP KBF_KEY KBF_DESC export KBF_GROUP KBF_KEY KBF_DESC
# Playlist title # Playlist title and playlist store
# ============== # =================================
TITLE_PLYLST="${TITLE_PLYLST:-" 🎶 ${CARTIST}Playlist${OFF} "}" TITLE_PLYLST="${TITLE_PLYLST:-" 🎶 ${CARTIST}Playlist${OFF} "}"
TITLE_PLYLST_LIST="${TITLE_PLYLST_LIST:-" 🎶 ${CARTIST}Stored Playlists${OFF} "}" TITLE_PLYLST_STORE="${TITLE_PLYLST_STORE:-" 🎶 ${CARTIST}Stored Playlists${OFF} "}"
export TITLE_PLYLST TITLE_PLYLST_LIST # In the following %f is the file name
PLYSTORE_PLAYLIST="${PLYSTORE_PLAYLIST:-"🎼 ${CPURPLE}%f${OFF}"}"
export TITLE_PLYLST TITLE_PLYLST_STORE PLYSTORE_PLAYLIST
# Lyrics
# ======
LYRICS_NO_LYRICS="${LYRICS_NO_LYRICS:-"(no lyrics)"}"
export LYRICS_NO_LYRICS
export THEME_LOADED=1 export THEME_LOADED=1
fi fi

View File

@@ -55,5 +55,25 @@ if [ ! "${TOOLS_LOADED:-}" ]; then
command -v "xsel" >/dev/null && CLIP="xsel" || CLIP="true" command -v "xsel" >/dev/null && CLIP="xsel" || CLIP="true"
export CLIP export CLIP
# Detect external editor
editor="${EDITOR:-vi}"
if command -v "$editor" >/dev/null; then
if command -v "kitty" >/dev/null; then
extedit=$(printf "kitty %s" "$editor")
elif command -v "x-terminal-emulator" >/dev/null; then
extedit=$(printf "x-terminal-emulator -e %s" "$editor")
elif command -v "gnome-terminal" >/dev/null; then
extedit=$(printf "gnome-terminal -- %s" "$editor")
elif command -v "xterm" >/dev/null; then
extedit=$(printf "xterm -e %s" "$editor")
else
extedit=""
fi
else
extedit=""
fi
EXTERNALEDIT="${EXTERNALEDIT:-"$extedit"}"
export EXTERNALEDIT
export TOOLS_LOADED=1 export TOOLS_LOADED=1
fi fi