Compare commits

...

20 Commits

Author SHA1 Message Date
ed12cef7e4 typos fixed 2025-10-16 21:01:42 +02:00
06485fbce7 typo fixed and feature update 2025-10-15 20:38:00 +02:00
214a1cda4c bugfix: playlist list 2025-10-15 15:22:40 +02:00
d13436b963 feat: lyrics support readme and examples 2025-10-15 09:12:02 +02:00
4343521a46 bugfix: enter past releases 2025-10-14 22:38:36 +02:00
6db95d3feb feat: lyrics support 2025-10-14 22:33:36 +02:00
a0f8e79665 bugfix: xargs delim 2025-10-13 22:30:04 +02:00
fde3b6edcb added example playlist 2025-10-13 22:14:27 +02:00
c0463c0d5e imprv: faster playlist handling and a few bugs fixed 2025-10-13 22:07:26 +02:00
3b66faed6f updated planned features 2025-10-11 23:06:19 +02:00
13f40e9720 feat: playlist store 2025-10-11 23:01:48 +02:00
2bf0506351 bugfix: right-align duration in playlist view 2025-10-10 11:01:43 +02:00
57ed2f6d0e minor typos fixed 2025-10-10 10:53:48 +02:00
6bfb7f0399 improved readme 2025-10-10 10:50:16 +02:00
05661c0d98 minor improvements 2025-10-10 10:17:53 +02:00
08ea6cf56c minor improvements 2025-10-10 09:55:49 +02:00
a4cb7d5c72 bugfix 2025-10-09 16:22:56 +02:00
8c67085f05 added screenshots 2025-10-09 16:22:04 +02:00
11446031bf readme improved 2025-10-09 16:10:39 +02:00
1c4611e833 typo in readme 2025-10-09 16:10:13 +02:00
21 changed files with 791 additions and 199 deletions

134
README.md
View File

@@ -1,24 +1,36 @@
A terminal-based music browser and player.
🎧 Description and Use Case
---------------------------
This application allows for a keyboard-controlled maneuvering of your music library and the MusicBrainz database.
Your local library becomes "embedded" in MusicBrainz, and all metadata remains up-to-date without the need of tagging your files.
Control playback from your terminal while browsing the vast world of music releases.
🔍 Key Features
---------------
- [Fuzzily](https://github.com/junegunn/fzf) browse your music library
- Treat your local library as part of the global [MusicBrainz universe](https://musicbrainz.org)
- Search the MusicBrainz database and discover music artists, and releases
- Treat your local library as part of the [MusicBrainz universe](https://musicbrainz.org)
- Search the MusicBrainz database and discover music artists and releases
- Handle metadata without touching your audio files
- Fast and Hackable application with [vim](https://www.vim.org)-like keybindings
🎧 Description and Use Case
---------------------------
This application allows for a keyboard-controlled maneuvering of your music
library and the MusicBrainz database. All metadata is fetched from the
MusicBrainz database without tagging your audio files. Effectively, your local
library becomes "embedded" in MusicBrainz. You can control playback while
browsing the vast world of music releases.
- Fast and hackable application with [vim](https://www.vim.org)-like keybindings
- Save and load playlists
- Lyrics support
🎞️ Screenshot
-------------
![Demo screenshot](./demo/screenshot.png)
### Search Artist on MusicBrainz
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.
They are played by hitting `<enter>`.
![MusicBrainz artist search](./screenshots/search.png)
### Browse Local Music Library
Here, we fuzzily search an album of _Daft Punk._
The LP _Random Access Memories_ can be appended to the playlist by pressing `ctrl-alt-m`.
Alternatively, you may switch back to normal mode (`esc`) and then hit `;`.
![Browse library](./screenshots/browse.png)
### Playlist Control
You can easily control playback and edit the playlist in the playlist view.
![Playlist](./screenshots/playlist.png)
🛠️ Requirements and Installation
--------------------------------
@@ -42,7 +54,7 @@ Run `./scripts/build.sh`, then copy `fuzic` to your preferred location, e.g., `~
🧩 Configuration
-----------------
Although no configuration is needed, this application may be customized using environment variables.
Although **no configuration** is needed, this application may be customized using environment variables.
If the file `$XDG_CONFIG_HOME/fuzic/config` (defaults to `$HOME/.config/fuzic/config`) exists, then it will be sourced.
You may also specify an alternative location of the configuration file with the environment variable `CONFIGFILE`.
@@ -53,10 +65,10 @@ You may execute `CONFIGFILE=share/theme/plain.sh fuzic` to launch `fuzic` using
Custom **filters** can be defined using the environment variables from `src/sh/filter.sh`.
For instance, when you launch `F_2_LIST_ARTISTS="jazz" fuzic`, then the artist list can be queried for the word `jazz` by pressing `alt-2`.
Also the *key bindings* can be reconfigured to your liking.
Also the **key bindings** can be reconfigured to your liking.
For that, adjust the environment variables defined in `src/sh/keys.sh`.
Finally, you can sort the artists according to their [sort name](https://musicbrainz.org/doc/Style/Artist/Sort_Name) by specifying `SORT_ARTIST_DEFAULT=sort-artist-sortname`,
Finally, you can **sort** the artists according to their [sort name](https://musicbrainz.org/doc/Style/Artist/Sort_Name) by specifying `SORT_ARTIST_DEFAULT=sort-artist-sortname`,
and the switch to alphabetical release-group sorting via `SORT_RG_DEFAULT=sort-rg-title`.
@@ -76,6 +88,8 @@ GENERAL OPTIONS:
--artist <mbid> List release groups of given artist <mbid>
--releasegroup <mbid> List releases in given release group <mbid>
--release <mbid> Show release given by <mbid>
--playlists List stored playlists and exit
--load-playlist <playlist> Load specified playlist
MANAGE LOCAL MUSIC:
--decorate <path> Decorate directory containing a tagged release
@@ -87,12 +101,12 @@ MANAGE LOCAL MUSIC:
🗄️ Local Music Library
----------------------
Instead of reading the tags from audio files, this application uses **decorations.**
The decoration of a folder is a JSON files that stores the relevant MusicBrainz IDs of the release the folder corresponds to.
The decoration of a folder is a JSON file that stores the relevant MusicBrainz IDs of the release the folder corresponds to.
This assumes that complete releases are stored within separate folders.
The application then fetches the relevant metadata, which is displayed to the user.
Folders can be decorated using `fuzic --decorate <path>` or `fuzic --decorate-as <path> <mbid>` commands.
The first version may be used on tagged files (using, e.g., [MusicBrainz Picard](https://picard.musicbrainz.org/)).
With this command, the folder gets decorated with the relevant MusicBrainz IDs are extracted from the files.
The first version may be used on previously tagged files (using, e.g., [MusicBrainz Picard](https://picard.musicbrainz.org/)).
With this command, the folder gets decorated with the MusicBrainz IDs extracted from the files.
The latter version does not require the files to be tagged.
Instead, it decorates the files found in that folder as the release given by the [MusicBrainz Release ID](https://musicbrainz.org/doc/MusicBrainz_Identifier) `<mbid>`.
@@ -100,6 +114,43 @@ After decorating your folders, you may run `fuzic --reload-database <path>` to b
This command searches the path specified for decorated folders.
Once the local music library is built, it will display automatically when `fuzic` is launched.
### Workflow Example
```
$ 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
$ 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: We discovered the following association:
Track 'Aubade' File './01 Aubade.m4a'
Track 'Obsolète' File './02 Obsolète.m4a'
Track 'Nouveau western' File './03 Nouveau western.m4a'
Track 'À la claire fontaine' File './04 À la claire fontaine.m4a'
Track 'Superstarr' File './05 Superstarr.m4a'
Track 'La Concubine de lhémoglobine' File './06 La Concubine de lhémoglobine.m4a'
Track 'Dévotion' File './07 Dévotion.m4a'
Track 'Temps mort' File './08 Temps mort.m4a'
Track 'LNMIACCdHTCK72KPDP' File './09 LNMIACCdHTCK72KPDP.m4a'
Track 'Séquelles' File './10 Séquelles.m4a'
Track 'Dieu ait son âme' File './11 Dieu ait son âme.m4a'
Track 'À dix de mes disciples' File './12 À dix de mes disciples.m4a'
Track 'La fin justifie les moyens' File './13 La fin justifie les moyens.m4a'
Track 'Relations humaines' File './14 Relations humaines.m4a'
Track 'Prose combat' File './15 Prose combat.m4a'
Info: Are the track correctly associated to the audio files? (yes/no) yes
$ fuzic --reload-database ~/Music
Info: Reloading information of local music directory /home/amin/Music
Info: Fetching missing releases
Info: 1/2 (5000a285-b67e-4cfc-b54b-2b98f1810d2e: Random Access Memories)
Info: 2/2 (69e5cf67-7cea-4fe8-9129-9779f0a93d69: Prose Combat)
Info: Fetching missing release groups
Info: 1/2 (aa997ea0-2936-40bd-884d-3af8a0e064dc: Random Access Memories)
Info: 2/2 (b199910b-0f90-3c88-b523-d6852b7612d3: Prose Combat)
Info: Fetching missing artists
Info: 1/2 (056e4f3e-d505-4dad-8ec1-d04f521cbb56: Daft Punk)
Info: 2/2 (bbbd2644-b4cb-4bb5-a442-315310f68a0b: MC Solaar)
Info: Done
```
⌨️ Basic Keys
-------------
As mentioned above, you may hit `alt-?` anytime to display the possible keys.
@@ -110,15 +161,50 @@ In the insert mode, triggered using `i`, `a`, or `/`, the keys directly modify t
Some central keys are `<enter>` to start playback of the selected entry, `<space>` to pause playback, and `ctrl-p` to open the currently loaded playlist.
📝 Playlist Store
-----------------
Playlists can be saved, loaded and _shared._
To save a playlist, simply press `ctrl-s` when viewing your playlist.
To load a playlist, open the playlist store using `ctrl-o` while viewing your playlist.
Alternatively, you may start `fuzic` using the command-line parameters `fuzic --load-playlist <playlist>`.
Under the hood, a playlist is not more than a sequence of MusicBrainz IDs.
This means that `fuzic` users may share their playlists irrespective of the audio file locations and formats.
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.
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
-------------------
The following features are planned:
- offline mode
- saving and loading playlists
- cover art and artist images
- lyrics support
- showing current position in track
- hooks when played track changes
- genre support
- MusicBrainz collections and favorites
🌐 Information Sources
----------------------

BIN
screenshots/browse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
screenshots/playlist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
screenshots/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -12,8 +12,10 @@ sed -E 's|\. "([^$].+)"$|cat src/\1|e' "$SRC" >"$tmpdir/1.sh"
echo "🐔 ${GREEN}Internalize awk scripts${OFF}"
sed -E 's|@@include (.+)$|cat src/\1|e' "$tmpdir/1.sh" >"$tmpdir/2.sh"
echo "🥚 ${GREEN}Internalize awk libraries${OFF}"
sed -E 's|@include "(.+)"$|cat src/\1|e' "$tmpdir/2.sh" >"$NAME"
echo "🐔 ${GREEN}Make executable and cleanup${OFF}"
sed -E 's|@include "(.+)"$|cat src/\1|e' "$tmpdir/2.sh" >"$tmpdir/3.sh"
echo "🐔 ${GREEN}Strip comments${OFF}"
grep -v "^ *# " "$tmpdir/3.sh" | grep -v "^ *#$" >"$NAME"
echo "🥚 ${GREEN}Make executable and cleanup${OFF}"
chmod +x "$NAME"
rm -rf "$tmpdir"
echo "🍳 ${GREEN}Done:${OFF} Sucessfully built ${BOLD}${GREEN}$NAME${OFF}"

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

@@ -66,6 +66,8 @@ BEGIN {
ds = dur % 60
if (ds <= 9)
ds = "0"ds
if (dh && dm <= 9)
dm = "0"dm
dur = dh ? dh":"dm":"ds : dm":"ds
} else {
dur = "??:??"

View File

@@ -36,6 +36,8 @@ VIEW_LIST_ARTISTS="list-artists"
VIEW_LIST_ALBUMS="list-albums"
VIEW_SELECT_ARTIST="select-artist"
VIEW_PLAYLIST="playlist"
VIEW_PLAYLIST_PLAYLISTSTORE="playlist-list"
VIEW_PLAYLIST_STORE="playlist-store"
VIEW_QUIT="quit"
MODE_NORMAL="hidden"
MODE_INSERT="show"
@@ -47,21 +49,27 @@ MODE_INSERT="show"
# Load logging methods
. "sh/log.sh"
# Load configuration
. "sh/config.sh"
# Load mpv methods
. "sh/mpv.sh"
# Load query methods
. "sh/query.sh"
# Load playback helper
. "sh/playback.sh"
# Load local file handling
. "sh/local.sh"
# Load playlist tools
. "sh/playlist.sh"
# Load playback helper
. "sh/playback.sh"
# Load MusicBrainz, Discogs, and wiki methods
. "sh/api.sh"
# Load mpv methods
. "sh/mpv.sh"
# Load preview methods
. "sh/preview.sh"
@@ -71,9 +79,6 @@ MODE_INSERT="show"
# Load MusicBrainz wrappers
. "sh/mb.sh"
# Load local file handling
. "sh/local.sh"
# Load list-generating methods
. "sh/lists.sh"
@@ -83,9 +88,6 @@ MODE_INSERT="show"
# Load keys
. "sh/keys.sh"
# Load configuration
. "sh/config.sh"
# Load sorting methods
. "sh/sort.sh"
@@ -98,6 +100,12 @@ MODE_INSERT="show"
# Load AWK scripts
. "sh/awk.sh"
# Load filters
. "sh/filter.sh"
# Load lyrics support
. "sh/lyrics.sh"
# Command-line options that may only be used internally.
# --lines
# --playback
@@ -110,6 +118,8 @@ MODE_INSERT="show"
# --preview
# --show-keybindings
# --remove-from-cache
# --edit-lyrics
# --lyrics-custom
case "${1:-}" in
"--lines")
# Print lines that are fed into fzf.
@@ -225,30 +235,30 @@ case "${1:-}" in
# @argument $5: MusicBrainz ID (optional)
#
# The argument `level` specifies the view relative to `view`: If `level` is
# set to +1, then the specified MusicBrainz ID is an ID of an object one level
# deeper than `view`. Alternatively, the argument `level` may be set to `-1`.
# Anything else is interpreted as "on the level of `view`".
# set to +1, then the specified MusicBrainz ID is an ID of an object one
# level deeper than `view`. Similarly, the argument `level` may be set to
# `-1`. Anything else is interpreted as "on the level of `view`".
#
# The choice of possible arguments ($5) depends on the view.
# These views are independent of the MusicBrainz ID ($4) and of the argument
# The choice of possible levels ($4) depends on the view.
# These views are independent of the MusicBrainz ID ($5) and of the argument
# ($5):
# - VIEW_SEARCH_ARTIST: Get ready to query MusicBrainz for artists
# - VIEW_SEARCH_ALBUM: Get ready to query MusicBrainz for albums
# - VIEW_LIST_ARTISTS: List all locally available artists
# - VIEW_LIST_ALBUMS: List al locally available albums
#
# If no argument ($5) is specified, then the remaining views act as follows:
# If no level ($4) is specified, then the remaining views act as follows:
# - VIEW_ARTIST: Display all release groups of that artist
# - VIEW_RELEASEGROUP: Display all releases within that release group
# - VIEW_RELEASE: Display track list of specified release
#
# Here, if the argument is set to `-1`, then the parent entry is displayed:
# Here, if the level is set to `-1`, then the parent entry is displayed:
# - VIEW_ARTIST: Divert view to VIEW_LIST_ARTISTS
# - VIEW_RELEASEGROUP: For single-artist release groups, divert to
# VIEW_ARTIST of that artist, else display the artist selection.
# - VIEW_RELEASE: Divert view to VIEW_LIST_RELEASEGROUP.
#
# Alternatively, if the argument is set to `+1`, then the child entry is
# Alternatively, if the level is set to `+1`, then the child entry is
# displayed:
# - VIEW_ARTIST: Divert view to VIEW_LIST_ARTISTS
# - VIEW_RELEASEGROUP: For single-artist release groups, divert to
@@ -281,6 +291,7 @@ case "${1:-}" in
"$VIEW_SEARCH_ARTIST" | "$VIEW_LIST_ARTISTS") view="$VIEW_ARTIST" ;;
"$VIEW_ARTIST" | "$VIEW_SEARCH_ALBUM" | "$VIEW_LIST_ALBUMS") view="$VIEW_RELEASEGROUP" ;;
"$VIEW_RELEASEGROUP") view="$VIEW_RELEASE" ;;
"$VIEW_RELEASE") exit 0 ;;
esac
;;
*) ;;
@@ -364,6 +375,20 @@ case "${1:-}" in
esac
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
# Non-interactive user commands intended to the user. These commands do not
@@ -416,6 +441,22 @@ case "${1:-}" in
info "Done"
exit 0
;;
"--playlists")
# List available 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
;;
"--help")
# Print help string
cat <<EOF
@@ -430,6 +471,10 @@ GENERAL OPTIONS:
--artist <mbid> List release groups of given artist <mbid>
--releasegroup <mbid> List releases in given release group <mbid>
--release <mbid> Show release given by <mbid>
--playlists List stored playlists and exit
--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:
--decorate <path> Decorate directory containing a tagged release
@@ -482,14 +527,23 @@ case "${1:-}" in
MODE="$MODE_NORMAL"
MBID=""
;;
"--load-playlist")
# We will load and play later
VIEW="$VIEW_PLAYLIST"
MODE="$MODE_NORMAL"
MBID=""
;;
*)
err "Unknown option $1 (see --help)"
exit 1
;;
esac
# For history purpose: previous view is always:
LASTVIEW="$VIEW_LIST_ARTISTS"
LASTARG=""
# Start application:
# - load and export preset filters
# - set title
# - check for missing data from MusicBrainz
# - precompute main views
@@ -497,9 +551,6 @@ esac
# - start mpv daemon
# - enter main loop and start fzf
# Load filters
. "sh/filter.sh"
# Set window title
printf '\033]0;%s\007' "$WINDOW_TITLE"
@@ -522,24 +573,31 @@ export LOCKFILE RESULTS PIDFILE
# Start mpv
mpv_start
# Playback possible now
if [ "${1:-}" = "--load-playlist" ]; then
sleep 1
$0 --playlist "$PLAYLIST_CMD_LOAD" "${2:-}"
fi
# main loop
# states are stored in (in)visible labels
#
# mode: [$MODE_NORMAL, $MODE_INSERT]
# The mode is reflected on the input visibility. The variable $FZF_INPUT_STATE
# is set to "hidden" if and only if the mode is `normal`. To swtich to `normal`
# mode, we call `hide-input`. To switch to insert mode, we call `show-input`.
# The mode is reflected on the input visibility. The variable
# `FZF_INPUT_STATE`` is set to "hidden" if and only if the mode is `normal`. To
# swtich to `normal` mode, we call `hide-input`. To switch to insert mode, we
# call `show-input`.
#
# view: [$VIEW_*]
# The view is stored in $FZF_LIST_LABEL. To set the view, call
# The view is stored in `FZF_LIST_LABEL`. To set the view, call
# `change-list-label($VIEW)`.
#
# argument: string
# The argument is stored in $FZF_LIST_LABEL. To set the argument, call
# `change-list-label($arg)`.
# mbid:
# The MusicBrainz ID of the current object is stored in `FZF_BORDER_LABEL`.
IN_NORMAL_MODE="[ \$FZF_INPUT_STATE = hidden ]"
IN_VIEW_PATTERN="[ \$FZF_LIST_LABEL = %s ]"
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_VIEW="\$FZF_LIST_LABEL"
FZF_CURRENT_MBID="\$FZF_BORDER_LABEL"
@@ -595,6 +653,70 @@ while true; do
LASTVIEW="$VIEW_SELECT_ARTIST"
LASTARG="$ARGS"
;;
"$VIEW_PLAYLIST_STORE")
VIEW="$VIEW_PLAYLIST"
ARGS=""
MBID=""
tmpf=$(mktemp)
list_playlist | cut -d "$(printf '\t')" -f "3,4" >"$tmpf"
# Make sure we store only nonempty playlists
[ -s "$tmpf" ] || continue
while true; do
infonn "Enter playlist name:"
read -r playlistname
[ "$playlistname" ] || continue
case "$playlistname" in
*[!a-zA-Z0-9._-]*)
info "Please use only alaphnumeric symbols and any of \".-_\" for the playlist name."
;;
*)
f="$PLAYLIST_DIRECTORY/$playlistname"
if [ -s "$f" ]; then
while true; do
infonn "Playlist with name \"$playlistname\" already exists. Do you want to overwrite it? (yes/no)"
read -r yn
case $yn in
"yes" | "no") break ;;
*) info "Please answer \"yes\" or \"no\"." ;;
esac
done
[ "$yn" = "yes" ] || continue
fi
break
;;
esac
done
mv "$tmpf" "$f"
;;
"$VIEW_PLAYLIST_PLAYLISTSTORE")
sel=$(
stored_playlists | $FZF \
--border=double \
--border-label="$TITLE_PLYLST_STORE" \
--margin="2%,10%" \
--bind="$KEYS_I_NORMAL:" \
--bind="$KEYS_DOWN:down" \
--bind="$KEYS_UP:up" \
--bind="$KEYS_HALFPAGE_DOWN:half-page-down" \
--bind="$KEYS_HALFPAGE_UP:half-page-up" \
--bind="$KEYS_OUT,$KEYS_QUIT:accept" \
--bind="$KEYS_KEYBINDINGS:preview:$0 --show-keybindings $VIEW_PLAYLIST_PLAYLISTSTORE" \
--bind="$KEYS_SCROLL_PREVIEW_DOWN:preview-down" \
--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_PLAYLISTSTORE_SELECT:transform:[ {1} ] && $0 --playlist $PLAYLIST_CMD_LOAD {2} && echo accept" \
--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
)
VIEW="$VIEW_PLAYLIST"
;;
"$VIEW_PLAYLIST")
sel=$(
list_playlist | $FZF \
@@ -604,6 +726,7 @@ while true; do
--border-label="$TITLE_PLYLST" \
--no-input \
--margin="2%,10%" \
--bind="$KEYS_I_NORMAL:" \
--bind="$KEYS_DOWN,$KEYS_N_DOWN:down" \
--bind="$KEYS_UP,$KEYS_N_UP:up" \
--bind="$KEYS_HALFPAGE_DOWN:half-page-down" \
@@ -627,7 +750,7 @@ while true; do
--bind="$KEYS_PREVIEW_TOGGLE_SIZE:change-preview-window(right,90%,border-line,nowrap|$FZF_DEFAULT_PREVIEW_WINDOW)" \
--bind="$KEYS_PREVIEW_CLOSE:hide-preview" \
--bind="$KEYS_PLAYBACK,$KEYS_N_PLAYBACK:transform($0 --playback $VIEW_PLAYLIST {3} {4} {5})+$FZF_RELOAD_PLAYLIST+$FZF_POS_PLAYLIST" \
--bind="$KEYS_PLAYLIST_RELOAD:$FZF_RELOAD_PLAYLIST+$FZF_POS_PLAYLIST" \
--bind="$KEYS_PLAYLIST_RELOAD,$KEYS_SHOW_PLAYLIST:$FZF_RELOAD_PLAYLIST+$FZF_POS_PLAYLIST" \
--bind="$KEYS_PLAYLIST_REMOVE:execute-silent($0 --playlist $PLAYLIST_CMD_REMOVE)+$FZF_RELOAD_PLAYLIST" \
--bind="$KEYS_PLAYLIST_UP:execute-silent($0 --playlist $PLAYLIST_CMD_UP)+up+$FZF_RELOAD_PLAYLIST" \
--bind="$KEYS_PLAYLIST_DOWN:execute-silent($0 --playlist $PLAYLIST_CMD_DOWN)+down+$FZF_RELOAD_PLAYLIST" \
@@ -637,6 +760,11 @@ while true; do
--bind="$KEYS_PLAYLIST_SHUFFLE:execute-silent($0 --playlist $PLAYLIST_CMD_SHUFFLE)+$FZF_RELOAD_PLAYLIST" \
--bind="$KEYS_PLAYLIST_UNSHUFFLE:execute-silent($0 --playlist $PLAYLIST_CMD_UNSHUFFLE)+$FZF_RELOAD_PLAYLIST" \
--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_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" \
--wrap-sign="" \
--delimiter="\t" \
@@ -732,6 +860,9 @@ open \"\$(dirname {5})\"" \
--bind="$KEYS_PREVIEW_CLOSE:hide-preview" \
--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_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" \
--preview-window="$FZF_DEFAULT_PREVIEW_WINDOW" \
--wrap-sign="" \

View File

@@ -115,6 +115,7 @@ awk_releasegroups() {
-v hassecondary_yes="$RGV_FMT_HASSECONDARY_YES" \
-v hassecondary_no="$RGV_FMT_HASSECONDARY_NO" \
-v fmtsecondary="$RGV_FMT_SECONDARY" \
-v secondary_compilation="$RGV_FMT_SECONDARY_COMPILATION" \
-v secondary_soundtrack="$RGV_FMT_SECONDARY_SOUNDTRACK" \
-v secondary_spokenword="$RGV_FMT_SECONDARY_SPOKENWORD" \
-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 }'
}
# 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
#
# This reads from stdin any number of paths (one per line)
@@ -295,3 +307,41 @@ cache_rm_release() {
info "Removing $d from cache"
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

@@ -16,6 +16,10 @@ fzf_command_set_header() {
$JQ '[.id, type, .name, .disambiguation] | join("\t")' |
awk_artist_header
)"
if [ ! "$header" ]; then
header="Possibly $mbid is not a MusicBrainz Artist ID"
err "$header"
fi
;;
"$VIEW_RELEASEGROUP")
header="$(
@@ -30,6 +34,10 @@ fzf_command_set_header() {
] | join("\t")' |
awk_releasegroup_header
)"
if [ ! "$header" ]; then
header="Possibly $mbid is not a MusicBrainz Release-Group ID"
err "$header"
fi
;;
"$VIEW_RELEASE")
header="$(
@@ -48,7 +56,15 @@ fzf_command_set_header() {
] | join("\t")' |
awk_release_header
)"
if [ ! "$header" ]; then
header="Possibly $mbid is not a MusicBrainz Release ID"
err "$header"
fi
;;
*)
header="We entered an unknown state"
err "$header"
;;
esac
printf "+change-header(%s)" "${header:-"???"}"
printf "+change-header(%s)" "$header"
}

View File

@@ -3,7 +3,7 @@ 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"
WINDOW_TITLE="🔎🎶 $APP_NAME | a simple music browser and player"
export APP_NAME APP_VERSION APP_WEBSITE WINDOW_TITLE
export INFO_LOADED=1

View File

@@ -63,6 +63,11 @@
# - KEYS_PREVIEW_TOGGLE_SIZE: Toggle size (small, large) of preview window
# - 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:
# - KEYS_PLAY: Play selected release or selected track
# - KEYS_QUEUE: Queue selected release or selected track
@@ -87,7 +92,10 @@
# - KEYS_PLAYLIST_GOTO_RELEASE: Jump to release or selected entry
# - KEYS_PLAYLIST_STORE: Store current playlist as file
# - KEYS_PLAYLIST_LOAD: Load playlist from file
# - KEYS_PLAYLIST_QUIT: Quit playlist view
#
# Playlist store (here, we don't have a `normal` mode):
# - KEYS_PLAYLISTSTORE_SELECT: Use selected playlist
# - KEYS_PLAYLISTSTORE_DELETE: Delete stored playlist
if [ ! "${KEYS_LOADED:-}" ]; then
# Mode selection:
@@ -103,7 +111,7 @@ if [ ! "${KEYS_LOADED:-}" ]; then
KEYS_N_DOWN="${KEYS_N_DOWN:-"j"}"
KEYS_N_UP="${KEYS_N_UP:-"k"}"
KEYS_N_BOT="${KEYS_N_BOT:-"G"}"
KEYS_N_TOP="${KEYS_N_TOP:-"g"}"
KEYS_N_TOP="${KEYS_N_TOP:-"1,g"}"
export KEYS_DOWN KEYS_UP KEYS_HALFPAGE_DOWN KEYS_HALFPAGE_UP KEYS_N_DOWN \
KEYS_N_UP KEYS_N_BOT KEYS_N_TOP
@@ -162,6 +170,12 @@ if [ ! "${KEYS_LOADED:-}" ]; then
KEYS_PREVIEW_OPEN KEYS_PREVIEW_TOGGLE_WRAP KEYS_PREVIEW_TOGGLE_SIZE \
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:
KEYS_PLAY="${KEYS_PLAY:-"enter"}"
KEYS_QUEUE="${KEYS_QUEUE:-"ctrl-alt-m"}" # That's actually alt-enter
@@ -199,11 +213,17 @@ if [ ! "${KEYS_LOADED:-}" ]; then
KEYS_PLAYLIST_UNSHUFFLE="${KEYS_PLAYLIST_UNSHUFFLE:-"S"}"
KEYS_PLAYLIST_GOTO_RELEASE="${KEYS_PLAYLIST_GOTO_RELEASE:-"ctrl-g"}"
KEYS_PLAYLIST_STORE="${KEYS_PLAYLIST_STORE:-"ctrl-s"}"
KEYS_PLAYLIST_LOAD="${KEYS_PLAYLIST_LOAD:-"ctrl-o"}"
KEYS_PLAYLIST_OPEN_STORE="${KEYS_PLAYLIST_LOAD:-"ctrl-o"}"
KEYS_PLAYLIST_DELETE="${KEYS_PLAYLIST_DELETE:-"del"}"
export KEYS_PLAYLIST_RELOAD KEYS_PLAYLIST_REMOVE KEYS_PLAYLIST_UP \
KEYS_PLAYLIST_DOWN KEYS_PLAYLIST_CLEAR KEYS_PLAYLIST_CLEAR_ABOVE \
KEYS_PLAYLIST_CLEAR_BELOW KEYS_PLAYLIST_SHUFFLE KEYS_PLAYLIST_UNSHUFFLE \
KEYS_PLAYLIST_GOTO_RELEASE KEYS_PLAYLIST_STORE KEYS_PLAYLIST_LOAD
KEYS_PLAYLIST_GOTO_RELEASE KEYS_PLAYLIST_STORE KEYS_PLAYLIST_OPEN_STORE
# Playlist store (here, we don't have a `normal` mode):
KEYS_PLAYLISTSTORE_SELECT="${KEYS_PLAYLISTSTORE_SELECT:-"enter"}"
KEYS_PLAYLISTSTORE_DELETE="${KEYS_PLAYLISTSTORE_DELETE:-"del"}"
export KEYS_PLAYLISTSTORE_SELECT KEYS_PLAYLISTSTORE_DELETE
export KEYS_LOADED=1
fi
@@ -281,7 +301,8 @@ print_keybindings() {
"$KEYS_N_TOP" "Go to first entry" \
"$KEYS_N_BOT" "Go to last entry" \
"$KEYS_OUT,$KEYS_N_OUT,$KEYS_QUIT,$KEYS_N_QUIT" "Leave playlist view" \
"$KEYS_SELECT_ARTIST" "Go to artist of selected item"
"$KEYS_SELECT_ARTIST" "Go to artist of selected item" \
"$KEYS_PLAYLIST_GOTO_RELEASE" "Show release of selected track"
__keybindinggroup_from_args "Views" \
"$KEYS_LIST_ARTISTS" "Display artists in local database" \
"$KEYS_LIST_ALBUMS" "Display albums in local database" \
@@ -297,7 +318,7 @@ print_keybindings() {
"$KEYS_PLAYLIST_CLEAR_BELOW" "Remove all tracks below" \
"$KEYS_PLAYLIST_SHUFFLE" "Shuffle" \
"$KEYS_PLAYLIST_UNSHUFFLE" "Undo shuffle" \
"$KEYS_PLAYLIST_GOTO_RELEASE" "Show release of selected track"
"$KEYS_PLAYLIST_OPEN_STORE" "Manage stored playlists"
__keybindinggroup_from_args "Playback" \
"$KEYS_PLAY,$KEYS_N_PLAY" "Play selected item" \
"$KEYS_QUEUE,$KEYS_N_QUEUE" "Queue selected item" \
@@ -307,12 +328,35 @@ print_keybindings() {
"$KEYS_PLAY_PREV,$KEYS_N_PLAY_PREV" "Play previous track" \
"$KEYS_SEEK_FORWARD,$KEYS_N_SEEK_FORWARD" "Seek forward" \
"$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" \
"$KEYS_BROWSE" "Open selected item in browser" \
"$KEYS_OPEN" "Open selected item in file manager" \
"$KEYS_N_YANK" "Copy MusicBrainz track ID" \
"$KEYS_YANK_CURRENT" "Copy MusicBrainz release ID"
;;
"$VIEW_PLAYLIST_PLAYLISTSTORE")
__keybindinggroup_from_args "Previews" \
"$KEYS_SCROLL_PREVIEW_DOWN" "Scroll preview down" \
"$KEYS_SCROLL_PREVIEW_UP" "Scroll preview up" \
"$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"
__keybindinggroup_from_args "Navigation" \
"$KEYS_DOWN" "Down" \
"$KEYS_UP" "Up" \
"$KEYS_HALFPAGE_DOWN" "Down half a page" \
"$KEYS_HALFPAGE_UP" "Up half a page" \
"$KEYS_OUT,$KEYS_QUIT" "Leave playlist store"
__keybindinggroup_from_args "Playlist store" \
"$KEYS_PLAYLISTSTORE_SELECT" "Load playlist" \
"$KEYS_PLAYLISTSTORE_DELETE" "Delete playlist"
;;
*)
__keybindinggroup_from_args "Switch between modes" \
"$KEYS_I_NORMAL" "Swtich to normal mode (insert)" \
@@ -376,6 +420,10 @@ print_keybindings() {
"$KEYS_N_PLAY_PREV" "Play previous track" \
"$KEYS_N_SEEK_FORWARD" "Seek forward" \
"$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" \
"$KEYS_SHOW_PLAYLIST" "Show playlist" \
"$KEYS_BROWSE" "Open selected item in browser" \

View File

@@ -98,6 +98,7 @@ list_artists_from_json() {
# Print playlist currently loaded
list_playlist() {
count=$(mpv_playlist_count)
[ "$count" ] || return 0
[ "$count" -eq 0 ] && return 0
mpvquery=""
for i in $(seq 0 $((count - 1))); do
@@ -105,3 +106,17 @@ list_playlist() {
done
__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"
rm -f "$tmpj" "$tmpf"
# Ask user if this is ok
info "We discovered the following associatoin:"
info "We discovered the following association:"
while IFS= read -r line; do
t="$(echo "$line" | cut -d "$(printf '\t')" -f 1)"
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
}
# 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
#
# 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
# `LOCALDATA_ARTISTS_LIST` and `LOCALDATA_RELEASEGROUPS_LIST`.
__precompute_lists() {
cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs \
cache_get_file_batch "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS" | xargs -d "\n" \
$JQ '[
.id,
.type,
@@ -239,7 +201,7 @@ __precompute_lists() {
.["life-span"].begin,
.["life-span"].end
] | 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 '[
.id,
."primary-type",
@@ -259,8 +221,6 @@ __precompute_lists() {
precompute_views() {
awk_artists "$SORT_ARTIST_DEFAULT" <"$LOCALDATA_ARTISTS_LIST" >"$LOCALDATA_ARTISTS_VIEW"
awk_releasegroups "$SORT_RG_DEFAULT" <"$LOCALDATA_RELEASEGROUPS_LIST" >"$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
@@ -280,11 +240,11 @@ reloaddb() {
tmpreleases=$(mktemp)
cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" |
tee "$tmpreleases" |
__batch_load_missing "$TYPE_RELEASE"
batch_load_missing "$TYPE_RELEASE"
tmpreleasefiles=$(mktemp)
cache_get_file_batch "$TYPE_RELEASE" <"$tmpreleases" >"$tmpreleasefiles"
(
xargs \
xargs -d "\n" \
$JQ '."release-group".id' \
<"$tmpreleasefiles" >"$LOCALDATA_RELEASEGROUPS"
tf1=$(mktemp)
@@ -292,7 +252,7 @@ reloaddb() {
mv "$tf1" "$LOCALDATA_RELEASEGROUPS"
) &
(
xargs \
xargs -d "\n" \
$JQ '."release-group"."artist-credit" | map(.artist.id) | join("\n")' \
<"$tmpreleasefiles" >"$LOCALDATA_ARTISTS"
tf2=$(mktemp)
@@ -301,8 +261,8 @@ reloaddb() {
) &
wait
rm -f "$tmpreleases" "$tmpreleasefiles"
__batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS"
__batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS"
batch_load_missing "$TYPE_RELEASEGROUP" <"$LOCALDATA_RELEASEGROUPS"
batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS"
__precompute_lists
}
@@ -313,10 +273,8 @@ reloaddb() {
# derivation of the MusicBrainz artist IDs and MusicBrainz release-group IDs
# from the MusicBrainz releases (see the `reloaddb` method above).
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_RELEASE" | xargs ls >/dev/null 2>&1 || return 1
return 0
cached "$TYPE_ARTIST" "$LOCALDATA_ARTISTS" || return 1
cached "$TYPE_RELEASEGROUP" "$LOCALDATA_RELEASEGROUPS" || return 1
}
# Load missing files
@@ -324,7 +282,6 @@ local_files_present() {
# If missing files were detected with `local_files_present`, then these missing
# files may be cached using the present method.
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"
batch_load_missing "$TYPE_ARTIST" <"$LOCALDATA_ARTISTS"
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

@@ -43,51 +43,6 @@ __playback_cmd_from_key() {
case ",$KEYS_N_SEEK_BACKWARD," in *",$key,"*) echo "$PLAYBACK_CMD_SEEK_BACKWARD" && return ;; esac
}
# Generate playlist from MB release ID and path to decoration
#
# @argument $1: MusicBrainz release ID
# @argument $2: Path to decoration file
# @argument $3: MusicBrainz track ID to select (optional)
__generate_playlist() {
printf "#EXTM3U\n"
dir="$(dirname "$2")"
mb_release "$1" |
$JQ \
--slurpfile decofile "$2" \
--arg base "$dir" \
--arg deco "$2" \
--arg tid "${3:-}" \
'$decofile[].tracks as $filenames |
. |
.id as $rid |
.media |
length as $l |
.[] |
.position as $pos |
.tracks |
if ($tid == "") then . else map(select(.id == $tid)) end |
map({
t: [
$rid,
.id,
$l,
$pos,
.number,
.length,
.title,
(."artist-credit" | map([.name, .joinphrase] | join("")) | join("")),
$deco
] | join("\t"),
length: (.length // 0 / 1000 | round | tostring),
$pos,
number: .number,
file: $filenames[.id]
}) |
map(if(.number | type == "string" and test("^[0-9]+$")) then .number |= tonumber else . end) |
sort_by([.pos, .number]) |
map("#EXTINF:" + .length + "," + .t + "\n" + $base + "/" + .file)[]'
}
# Main playback method
#
# @argument $1: view
@@ -122,10 +77,10 @@ playback() {
rpath="$(echo "$line" | cut -d "$(printf '\t')" -f 5)"
[ "$rpath" ] || continue
if [ ! "$queue" ]; then
__generate_playlist "$rmbid" "$rpath" | mpv_play_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_play_list >/dev/null
queue=1
else
__generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
fi
done
queue=1
@@ -138,15 +93,15 @@ playback() {
rpath="$(echo "$line" | cut -d "$(printf '\t')" -f 5)"
[ "$rpath" ] || continue
if [ ! "${queue:-}" ]; then
__generate_playlist "$rmbid" "$rpath" | mpv_play_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_play_list >/dev/null
queue=1
else
__generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
fi
done
;;
"$VIEW_RELEASEGROUP") __generate_playlist "$mbid" "$path" | mpv_play_list >/dev/null ;;
"$VIEW_RELEASE") __generate_playlist "$mbid_current" "$path" "$mbid" | mpv_play_list >/dev/null ;;
"$VIEW_RELEASEGROUP") generate_playlist "$mbid" "$path" | mpv_play_list >/dev/null ;;
"$VIEW_RELEASE") generate_playlist "$mbid_current" "$path" "$mbid" | mpv_play_list >/dev/null ;;
"$VIEW_PLAYLIST") mpv_play_index $((FZF_POS - 1)) >/dev/null ;;
esac
;;
@@ -164,7 +119,7 @@ playback() {
rmbid="$(echo "$line" | cut -d "$(printf '\t')" -f 4)"
rpath="$(echo "$line" | cut -d "$(printf '\t')" -f 5)"
[ "$rpath" ] || continue
__generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
done
done
;;
@@ -174,12 +129,12 @@ playback() {
rmbid="$(echo "$line" | cut -d "$(printf '\t')" -f 4)"
rpath="$(echo "$line" | cut -d "$(printf '\t')" -f 5)"
[ "$rpath" ] || continue
__generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_queue_list >/dev/null
done
;;
"$VIEW_RELEASEGROUP") __generate_playlist "$mbid" "$path" | mpv_queue_list >/dev/null ;;
"$VIEW_RELEASE") __generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_list >/dev/null ;;
"$VIEW_PLAYLIST") __generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_list >/dev/null ;;
"$VIEW_RELEASEGROUP") generate_playlist "$mbid" "$path" | mpv_queue_list >/dev/null ;;
"$VIEW_RELEASE") generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_list >/dev/null ;;
"$VIEW_PLAYLIST") generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_list >/dev/null ;;
esac
;;
"$PLAYBACK_CMD_QUEUE_NEXT")
@@ -196,7 +151,7 @@ playback() {
rmbid="$(echo "$line" | cut -d "$(printf '\t')" -f 4)"
rpath="$(echo "$line" | cut -d "$(printf '\t')" -f 5)"
[ "$rpath" ] || continue
__generate_playlist "$rmbid" "$rpath" | mpv_queue_next_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_queue_next_list >/dev/null
done
done
;;
@@ -206,12 +161,12 @@ playback() {
rmbid="$(echo "$line" | cut -d "$(printf '\t')" -f 4)"
rpath="$(echo "$line" | cut -d "$(printf '\t')" -f 5)"
[ "$rpath" ] || continue
__generate_playlist "$rmbid" "$rpath" | mpv_queue_next_list >/dev/null
generate_playlist "$rmbid" "$rpath" | mpv_queue_next_list >/dev/null
done
;;
"$VIEW_RELEASEGROUP") __generate_playlist "$mbid" "$path" | mpv_queue_next_list >/dev/null ;;
"$VIEW_RELEASE") __generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_next_list >/dev/null ;;
"$VIEW_PLAYLIST") __generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_next_list >/dev/null ;;
"$VIEW_RELEASEGROUP") generate_playlist "$mbid" "$path" | mpv_queue_next_list >/dev/null ;;
"$VIEW_RELEASE") generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_next_list >/dev/null ;;
"$VIEW_PLAYLIST") generate_playlist "$mbid_current" "$path" "$mbid" | mpv_queue_next_list >/dev/null ;;
esac
;;
"$PLAYBACK_CMD_TOGGLE_PLAYBACK") mpv_toggle_pause ;;

View File

@@ -3,6 +3,7 @@
# This files provides an interface to manipulate the playlist. The available
# commands are defined in the following variables.
if [ ! "${PLAYLIST_LOADED:-}" ]; then
# Playlist commands
PLAYLIST_CMD_REMOVE="rm"
PLAYLIST_CMD_UP="up"
PLAYLIST_CMD_DOWN="down"
@@ -11,13 +12,151 @@ if [ ! "${PLAYLIST_LOADED:-}" ]; then
PLAYLIST_CMD_CLEAR_BELOW="clear-below"
PLAYLIST_CMD_SHUFFLE="shuffle"
PLAYLIST_CMD_UNSHUFFLE="unshuffle"
PLAYLIST_CMD_LOAD="load"
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
PLAYLIST_CMD_SHUFFLE PLAYLIST_CMD_UNSHUFFLE PLAYLIST_CMD_LOAD
# Storage and loading of playlists
PLAYLIST_DIRECTORY="${PLAYLIST_DIRECTORY:-"$LOCALDATADIR/playlists"}"
[ -d "$PLAYLIST_DIRECTORY" ] || mkdir -p "$PLAYLIST_DIRECTORY"
export PLAYLIST_DIRECTORY
export PLAYLIST_LOADED=1
fi
# List stored playlists
#
# This prints the names of the stored playlists.
stored_playlists() {
find "$PLAYLIST_DIRECTORY" -mindepth 1 -maxdepth 1 -type f -printf "$PLYSTORE_PLAYLIST\t%f\n" |
sort
}
# Generate playlist from MB release ID and path to decoration
#
# @argument $1: MusicBrainz release ID
# @argument $2: Path to decoration file
# @argument $3: MusicBrainz track ID to select (optional)
generate_playlist() {
printf "#EXTM3U\n"
dir="$(dirname "$2")"
mb_release "$1" |
$JQ \
--slurpfile decofile "$2" \
--arg base "$dir" \
--arg deco "$2" \
--arg tid "${3:-}" \
'$decofile[].tracks as $filenames |
. |
.id as $rid |
.media |
length as $l |
.[] |
.position as $pos |
.tracks |
if ($tid == "") then . else map(select(.id == $tid)) end |
map({
t: [
$rid,
.id,
$l,
$pos,
.number,
.length,
.title,
(."artist-credit" | map([.name, .joinphrase] | join("")) | join("")),
$deco
] | join("\t"),
length: (.length // 0 / 1000 | round | tostring),
$pos,
number: .number,
file: $filenames[.id]
}) |
map(if(.number | type == "string" and test("^[0-9]+$")) then .number |= tonumber else . end) |
sort_by([.pos, .number]) |
map("#EXTINF:" + .length + "," + .t + "\n" + $base + "/" + .file)[]'
}
# Generate m3u playlist from stored playlist
#
# @argument $1: Playlist file
generate_playlist_stored() {
f="${1:-}"
[ -s "$f" ] || return
# Check that we have all releases cached, else fetch missing ones
relf=$(mktemp)
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' \
-v rfile="$LOCALDATA_RELEASES" \
'BEGIN {
OFS="\t"
while ((getline < rfile) == 1)
release[$1] = $2
close(rfile)
print "["
}
NR > 1 { print "," }
{ print "{\"rid\":\"" $1 "\",\"tid\":\"" $2 "\",\"deco\":\"" (release[$1] ? release[$1] : "") "\"}" }
END {print "]"}' <"$f" >"$jpf"
$JQ 'map(.deco) | join("\n")' "$jpf" |
grep '.' |
xargs -d '\n' cat >"$jdecf"
# Merge all data using jq and print playlist
printf "#EXTM3U\n"
$JQ \
--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
#
# @argument $1: playlist command
@@ -43,5 +182,10 @@ playlist() {
;;
"$PLAYLIST_CMD_SHUFFLE") mpv_playlist_shuffle ;;
"$PLAYLIST_CMD_UNSHUFFLE") mpv_playlist_unshuffle ;;
"$PLAYLIST_CMD_LOAD")
f="$PLAYLIST_DIRECTORY/${2:-}"
[ -s "$f" ] || return
generate_playlist_stored "$f" | mpv_play_list >/dev/null
;;
esac
}

View File

@@ -19,6 +19,7 @@ if [ ! "${THEME_LOADED:-}" ]; then
CNOTE="${ESC}[38;5;242m"
CXXX="${ESC}[38;5;109m"
CDESC="${ESC}[38;5;254m"
CPURPLE="${ESC}[38;5;213m"
CKB="${ESC}[38;5;224m"
OFF="${ESC}[m"
@@ -99,6 +100,7 @@ if [ ! "${THEME_LOADED:-}" ]; then
RGV_FMT_HASSECONDARY_YES="${RGV_FMT_HASSECONDARY_YES:-"☼"}"
RGV_FMT_HASSECONDARY_NO="${RGV_FMT_HASSECONDARY_NO:-""}"
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_SPOKENWORD="${RGV_FMT_SECONDARY_SPOKENWORD:-"📖 spokenword"}"
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_TYPE_EP RGV_FMT_TYPE_BROADCAST RGV_FMT_TYPE_OTHER \
RGV_FMT_TYPE_UNKNOWN RGV_FMT_HASSECONDARY_YES RGV_FMT_HASSECONDARY_NO \
RGV_FMT_SECONDARY RGV_FMT_SECONDARY_SOUNDTRACK \
RGV_FMT_SECONDARY_SPOKENWORD RGV_FMT_SECONDARY_INTERVIEW \
RGV_FMT_SECONDARY_AUDIOBOOK RGV_FMT_SECONDARY_AUDIODRAMA \
RGV_FMT_SECONDARY_LIVE RGV_FMT_SECONDARY_REMIX RGV_FMT_SECONDARY_DJMIX \
RGV_FMT_SECONDARY_MIXTAPE RGV_FMT_SECONDARY_DEMO \
RGV_FMT_SECONDARY_FIELDREC RGV_FMT_TITLE RGV_FMT_ARTIST RGV_FMT_YEAR
RGV_FMT_SECONDARY RGV_FMT_SECONDARY_COMPILATION \
RGV_FMT_SECONDARY_SOUNDTRACK RGV_FMT_SECONDARY_SPOKENWORD \
RGV_FMT_SECONDARY_INTERVIEW RGV_FMT_SECONDARY_AUDIOBOOK \
RGV_FMT_SECONDARY_AUDIODRAMA RGV_FMT_SECONDARY_LIVE \
RGV_FMT_SECONDARY_REMIX RGV_FMT_SECONDARY_DJMIX \
RGV_FMT_SECONDARY_MIXTAPE RGV_FMT_SECONDARY_DEMO RGV_FMT_SECONDARY_FIELDREC \
RGV_FMT_TITLE RGV_FMT_ARTIST RGV_FMT_YEAR
# Release view
# ============
@@ -172,7 +175,7 @@ if [ ! "${THEME_LOADED:-}" ]; then
# <<duration>> string Track 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_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_NO_LOCAL="${REC_FMT_FLAG_NO_LOCAL:-""}"
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_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_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_SPOKENWORD="${HEADER_RG_FMT_SECONDARY_SPOKENWORD:-"$RGV_FMT_SECONDARY_SPOKENWORD"}"
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_UNKNOWN HEADER_RG_FMT_HASSECONDARY_YES \
HEADER_RG_FMT_HASSECONDARY_NO HEADER_RG_FMT_SECONDARY \
HEADER_RG_FMT_SECONDARY_SOUNDTRACK HEADER_RG_FMT_SECONDARY_SPOKENWORD \
HEADER_RG_FMT_SECONDARY_INTERVIEW HEADER_RG_FMT_SECONDARY_AUDIOBOOK \
HEADER_RG_FMT_SECONDARY_AUDIODRAMA HEADER_RG_FMT_SECONDARY_LIVE \
HEADER_RG_FMT_SECONDARY_REMIX HEADER_RG_FMT_SECONDARY_DJMIX \
HEADER_RG_FMT_SECONDARY_MIXTAPE HEADER_RG_FMT_SECONDARY_DEMO \
HEADER_RG_FMT_SECONDARY_FIELDREC HEADER_RG_FMT_TITLE HEADER_RG_FMT_ARTIST \
HEADER_RG_FMT_YEAR
HEADER_RG_FMT_SECONDARY_COMPILATION HEADER_RG_FMT_SECONDARY_SOUNDTRACK \
HEADER_RG_FMT_SECONDARY_SPOKENWORD HEADER_RG_FMT_SECONDARY_INTERVIEW \
HEADER_RG_FMT_SECONDARY_AUDIOBOOK HEADER_RG_FMT_SECONDARY_AUDIODRAMA \
HEADER_RG_FMT_SECONDARY_LIVE HEADER_RG_FMT_SECONDARY_REMIX \
HEADER_RG_FMT_SECONDARY_DJMIX HEADER_RG_FMT_SECONDARY_MIXTAPE \
HEADER_RG_FMT_SECONDARY_DEMO HEADER_RG_FMT_SECONDARY_FIELDREC \
HEADER_RG_FMT_TITLE HEADER_RG_FMT_ARTIST HEADER_RG_FMT_YEAR
# 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>>"}"
@@ -387,10 +391,18 @@ if [ ! "${THEME_LOADED:-}" ]; then
KBF_DESC="${KBF_DESC:-"${CKB}%s${OFF}"}"
export KBF_GROUP KBF_KEY KBF_DESC
# Playlist title
# ==============
# Playlist title and playlist store
# =================================
TITLE_PLYLST="${TITLE_PLYLST:-" 🎶 ${CARTIST}Playlist${OFF} "}"
export TITLE_PLYLST
TITLE_PLYLST_STORE="${TITLE_PLYLST_STORE:-" 🎶 ${CARTIST}Stored Playlists${OFF} "}"
# 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
fi

View File

@@ -55,5 +55,25 @@ if [ ! "${TOOLS_LOADED:-}" ]; then
command -v "xsel" >/dev/null && CLIP="xsel" || CLIP="true"
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
fi