From 6fba8ac8212b3dfea0e51137cf79e39a019324ea Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Fri, 6 Aug 2021 20:51:54 +0300 Subject: [PATCH] feat: download album cover from genius --- lyrics-fetcher-genius.el | 95 +++++++++++++++++++++++++++++++++++++--- lyrics-fetcher.el | 11 ++++- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/lyrics-fetcher-genius.el b/lyrics-fetcher-genius.el index e1bc44f..6273857 100644 --- a/lyrics-fetcher-genius.el +++ b/lyrics-fetcher-genius.el @@ -29,6 +29,7 @@ ;;; Code: (require 'request) +(require 'cl-lib) (require 'json) (require 'seq) (require 'shr) @@ -61,7 +62,7 @@ user to pick the matching search result." :success (cl-function (lambda (&key data &allow-other-keys) (lyrics-fetcher--genius-fetch-lyrics - (lyrics-fetcher--genius-get-url-from-response data sync) + (lyrics-fetcher--genius-get-data-from-response data 'key sync) callback sync))) :error (cl-function @@ -89,8 +90,8 @@ contains `info-albumartist' or `info-artist' and `info-title'" (cdr (assoc 'full_title result)) (cdr (assoc 'lyrics_state result))))) -(defun lyrics-fetcher--genius-get-url-from-response (data &optional ask) - "Retrive a song URL from the Genius response DATA. +(defun lyrics-fetcher--genius-get-data-from-response (data key &optional ask) + "Retrive a song KEY from the Genius response DATA. If ASK is non-nil, prompt user for a choice, otherwise select the first song." @@ -109,7 +110,7 @@ first song." (mapcar (lambda (entry) (cons (lyrics-fetcher--genius-format-song-title entry) - (assoc 'url (assoc 'result entry)))) + (assoc key (assoc 'result entry)))) results-songs))) (cdr (assoc @@ -118,7 +119,7 @@ first song." results-songs-for-select nil t) results-songs-for-select))) - (assoc 'url (assoc 'result (car results-songs))))))))) + (assoc key (assoc 'result (car results-songs))))))))) (defun lyrics-fetcher--genius-fetch-lyrics (url callback &optional sync) "Fetch lyrics from genius.com page at URL and call CALLBACK with result. @@ -148,5 +149,89 @@ expensive." (lambda (&key error-thrown &allow-other-keys) (message "Error!: %S" error-thrown))))) +(defun lyrics-fetcher-genius-download-cover (track callback folder) + "Downloads album cover of TRACK. + +Requies `lyrics-fetcher-genius-access-token' to be set. + +TRACK should be EMMS-compatible alist or string, take a look at +`lyrics-fetcher--genius-format-query'. If the search is +successful, CALLBACK will be called with the resulting lyrics +text. + +The file will be saved to FOLDER and will be named +\"cover_full.\". + +CALLBACK will be called with a path to the resulting file." + (if (string-empty-p lyrics-fetcher-genius-access-token) + (message "Genius client access token not set!") + (message "Sending a query to genius API...") + (request "https://api.genius.com/search" + :params `(("q" . ,(lyrics-fetcher--genius-format-query track)) + ("access_token" . ,lyrics-fetcher-genius-access-token)) + :parser 'json-read + :success (cl-function + (lambda (&key data &allow-other-keys) + (lyrics-fetcher--genius-save-album-picture + (lyrics-fetcher--genius-get-data-from-response data 'id) + callback + folder))) + :error (cl-function + (lambda (&key error-thrown &allow-other-keys) + (message "Error!: %S" error-thrown)))))) + +(defun lyrics-fetcher--genius-save-album-picture (id callback folder) + "Save an album cover of a song of given ID. + +The file will be saved to FOLDER and will be named +\"cover_full.\". + +CALLBACK will be called with a path to the resulting file." + (request + (format "https://api.genius.com/songs/%s" id) + :parser 'json-read + :params `(("access_token" . ,lyrics-fetcher-genius-access-token)) + :success (cl-function + (lambda (&key data &allow-other-keys) + (lyrics-fetcher--genius-save-album-url data callback folder))) + :error (cl-function + (lambda (&key error-thrown &allow-other-keys) + (message "Error!: %S" error-thrown))))) + +(defun lyrics-fetcher--genius-save-album-url (data callback folder) + "Save album cover of DATA to FOLDER. + +DATA should be a response from GET /songs/:id. The file will be saved +to FOLDER and will be name \"cover_full.\". + +CALLBACK will be called with the path to the resulting file." + (if (not (= (cdr (assoc 'status (assoc 'meta data))) 200)) + (message "Error: %" (cdr (assoc 'message (assoc 'meta data)))) + (let ((url (cdr + (assoc 'cover_art_url + (assoc 'album + (assoc 'song + (assoc 'response my/test-song))))))) + (if (not url) + (message "Album cover not found") + (message "Downloading cover image...") + (request url + :encoding 'binary + :complete + (cl-function + (lambda (&key data &allow-other-keys) + (let ((filename + (concat folder "cover_full" (url-file-extension url)))) + (with-temp-file filename + (toggle-enable-multibyte-characters) + (set-buffer-file-coding-system 'raw-text) + (seq-doseq (char my/picture) + (insert char))) + (funcall callback filename)))) + :error + (cl-function + (lambda (&key error-thrown &allow-other-keys) + (message "Error!: %S" error-thrown)))))))) + (provide 'lyrics-fetcher-genius) ;;; lyrics-fetcher-genius.el ends here diff --git a/lyrics-fetcher.el b/lyrics-fetcher.el index 3c84185..b6d3d77 100644 --- a/lyrics-fetcher.el +++ b/lyrics-fetcher.el @@ -39,7 +39,7 @@ 'lyrics-fetcher-genius-do-search "A function to perform fetching. -As for now, only genius is available, but this is a point of +As of now, only genius is available, but this is a point of extensibility." :type 'function :options '(lyrics-fetcher-genius-do-search) @@ -83,6 +83,15 @@ Has to receive either a string or EMMS alist. Take a look at :type 'function :group 'lyrics-fetcher) +(defcustom lyrics-fetcher-download-cover-method + 'lyrics-fetcher-genius-download-cover + "A function to perform downloading album cover. + +As of now, only genius is available, but this is a point of +extensibility." + :type 'function + :group 'lyrics-fetcher) + (defun lyrics-fetcher-format-song-name (track) "Format TRACK to a human-readable form.