feat: fetch one or multiple lyrics from EMMS

This commit is contained in:
Pavel Korytov 2021-08-06 19:08:10 +03:00
parent f55238d2f8
commit 0e34077165
2 changed files with 90 additions and 15 deletions

View file

@ -65,7 +65,7 @@ user to pick the matching search result."
callback callback
sync))) sync)))
:error (cl-function :error (cl-function
(lambda (&&key error-thrown &allow-other-keys) (lambda (&key error-thrown &allow-other-keys)
(message "Error!: %S" error-thrown)))))) (message "Error!: %S" error-thrown))))))
(defun lyrics-fetcher--genius-format-query (track) (defun lyrics-fetcher--genius-format-query (track)
@ -143,8 +143,9 @@ expensive."
(buffer-substring-no-properties (buffer-substring-no-properties
(point-min) (point-min)
(point-max))))))) (point-max)))))))
:error
(cl-function (cl-function
(lambda (&&key error-thrown &allow-other-keys) (lambda (&key error-thrown &allow-other-keys)
(message "Error!: %S" error-thrown))))) (message "Error!: %S" error-thrown)))))
(provide 'lyrics-fetcher-genius) (provide 'lyrics-fetcher-genius)

View file

@ -124,11 +124,11 @@ The function has to take into account that:
(substring artist 0 (min (length artist) 40)) (substring artist 0 (min (length artist) 40))
(substring title 0 (min (length title) 190)))))) (substring title 0 (min (length title) 190))))))
(defun lyrics-fetcher-show-lyrics (&optional track) (cl-defun lyrics-fetcher-show-lyrics (&optional track &key suppress-open callback force-fetch sync)
"Show lyrics for TRACK. "Show lyrics for TRACK.
TRACK can be either a string or an EMMS alist. If TRACK is not TRACK can be either a string or an EMMS alist. If TRACK is not
set, e.g. when called interactively, then set, for instance when called interactively, then
`lyrics-fetcher-current-track-method' will be used to get the `lyrics-fetcher-current-track-method' will be used to get the
current playing track. current playing track.
@ -137,12 +137,18 @@ otherwise performs fetch according to
`lyrics-fetcher-current-track-method'. The resulting file will be `lyrics-fetcher-current-track-method'. The resulting file will be
saved with a name from `lyrics-fetcher-format-file-name-method'. saved with a name from `lyrics-fetcher-format-file-name-method'.
If called with \\[universal-argument], then ask the user to select a If SUPPRESS-OPEN is non-nil, don't pop up a window with lyrics. This
matching song. This may be useful if there are multiple tracks with is useful when performing a mass fetch.
similar names, and the top one isnt the one required.
If called with \\[universal-argument] \\[universal-argument], If CALLBACK is non-nil, call it with the resulting filename.
then also always fetch the lyric text."
If called with \\[universal-argument] or FORCE-FETCH is non-nil, then
always refetch the lyrics text.
If called with \\[universal-argument] \\[universal-argument] or SYNC
is non-nil, then ask the user to select a matching song. This may be
useful if there are multiple tracks with similar names, and the top
one isnt the one required."
(interactive) (interactive)
(when (not track) (when (not track)
(setq track (funcall lyrics-fetcher-current-track-method))) (setq track (funcall lyrics-fetcher-current-track-method)))
@ -150,15 +156,27 @@ then also always fetch the lyric text."
(message "Error: no track found!") (message "Error: no track found!")
(let ((song-name (funcall lyrics-fetcher-format-song-name-method track)) (let ((song-name (funcall lyrics-fetcher-format-song-name-method track))
(file-name (funcall lyrics-fetcher-format-file-name-method track)) (file-name (funcall lyrics-fetcher-format-file-name-method track))
(sync (member (prefix-numeric-value current-prefix-arg) '(4 16))) ;; The function is indented to be called both interactive
(force-fetch (member (prefix-numeric-value current-prefix-arg) '(16)))) ;; and via recursion with asyncronous callbacks, during with
;; `current-prefix-arg' will be unset. So this is necessary
;; to pass the behavior down the recursion.
(force-fetch (or force-fetch (member (prefix-numeric-value current-prefix-arg) '(4 16))))
(sync (or sync (member (prefix-numeric-value current-prefix-arg) '(16)))))
(if (and (not force-fetch) (lyrics-fetcher--lyrics-saved-p file-name)) (if (and (not force-fetch) (lyrics-fetcher--lyrics-saved-p file-name))
(lyrics-fetcher--open-lyrics file-name track) (progn
(message "Found fetched lyrics for: %s" song-name)
(when callback
(funcall callback file-name))
(unless suppress-open
(lyrics-fetcher--open-lyrics file-name track)))
(funcall (funcall
lyrics-fetcher-fetch-method track lyrics-fetcher-fetch-method track
(lambda (result) (lambda (result)
(lyrics-fetcher--save-lyrics result file-name) (lyrics-fetcher--save-lyrics result file-name)
(lyrics-fetcher--open-lyrics file-name track)) (unless suppress-open
(lyrics-fetcher--open-lyrics file-name track))
(when callback
(funcall callback file-name)))
sync))))) sync)))))
(defun lyrics-fetcher-show-lyrics-query (query) (defun lyrics-fetcher-show-lyrics-query (query)
@ -171,6 +189,62 @@ See `lyrics-fetcher-show-lyrics' for behavior."
(interactive "sEnter query: ") (interactive "sEnter query: ")
(lyrics-fetcher-show-lyrics query)) (lyrics-fetcher-show-lyrics query))
(defun lyrics-fetcher-emms-browser-fetch-at-point ()
"Fetch data for the current point in EMMS browser.
If the point contains just one song, it will be fetched the usual way
via `lyrics-fetcher-show-lyrics'. Lyrics will be show upon successful
completion.
If the point contains many songs (e.g. it's an album), the lyrics
will be fetched consequentially for every song. Note that the
process will be stopped at the first failure.
Behavior of the function is modified by \\[universal-argument]
the same way as `lyrics-fetcher-show-lyrics'."
(interactive)
(let ((data (emms-browser-bdata-at-point)))
(if (not data)
(message "Nothing is found at point!")
(if (eq (cdr (assoc 'type data)) 'info-title)
(lyrics-fetcher-show-lyrics (cdadr (assoc 'data data)))
(lyrics-fetcher--fetch-many
(lyrics-fetcher--emms-extract-songs data))))))
(defun lyrics-fetcher--emms-extract-songs (bdata)
"Extract list song alists from EMMS BDATA at point."
(if (eq (cdr (assoc 'type bdata)) 'info-title)
(list (cdadr (assoc 'data bdata)))
(let ((songs '()))
(dolist (datum (cdr (assoc 'data bdata)))
(setq songs (append songs (lyrics-fetcher--emms-extract-songs datum))))
songs)))
(cl-defun lyrics-fetcher--fetch-many (tracks &optional &key start force-fetch sync)
"Fetch lyrics for every track in the TRACKS list.
This functions calls itself recursively. START is an indicator of
position in the list.
FORCE-FETCH and SYNC are passed to `lyrics-fetcher-show-lyrics'."
(unless start
(setq start 0))
(message "Fetching lyrics for %s / %s songs" start (+ start (length tracks)))
(let ((current-prefix-arg current-prefix-arg)
(force-fetch (or force-fetch (member (prefix-numeric-value current-prefix-arg) '(4 16))))
(sync (or sync (member (prefix-numeric-value current-prefix-arg) '(16)))))
(unless (seq-empty-p tracks)
(lyrics-fetcher-show-lyrics
(car tracks)
:suppress-open t
:callback
(lambda (&rest _)
(lyrics-fetcher--fetch-many
(cdr tracks)
:start (+ start 1)
:force-fetch force-fetch
:sync sync))))))
(defun lyrics-fetcher--lyrics-saved-p (filename) (defun lyrics-fetcher--lyrics-saved-p (filename)
"Check if lyrics for FILENAME are already saved." "Check if lyrics for FILENAME are already saved."
(file-exists-p (lyrics-fetcher--process-filename filename))) (file-exists-p (lyrics-fetcher--process-filename filename)))
@ -213,8 +287,8 @@ TRACK is either a string or EMMS alist."
(defvar lyrics-fetcher-view-mode-map (defvar lyrics-fetcher-view-mode-map
(let ((keymap (make-sparse-keymap))) (let ((keymap (make-sparse-keymap)))
(define-key keymap (kbd "q") 'lyrics-fetcher--close-lyrics) (define-key keymap (kbd "q") 'lyrics-fetcher--close-lyrics)
(when (fboundp 'evil-define-key) (when (fboundp 'evil-define-key*)
(evil-define-key 'normal keymap (evil-define-key* 'normal keymap
"q" 'lyrics-fetcher--close-lyrics)) "q" 'lyrics-fetcher--close-lyrics))
keymap) keymap)
"Keymap for `lyrics-fetcher-mode'.") "Keymap for `lyrics-fetcher-mode'.")