-
Notifications
You must be signed in to change notification settings - Fork 2
/
consult-emms-embark.el
373 lines (302 loc) · 14.7 KB
/
consult-emms-embark.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
;;; consult-emms-embark.el --- Embark actions for consult-emms
;; Author: Hugo Heagren <hugo@heagren.com>
;; Version: 0.1
;; Package-Requires: ((consult-emms) (embark))
;; Keywords: consult, emms, embark
;;; Code:
(require 'emms)
(require 'consult-emms)
(require 'embark)
;;;; Tracks
(defun consult-emms-embark--add-track-playlist (track-name)
"Choose an EMMS playlist to add track TRACK-NAME to."
(let ((file (consult-emms--track-name-get track-name 'name)))
(consult-emms--with-chosen-current-playlist
(emms-add-file file))))
(defun consult-emms-embark--track-goto-album (track-name)
"Select a track from the album to which TRACK-NAME belongs.
Selected track is added to the current playlist."
(let* ((album (consult-emms--track-name-get track-name 'info-album)))
(consult-emms--choose-track-album album)))
(defun consult-emms-embark--track-goto-artist (track-name)
"Select a track by TRACK-NAME's artist.
Selected track is added to the current playlist."
(let* ((artist (consult-emms--track-name-get track-name 'info-artist)))
(consult-emms--choose-track-or-album-artist artist)))
(defun consult-emms-embark--track-goto-genre (track-name)
"Select a track from the genre to which TRACK-NAME belongs.
Selected track is added to the current playlist."
(let* ((genre (consult-emms--track-name-get track-name 'info-genre)))
(consult-emms--choose-track-or-album-genre genre)))
(defun consult-emms-embark--track-add-album (track-name)
"Add album to which TRACK-NAME belongs to current playlist."
(let* ((album (consult-emms--track-name-get track-name 'info-album)))
(consult-emms--add-album album)))
(defun consult-emms-embark--track-add-artist (track-name)
"Add TRACK-NAME's artist to current playlist."
(let* ((artist (consult-emms--track-name-get track-name 'info-artist)))
(consult-emms--add-artist artist)))
(defun consult-emms-embark--track-add-genre (track-name)
"Add TRACK-NAME's genre to current playlist."
(let* ((genre (consult-emms--track-name-get track-name 'info-genre)))
(consult-emms--add-genre genre)))
(defun consult-emms-embark--edit-track-tags (track-name)
"Edit TRACK-NAME's tags in EMMS' tag editor."
(let* ((key (get-text-property 0 'consult-emms-track-key track-name))
(track (gethash key emms-cache-db)))
(emms-tag-editor-edit-track track)))
(embark-define-keymap consult-emms-embark-track-actions
"Keymap for actions on tracks in `consult-emms'."
("p" '("Add to playlist" . consult-emms-embark--add-track-playlist))
("g" '("Goto..." . consult-emms-embark-track-goto))
("a" '("Add... " . consult-emms-embark-track-add))
("e" '("Edit tags" . consult-emms-embark--edit-track-tags)))
(defvar consult-emms-embark-track-goto
(let ((map (make-sparse-keymap)))
(keymap-set map "a" '("Artist" . consult-emms-embark--track-goto-artist))
(keymap-set map "b" '("Album" . consult-emms-embark--track-goto-album))
(keymap-set map "g" '("Genre" . consult-emms-embark--track-goto-genre))
map)
"Keymap for actions moving from a track to an associated entity.")
(fset 'consult-emms-embark-track-goto consult-emms-embark-track-goto)
(defvar consult-emms-embark-track-add
(let ((map (make-sparse-keymap)))
(keymap-set map "a" '("Artist" . consult-emms-embark--track-add-artist))
(keymap-set map "b" '("Album" . consult-emms-embark--track-add-album))
(keymap-set map "g" '("Genre" . consult-emms-embark--track-add-genre))
map)
"Keymap for actions queuing track-associated entities.")
(fset 'consult-emms-embark-track-add consult-emms-embark-track-add)
(add-to-list 'embark-keymap-alist '(track . consult-emms-embark-track-actions))
;;;;; Playlist Tracks
;; (tracks which have a position on a playlist)
(defun consult-emms-embark--kill-playlist-track (track-name)
(consult-emms--do-playlist-track
track-name (emms-playlist-mode-kill-entire-track)))
(embark-define-keymap consult-emms-embark-playlist-track-actions
"Keymap for actions on tracks in playlists in `consult-emms'."
:parent consult-emms-embark-track-actions
("k" '("Kill track" . consult-emms-embark--kill-playlist-track)))
(add-to-list 'embark-keymap-alist '(playlist-track . consult-emms-embark-playlist-track-actions))
;;;; Albums
(defun consult-emms-embark--add-album-playlist (album-name)
"Choose an EMMS playlist to add album ALBUM-NAME to."
(consult-emms--with-chosen-current-playlist
(consult-emms--add-album album-name)))
(defun consult-emms-embark--album-goto-artist (album)
"Select a track by ALBUM's artist.
Selected track is added to the current playlist."
;; All the tracks will have the same album-artist, so we just check
;; the first one
(let* ((any-track (car (consult-emms--get-album-tracks album)))
;; If there is an explicit 'albumartist' tag, use that. If
;; not (lots of files are not very well tagged), default to
;; the artist of the song.
(artist (or
(emms-track-get (gethash any-track emms-cache-db) 'info-albumartist)
(emms-track-get (gethash any-track emms-cache-db) 'info-artist))))
(consult-emms--choose-track-or-album-artist artist)))
(defun consult-emms-embark--album-goto-genre (album)
"Select a track or album in ALBUM's genre.
The first song in ALBUM is examined. Selection is added to the
current playlist."
(let* ((any-track (car (consult-emms--get-album-tracks album)))
(genre (emms-track-get (gethash any-track emms-cache-db) 'info-genre)))
(consult-emms--choose-track-or-album-genre genre)))
;; NOTE This blatantly copies the structure of the above function, but
;; two uses really enough to justify abstracting it out.
(defun consult-emms-embark--album-add-artist (album)
"Add all tracks by ALBUM's artist to current playlist.
The first song in ALBUM is examined. If it has an `albumartist'
tag, that value is used, otherwise use the value of `artist'."
;; All the tracks will have the same album-artist, so we just check
;; the first one
(let* ((any-track (car (consult-emms--get-album-tracks album)))
;; If there is an explicit 'albumartist' tag, use that. If
;; not (lots of files are not very well tagged), default to
;; the artist of the song.
(artist (or
(emms-track-get (gethash any-track emms-cache-db) 'info-albumartist)
(emms-track-get (gethash any-track emms-cache-db) 'info-artist))))
(consult-emms--add-artist artist)))
(defun consult-emms-embark--album-add-genre (album)
"Add all tracks of ALBUM's genre to current playlist.
The first song in ALBUM is examined for its `genre' tag."
;; Tracks in albums generally have the same genre, so just assume
;; the first track is a good guide. If the user wants the genre of a
;; SPECIFIC track, they can just navigate to that track and use the
;; genre controls from there.
(let* ((any-track (car (consult-emms--get-album-tracks album)))
(genre (emms-track-get (gethash any-track emms-cache-db) 'info-genre)))
(consult-emms--add-genre genre)))
(embark-define-keymap consult-emms-embark-album-actions
"Keymap for actions on albums in `consult-emms'."
("p" '("Add to playlist". consult-emms-embark--add-album-playlist))
("g" '("Goto..." . consult-emms-embark-album-goto))
("a" '("Add..." . consult-emms-embark-album-add)))
(defvar consult-emms-embark-album-goto
(let ((map (make-sparse-keymap)))
(keymap-set map "a" '("Artist" . consult-emms-embark--album-goto-artist))
(keymap-set map "b" '("Album" . consult-emms--choose-track-album))
(keymap-set map "g" '("Genre" . consult-emms-embark--album-goto-genre))
map)
"Keymap for actions moving from a track to an associated entity.")
(fset 'consult-emms-embark-album-goto consult-emms-embark-album-goto)
(defvar consult-emms-embark-album-add
(let ((map (make-sparse-keymap)))
(keymap-set map "a" '("Artist" . consult-emms-embark--album-add-artist))
(keymap-set map "g" '("Genre" . consult-emms-embark--album-add-genre))
map)
"Keymap for actions queuing an album-associated entity.")
(fset 'consult-emms-embark-album-add consult-emms-embark-album-add)
(add-to-list 'embark-keymap-alist '(album . consult-emms-embark-album-actions))
;;;; Artists
(defun consult-emms-embark--add-artist-playlist (artist-name)
"Choose an EMMS playlist to add artist ARTIST-NAME to."
(consult-emms--with-chosen-current-playlist
(consult-emms--add-artist artist-name)))
(embark-define-keymap consult-emms-embark-artist-actions
"Keymap for actions on artists in `consult-emms'."
("p" '("Add to playlist". consult-emms-embark--add-artist-playlist))
("g" '("Goto...". consult-emms-embark-artist-goto)))
(defvar consult-emms-embark-artist-goto
(let ((map (make-sparse-keymap)))
(keymap-set map "a" '("Artist" . consult-emms--choose-track-or-album-artist))
map)
"Keymap for actions moving from a track to an associated entity.")
(fset 'consult-emms-embark-artist-goto consult-emms-embark-artist-goto)
(add-to-list 'embark-keymap-alist '(artist . consult-emms-embark-artist-actions))
;;;; Playlists
(defun consult-emms-embark--write-playlist (playlist)
"Write PLAYLIST to a file (prompts for filename)."
;; If the playlist is the current buffer, EMMS won't raise
;; exceptions about the buffer not being current (which the user
;; likely knows already if they are using `consult-emms'!)
(with-current-buffer playlist
(consult-emms--with-current-playlist
playlist (call-interactively 'emms-playlist-save))))
(defun consult-emms-embark--clear-playlist (playlist-name)
"Clear playlist in buffer PLAYLIST-NAME."
(with-current-buffer playlist-name
(emms-playlist-clear)))
(defun consult-emms-embark--shuffle-playlist (playlist-name)
"Shuffle playlist in buffer PLAYLIST-NAME."
(with-current-buffer playlist-name (emms-shuffle)))
(defun consult-emms-embark--insert-playlist (playlist-name)
"Append playlist in buffer PLAYLIST-NAME to another playlist."
(let ((new-playlist (consult-emms--choose-buffer)))
(with-current-buffer new-playlist
(let ((inhibit-read-only t))
(save-excursion
(goto-char (point-max))
(insert-buffer playlist-name))))))
(defun consult-emms-embark--playlist-set-active (playlist-name)
"Make buffer PLAYLIST-NAME the current/active EMMS playlist."
(emms-playlist-set-playlist-buffer playlist-name))
(embark-define-keymap consult-emms-embark-playlist-actions
"Keymap for actions on playlists in `consult-emms'."
:parent embark-buffer-map
("W" '("Write to file" . consult-emms-embark--write-playlist))
("c" '("Clear playlist" . consult-emms-embark--clear-playlist))
("s" '("Shuffle playlist" . consult-emms-embark--shuffle-playlist))
("i" '("Insert into playlist" . consult-emms-embark--insert-playlist))
("a" '("Make active/current playlist" . consult-emms-embark--playlist-set-active)))
(add-to-list 'embark-keymap-alist '(playlist . consult-emms-embark-playlist-actions))
;;;; Genre
(defun consult-emms-embark--add-genre-playlist (genre-name)
"Choose an EMMS playlist to add genre GENRE-NAME to."
(consult-emms--with-chosen-current-playlist
(consult-emms--add-genre genre-name)))
(defun consult-emms--choose-track-or-album-genre (genre)
"Choose a track or album from those in GENRE.
The selected item is added to the current playlist.
The two lists are presented with `consult--multi'. The track list
is built with `consult-emms--get-genre-tracks', and the album
list is generated by extracting the album names of each track in
the track list."
(let* (;; Tracks
(tracks-list
(mapcar #'consult-emms--propertize-track-title
(consult-emms--get-genre-tracks genre)))
(tracks-source (plist-put
(purecopy consult-emms--source-track)
:items tracks-list))
;; Albums
(albums-list
(mapcar (lambda (trk) (consult-emms--track-name-get trk 'info-album))
tracks-list))
(albums-source (plist-put
(purecopy consult-emms--source-album)
:items albums-list)))
(consult--multi `(,tracks-source ,albums-source)
:require-match t
:prompt (format "%s: " genre))))
(embark-define-keymap consult-emms-embark-genre-actions
"Keymap for actions on genres in `consult-emms'."
("p" '("Add to playlist". consult-emms-embark--add-genre-playlist))
("g" '("Goto...". consult-emms-embark-genre-goto)))
(defvar consult-emms-embark-genre-goto
(let ((map (make-sparse-keymap)))
(keymap-set map "g" '("Genre" . consult-emms--choose-track-or-album-genre))
map)
"Keymap for actions moving from a track to an associated entity.")
(fset 'consult-emms-embark-genre-goto consult-emms-embark-genre-goto)
(add-to-list 'embark-keymap-alist '(genre . consult-emms-embark-genre-actions))
;;;; Streams
(defun consult-emms-embark--add-stream-playlist (stream-name)
"Choose an EMMS playlist to add stream STREAM-NAME to."
(consult-emms--with-chosen-current-playlist
(consult-emms--add-stream stream-name)))
(embark-define-keymap consult-emms-embark-stream-actions
"Keymap for actions on streams in `consult-emms'."
("p" '("Add to playlist" . consult-emms-embark--add-stream-playlist)))
(add-to-list 'embark-keymap-alist '(stream . consult-emms-embark-stream-actions))
;;;; EMMS Buffer Embark Targets
(defun consult-emms-embark-identify-music-at-point ()
"Identify musical object at point and its type.
If an EMMS track, artist or album is at point, return a list of
the form (TYPE ID BEG . END), where:
- TYPE is the relevant completion type, as recognised by embark and
consult ('album, 'track, etc.)
- ID is an identifier for the thing, formatted appropriately for its
type
- BEG is the position of the BOL
- END is the position of the EOL."
(when-let ((data (emms-browser-bdata-at-point))
(type-indicator
(assoc-default 'type data))
(type (assoc-default
type-indicator
'((info-title . track)
(info-album . album)
(info-artist . artist))))
(str (pcase type
('track (consult-emms--propertize-track-title
(assoc-default
'name
(car (assoc-default 'data data)))))
((or album artist) (assoc-default 'name data)))))
`(,type ,str ,(line-beginning-position) . ,(line-end-position))))
(add-to-list
'embark-target-finders
'consult-emms-embark-identify-music-at-point)
(defun consult-emms-embark-identify-playlist-at-point ()
"Identify EMMS playlist at point.
In an EMMS metaplaylist buffer, if there is a name of a buffer at
point, return a list `(playlist BUFFER-NAME BEG END)' where:
- BUFFER-NAME is the name of the buffer
- BEG is the position of the BOL
- END is the position of the EOL."
(when-let* (((eq major-mode 'emms-metaplaylist-mode))
(bol (line-beginning-position))
(eol (line-end-position))
(buffer-name (buffer-substring bol eol))
;; This effectively acts a check to see if there really
;; IS a buffer with the name `buffer-name'
((get-buffer buffer-name)))
`(playlist ,buffer-name ,bol . ,eol)))
(add-to-list
'embark-target-finders
'consult-emms-embark-identify-playlist-at-point)
(provide 'consult-emms-embark)
;;; consult-emms-embark.el ends here