Compare commits
20 Commits
076e3c0c5e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ed12cef7e4 | |||
| 06485fbce7 | |||
| 214a1cda4c | |||
| d13436b963 | |||
| 4343521a46 | |||
| 6db95d3feb | |||
| a0f8e79665 | |||
| fde3b6edcb | |||
| c0463c0d5e | |||
| 3b66faed6f | |||
| 13f40e9720 | |||
| 2bf0506351 | |||
| 57ed2f6d0e | |||
| 6bfb7f0399 | |||
| 05661c0d98 | |||
| 08ea6cf56c | |||
| a4cb7d5c72 | |||
| 8c67085f05 | |||
| 11446031bf | |||
| 1c4611e833 |
134
README.md
134
README.md
@@ -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
|
||||
-------------
|
||||

|
||||
### 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>`.
|
||||

|
||||
|
||||
### 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 `;`.
|
||||

|
||||
|
||||
### Playlist Control
|
||||
You can easily control playback and edit the playlist in the playlist view.
|
||||

|
||||
|
||||
🛠️ 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 l’hémoglobine' File './06 La Concubine de l’hémoglobine.m4a'
|
||||
Track 'Dévotion' File './07 Dévotion.m4a'
|
||||
Track 'Temps mort' File './08 Temps mort.m4a'
|
||||
Track 'L’NMIACCd’HTCK72KPDP' File './09 L’NMIACCd’HTCK72KPDP.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
BIN
screenshots/browse.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
BIN
screenshots/playlist.png
Normal file
BIN
screenshots/playlist.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
screenshots/search.png
Normal file
BIN
screenshots/search.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
@@ -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
25
share/lyrics/example.sh
Executable 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
7
share/playlists/example
Normal 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
|
||||
@@ -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 = "??:??"
|
||||
|
||||
193
src/main.sh
193
src/main.sh
@@ -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="" \
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
121
src/sh/lyrics.sh
Normal 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"
|
||||
}
|
||||
@@ -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 ;;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user