fordon_greeman / voidfiles

Branch: main
Switch branches/tags
main
theidioticdev add emacs 2079ec5 1d ago
2229 lines · 82.8 KB Raw Blame History
1 ;;; emms-browser.el --- a track browser supporting covers and filtering -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2006-2025 Free Software Foundation, Inc.
4
5 ;; Author: Damien Elmes <emacs@repose.cx>
6 ;; Keywords: emms, mp3, mpeg, multimedia
7
8 ;; This file is part of EMMS.
9
10 ;; EMMS is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 3, or (at your option)
13 ;; any later version.
14
15 ;; EMMS is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with EMMS; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
24
25 ;;; Commentary:
26
27 ;; This code allows you to browse the metadata cache and add tracks to
28 ;; your playlist. To be properly useful, you should M-x
29 ;; emms-add-directory-tree to all the files you own at least once so
30 ;; that the cache is fully populated.
31
32 ;; Usage
33 ;; -------------------------------------------------------------------
34
35 ;; To use, run (emms-all) and then bind `emms-smart-browse' to a key,
36 ;; like:
37
38 ;; (global-set-key (kbd "<f2>") 'emms-smart-browse)
39
40 ;; The 'smart browsing' code attempts to link the browser and playlist
41 ;; windows together, so that closing one will close both. Activating
42 ;; it will toggle between three states:
43
44 ;; a) both windows displayed, with the browser focused
45 ;; b) focus switched to the playlist window
46 ;; c) the extra window closed, and both buffers buried
47
48 ;; If you just want access to the browser, try M-x
49 ;; emms-browse-by-TYPE, where TYPE is one of artist, album, composer,
50 ;; genre or year. These commands can also be used while smart browsing to
51 ;; change the browsing category.
52
53 ;; If you don't want to activate the code with (emms-devel), you can
54 ;; activate it manually with:
55
56 ;; (require 'emms-browser)
57
58 ;; Searching and filtering.
59 ;; -------------------------------------------------------------------
60 ;; See Emms-filters
61
62 ;; Displaying covers
63 ;; -------------------------------------------------------------------
64
65 ;; The browser will attempt to display cover images if they're
66 ;; available. By default it looks for images cover_small.jpg,
67 ;; cover_med.jpg, etc. Customize emms-browser-covers to use your own
68 ;; covers. Note that you'll probably want to resize your existing
69 ;; covers to particular sizes. Suggested sizes are 100x100 for small,
70 ;; and 200x200 for medium.
71
72 ;; Also emacs by default will jump around a lot when scrolling a
73 ;; buffer with images. Set the following variables to prevent that:
74
75 ;; scroll-up-aggressively 0.0
76 ;; scroll-down-aggressively 0.0
77
78 ;; To show a 'no cover' image for albums which don't have a cover, add
79 ;; the following code to your .emacs:
80
81 ;; (setq emms-browser-default-covers
82 ;; (list "/path/to/cover_small.jpg" nil nil)
83
84 ;; (the medium and large images can be set too, if you want)
85
86 ;; You can download an example 'no cover' image from:
87 ;; http://repose.cx/cover_small.jpg
88
89 ;; Changing tree structure
90 ;; -------------------------------------------------------------------
91
92 ;; You can change the way the tree is displayed by modifying
93 ;; `emms-browser-next-mapping-type'. The following code displays
94 ;; artist->track instead of artist->album->track when you switch to
95 ;; the 'singles' filter.
96
97 ;; (advice-add 'emms-browser-next-mapping-type :around #'my-emms--types)
98 ;; (defun my-emms--types (orig-fun &rest args)
99 ;; (let ((type (apply orig-fun args)))
100 ;; (if (eq type 'info-album)
101 ;; 'info-title)
102 ;; type)))
103
104 ;; (defun toggle-album-display ()
105 ;; (if (string= emms-filters-current-filter-name "singles")
106 ;; (ad-activate 'emms-browser-next-mapping-type)
107 ;; (ad-deactivate 'emms-browser-next-mapping-type)))
108
109 ;; (add-hook 'emms-filters-filter-changed-hook 'toggle-album-display)
110
111 ;; Changing display format
112 ;; -------------------------------------------------------------------
113
114 ;; Format strings govern the way items are displayed in the browser
115 ;; and playlist. You can customize these if you wish.
116
117 ;; `emms-browser-default-format' controls the format to use when no
118 ;; other format has been explicitly defined. By default, only track and
119 ;; albums deviate from the default.
120
121 ;; To customise the format of a particular type, find the name of the
122 ;; field you want to use (eg `info-artist', `info-title', etc), and
123 ;; insert that into emms-browser-<type>-format or
124 ;; emms-browser-playlist-<type>-format. For example, if you wanted to
125 ;; remove track numbers from tracks in both the browser and playlist,
126 ;; you could do:
127
128 ;; (defvar emms-browser-info-title-format "%i%n")
129 ;; (defvar emms-browser-playlist-info-title-format
130 ;; emms-browser-info-title-format)
131
132 ;; The format specifiers available include:
133
134 ;; %i indent relative to the current level
135 ;; %n the value of the item - eg -info-artist might be "pink floyd"
136 ;; %y the album year
137 ;; %A the album name
138 ;; %a the artist name of the track
139 ;; %o the album artist
140 ;; %C the composer name of the track
141 ;; %p the performer name of the track
142 ;; %g the genre of the track.
143 ;; %t the title of the track
144 ;; %T the track number
145 ;; %cS a small album cover
146 ;; %cM a medium album cover
147 ;; %cL a big album cover
148
149 ;; Note that if you use track-related items like %t, it will take the
150 ;; data from the first track.
151
152 ;; Changing display faces
153 ;; -------------------------------------------------------------------
154
155 ;; The faces used to display the various fields are also customizable.
156 ;; They are in the format emms-browser-<type>-face, where type is one
157 ;; of "year/genre", "artist", "composer", "performer", "album" or
158 ;; "track". Note that faces lack the initial "info-" part. For example,
159 ;; to change the artist face, type
160 ;; M-x customize-face emms-browser-artist-face.
161
162 ;; Deleting files
163 ;; -------------------------------------------------------------------
164
165 ;; You can use the browser to delete tracks from your hard disk.
166 ;; Because this is dangerous, it is disabled by default.
167
168 ;; The following code will delete covers at the same time, and remove
169 ;; parent directories if they're now empty.
170
171 ;; (defun de-kill-covers-and-parents (dir tracks)
172 ;; (when (> (length tracks) 1)
173 ;; ;; if we're not deleting an individual file, delete covers too
174 ;; (dolist (cover '("cover.jpg"
175 ;; "cover_med.jpg"
176 ;; "cover_small.jpg"
177 ;; "folder.jpg"))
178 ;; (condition-case nil
179 ;; (delete-file (concat dir cover))
180 ;; (error nil)))
181 ;; ;; try and delete empty parents - we actually do the work of the
182 ;; ;; calling function here, too
183 ;; (let (failed)
184 ;; (while (and (not (string= dir "/"))
185 ;; (not failed))
186 ;; (condition-case nil
187 ;; (delete-directory dir)
188 ;; (error (setq failed t)))
189 ;; (setq dir (file-name-directory (directory-file-name dir)))))))
190 ;; (add-hook 'emms-browser-delete-files-hook 'de-kill-covers-and-parents)
191
192 ;;; Code:
193
194 (require 'emms)
195 (require 'emms-cache)
196 (require 'emms-volume)
197 (require 'emms-source-file)
198 (require 'emms-playlist-sort)
199 (require 'sort)
200 (require 'seq)
201 (require 'emms-filters)
202 (require 'emms-cache)
203
204
205 ;; --------------------------------------------------
206 ;; Variables and configuration
207 ;; --------------------------------------------------
208
209 (defvar emms-browser-mode-hook nil
210 "Emms browser mode hook.")
211
212 (defgroup emms-browser nil
213 "*The Emacs Multimedia System browser"
214 :prefix "emms-browser-"
215 :group 'multimedia
216 :group 'applications)
217
218 (defcustom emms-browser-default-browse-type
219 'info-artist
220 "The default browsing mode."
221 :type 'function)
222
223 (defcustom emms-browser-get-track-field-function
224 #'emms-browser-get-track-field-albumartist
225 "A function to get an element from a track.
226 Change this to customize the way data is organized in the
227 browser. For example,
228 `emms-browser-get-track-field-use-directory-name' uses the
229 directory name to determine the artist. This means that
230 soundtracks, compilations and so on don't populate the artist
231 view with lots of 1-track elements."
232 :type '(choice (function :tag "Sort by album-artist" emms-browser-get-track-field-albumartist)
233 (function :tag "Simple" emms-browser-get-track-field-simple)))
234
235 (defcustom emms-browser-covers
236 '("cover_small" "cover_med" "cover_large")
237 "Control how cover images are found.
238 Can be either a list of small, medium and large images (large
239 currently not used), a function which takes a directory and one
240 of the symbols `small', `medium' or `large', and should return a
241 path to the cover, or nil to turn off cover loading."
242 :type '(choice (list :tag "Cover image names"
243 (string :tag "Small cover")
244 (string :tag "Medium cover")
245 (string :tag "Large cover"))
246 function
247 (const :tag "No cover loading")))
248
249 (defcustom emms-browser-covers-file-extensions
250 '("jpg" "jpeg" "png" "gif" "bmp")
251 "File extensions accepted for `emms-browser-covers'.
252 Should be a list of extensions as strings. Should be set before
253 emms-browser is required."
254 :type '(repeat (string :tag "Extension")))
255
256 (defconst emms-browser--covers-filename nil
257 "*List of potential cover art names.")
258
259 (defcustom emms-browser-default-covers nil
260 "A list of default images to use if a cover isn't found."
261 :type '(choice (const :tag "No defaults" nil)
262 (list (radio :tag "Small cover" (file :must-match t)
263 (const :tag "No default" nil))
264 (radio :tag "Medium cover" (file :must-match t)
265 (const :tag "No default" nil))
266 (radio :tag "Large cover" (file :must-match t)
267 (const :tag "No default" nil)))))
268
269 (defcustom emms-browser-comparison-test
270 (if (fboundp 'define-hash-table-test)
271 'case-fold
272 'equal)
273 "A method for comparing entries in the cache.
274 The default is to compare case-insensitively."
275 :type 'symbol)
276
277 (defcustom emms-browser-track-sort-function
278 #'emms-sort-natural-order-less-p
279 "How to sort tracks in the browser.
280 Ues nil for no sorting."
281 :type 'function)
282
283 (defcustom emms-browser-alpha-sort-function
284 (if (functionp 'string-collate-lessp) #'string-collate-lessp #'string<)
285 "How to sort artists/albums/etc. in the browser.
286 Use nil for no sorting."
287 :type 'function)
288
289 (defcustom emms-browser-album-sort-function
290 #'emms-browser-sort-by-year-or-name
291 "How to sort artists/albums/etc. in the browser.
292 Use nil for no sorting."
293 :type 'function)
294
295 (defcustom emms-browser-show-display-hook nil
296 "Hooks to run when starting or switching to a browser buffer."
297 :type 'hook)
298
299 (defcustom emms-browser-hide-display-hook nil
300 "Hooks to run when burying or removing a browser buffer."
301 :type 'hook)
302
303 (defcustom emms-browser-tracks-added-hook nil
304 "Hooks to run when tracks are added to the playlist."
305 :type 'hook)
306
307 (defcustom emms-browser-filter-tracks-hook nil
308 "Given a track, return t if the track should be ignored."
309 :type 'hook)
310
311 ;; Deprecated. See emms-filters-filter-changed-hook.
312 (defcustom emms-browser-filter-changed-hook nil
313 "Hook run after the filter has changed."
314 :type 'hook)
315
316 (defcustom emms-browser-delete-files-hook nil
317 "Hook run after files have been deleted.
318 This hook can be used to clean up extra files, such as album covers.
319 Called once for each directory."
320 :type 'hook)
321
322 (defvar emms-browser-buffer nil
323 "The current browser buffer, if any.")
324
325 (defvar emms-browser-buffer-name "*EMMS Browser*"
326 "The default buffer name.")
327 (defvar emms-browser-top-level-hash nil
328 "The current mapping db, eg. artist -> track.")
329 (make-variable-buffer-local 'emms-browser-top-level-hash)
330
331 (defvar emms-browser-top-level-type nil
332 "The current mapping type, eg. \\='info-artist.")
333 (make-variable-buffer-local 'emms-browser-top-level-type)
334
335 (defvar emms-browser-current-indent nil
336 "Used to override the current indent, for the playlist, etc.")
337
338 ;; Set the hooks for Emms-filters to say when to re-render.
339 ;; this is just a variable to mirror the browser's hook.
340 ;; It should probably just be set directly, and the browser's
341 ;; hook be deprecated. It will have to be set if anyone changes it...
342 ;; Potential problem if someone us using this hook.
343 (add-hook 'emms-browser-filter-tracks-hook 'emms-filters-browser-filter-hook-function)
344 (add-hook 'emms-filters-make-and-render-hash-hook 'emms-browse-by)
345 (add-hook 'emms-filters-expand-render-hook 'emms-browser-expand-all)
346
347
348 (defvar emms-browser-tree-node-map-default
349 '((info-albumartist . info-artist)
350 (info-artist . info-album)
351 (info-composer . info-album)
352 (info-performer . info-album)
353 (info-album . info-title)
354 (info-genre . info-artist)
355 (info-year . info-artist))
356 "How to build the browse tree, by album artist, artist, album.")
357
358 (defvar emms-browser-tree-node-map-AAgAt
359 '((info-albumartist . info-genre)
360 (info-artist . info-title)
361 (info-composer . info-album)
362 (info-performer . info-album)
363 (info-album . info-albumartist)
364 (info-genre . info-artist)
365 (info-year . info-album))
366 "How to build the browse tree, by album artist, genre, artist")
367
368 (defvar emms-browser-tree-node-map-AAAgt
369 '((info-albumartist . info-artist)
370 (info-artist . info-genre)
371 (info-composer . info-album)
372 (info-performer . info-album)
373 (info-album . info-albumartist)
374 (info-genre . info-title)
375 (info-year . info-album))
376 "How to build the browse tree, by album artist, artist, genre")
377
378 (defvar emms-browser-tree-node-map emms-browser-tree-node-map-default
379 "The alist mapping of the browser tree node map.")
380
381 (defvar emms-browser-mode-map
382 (let ((map (make-sparse-keymap)))
383 (define-key map (kbd "Q") #'emms-filters-pop-cache)
384 (define-key map (kbd "q") #'emms-browser-bury-buffer)
385 (define-key map (kbd "/") #'emms-isearch-buffer)
386 (define-key map (kbd "r") #'emms-browser-goto-random)
387 (define-key map (kbd "n") #'next-line)
388 (define-key map (kbd "p") #'previous-line)
389 (define-key map (kbd "C") #'emms-browser-clear-playlist)
390 (define-key map (kbd "?") #'describe-mode)
391 (define-key map (kbd "C-/") #'emms-playlist-mode-undo)
392 (define-key map (kbd "SPC") #'emms-browser-toggle-subitems)
393 (define-key map (kbd "^") #'emms-browser-move-up-level)
394 (define-key map (kbd "RET") #'emms-browser-add-tracks)
395 (define-key map (kbd "<C-return>") #'emms-browser-add-tracks-and-play)
396 (define-key map (kbd "C-j") #'emms-browser-add-tracks-and-play)
397 (define-key map (kbd "<tab>") #'emms-browser-toggle-subitems)
398 (define-key map (kbd "<backtab>") #'emms-browser-prev-non-track)
399 (define-key map (kbd "d") #'emms-browser-view-in-dired)
400 (define-key map (kbd "D") #'emms-browser-delete-files)
401 (define-key map (kbd "E") #'emms-browser-expand-all)
402 (define-key map (kbd "1") #'emms-browser-collapse-all)
403 (define-key map (kbd "2") #'emms-browser-expand-to-level-2)
404 (define-key map (kbd "3") #'emms-browser-expand-to-level-3)
405 (define-key map (kbd "4") #'emms-browser-expand-to-level-4)
406 (define-key map (kbd "b 1") #'emms-browse-by-artist)
407 (define-key map (kbd "b 2") #'emms-browse-by-album)
408 (define-key map (kbd "b 3") #'emms-browse-by-genre)
409 (define-key map (kbd "b 4") #'emms-browse-by-year)
410 (define-key map (kbd "b 5") #'emms-browse-by-composer)
411 (define-key map (kbd "b 6") #'emms-browse-by-performer)
412 (define-key map (kbd "b 7") #'emms-browse-by-albumartist)
413 (define-key map (kbd "W o w") #'emms-browser-lookup-albumartist-on-wikipedia)
414 (define-key map (kbd "W A w") #'emms-browser-lookup-artist-on-wikipedia)
415 (define-key map (kbd "W C w") #'emms-browser-lookup-composer-on-wikipedia)
416 (define-key map (kbd "W P w") #'emms-browser-lookup-performer-on-wikipedia)
417 (define-key map (kbd "W a w") #'emms-browser-lookup-album-on-wikipedia)
418 (define-key map (kbd "+") #'emms-volume-raise)
419 (define-key map (kbd "-") #'emms-volume-lower)
420
421 (define-key map (kbd ">") #'emms-filters-next-ring-filter)
422 (define-key map (kbd "<") #'emms-filters-previous-ring-filter)
423 (define-key map (kbd "f !") #'emms-filters-clear-ring-filter)
424 (define-key map (kbd "f >") #'emms-filters-next-ring-filter)
425 (define-key map (kbd "f <") #'emms-filters-previous-ring-filter)
426
427 (define-key map (kbd "i s") #'emms-filters-status-print)
428 (define-key map (kbd "i f") #'emms-filters-show-filters)
429 (define-key map (kbd "i m") #'emms-filters-show-filter-menu)
430 (define-key map (kbd "i F") #'emms-filters-show-filter-factories)
431 (define-key map (kbd "i r") #'emms-filters-show-filter-ring)
432 (define-key map (kbd "i c") #'emms-filters-show-cache-stack)
433 (define-key map (kbd "i S") #'emms-filters-show-cache-stash)
434
435 (define-key map (kbd "f q") #'emms-filters-pop)
436 (define-key map (kbd "f h") #'emms-filters-hard-filter)
437 (define-key map (kbd "f r") #'emms-filters-swap) ; rotate ?
438 (define-key map (kbd "f R") #'emms-filters-swap-pop) ; rotate-eject, ,pop-previous
439 (define-key map (kbd "f f") #'emms-filters-squash) ;flatten
440 (define-key map (kbd "f k") #'emms-filters-keep)
441 (define-key map (kbd "f C") #'emms-filters-clear-all)
442 (define-key map (kbd "f c") #'emms-filters-clear)
443 (define-key map (kbd "f p") #'emms-filters-push)
444 (define-key map (kbd "f s") #'emms-filters-smash)
445 (define-key map (kbd "f o") #'emms-filters-or)
446 (define-key map (kbd "f a") #'emms-filters-and)
447 (define-key map (kbd "f n") #'emms-filters-and-not)
448
449 (define-key map (kbd "c p") #'emms-filters-push-cache)
450 (define-key map (kbd "c z") #'emms-filters-stash-pop-cache)
451 (define-key map (kbd "c Z") #'emms-filters-stash-cache)
452 (define-key map (kbd "c q") #'emms-filters-pop-cache)
453 (define-key map (kbd "c h") #'emms-filters-hard-filter)
454 (define-key map (kbd "c r") #'emms-filters-swap-cache)
455 (define-key map (kbd "c R") #'emms-filters-swap-pop-cache)
456 (define-key map (kbd "c S") #'emms-filters-squash-caches)
457 (define-key map (kbd "c c") #'emms-filters-clear-caches)
458
459 (define-key map (kbd "s o") #'emms-filters-search-by-albumartist)
460 (define-key map (kbd "s a") #'emms-filters-search-by-artist)
461 (define-key map (kbd "s c") #'emms-filters-search-by-composer)
462 (define-key map (kbd "s p") #'emms-filters-search-by-performer)
463 (define-key map (kbd "s A") #'emms-filters-search-by-album)
464 (define-key map (kbd "s t") #'emms-filters-search-by-title)
465 (define-key map (kbd "s T") #'emms-filters-search-by-titles)
466 (define-key map (kbd "s n") #'emms-filters-search-by-names)
467 (define-key map (kbd "s s") #'emms-filters-search-by-names-and-titles)
468 (define-key map (kbd "s e") #'emms-filters-search-by-all-text) ;everything.
469 map)
470 "Keymap for `emms-browser-mode'.")
471
472 ;; --------------------------------------------------
473 ;; Compatability functions
474 ;; --------------------------------------------------
475
476 (eval-and-compile
477 (if (fboundp 'with-selected-window) ;Emacs-22
478 (defalias 'emms-browser-with-selected-window #'with-selected-window)
479 (defmacro emms-browser-with-selected-window (window &rest body)
480 (ignore window)
481 ;; this emulates the behavior introduced earlier, though it
482 ;; might be best to do something with `window'
483 `(save-selected-window ,body)))
484 (put 'emms-browser-with-selected-window 'lisp-indent-function 1)
485 (put 'emms-browser-with-selected-window 'edebug-form-spec '(form body)))
486
487 (defalias 'emms-browser-run-mode-hooks
488 (if (fboundp 'run-mode-hooks) ;Emacs-22
489 #'run-mode-hooks
490 #'run-hooks))
491
492 ;; --------------------------------------------------
493 ;; General mode setup
494 ;; --------------------------------------------------
495
496 ;;;###autoload
497 (defun emms-browser ()
498 "Launch or switch to the EMMS Browser."
499 (interactive)
500 (emms-browser-create-or-focus
501 emms-browser-default-browse-type))
502
503 (defun emms-browser-create-or-focus (type)
504 "Create a new browser buffer with BROWSE-FUNC, or switch.
505 BROWSE-FUNC should fill the buffer with something of interest. An
506 example function is `emms-browse-by-artist'."
507 (let ((buf (emms-browser-get-buffer))
508 wind)
509 (if buf
510 ;; if the buffer is displayed, switch the window instead
511 (progn
512 (setq wind (get-buffer-window buf))
513 (if wind
514 (select-window wind)
515 (switch-to-buffer buf))
516 (emms-browser-run-mode-hooks 'emms-browser-show-display-hook))
517 ;; if there's no buffer, create a new window
518 (emms-browser-create)
519 (emms-browse-by type))))
520
521 (defun emms-browser-create ()
522 "Create a new emms-browser buffer and start emms-browser-mode."
523 (emms-browser-new-buffer)
524 (emms-browser-mode)
525 (emms-browser-run-mode-hooks 'emms-browser-show-display-hook))
526
527 (defun emms-browser-mode (&optional no-update)
528 "A major mode for the Emms browser.
529 Does not set the browser buffer to current unless NO-UPDATE is set.
530 \\{emms-browser-mode-map}"
531 ;; create a new buffer
532 (interactive)
533 (kill-all-local-variables)
534
535 (setq major-mode 'emms-browser-mode
536 mode-name "Emms-Browser")
537
538 (use-local-map emms-browser-mode-map)
539
540 (setq buffer-read-only t)
541 (unless no-update
542 (setq emms-browser-buffer (current-buffer)))
543
544 (run-hooks 'emms-browser-mode-hook))
545
546 (defun emms-browser-new-buffer ()
547 "Create a new browser buffer, and switch to it."
548 (switch-to-buffer (generate-new-buffer
549 emms-browser-buffer-name)))
550
551 (defun emms-browser-clear ()
552 "Create or switch to a browser buffer, clearing it."
553 (let ((buf (emms-browser-get-buffer)))
554 (if buf
555 (progn
556 (switch-to-buffer buf)
557 (emms-with-inhibit-read-only-t
558 (delete-region (point-min) (point-max))))
559 (emms-browser-create))))
560
561 (defun emms-browser-get-buffer ()
562 "Return the current buffer if it exists, or nil."
563 (unless (or (null emms-browser-buffer)
564 (not (buffer-live-p emms-browser-buffer)))
565 emms-browser-buffer))
566
567 (defun emms-browser-ensure-browser-buffer ()
568 "Ensure the current buffer is the browser buffer."
569 (unless (eq major-mode 'emms-browser-mode)
570 (error "Current buffer is not an emms-browser buffer")))
571
572 (defun emms-browser-bury-buffer ()
573 "Bury the browser buffer, running hooks."
574 (interactive)
575 (emms-browser-run-mode-hooks 'emms-browser-hide-display-hook)
576 (bury-buffer))
577
578 ;; --------------------------------------------------
579 ;; Top-level browsing methods - by artist/album/etc
580 ;; --------------------------------------------------
581
582 ;; Since the number of tracks may be rather large, we use a hash to
583 ;; sort the top level elements into various categories. All
584 ;; subelements will be stored in a bdata alist structure.
585
586 (defmacro emms-browser-add-category (name type)
587 "Create an interactive function with NAME and info TYPE emms-browse-by-NAME."
588 (let ((funname (intern (concat "emms-browse-by-" name)))
589 (funcdesc (concat "Browse by " name ".")))
590 `(defun ,funname ()
591 ,funcdesc
592 (interactive)
593 (emms-browse-by ,type))))
594
595 (defun emms-browse-by (&optional type)
596 "Render a top level buffer based on TYPE.
597 If TYPE is not given default to top-level-type
598 or the default-browse-type"
599 (if (not type)
600 (setq type (or emms-browser-top-level-type
601 emms-browser-default-browse-type)))
602 ;; FIXME: assumes we only browse by info-*
603 (let* ((name (substring (symbol-name type) 5))
604 (modedesc (concat "Browsing by: " name))
605 (hash (emms-browser-make-hash-by type))
606 (current-filter-name (emms-filters-full-name)))
607 (when current-filter-name
608 (setq modedesc (concat modedesc
609 " [" current-filter-name "]")))
610 (emms-browser-clear)
611 (rename-buffer modedesc)
612 (emms-browser-render-hash hash type)
613 (setq emms-browser-top-level-hash hash)
614 (setq emms-browser-top-level-type type)
615
616 (goto-char (point-min))
617
618 (if (not (> (hash-table-count hash) 0))
619 (if (emms-filters-is-filtering)
620 (emms-browser-show-empty-result-message)
621 (emms-browser-show-empty-cache-message)))))
622
623 (emms-browser-add-category "albumartist" 'info-albumartist)
624 (emms-browser-add-category "artist" 'info-artist)
625 (emms-browser-add-category "composer" 'info-composer)
626 (emms-browser-add-category "performer" 'info-performer)
627 (emms-browser-add-category "album" 'info-album)
628 (emms-browser-add-category "genre" 'info-genre)
629 (emms-browser-add-category "year" 'info-year)
630
631 (defun emms-browser-get-track-field (track type)
632 "Return TYPE from TRACK.
633 This can be customized to group different artists into one for
634 compilations, etc."
635 (funcall emms-browser-get-track-field-function track type))
636
637 (defun emms-browser-get-track-field-simple (track type)
638 "Return TYPE from TRACK without any heuristic.
639 This function can be used as
640 `emms-browser-get-track-field-function'."
641 (emms-track-get track type "misc"))
642
643 (defun emms-browser-get-track-field-albumartist (track type)
644 "Return TYPE from TRACK with an albumartist-oriented heuristic.
645 For \\='info-artist TYPE, use \\='info-albumartistsort,
646 \\='info-albumartist, \\='info-artistsort.
647 For \\='info-year TYPE, use \\='info-originalyear, \\='info-originaldate and
648 \\='info-date symbols."
649 (cond ((eq type 'info-artist)
650 (or (emms-track-get track 'info-albumartist)
651 (emms-track-get track 'info-albumartistsort)
652 (emms-track-get track 'info-artist)
653 (emms-track-get track 'info-artistsort "<unknown artist>")))
654 ((eq type 'info-year)
655 (let ((date (or (emms-track-get track 'info-originaldate)
656 (emms-track-get track 'info-originalyear)
657 (emms-track-get track 'info-date)
658 (emms-track-get track 'info-year "<unknown year>"))))
659 (emms-format-date-to-year date)))
660 (t (emms-track-get track type "misc"))))
661
662 (defun emms-browser-get-track-field-use-directory-name (track type)
663 (if (eq type 'info-artist)
664 (emms-browser-get-artist-from-path
665 track)
666 (emms-track-get track type "misc")))
667
668 (defun emms-browser-get-artist-from-path (track)
669 (let* ((path (emms-track-get track 'name))
670 (dir (file-name-directory path))
671 (basedir
672 (file-name-nondirectory
673 (directory-file-name
674 (file-name-directory dir)))))
675 (car (split-string basedir " - "))))
676
677 (defun emms-browser-make-hash-by (type)
678 "Make a hash, mapping with TYPE, eg artist -> tracks."
679 (let ((hash (make-hash-table
680 :test emms-browser-comparison-test))
681 field existing-entry)
682 (maphash (lambda (_path track)
683 (when (run-hook-with-args-until-success
684 'emms-browser-filter-tracks-hook track)
685 (setq field
686 (emms-browser-get-track-field track type))
687 (when field
688 (setq existing-entry (gethash field hash))
689 (if existing-entry
690 (puthash field (cons track existing-entry) hash)
691 (puthash field (list track) hash)))))
692 (emms-filters-last-search-cache))
693 hash))
694
695 (defun emms-browser-render-hash (db type)
696 "Render a mapping (DB) with TYPE into a browser buffer."
697 (maphash (lambda (desc data)
698 (emms-browser-insert-top-level-entry desc data type))
699 db)
700 (emms-with-inhibit-read-only-t
701 (let ((sort-fold-case t))
702 (if emms-browser-alpha-sort-function
703 (progn
704 (goto-char (point-min))
705 (sort-subr nil
706 #'forward-line #'end-of-line
707 (lambda () (buffer-substring-no-properties
708 (line-beginning-position) (line-end-position)))
709 nil
710 emms-browser-alpha-sort-function))
711 (sort-lines nil (point-min) (point-max))))))
712
713 (defun case-fold-string= (a b)
714 (eq t (compare-strings a nil nil b nil nil t)))
715
716 (defun case-fold-string-hash (a)
717 (sxhash (upcase a)))
718
719 (when (fboundp 'define-hash-table-test)
720 (define-hash-table-test 'case-fold
721 'case-fold-string= 'case-fold-string-hash))
722
723 (defun emms-browser-insert-top-level-entry (name tracks type)
724 "Insert a single top level entry into the buffer."
725 (emms-browser-ensure-browser-buffer)
726 (let ((bdata (emms-browser-make-bdata-tree type 1 tracks name)))
727 (emms-browser-insert-format bdata)))
728
729 (defun emms-browser-show-empty-result-message ()
730 "Display some help if the cache-db exists but the result hash is empty."
731 (emms-with-inhibit-read-only-t
732 (insert (emms-filters-empty-result-message))))
733
734 (defun emms-browser-show-empty-cache-message ()
735 "Display some help if the cache is empty."
736 (emms-with-inhibit-read-only-t
737 (insert "
738 Welcome to EMMS.
739
740 There are currently no files in the EMMS database.
741 To browse music, you need to tell EMMS where your
742 files are.
743
744 Try the following commands:
745
746 M-x emms-add-directory-tree:
747 Add all music in a directory and its subdirectories.
748
749 M-x emms-add-directory:
750 Add all music in a directory
751
752 M-x emms-add-file: Add a single music file.
753
754 After you have added some files, wait for EMMS to say
755 'all track information loaded,' then return to the
756 browser, and hit 'b 1' to refresh.")))
757
758 ;; --------------------------------------------------
759 ;; Building a subitem tree
760 ;; --------------------------------------------------
761 (defun emms-browser-next-mapping-type (current-mapping)
762 "Return the next sensible mapping.
763 Eg. if CURRENT-MAPPING is currently \\='info-artist, return
764 \\='info-album."
765 (alist-get current-mapping emms-browser-tree-node-map))
766
767 (defun emms-browser-make-bdata-tree (type level tracks name)
768 "Build a tree of browser DB elements for tracks."
769 (emms-browser-make-bdata
770 (emms-browser-make-bdata-tree-recurse
771 type level tracks)
772 name
773 type level))
774
775 (defun emms-browser-make-bdata-tree-recurse (type level tracks)
776 "Build a tree of alists based on TYPE, LEVEL and a list of tracks, TRACKS.
777 For example, if TYPE is \\='info-year, return an alist like:
778 artist1 -> album1 -> *track* 1.."
779 (let* ((next-type (emms-browser-next-mapping-type type))
780 (next-level (1+ level))
781 alist name _new-db new-tracks)
782 ;; if we're at a leaf, the db data is a list of tracks
783 (if (eq type 'info-title)
784 tracks
785 ;; otherwise, make DBs from the sub elements
786 (setq alist
787 (emms-browser-make-sorted-alist
788 next-type tracks))
789 (mapcar (lambda (entry)
790 (setq name (emms-browser-make-name
791 entry next-type))
792 (setq new-tracks (cdr entry))
793 (emms-browser-make-bdata
794 (emms-browser-make-bdata-tree-recurse
795 next-type next-level new-tracks)
796 name next-type next-level))
797 alist))))
798
799 (defun emms-browser-make-name (entry type)
800 "Return a name for ENTRY and TYPE, used for making a bdata object."
801
802 (if (eq type 'info-title)
803 (let* ((track (cadr entry))
804 (artist (emms-track-get track 'info-artist))
805 (aartist (emms-track-get track 'info-albumartist))
806 (title (emms-track-get track 'info-title))
807
808 (artist (if (and artist aartist)
809 (concat aartist " : " artist)
810 (if (and (not artist) aartist)
811 artist
812 artist))))
813
814 ;; return a title or the car of entry
815 (if (and artist title)
816 (concat artist " - " title)
817 (car entry)))
818 (car entry)))
819
820 (defun emms-browser-track-number (track)
821 "Return a string representation of a track number.
822 The string will end in a space. If no track number is available,
823 return an empty string."
824 (let ((tracknum (emms-track-get track 'info-tracknumber)))
825 (if (or (not (stringp tracknum)) (string= tracknum "0"))
826 ""
827 (concat
828 (if (eq (length tracknum) 1)
829 (concat "0" tracknum)
830 tracknum)))))
831
832 (defun emms-browser-disc-number (track)
833 "Return a string representation of the TRACK number.
834 The string will end in a space. If no track number is available,
835 return an empty string."
836 (let ((discnum (emms-track-get track 'info-discnumber)))
837 (if (or (not (stringp discnum)) (string= discnum "0"))
838 ""
839 discnum)))
840
841 (defun emms-browser-year-number (track)
842 "Return a string representation of a TRACK\\='s year.
843 This will be in the form \\='(1998) \\='."
844 (let ((year (emms-track-get-year track)))
845 (if (or (not (stringp year)) (string= year "0"))
846 ""
847 (concat
848 "(" year ") "))))
849
850 (defun emms-browser-track-duration (track)
851 "Return a string representation of the TRACK duration.
852 If no duration is available, return an empty string."
853 (let ((pmin (emms-track-get track 'info-playing-time-min))
854 (psec (emms-track-get track 'info-playing-time-sec))
855 (ptot (emms-track-get track 'info-playing-time)))
856 (cond ((and pmin psec) (format "%02d:%02d" pmin psec))
857 (ptot (format "%02d:%02d" (/ ptot 60) (% ptot 60)))
858 (t ""))))
859
860 (defun emms-browser-make-bdata (data name type level)
861 "Return a browser data item from ALIST.
862 DATA should be a list of DB items, or a list of tracks.
863 NAME is a name for the DB item.
864 TYPE is a category the data is organised by, such as \\='info-artist.
865 LEVEL is the number of the sublevel the db item will be placed in."
866 (list (cons 'type type)
867 (cons 'level level)
868 (cons 'name name)
869 (cons 'data data)))
870
871 (defun emms-browser-make-alist (type tracks)
872 "Make an alist mapping of TYPE -> TRACKS.
873 Items with no metadata for TYPE will be placed in \\='misc\\='"
874 (let (db key existing tracknum)
875 (dolist (track tracks)
876 (setq key (emms-browser-get-track-field track type))
877 (when (eq type 'info-title)
878 ;; try and make every track unique
879 (setq tracknum (emms-browser-track-number track))
880 (if (string= tracknum "")
881 (setq key (file-name-nondirectory
882 (emms-track-get track 'name)))
883 (setq key (concat tracknum key))))
884 (setq existing (assoc key db))
885 (if existing
886 (setcdr existing (cons track (cdr existing)))
887 (push (cons key (list track)) db)))
888 ;; sort the entries we've built
889 (dolist (item db)
890 (setcdr item (nreverse (cdr item))))
891 db))
892
893 (defun emms-browser-make-sorted-alist (type tracks)
894 "Return a sorted alist of TRACKS.
895 TYPE is the metadata to make the alist by - eg. if it\\='s
896 \\='info-artist, an alist of artists will be made."
897 (emms-browser-sort-alist
898 (emms-browser-make-alist type tracks)
899 type))
900
901 ;; --------------------------------------------------
902 ;; BDATA accessors and predicates
903 ;; --------------------------------------------------
904
905 (defun emms-browser-bdata-level (bdata)
906 (cdr (assq 'level bdata)))
907
908 (defun emms-browser-bdata-name (bdata)
909 (cdr (assq 'name bdata)))
910
911 (defun emms-browser-bdata-type (bdata)
912 (cdr (assq 'type bdata)))
913
914 (defun emms-browser-bdata-data (bdata)
915 (cdr (assq 'data bdata)))
916
917 (defun emms-browser-bdata-p (obj)
918 "True if obj is a BDATA object."
919 (consp (assq 'data obj)))
920
921 ;; --------------------------------------------------
922 ;; Sorting expanded entries
923 ;; --------------------------------------------------
924
925 (defmacro emms-browser-sort-cadr (sort-func)
926 "Return a function to sort an alist using SORT-FUNC.
927 This sorting predicate will compare the cadr of each entry.
928 SORT-FUNC should be a playlist sorting predicate like
929 `emms-playlist-sort-by-natural-order'."
930 (declare (debug t))
931 `(lambda (a b)
932 (funcall ,sort-func (cadr a) (cadr b))))
933
934 (defmacro emms-browser-sort-car (sort-func)
935 "Return a function to sort an alist using SORT-FUNC.
936 This sorting predicate will compare the car of each entry.
937 SORT-FUNC should be a playlist sorting predicate like
938 `emms-playlist-sort-by-natural-order'."
939 (declare (debug t))
940 `(lambda (a b)
941 (funcall ,sort-func (car a) (car b))))
942
943 (defun emms-browser-sort-by-track (alist)
944 "Sort an ALIST by the tracks in each entry.
945 Uses `emms-browser-track-sort-function'."
946 (if emms-browser-track-sort-function
947 (sort alist (emms-browser-sort-cadr
948 emms-browser-track-sort-function))
949 alist))
950
951 (defun emms-browser-sort-by-name (alist)
952 "Sort ALIST by keys alphabetically.
953 Uses `emms-browser-alpha-sort-function'."
954 (if emms-browser-alpha-sort-function
955 (sort alist (emms-browser-sort-car
956 emms-browser-alpha-sort-function))
957 alist))
958
959 (defun emms-browser-sort-by-year-or-name (alist)
960 "Sort ALIST based on year or name."
961 (sort alist (emms-browser-sort-cadr
962 'emms-browser-sort-by-year-or-name-p)))
963
964 (defun emms-browser-sort-by-year-or-name-p (a b)
965 "Sort A and B by on year or name."
966 ;; FIXME: this is a bit of a hack
967 (let ((a-desc (concat
968 (emms-browser-year-number a)
969 (emms-track-get a 'info-album "misc")))
970 (b-desc (concat
971 (emms-browser-year-number b)
972 (emms-track-get b 'info-album "misc"))))
973 (string< a-desc b-desc)))
974
975 (defun emms-browser-sort-alist (alist type)
976 "Sort ALIST using the sorting function for TYPE."
977 (let ((sort-func
978 (cond
979 ((or
980 (eq type 'info-albumartist)
981 (eq type 'info-artist)
982 (eq type 'info-composer)
983 (eq type 'info-performer)
984 (eq type 'info-year)
985 (eq type 'info-genre))
986 'emms-browser-sort-by-name)
987 ((eq type 'info-album)
988 emms-browser-album-sort-function)
989 ((eq type 'info-title)
990 'emms-browser-sort-by-track)
991 (t (message (concat "Can't sort unknown mapping!" type))))))
992 (funcall sort-func alist)))
993
994
995 ;; --------------------------------------------------
996 ;; Subitem operations on the buffer
997 ;; --------------------------------------------------
998
999 (defun emms-browser-bdata-at-point ()
1000 "Return the bdata object at point.
1001 Includes information at point (such as album name), and metadata."
1002 (get-text-property (line-beginning-position)
1003 'emms-browser-bdata))
1004
1005 (defun emms-browser-data-at-point ()
1006 "Return the data stored under point.
1007 This will be a list of DB items."
1008 (emms-browser-bdata-data (emms-browser-bdata-at-point)))
1009
1010 (defun emms-browser-level-at-point ()
1011 "Return the current level at point."
1012 (emms-browser-bdata-level (emms-browser-bdata-at-point)))
1013
1014 (defun emms-browser-tracks-at-point (&optional node)
1015 "Return a list of tracks at point."
1016 (let (tracks)
1017 (dolist (node (if node
1018 node
1019 (emms-browser-data-at-point)))
1020 (if (not (emms-browser-bdata-p node))
1021 (setq tracks (cons node tracks))
1022 (setq tracks
1023 (append tracks
1024 (emms-browser-tracks-at-point
1025 (emms-browser-bdata-data node))))))
1026 tracks))
1027
1028 (defun emms-browser-expand-one-level ()
1029 "Expand the current line by one sublevel."
1030 (interactive)
1031 (let* ((data (emms-browser-data-at-point)))
1032 (save-excursion
1033 (forward-line 1)
1034 (beginning-of-line)
1035 (dolist (data-item data)
1036 (emms-browser-insert-data-item data-item)))))
1037
1038 (defun emms-browser-insert-data-item (data-item)
1039 "Insert DATA-ITEM into the buffer.
1040 This checks DATA-ITEM's level to determine how much to indent.
1041 The line will have a property emms-browser-bdata storing subitem
1042 information."
1043 (emms-browser-insert-format data-item))
1044
1045 (defun emms-browser-find-entry-more-than-level (level)
1046 "Move point to next entry more than LEVEL and return point.
1047 If no entry exits, return nil.
1048 Returns point if currently on a an entry more than LEVEL."
1049 (let ((old-pos (point))
1050 level-at-point)
1051 (forward-line 1)
1052 (setq level-at-point (emms-browser-level-at-point))
1053 (if (and level-at-point
1054 (> level-at-point level))
1055 (point)
1056 (goto-char old-pos)
1057 nil)))
1058
1059 (defun emms-browser-subitems-visible ()
1060 "True if there are any subentries visible point."
1061 (let ((current-level (emms-browser-level-at-point))
1062 new-level)
1063 (save-excursion
1064 (re-search-forward "\n" nil t)
1065 (when (setq new-level (emms-browser-level-at-point))
1066 (> new-level current-level)))))
1067
1068 (defun emms-browser-subitems-exist ()
1069 "True if it's possible to expand the current line."
1070 (not (eq (emms-browser-bdata-type
1071 (emms-browser-bdata-at-point))
1072 'info-title)))
1073
1074 (defun emms-browser-move-up-level (&optional direction)
1075 "Move up one level if possible.
1076 Return true if we were able to move up.
1077 If DIRECTION is 1, move forward, otherwise move backwards."
1078 (interactive "P")
1079 (let ((moved nil)
1080 (continue t)
1081 (current-level (emms-browser-level-at-point)))
1082 (while (and
1083 continue
1084 (zerop (forward-line
1085 (or (and (numberp direction) direction) -1))))
1086 (when (> current-level (or (emms-browser-level-at-point) 0))
1087 (setq moved t)
1088 (setq continue nil)))
1089 moved))
1090
1091 (defun emms-browser-toggle-subitems ()
1092 "Show or hide (kill) subitems under the current line."
1093 (interactive)
1094 (if (emms-browser-subitems-visible)
1095 (emms-browser-kill-subitems)
1096 (if (emms-browser-subitems-exist)
1097 (emms-browser-show-subitems)
1098 (cl-assert (emms-browser-move-up-level))
1099 (emms-browser-kill-subitems))))
1100
1101 (defun emms-browser-toggle-subitems-recursively ()
1102 "Recursively toggle all subitems under the current line.
1103 If there is no more subitems to expand, collapse the current node."
1104 (interactive)
1105 (let ((current-level (emms-browser-level-at-point))
1106 first-expandable-level)
1107 (save-excursion
1108 (while (or (and (emms-browser-subitems-exist)
1109 (not (emms-browser-subitems-visible))
1110 (or (and (not first-expandable-level)
1111 (setq first-expandable-level (emms-browser-level-at-point)))
1112 (= first-expandable-level (emms-browser-level-at-point)))
1113 (emms-browser-show-subitems))
1114 (emms-browser-find-entry-more-than-level current-level))))
1115 (unless first-expandable-level
1116 (emms-browser-kill-subitems))))
1117
1118 (defun emms-browser-show-subitems ()
1119 "Show subitems under the current line."
1120 (unless (emms-browser-subitems-visible)
1121 (if (emms-browser-subitems-exist)
1122 (emms-browser-expand-one-level))))
1123
1124 (defun emms-browser-kill-subitems ()
1125 "Remove all subitems under the current line.
1126 Stops at the next line at the same level, or EOF."
1127 (when (emms-browser-subitems-visible)
1128 (let ((current-level (emms-browser-level-at-point))
1129 (next-line (line-beginning-position 2)))
1130 (emms-with-inhibit-read-only-t
1131 (delete-region next-line
1132 (save-excursion
1133 (while
1134 (emms-browser-find-entry-more-than-level
1135 current-level))
1136 (line-beginning-position 2)))))))
1137
1138 ;; --------------------------------------------------
1139 ;; Dealing with the playlist (queuing songs, etc)
1140 ;; --------------------------------------------------
1141
1142 (defun emms-browser-playlist-insert-group (bdata)
1143 "Insert a group description of BDATA into the playlist buffer."
1144 (let ((name (emms-browser-format-line bdata 'playlist)))
1145 (with-current-emms-playlist
1146 (goto-char (point-max))
1147 (insert name "\n"))))
1148
1149 (defun emms-browser-playlist-insert-track (bdata)
1150 "Insert a track from BDATA into the playlist buffer."
1151 (let ((name (emms-browser-format-line bdata 'playlist)))
1152 (with-current-emms-playlist
1153 (goto-char (point-max))
1154 (insert name "\n"))))
1155
1156 (defun emms-browser-playlist-insert-bdata (bdata starting-level)
1157 "Add all tracks in BDATA at STARTING-LEVEL to the playlist."
1158 (let ((type (emms-browser-bdata-type bdata))
1159 (level (emms-browser-bdata-level bdata))
1160 emms-browser-current-indent)
1161
1162 ;; adjust the indentation relative to the starting level
1163 (when starting-level
1164 (setq level (- level (1- starting-level))))
1165 ;; we temporarily rebind the current indent to the relative indent
1166 (setq emms-browser-current-indent
1167 (emms-browser-make-indent level))
1168
1169 ;; add a group heading?
1170 (unless (eq type 'info-title)
1171 (emms-browser-playlist-insert-group bdata))
1172
1173 ;; recurse or add tracks
1174 (dolist (item (emms-browser-bdata-data bdata))
1175 (if (not (eq type 'info-title))
1176 (emms-browser-playlist-insert-bdata item starting-level)
1177 (emms-browser-playlist-insert-track bdata)))))
1178
1179 ;; --------------------------------------------------
1180 ;; Expanding/contracting
1181 ;; --------------------------------------------------
1182
1183 (defun emms-browser-expand-to-level (level)
1184 "Expand to a depth specified by LEVEL.
1185 After expanding, jump to the currently marked entry."
1186 (let ((count 1)
1187 (total 0)
1188 progress-reporter)
1189 (goto-char (point-min))
1190 (while (not (eq (buffer-end 1) (point)))
1191 (when (= (emms-browser-level-at-point) 1)
1192 (setq total (1+ total)))
1193 (emms-browser-next-non-track))
1194 (goto-char (point-min))
1195 (setq progress-reporter
1196 (make-progress-reporter "Expanding EMMS browser entries..."
1197 0 total))
1198 (while (not (eq (buffer-end 1) (point)))
1199 (when (= (emms-browser-level-at-point) 1)
1200 (progress-reporter-update progress-reporter count)
1201 (setq count (1+ count)))
1202 (if (< (emms-browser-level-at-point) level)
1203 (emms-browser-show-subitems))
1204 (emms-browser-next-non-track))
1205 (progress-reporter-done progress-reporter)
1206 (emms-browser-pop-mark)
1207 (recenter '(4))))
1208
1209 (defun emms-browser-mark-and-collapse ()
1210 "Save the current top level element, and collapse."
1211 (emms-browser-mark-entry)
1212 (goto-char (point-max))
1213 (while (not (eq (buffer-end -1) (point)))
1214 (emms-browser-prev-non-track)
1215 (emms-browser-kill-subitems)))
1216
1217 (defun emms-browser-find-top-level ()
1218 "Move up until reaching a top-level element."
1219 (while (not (eq (emms-browser-level-at-point) 1))
1220 (forward-line -1)))
1221
1222 (defun emms-browser-mark-entry ()
1223 "Mark the current top level entry."
1224 (save-excursion
1225 (emms-browser-find-top-level)
1226 (emms-with-inhibit-read-only-t
1227 (add-text-properties (line-beginning-position)
1228 (line-end-position)
1229 (list 'emms-browser-mark t)))))
1230
1231 (defun emms-browser-pop-mark ()
1232 "Return to the last marked entry, and remove the mark."
1233 (goto-char (point-min))
1234 (let ((pos (text-property-any (point-min) (point-max)
1235 'emms-browser-mark t)))
1236 (if pos
1237 (progn
1238 (goto-char pos)
1239 (emms-with-inhibit-read-only-t
1240 (remove-text-properties (line-beginning-position)
1241 (line-end-position)
1242 (list 'emms-browser-mark))))
1243 (message "No mark saved!"))))
1244
1245 (defun emms-browser-go-to-parent ()
1246 "Move point to the parent of the current node.
1247 Return point. If at level one, return the current point."
1248 (let ((current-level (emms-browser-level-at-point)))
1249 (unless (eq current-level 1)
1250 (while (<= current-level (emms-browser-level-at-point))
1251 (forward-line -1)))
1252 (point)))
1253
1254 (defun emms-browser-delete-current-node ()
1255 "Remove the current node, and empty parents."
1256 ;; set the data to empty
1257 (setcdr (assq 'data (emms-browser-bdata-at-point)) nil)
1258 (emms-browser-delete-node-if-empty))
1259
1260 (defun emms-browser-delete-node-if-empty ()
1261 "If empty, remove node and empty parents."
1262 (when (zerop (length (emms-browser-data-at-point)))
1263 (save-excursion
1264 (let ((child-bdata (emms-browser-bdata-at-point))
1265 parent-bdata parent-point)
1266 ;; record the parent's position before we delete anything
1267 (save-excursion
1268 (setq parent-point (emms-browser-go-to-parent)))
1269 ;; delete the current line
1270 (when (emms-browser-subitems-visible)
1271 (emms-browser-kill-subitems))
1272 (emms-with-inhibit-read-only-t
1273 (goto-char (line-beginning-position))
1274 (kill-line 1))
1275 (unless (eq (emms-browser-bdata-level child-bdata) 1)
1276 ;; remove the node from the parent, and recurse
1277 (goto-char parent-point)
1278 (setq parent-bdata (emms-browser-bdata-at-point))
1279 (setcdr (assq 'data parent-bdata)
1280 (delq child-bdata
1281 (emms-browser-bdata-data parent-bdata)))
1282 (emms-browser-delete-node-if-empty))))))
1283
1284 ;; --------------------------------------------------
1285 ;; User-visible commands
1286 ;; --------------------------------------------------
1287
1288 (defun emms-browser-add-tracks ()
1289 "Add all tracks at point or in region if active.
1290 When the region is not active, a numeric prefix argument inserts that many
1291 tracks from point.
1292 Return the playlist buffer point-max before adding."
1293 (interactive)
1294 (let ((count (cond
1295 ((use-region-p)
1296 (1+ (- (line-number-at-pos (region-end)) (line-number-at-pos (region-beginning)))))
1297 ((numberp current-prefix-arg)
1298 current-prefix-arg)
1299 (t 1)))
1300 (first-new-track (with-current-emms-playlist (point-max))))
1301 (when (use-region-p) (goto-char (region-beginning)))
1302 (dotimes (_ count first-new-track)
1303 (let ((bdata (emms-browser-bdata-at-point)))
1304 (when bdata
1305 (emms-browser-playlist-insert-bdata
1306 bdata (emms-browser-bdata-level bdata))
1307 (forward-line))))
1308 (run-hook-with-args 'emms-browser-tracks-added-hook
1309 first-new-track)
1310 (deactivate-mark)
1311 first-new-track))
1312
1313 (defun emms-browser-add-tracks-and-play ()
1314 "Add all tracks at point, and play the first added track."
1315 (interactive)
1316 (let ((old-pos (emms-browser-add-tracks)))
1317 (with-current-emms-playlist
1318 (goto-char old-pos)
1319 ;; Stop what we are currently playing to allow `emms-stop'
1320 ;; related hooks to run.
1321 (when emms-player-playing-p
1322 (emms-stop))
1323 ;; if we're sitting on a group name, move forward
1324 (unless (emms-playlist-track-at (point))
1325 (emms-playlist-next))
1326 (emms-playlist-select (point)))
1327 (emms-start)))
1328
1329 (defun emms-isearch-buffer ()
1330 "Isearch through the buffer."
1331 (interactive)
1332 (goto-char (point-min))
1333 (when (isearch-forward)
1334 (unless (emms-browser-subitems-visible)
1335 (emms-browser-show-subitems))))
1336
1337 (defun emms-browser-next-non-track (&optional direction)
1338 "Jump to the next non-track element in DIRECTION."
1339 (interactive)
1340 (let ((continue t))
1341 (while (and continue
1342 (forward-line (or direction 1)))
1343 (unless (eq (emms-browser-bdata-type
1344 (emms-browser-bdata-at-point)) 'info-title)
1345 (setq continue nil)))))
1346
1347 (defun emms-browser-prev-non-track ()
1348 "Jump to the previous non-track element."
1349 (interactive)
1350 (emms-browser-next-non-track -1))
1351
1352 (defun emms-browser-expand-all ()
1353 "Expand everything.
1354 This function is used by Emms-filters as the expand-render-hook, it must
1355 must be certain that there is a bdata tree to expand."
1356 (interactive)
1357 (when (emms-browser-level-at-point)
1358 (emms-browser-mark-and-collapse)
1359 (emms-browser-expand-to-level 99)))
1360
1361 (defun emms-browser-expand-to-level-2 ()
1362 "Expand all top level items one level."
1363 (interactive)
1364 (emms-browser-mark-and-collapse)
1365 (emms-browser-expand-to-level 2))
1366
1367 (defun emms-browser-expand-to-level-3 ()
1368 "Expand all top level items two levels."
1369 (interactive)
1370 (emms-browser-mark-and-collapse)
1371 (emms-browser-expand-to-level 3))
1372
1373 (defun emms-browser-expand-to-level-4 ()
1374 "Expand all top level items three levels."
1375 (interactive)
1376 (emms-browser-mark-and-collapse)
1377 (emms-browser-expand-to-level 4))
1378
1379 (defun emms-browser-collapse-all ()
1380 "Collapse everything, saving and restoring the mark."
1381 (interactive)
1382 (emms-browser-mark-and-collapse)
1383 (emms-browser-pop-mark)
1384 (recenter '(4)))
1385
1386 (defvar emms-browser-seed-pending t
1387 "Do we need to seed (random)?")
1388
1389 (defun emms-browser-goto-random ()
1390 "Move cursor to random item with the lowest visible level."
1391 (interactive)
1392 (when emms-browser-seed-pending
1393 (random t)
1394 (setq emms-browser-seed-pending nil))
1395 (while (progn (goto-char (point-min))
1396 (forward-line (1- (random (count-lines (point-min) (point-max)))))
1397 (emms-browser-subitems-visible))))
1398
1399 (defun emms-browser-view-in-dired (&optional bdata)
1400 "View the current directory from BDATA or bdata at point in DIRED."
1401 ;; FIXME: currently just grabs the directory from the first track
1402 (interactive)
1403 (if bdata
1404 (if (eq (emms-browser-bdata-type bdata) 'info-title)
1405 (let* ((track (car (emms-browser-bdata-data bdata)))
1406 (path (emms-track-get track 'name))
1407 (dir (file-name-directory path)))
1408 (find-file dir))
1409 (emms-browser-view-in-dired (car (emms-browser-bdata-data bdata))))
1410 (emms-browser-view-in-dired (emms-browser-bdata-at-point))))
1411
1412 (defun emms-browser-remove-tracks (&optional delete start end)
1413 "Remove all tracks at point or in region if active.
1414 Unless DELETE is non-nil or with prefix argument, this only acts on the
1415 browser,files are untouched. Optionally with line number at position
1416 START and position of END within the region. If caching is enabled,
1417 files are removed from the cache as well. When the region is not active,
1418 a numeric prefix argument remove that many tracks from point, it does
1419 not delete files."
1420 (interactive "P\nr")
1421 (let ((count (cond
1422 ((use-region-p)
1423 (1+ (- (line-number-at-pos end) (line-number-at-pos start))))
1424 ((numberp current-prefix-arg)
1425 current-prefix-arg)
1426 (t 1)))
1427 dirs path tracks)
1428 ;; If numeric prefix argument, never delete files.
1429 (when (numberp delete) (setq delete nil))
1430 (when delete
1431 (save-mark-and-excursion
1432 (when (use-region-p) (goto-char start))
1433 (let ((lines (min count (- (line-number-at-pos (point-max)) (line-number-at-pos (point))))))
1434 (dotimes (_ lines)
1435 ;; TODO: Test this!
1436 (setq tracks (append tracks (emms-browser-tracks-at-point)))
1437 (forward-line))))
1438 (unless (yes-or-no-p
1439 (format "Really permanently delete these %d tracks? " (length tracks)))
1440 (error "Cancelled!"))
1441 (message "Deleting files..."))
1442 (when (use-region-p) (goto-char start))
1443 (dotimes (_ count)
1444 (dolist (track (emms-browser-tracks-at-point))
1445 (setq path (emms-track-get track 'name))
1446 (when delete
1447 (delete-file path))
1448 (cl-pushnew (file-name-directory path) dirs)
1449 (emms-cache-del path))
1450 ;; remove the item from the browser
1451 (when (emms-browser-tracks-at-point)
1452 (emms-browser-delete-current-node)))
1453 (deactivate-mark)
1454 ;; remove empty dirs
1455 (when delete
1456 (dolist (dir dirs)
1457 (run-hook-with-args 'emms-browser-delete-files-hook dir tracks)
1458 (condition-case nil
1459 (delete-directory dir)
1460 (error nil))))
1461 (when delete
1462 (message "Deleting files...done"))))
1463
1464 (defalias 'emms-browser-delete-files 'emms-browser-remove-tracks)
1465 (put 'emms-browser-delete-files 'disabled t)
1466
1467 (defun emms-browser-clear-playlist ()
1468 "Clear playlist."
1469 (interactive)
1470 (with-current-emms-playlist
1471 (emms-playlist-clear)))
1472
1473 (defun emms-browser-lookup (field url)
1474 (let ((data
1475 (emms-track-get (emms-browser-bdata-first-track
1476 (emms-browser-bdata-at-point))
1477 field)))
1478 (when data
1479 (browse-url
1480 (concat url data)))))
1481
1482 (defun emms-browser-lookup-wikipedia (field)
1483 "Lookup contents of FIELD in wikipedia."
1484 (emms-browser-lookup
1485 field "http://en.wikipedia.org/wiki/Special:Search?search="))
1486
1487 (defun emms-browser-lookup-albumartist-on-wikipedia ()
1488 (interactive)
1489 (emms-browser-lookup-wikipedia 'info-albumartist))
1490
1491 (defun emms-browser-lookup-artist-on-wikipedia ()
1492 (interactive)
1493 (emms-browser-lookup-wikipedia 'info-artist))
1494
1495 (defun emms-browser-lookup-composer-on-wikipedia ()
1496 (interactive)
1497 (emms-browser-lookup-wikipedia 'info-composer))
1498
1499 (defun emms-browser-lookup-performer-on-wikipedia ()
1500 (interactive)
1501 (emms-browser-lookup-wikipedia 'info-performer))
1502
1503 (defun emms-browser-lookup-album-on-wikipedia ()
1504 (interactive)
1505 (emms-browser-lookup-wikipedia 'info-album))
1506
1507
1508 ;; --------------------------------------------------
1509 ;; Linked browser and playlist windows
1510 ;; --------------------------------------------------
1511
1512 (defcustom emms-browser-switch-to-playlist-on-add
1513 nil
1514 "Whether to switch to to the playlist after adding files."
1515 :type 'boolean)
1516
1517 ;;;###autoload
1518 (defun emms-smart-browse ()
1519 "Display browser and playlist.
1520 Toggle between selecting browser, playlist or hiding both. Tries
1521 to behave sanely if the user has manually changed the window
1522 configuration."
1523 (interactive)
1524 (add-hook 'emms-browser-show-display-hook
1525 #'emms-browser-display-playlist)
1526 (add-hook 'emms-browser-hide-display-hook
1527 #'emms-browser-hide-linked-window)
1528 ;; switch to the playlist window when adding tracks?
1529 (add-hook 'emms-browser-tracks-added-hook
1530 (lambda (start-of-tracks) (interactive)
1531 (let (playlist-window)
1532 (when emms-browser-switch-to-playlist-on-add
1533 (emms-smart-browse))
1534 ;; center on the first added track/group name
1535 (when
1536 (setq playlist-window
1537 (emms-browser-get-linked-window))
1538 (emms-browser-with-selected-window
1539 playlist-window
1540 (goto-char start-of-tracks)
1541 (recenter '(4)))))))
1542 (let (wind)
1543 (cond
1544 ((eq major-mode 'emms-browser-mode)
1545 (setq wind (emms-browser-get-linked-window))
1546 ;; if the playlist window is visible, select it
1547 (if wind
1548 (select-window wind)
1549 ;; otherwise display and select it
1550 (select-window (emms-browser-display-playlist))))
1551 ((eq major-mode 'emms-playlist-mode)
1552 (setq wind (emms-browser-get-linked-window))
1553 ;; if the playlist window is selected, and the browser is visible,
1554 ;; hide both
1555 (if wind
1556 (progn
1557 (select-window wind)
1558 (emms-browser-bury-buffer)
1559 ;; After a browser search, the following buffer could be the
1560 ;; unfiltered browser, which we want to bury as well. We don't want
1561 ;; to call `emms-browser-hide-display-hook' for this one so we bury it
1562 ;; directly.
1563 (when (eq major-mode 'emms-browser-mode)
1564 (bury-buffer)))
1565 ;; otherwise bury both
1566 (bury-buffer)
1567 (emms-browser-hide-linked-window)))
1568 (t
1569 ;; show both
1570 (emms-browser)))))
1571
1572 (defun emms-browser-get-linked-buffer ()
1573 "Return linked buffer (eg browser if playlist is selected."
1574 (cond
1575 ((eq major-mode 'emms-browser-mode)
1576 (car (emms-playlist-buffer-list)))
1577 ((eq major-mode 'emms-playlist-mode)
1578 (emms-browser-get-buffer))))
1579
1580 (defun emms-browser-get-linked-window ()
1581 "Return linked window (eg browser if playlist is selected."
1582 (let ((buf (emms-browser-get-linked-buffer)))
1583 (when buf
1584 (get-buffer-window buf))))
1585
1586 (defun emms-browser-display-playlist ()
1587 "A hook to show the playlist when the browser is displayed.
1588 Returns the playlist window."
1589 (interactive)
1590 (let ((pbuf (emms-browser-get-linked-buffer))
1591 (pwin (emms-browser-get-linked-window)))
1592 ;; if the window isn't alive..
1593 (unless (window-live-p pwin)
1594 (save-selected-window
1595 (split-window-horizontally)
1596 (other-window 1)
1597 (if pbuf
1598 (switch-to-buffer pbuf)
1599 ;; there's no playlist - create one
1600 (setq pbuf (emms-playlist-current-clear))
1601 (switch-to-buffer pbuf))
1602 ;; make q in the playlist window hide the linked browser
1603 (when (boundp 'emms-playlist-mode-map)
1604 (define-key emms-playlist-mode-map (kbd "q")
1605 (lambda ()
1606 (interactive)
1607 (emms-browser-hide-linked-window)
1608 (bury-buffer))))
1609 (setq pwin (get-buffer-window pbuf))))
1610 pwin))
1611
1612 (defun emms-browser-hide-linked-window ()
1613 "Delete a playlist or browser window when the other is hidden."
1614 (interactive)
1615 (let ((other-buf (emms-browser-get-linked-buffer))
1616 (other-win (emms-browser-get-linked-window)))
1617 (when (and other-win
1618 (window-live-p other-win))
1619 (delete-window other-win))
1620 ;; bury the buffer, or it becomes visible when we hide the
1621 ;; linked buffer
1622 (bury-buffer other-buf)))
1623
1624
1625 ;; --------------------------------------------------
1626 ;; Album covers
1627 ;; --------------------------------------------------
1628
1629 (defun emms-browser--build-cover-filename ()
1630 "Build `emms-browser--covers-filename'.
1631
1632 Based on from `emms-browser-covers' (when a list) and
1633 `emms-browser-covers-file-extensions'."
1634 (setq emms-browser--covers-filename
1635 (mapcar (lambda (cover)
1636 (if (file-name-extension cover)
1637 (list cover)
1638 (mapcar (lambda (ext) (concat cover "." ext))
1639 emms-browser-covers-file-extensions)))
1640 emms-browser-covers)))
1641
1642 (defun emms-browser-get-cover-from-album (bdata &optional size)
1643 (cl-assert (eq (emms-browser-bdata-type bdata) 'info-album))
1644 (let* ((track1data (emms-browser-bdata-data bdata))
1645 (track1 (car (emms-browser-bdata-data (car track1data))))
1646 (path (emms-track-get track1 'name)))
1647 (emms-browser-get-cover-from-path path size)))
1648
1649 (defun emms-browser-get-cover-from-path (path &optional size)
1650 "Return a cover filename, if it exists."
1651 (unless size
1652 (setq size 'medium))
1653 (let* ((size-idx (cond
1654 ((eq size 'small) 0)
1655 ((eq size 'medium) 1)
1656 ((eq size 'large) 2)))
1657 (cover
1658 (cond
1659 ((functionp emms-browser-covers)
1660 (funcall emms-browser-covers (file-name-directory path) size))
1661 ((and (listp emms-browser-covers)
1662 (nth size-idx emms-browser-covers))
1663 (unless emms-browser--covers-filename
1664 (emms-browser--build-cover-filename))
1665 (car (delq nil
1666 (mapcar (lambda (cover)
1667 (let ((coverpath
1668 (concat (file-name-directory path) cover)))
1669 (and (file-exists-p coverpath) coverpath)))
1670 (nth size-idx emms-browser--covers-filename))))))))
1671 (if (and cover (file-readable-p cover))
1672 cover
1673 ;; no cover found, use default
1674 (when emms-browser-default-covers
1675 (nth size-idx emms-browser-default-covers)))))
1676
1677 (defun emms-browser-insert-cover (path)
1678 (insert (emms-browser-make-cover path)))
1679
1680 (defun emms-browser-make-cover (path)
1681 (let* ((ext (file-name-extension path))
1682 (type (cond
1683 ((string= ext "png") 'png)
1684 ((string= ext "xbm") 'xbm)
1685 ((string= ext "xpm") 'xpm)
1686 ((string= ext "pbm") 'pbm)
1687 ((string-match "e?ps"
1688 ext) 'postscript)
1689 ((string= ext "gif") 'gif)
1690 ((string= ext "tiff") 'tiff)
1691 (t 'jpeg))))
1692 (emms-propertize " "
1693 'display `(image
1694 :type ,type
1695 :margin 5
1696 :file ,path)
1697 'rear-nonsticky '(display))))
1698
1699 (defun emms-browser-get-cover-str (path size)
1700 (let ((cover (emms-browser-get-cover-from-path path size)))
1701 (if cover
1702 (emms-browser-make-cover cover)
1703 ;; we use a single space so that cover & no cover tracks line up
1704 ;; in a terminal
1705 " ")))
1706
1707 ;; --------------------------------------------------
1708 ;; Display formats
1709 ;; --------------------------------------------------
1710
1711 (defun emms-browser-bdata-first-track (bdata)
1712 "Return the first track from a given bdata.
1713 If > album level, most of the track data will not make sense."
1714 (let ((type (emms-browser-bdata-type bdata)))
1715 (if (eq type 'info-title)
1716 (car (emms-browser-bdata-data bdata))
1717 ;; recurse
1718 (emms-browser-bdata-first-track
1719 (car (emms-browser-bdata-data bdata))))))
1720
1721 (defun emms-browser-insert-format (bdata)
1722 (emms-with-inhibit-read-only-t
1723 (insert
1724 (emms-browser-format-line bdata)
1725 "\n")))
1726
1727 (defun emms-browser-make-indent (level)
1728 (or
1729 emms-browser-current-indent
1730 (make-string (* 1 (1- level)) ?\s)))
1731
1732 (defun emms-browser-format-elem (format-string elem)
1733 (cdr (assoc elem format-string)))
1734
1735 (defun emms-browser-format-line (bdata &optional target)
1736 "Return a propertized string to be inserted in the buffer."
1737 (unless target
1738 (setq target 'browser))
1739 (let* ((name (or (emms-browser-bdata-name bdata) "misc"))
1740 (level (emms-browser-bdata-level bdata))
1741 (type (emms-browser-bdata-type bdata))
1742 (indent (emms-browser-make-indent level))
1743 (track (emms-browser-bdata-first-track bdata))
1744 (path (emms-track-get track 'name))
1745 (face (emms-browser-get-face bdata))
1746 (format (emms-browser-get-format bdata target))
1747 (props (list 'emms-browser-bdata bdata))
1748 (format-choices
1749 `(("i" . ,indent)
1750 ("n" . ,name)
1751 ("y" . ,(emms-track-get-year track))
1752 ("A" . ,(emms-track-get track 'info-album))
1753 ("a" . ,(emms-track-get track 'info-artist))
1754 ("o" . ,(emms-track-get track 'info-albumartist))
1755 ("C" . ,(emms-track-get track 'info-composer))
1756 ("p" . ,(emms-track-get track 'info-performer))
1757 ("t" . ,(emms-track-get track 'info-title))
1758 ("g" . ,(emms-track-get track 'info-genre))
1759 ("D" . ,(emms-browser-disc-number track))
1760 ("T" . ,(emms-browser-track-number track))
1761 ("d" . ,(emms-browser-track-duration track))))
1762 str)
1763 (when (equal type 'info-album)
1764 (setq format-choices
1765 (append format-choices
1766 `(("cS" . ,(emms-browser-get-cover-str path 'small))
1767 ("cM" . ,(emms-browser-get-cover-str path 'medium))
1768 ("cL" . ,(emms-browser-get-cover-str path 'large))))))
1769
1770
1771 (when (functionp format)
1772 (setq format (funcall format bdata format-choices)))
1773
1774 (setq str
1775 (with-temp-buffer
1776 (insert format)
1777 (goto-char (point-min))
1778 (let ((start (point-min)))
1779 ;; jump over any image
1780 (when (re-search-forward "%c[SML]" nil t)
1781 (setq start (point)))
1782 ;; jump over the indent
1783 (when (re-search-forward "%i" nil t)
1784 (setq start (point)))
1785 (add-text-properties start (point-max)
1786 (list 'face face)))
1787 (buffer-string)))
1788
1789 (setq str (emms-browser-format-spec str format-choices))
1790
1791 ;; give tracks a 'boost' if they're not top-level
1792 ;; (covers take up an extra space)
1793 (when (and (eq type 'info-title)
1794 (not (string= indent "")))
1795 (setq str (concat " " str)))
1796
1797 ;; if we're in playlist mode, add a track
1798 (when (and (eq target 'playlist)
1799 (eq type 'info-title))
1800 (setq props
1801 (append props `(emms-track ,track))))
1802
1803 ;; add properties to the whole string
1804 (add-text-properties 0 (length str) props str)
1805 str))
1806
1807 (defun emms-browser-get-face (bdata)
1808 "Return a suitable face for BDATA."
1809 (let* ((type (emms-browser-bdata-type bdata))
1810 (name (cond
1811 ((or (eq type 'info-year)
1812 (eq type 'info-genre)) "year/genre")
1813 ((eq type 'info-albumartist) "albumartist")
1814 ((eq type 'info-artist) "artist")
1815 ((eq type 'info-composer) "composer")
1816 ((eq type 'info-performer) "performer")
1817 ((eq type 'info-album) "album")
1818 ((eq type 'info-title) "track"))))
1819 (intern
1820 (concat "emms-browser-" name "-face"))))
1821
1822 ;; based on gnus code
1823 (defun emms-browser-format-spec (format specification)
1824 "Return a string based on FORMAT and SPECIFICATION.
1825 FORMAT is a string containing `format'-like specs like \"bash %u %k\",
1826 while SPECIFICATION is an alist mapping from format spec characters
1827 to values. Any text properties on a %-spec itself are propagated to
1828 the text that it generates."
1829 (with-temp-buffer
1830 (insert format)
1831 (goto-char (point-min))
1832 (while (search-forward "%" nil t)
1833 (cond
1834 ;; Quoted percent sign.
1835 ((eq (char-after) ?%)
1836 (delete-char 1))
1837 ;; Valid format spec.
1838 ((looking-at "\\([-0-9.]*\\)\\([a-zA-Z]+\\)")
1839 (let* ((num (match-string 1))
1840 (spec (match-string 2))
1841 (val-alist (assoc spec specification))
1842 (val (cdr val-alist)))
1843 (unless val-alist
1844 (error "Invalid format character: %s" spec))
1845 ;; Value for a valid spec may not exist. Not an error, just nothing to show.
1846 (unless val (setq val ""))
1847 ;; Pad result to desired length.
1848 (let ((text (format (concat "%" num "s") val)))
1849 ;; Insert first, to preserve text properties.
1850 (insert-and-inherit text)
1851 ;; Delete the specifier body.
1852 (delete-region (+ (match-beginning 0) (length text))
1853 (+ (match-end 0) (length text)))
1854 ;; Delete the percent sign.
1855 (delete-region (1- (match-beginning 0)) (match-beginning 0)))))
1856 ;; Signal an error on bogus format strings.
1857 (t
1858 (error "Invalid format string"))))
1859 (buffer-string)))
1860
1861 ;; --------------------------------------------------
1862 ;; Display formats - defaults
1863 ;; --------------------------------------------------
1864
1865 ;; FIXME: optional format strings would avoid having to define a
1866 ;; function for specifiers which may be empty.
1867
1868 (defvar emms-browser-default-format "%i%n"
1869 "Indent + name.")
1870
1871 ;; tracks
1872 (defvar emms-browser-info-title-format
1873 'emms-browser-track-artist-and-title-format)
1874 (defvar emms-browser-playlist-info-title-format
1875 'emms-browser-track-artist-and-title-format)
1876
1877 (defun emms-browser-get-format (bdata target)
1878 (let* ((type (emms-browser-bdata-type bdata))
1879 (target-str (or
1880 (and (eq target 'browser) "")
1881 (concat (symbol-name target) "-")))
1882 (sym
1883 (intern
1884 (concat "emms-browser-"
1885 target-str
1886 (symbol-name type)
1887 "-format"))))
1888 (if (boundp sym)
1889 (symbol-value sym)
1890 emms-browser-default-format)))
1891
1892 (defun emms-browser-track-artist-and-title-format (_bdata fmt)
1893 (concat
1894 "%i"
1895 (let ((track (emms-browser-format-elem fmt "T")))
1896 (if (and track (not (string= track "0")))
1897 "%T. "
1898 ""))
1899 "%n"))
1900
1901 ;; albums - we define two formats, one for a small cover (browser),
1902 ;; and one for a medium sized cover (playlist).
1903 (defvar emms-browser-info-album-format
1904 'emms-browser-year-and-album-fmt)
1905 (defvar emms-browser-playlist-info-album-format
1906 'emms-browser-year-and-album-fmt-med)
1907
1908 (defun emms-browser-year-and-album-fmt (_bdata fmt)
1909 (concat
1910 "%i%cS"
1911 (let ((year (emms-browser-format-elem fmt "y")))
1912 (if (and year (not (string= year "0")))
1913 "(%y) "
1914 ""))
1915 "%n"))
1916
1917 (defun emms-browser-year-and-album-fmt-med (_bdata fmt)
1918 (concat
1919 "%i%cM"
1920 (let ((year (emms-browser-format-elem fmt "y")))
1921 (if (and year (not (string= year "0")))
1922 "(%y) "
1923 ""))
1924 "%n"))
1925
1926 ;; --------------------------------------------------
1927 ;; Display faces
1928 ;; --------------------------------------------------
1929
1930 (defmacro emms-browser-make-face (name dark-col light-col height)
1931 (let ((face-name (intern (concat "emms-browser-" name "-face"))))
1932 `(defface ,face-name
1933 '((((class color) (background dark))
1934 (:foreground ,dark-col
1935 :height ,height))
1936 (((class color) (background light))
1937 (:foreground ,light-col
1938 :height ,height))
1939 (((type tty) (class mono))
1940 (:inverse-video t))
1941 (t (:background ,dark-col)))
1942 ,(concat "Face for "
1943 name
1944 " in a browser/playlist buffer."))))
1945
1946 (emms-browser-make-face "albumartist" "#aaaabb" "#444455" 1.3)
1947 (emms-browser-make-face "year/genre" "#aaaaff" "#444477" 1.5)
1948 (emms-browser-make-face "artist" "#aaaaff" "#444477" 1.3)
1949 (emms-browser-make-face "composer" "#aaaaff" "#444477" 1.3)
1950 (emms-browser-make-face "performer" "#aaaaff" "#444477" 1.3)
1951 (emms-browser-make-face "album" "#aaaaff" "#444477" 1.1)
1952 (emms-browser-make-face "track" "#aaaaff" "#444477" 1.0)
1953
1954 ;; --------------------------------------------------
1955 ;; Filtering
1956 ;; --------------------------------------------------
1957 ;;; Deprecated Browser filter making.
1958 ;;; Filters made in this way will continue working
1959 ;;; with emms-filters.
1960 ;;; Working directly with emms-filters is better.
1961 ;;; See emms-filters.el.
1962 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1963 ;; I dont know if anyone uses this function, but here is one
1964 ;; just in case. Probably a bad idea.
1965 (defun emms-browser-refilter (filter)
1966 "Push FILTER to emms-filter-stack and re-render.
1967 Filter can be a string name or a filter cons.
1968 Non-destructive if a filter of the same filter name already
1969 exists.
1970
1971 Deprecated. See emms-filters.
1972
1973 Equivalent to `(emms-filters-push filter-name)' when using a registered
1974 emf filter directly.
1975
1976 The FILTER will be registered with emms-filters if it is
1977 a cons filter and its name is not already taken.
1978
1979 The filter name will be pushed to the emms-filters-filter-stack, making
1980 it the active filter."
1981 (when (not (stringp filter))
1982 (emms-filters-register-if-missing filter))
1983 (emms-filters-push
1984 (if (stringp filter)
1985 filter
1986 (car filter))))
1987
1988 (defmacro emms-browser-make-filter (name filter-func)
1989 "Make a user-level function for filtering tracks and put
1990 it into the emms-filters-filter-ring.
1991
1992 Deprecated: See emms-filters-make-filter
1993
1994 Altered from the original to invert the return value of the filter.
1995 The resulting inverted filter is used directly in emms-filters like any
1996 other emf filter.
1997
1998 This:
1999 - Defines a filter cons variable emms-browser-filter-NAME of (name . func).
2000 - The filter is registered into emms-filters-filters.
2001 - Added to the emf filter menus under `browser-filters'.
2002 - Added to the emms-filters-filter-ring.
2003 - Defines an interactive function emms-browser-show-NAME."
2004 (let ((funcnam (intern (concat "emms-browser-show-" name)))
2005 (var (intern (concat "emms-browser-filter-" name)))
2006 (desc (concat "Filter the cache using rule '"
2007 name "'")))
2008 `(progn
2009 (defvar ,var nil ,desc)
2010 (setq ,var (cons ,name
2011 (lambda (track) ;; invert the func.
2012 (not (funcall ,filter-func track)))))
2013 ;;(add-to-list 'emms-browser-filters ,var)
2014 (emms-filters-register-filter-into-ring ,var)
2015 (defun ,funcnam ()
2016 ,desc
2017 (interactive)
2018 (emms-filters-push name)))))
2019
2020 ;; The original, inverted, emms-browser filters examples
2021 ;; Works with the above make-filter macro and Emms-filters.
2022 ;; Deprecated.
2023 (defun emms-browser-filter-only-dir (dirname)
2024 "Generate a function to check if a track is in DIRNAME.
2025 If the track is not in DIRNAME, return t."
2026 (let ((re (concat "^" (expand-file-name dirname))))
2027 (lambda (track)
2028 (not (string-match re (emms-track-get track 'name))))))
2029
2030 (defun emms-browser-filter-only-type (type)
2031 "Generate a function to check a track's type.
2032 If the track is not of TYPE, return t."
2033 (lambda (track)
2034 (not (eq type (emms-track-get track 'type)))))
2035
2036 ;; seconds in a day (* 60 60 24) = 86400
2037 (defun emms-browser-filter-only-recent (days)
2038 "Show only tracks played within the last number of DAYS."
2039 (lambda (track)
2040 (let ((min-date (time-subtract
2041 (current-time)
2042 (seconds-to-time (* days 86400))))
2043 last-played)
2044 (not (and (setq last-played
2045 (emms-track-get track 'last-played nil))
2046 (time-less-p min-date last-played))))))
2047
2048 ;; --------------------------------------------------
2049 ;; Searching
2050 ;; --------------------------------------------------
2051 ;; These functions are here for backward compatibility.
2052 ;; See emms-filters.el.
2053
2054 (defun emms-browser-search-by-albumartist ()
2055 (interactive)
2056 (emms-filters-search-by-albumartist))
2057
2058 (defun emms-browser-search-by-artist ()
2059 (interactive)
2060 (emms-filters-search-by-artist))
2061
2062 (defun emms-browser-search-by-composer ()
2063 (interactive)
2064 (emms-filters-search-by-composer))
2065
2066 (defun emms-browser-search-by-performer ()
2067 (interactive)
2068 (emms-filters-search-by-performer))
2069
2070 (defun emms-browser-search-by-title ()
2071 (interactive)
2072 (emms-filters-search-by-title))
2073
2074 (defun emms-browser-search-by-album ()
2075 (interactive)
2076 (emms-filters-search-by-album))
2077
2078 (defun emms-browser-search-by-names ()
2079 (interactive)
2080 (emms-filters-search-by-names-and-titles))
2081
2082
2083 ;;; Thumbnails
2084 ;; TODO: Add function to clear the cache from thumbnails that have no associated
2085 ;; cover folders. This is especially useful in case the music library path
2086 ;; changes: currently, all covers will have to be re-cached while the old ones
2087 ;; are left as is, useless.
2088
2089 ;; TODO: `emms-browser-expand-all' is slow because of all the covers (about 30
2090 ;; sec fot 1500 covers in my case). Try to profile & optimize. It will
2091 ;; probably not be enough and we might need to run emms-browser-expand-all
2092 ;; asynchronously.
2093
2094
2095 (defvar emms-browser-thumbnail-directory (expand-file-name "thumbnails" emms-directory)
2096 "Directory where to store cover thumbnails.")
2097
2098 (defvar emms-browser-thumbnail-small-size 128
2099 "Cover thumbnail will be resized if necessary so that neither
2100 width nor height exceed this dimension.")
2101 (defvar emms-browser-thumbnail-medium-size 256
2102 "Cover thumbnail will be resized if necessary so that neither
2103 width nor height exceed this dimension.")
2104 (defvar emms-browser-thumbnail-large-size 1024 ; Emms does not use large covers as of 2017-11-26.
2105 "Cover thumbnail will be resized if necessary so that neither
2106 width nor height exceed this dimension.")
2107
2108 (defun emms-browser-thumbnail-filter-default (dir)
2109 "Select covers containing \\='front\\=' or \\='cover\\=' in DIR.
2110 If none was found, fallback on `emms-browser-thumbnail-filter-all\\='.
2111
2112 See `emms-browser-thumbnail-filter\\='."
2113 (when (file-directory-p dir)
2114 (let ((ls (directory-files dir t nil t))
2115 (case-fold-search t)
2116 covers)
2117 (dolist (ext emms-browser-covers-file-extensions)
2118 (setq covers (append (seq-filter (lambda (c) (string-match (concat "\\(front\\|cover\\).*\\." ext) c)) ls) covers)))
2119 (unless covers
2120 (setq covers (emms-browser-thumbnail-filter-all dir)))
2121 covers)))
2122
2123 (defun emms-browser-thumbnail-filter-all (dir)
2124 "Return the list of all files with `emms-browser-covers-file-extensions\\=' in DIR.
2125
2126 See `emms-browser-thumbnail-filter'."
2127 (let (covers)
2128 (dolist (ext emms-browser-covers-file-extensions covers)
2129 (setq covers (append (file-expand-wildcards (expand-file-name (concat "*." ext) dir)) covers)))))
2130
2131 (defvar emms-browser-thumbnail-filter 'emms-browser-thumbnail-filter-default
2132 "This filter must hold a function that takes a directory argument and returns
2133 a list of cover file names.
2134
2135 The list will be processed by `emms-browser-cache-thumbnail'.
2136 See also `emms-browser-thumbnail-filter-default'.")
2137
2138 (defvar emms-browser-thumbnail-convert-program (executable-find "convert")
2139 "The ImageMagick's `convert' program.")
2140
2141 (defun emms-browser-cache-thumbnail (dir size)
2142 "Return cached cover SIZE for album in DIR.
2143
2144 SIZE must be \\='small, \\='medium or \\='large. It will determine the
2145 resolution of the cached file. See the variables
2146 `emms-browser-thumbnail-SIZE-size\\='.
2147
2148 If cover is not cached or if cache is out-of-date, re-cache it.
2149 If both the width and the height of the cover are smaller than
2150 `emms-browser-thumbnail-SIZE-size\\=', it need not be cached and
2151 will be used directly.
2152
2153 Emms assumes that you have one album per folder. This function
2154 will always use the same cover per folder.
2155
2156 `emms-browser-covers\\=' can be `fset\\=' to this function."
2157 (if (eq size 'large)
2158 ;; 'large is unused for now. Return empty.
2159 nil
2160 (let (covers
2161 cover
2162 (cover-width 0) (cover-height 0)
2163 (size-value (symbol-value (intern (concat "emms-browser-thumbnail-" (symbol-name size) "-size"))))
2164 cache-dest-file)
2165 (setq covers (funcall emms-browser-thumbnail-filter dir))
2166 (if (not covers)
2167 nil
2168 ;; Find best quality cover.
2169 (let (res)
2170 (dolist (c covers)
2171 (setq res (image-size (create-image c) t))
2172 ;; image-size does not error, it returns (30 . 30) instead.
2173 (and (> (car res) 30) (> (cdr res) 30)
2174 (< cover-width (car res)) (< cover-height (cdr res))
2175 (setq cover-width (car res) cover-height (cdr res) cover c))))
2176 (if (and (>= size-value cover-width) (>= size-value cover-height))
2177 ;; No need to resize and cache.
2178 cover
2179 (let ((cache-dest (concat emms-browser-thumbnail-directory (file-name-directory cover))))
2180 (mkdir cache-dest t)
2181 (setq cache-dest-file (concat
2182 (expand-file-name "cover_" cache-dest)
2183 (symbol-name size)
2184 "." (file-name-extension cover))))
2185 (and emms-browser-thumbnail-convert-program
2186 (or (not (file-exists-p cache-dest-file))
2187 (time-less-p (nth 5 (file-attributes cache-dest-file))
2188 (nth 5 (file-attributes cover)) ))
2189 (let (err msg)
2190 ;; An Elisp function would be faster, but Emacs does not seem be be
2191 ;; able to resize image files. It can resize image displays though.
2192 ;; TODO: Add image resizing support to Emacs.
2193 (setq msg (with-output-to-string
2194 (with-current-buffer standard-output
2195 (setq err (call-process (executable-find "convert") nil '(t t) nil
2196 "-resize" (format "%sx%s" size-value size-value)
2197 cover
2198 cache-dest-file)))))
2199 (when (/= err 0)
2200 (warn "%s" msg)
2201 (setq cache-dest-file nil))))
2202 cache-dest-file)))))
2203
2204 (defvar emms-browser--cache-hash nil
2205 "Cache for `emms-browser-cache-thumbnail-async'.")
2206
2207 (defun emms-browser-cache-thumbnail-async (dir size)
2208 "Like `emms-browser-cache-thumbnail' but caches queries for faster lookups.
2209 The drawback is that if changes are made to the covers in DIR
2210 after `emms-browser-cache-thumbnail-async' queried them, it won't
2211 be taken into account. Call `emms-browser-clear-cache-hash' to
2212 refresh the cache."
2213 (unless emms-browser--cache-hash
2214 (setq emms-browser--cache-hash (make-hash-table :test 'equal)))
2215 (let* ((key (cons dir size))
2216 (val (gethash key emms-browser--cache-hash)))
2217 (or val
2218 (puthash key (emms-browser-cache-thumbnail dir size)
2219 emms-browser--cache-hash))))
2220
2221 (defun emms-browser-clear-cache-hash ()
2222 "Resets `emms-browser-cache-thumbnail-async' cache.
2223 This is useful if there were changes on disk after
2224 `emms-browser-cache-thumbnail-async' first cached them."
2225 (interactive)
2226 (clrhash emms-browser--cache-hash))
2227
2228 (provide 'emms-browser)
2229 ;;; emms-browser.el ends here