From 2386189ec8a19a74d7b8a46e08a9fa6d974a6305 Mon Sep 17 00:00:00 2001 From: nik gaffney Date: Tue, 30 May 2023 09:41:39 +0200 Subject: [PATCH] Then they left without a sound --- README.org | 23 ++++++++- listenbrainz.el | 25 ++++++---- musicbrainz.el | 126 +++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 146 insertions(+), 28 deletions(-) diff --git a/README.org b/README.org index d63d246..8da83cc 100644 --- a/README.org +++ b/README.org @@ -3,11 +3,12 @@ #+author: #+title: MusicBrainz & ListenBrainz & others - +* Thus :TOC_2_gh: - [[#musicbrainz][MusicBrainz]] - [[#musicbrainz-api][MusicBrainz API]] - [[#searching--browsing][searching & browsing]] - [[#some-examples][some examples]] + - [[#cover-art][cover art]] - [[#mbid][MBID]] - [[#ambiguity][ambiguity]] - [[#incompleteness][incompleteness]] @@ -104,6 +105,25 @@ The MBID can be used for specific lookups (and checked if needed using =musicbra | 91 | Universal Music | plain logo: “Universal Music” (ongoing) | [[https://musicbrainz.org/label/13a464dc-b9fd-4d16-a4f4-d4316f6a46c7][13a464dc-b9fd-4d16-a4f4-d4316f6a46c7]] | | 90 | ZYX Music | (1992 ongoing) | [[https://musicbrainz.org/label/6844efda-a451-431e-8cc1-48ab111b4711][6844efda-a451-431e-8cc1-48ab111b4711]] | +** cover art + +Cover art for a release may be available from the [[http://coverartarchive.org/][Cover Art Archive]] and can be accessed via the [[https://musicbrainz.org/doc/Cover_Art_Archive/API][API]] + +#+BEGIN_SRC emacs-lisp +(musicbrainz-coverart "a929130a-535c-4827-8663-f048e1a7ca0d") +#+END_SRC + + +#+BEGIN_SRC emacs-lisp +(musicbrainz-coverart-front "a929130a-535c-4827-8663-f048e1a7ca0d") +#+END_SRC + + +#+BEGIN_SRC emacs-lisp +(musicbrainz-lookup-release "a929130a-535c-4827-8663-f048e1a7ca0d") +#+END_SRC + + | Head Cleaning Cassette | Cassette Case | [[https://musicbrainz.org/release/a929130a-535c-4827-8663-f048e1a7ca0d][a929130a-535c-4827-8663-f048e1a7ca0d]] | ** MBID @@ -119,6 +139,7 @@ returns =410c9baf-5469-44f6-9852-826524b80c61= (musicbrainz-mbid-p "410c9baf-5469-44f6-9852-826524b80c61") #+END_SRC + ** ambiguity From the docs… diff --git a/listenbrainz.el b/listenbrainz.el index fc32c66..43084f7 100644 --- a/listenbrainz.el +++ b/listenbrainz.el @@ -262,7 +262,8 @@ macroexpands to something like -> :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "Listens for user: %s" username))))))) + (message "Listens for user: %s\n%s" username + (if data data "")))))))) (princ (listenbrainz--format-listens response)))) @@ -279,7 +280,8 @@ macroexpands to something like -> :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "User playing now: %s" username))))))) + (message "User playing now: %s\n%s" username + (if data data "")))))))) (princ (listenbrainz--format-playing response)))) @@ -358,7 +360,8 @@ possible values are week, month, year, all_time, defaults to all_time." :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "Top recordings for user: %s" username))))))) + (message "Top recordings for user: %s\n%s" username + (if data data "")))))))) (princ (listenbrainz--format-stats-2 response)))) @@ -381,7 +384,8 @@ possible values are week, month, year, all_time, defaults to all_time." :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "Top releases for user: %s" username))))))) + (message "Top releases for user: %s\n%s" username + (if data data "")))))))) (princ (listenbrainz--format-stats-0 response)))) @@ -404,7 +408,8 @@ possible values are week, month, year, all_time, defaults to all_time." :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "Top artists for user: %s" username))))))) + (message "Top artists for user: %s\n%s" username + (if data data "")))))))) (princ (listenbrainz--format-stats-1 response)))) @@ -430,10 +435,11 @@ OUTPUT format can be either `list' (default) or `graph'." :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "Followers for %s" username))))))) + (message "Followers for %s\n%s" username + (if data data "")))))))) (if (string= "graph" output) - (princ (listenbrainz--format-followers-graph response)) - (princ (listenbrainz--format-followers-list response))))) + (princ (listenbrainz--format-followers-graph response)) + (princ (listenbrainz--format-followers-list response))))) ;;;###autoload (defun listenbrainz-following (username) @@ -448,7 +454,8 @@ OUTPUT format can be either `list' (default) or `graph'." :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "Users %s is following" username))))))) + (message "Users %s is following\n%s" username + (if data data "")))))))) (princ (listenbrainz--format-following response)))) diff --git a/musicbrainz.el b/musicbrainz.el index 506a23b..50d5a1d 100644 --- a/musicbrainz.el +++ b/musicbrainz.el @@ -43,6 +43,7 @@ (require 'request) (require 'json) +(require 'pp) ;;; ;; ;; ; ; ; ; ; ; ;; @@ -51,11 +52,17 @@ ;;; ; ;; ;; (defcustom musicbrainz-api-url "https://musicbrainz.org/ws/2" - "URL for musicbrainz API. + "URL for MusicBrainz API. Documentation available at https://musicbrainz.org/doc/MusicBrainz_API" :type 'string :group 'musicbrainz) +(defcustom musicbrainz-coverart-api-url "http://coverartarchive.org" + "URL for MusicBrainz Cover Art Archive API. +Documentation available at https://musicbrainz.org/doc/Cover_Art_Archive/API" + :type 'string + :group 'musicbrainz) + (defcustom musicbrainz-api-token "" "An auth token is required for some functions." :type 'string @@ -238,6 +245,7 @@ The QUERY field supports the full Lucene Search syntax, some details can be found near https://musicbrainz.org/doc/MusicBrainz_API/Search or in the Lucene docs." + (interactive "sMusicBrainz search type: \nsMusicBrainz search query: ") (message "MusicBrainz: searching %s=%s" type query) ;; queries may need to be escaped (let* ((max (if limit limit 1)) @@ -252,10 +260,13 @@ or in the Lucene docs." :headers (list `("User-Agent" . ,musicbrainz-user-agent)) :parser 'json-read :sync t - :success (cl-function - (lambda (&key data &allow-other-keys) - (message "ok"))))))) - response)) + :sucess (cl-function + (lambda (&key data &allow-other-keys) + (message "ok: %s" data))) + )))) + (if (called-interactively-p 'any) + (message "%s" (pp response)) + response))) ;;;###autoload @@ -269,14 +280,14 @@ Heuristics. - if QUERY is an MBID, check artist, recording, etc - if QUERY is text, search for artists or recordings, etc" - (message "MusicBrainz: query %s" query) + (message "MusicBrainz: query %s %s" query (if extras extras "")) (if (musicbrainz-mbid-p query) ;; search (lookup) for things that could have an mbid (let ((mbid query)) - (message "searching mbid: %s" mbid)) - ;; search (search/browse/query) for other things - (progn - (message "searching other: %s" mbid)))) + (message "searching mbid: %s" mbid) + ;; search (search/browse/query) for other things + (progn + (message "searching other: %s" mbid))))) ;; generate search functions @@ -291,14 +302,14 @@ Optionally return LIMIT number of results." name))) `(defun ,f (query &optional limit) ,doc (let* ((max (if limit limit 1)) (response - (musicbrainz-search ,name query max))) + (musicbrainz-search ,name query max))) (let-alist response (format ,format-string ,@format-args)))))) (defmacro musicbrainz--defsearch-2 (name format-string format-args alist) "Generate lookup function to format multiple items. -QUERY SUBQUERY FORMAT-STRING FORMAT-ARGS ALIST +NAME FORMAT-STRING FORMAT-ARGS ALIST See listenbrainz--deformatter for details." (let ((f (intern (concat "musicbrainz-search-" name))) (doc (format "Search for %s using QUERY and show matches. @@ -480,6 +491,7 @@ Subqueries /ws/2/work /ws/2/url" + (interactive "sMusicBrainz entity type: \nsMusicBrainz MBID for entity: ") (message "MusicBrainz: lookup: %s/%s" entity mbid) (if (and (musicbrainz-core-entity-p entity) (musicbrainz-mbid-p mbid)) @@ -495,10 +507,13 @@ Subqueries :sync t :success (cl-function (lambda (&key data &allow-other-keys) - (message "%s data: %s" entity mbid))))))) - response) - (error "MusicBrainz: search requires a valid MBID and entity (i.e. one of %s)" - musicbrainz-entities-core))) + (when data + (message "%s data: %s" entity mbid)))))))) + (if (called-interactively-p 'any) + (message "%s" (pp response)) + response)) + (user-error "MusicBrainz: search requires a valid MBID and entity (i.e. one of %s)" + musicbrainz-entities-core))) ;; relationship lookups @@ -515,8 +530,10 @@ Subqueries NAME FORMAT-STRING FORMAT-ARGS See listenbrainz--deformatter for details." (let ((f (intern (concat "musicbrainz-lookup-" name))) - (doc "MusicBrainz lookup.")) + (doc "MusicBrainz lookup.") + (prompt (format "sMusicBrainz lookup %s by MBID: " name))) `(defun ,f (mbid) ,doc + (interactive ,prompt) (let ((response (musicbrainz-lookup ,name mbid))) (let-alist response @@ -528,8 +545,10 @@ See listenbrainz--deformatter for details." QUERY SUBQUERY FORMAT-STRING FORMAT-ARGS ALIST See listenbrainz--deformatter for details." (let ((f (intern (format "musicbrainz-lookup-%s-%s" query subquery))) - (doc "MusicBrainz lookup.")) + (doc "MusicBrainz lookup.") + (prompt (format "sMusicBrainz lookup %s %s by MBID: " query subquery))) `(defun ,f (mbid) ,doc + (interactive ,prompt) (let ((response (musicbrainz-lookup ,query mbid ,subquery))) (let-alist response @@ -767,6 +786,77 @@ Optionally limit the search to TYPE results for ENTITY." +;;;;;; ; ; ;; ; ; ; ; ; ;; ; +;; +;; Cover Art Archive API +;; https://musicbrainz.org/doc/Cover_Art_Archive/API +;; +;;;; ; ; ; ; ; + +;; /release/{mbid}/ +;; /release/{mbid}/front +;; /release/{mbid}/back +;; /release/{mbid}/{id} +;; /release/{mbid}/({id}|front|back)-(250|500|1200) +;; +;; /release-group/{mbid}/ +;; /release-group/{mbid}/front[-(250|500|1200)] + +;;;###autoload +(defun musicbrainz-coverart (mbid &optional release-group) + "Search MusicBrainz Cover Art Archive for release MBID. +When RELEASE-GROUP is non-nil MBID is for a release group, rather than release." + (message "MusicBrainz: cover art for %s" mbid) + (message "url: %s/release/%s" musicbrainz-coverart-api-url mbid) + (let ((response + (request-response-data + (request + (url-encode-url + (format "%s/release/%s" musicbrainz-coverart-api-url mbid)) + :type "GET" + :header (list `("User-Agent" . ,musicbrainz-user-agent)) + :parser 'json-read + :sync t + :success (cl-function + (lambda (&key data &allow-other-keys) + (message "ok"))))))) + response)) + +(defun musicbrainz-coverart-file-front (mbid) + "Get the MusicBrainz Cover Art front cover file for MBID." + (message "MusicBrainz: cover art (front) for %s" mbid) + (message "url: %s/release/%s/front" musicbrainz-coverart-api-url mbid) + (let ((response + (request-response-data + (request + (url-encode-url + (format "%s/release/%s/front" musicbrainz-coverart-api-url mbid)) + :type "GET" + :header (list `("User-Agent" . ,musicbrainz-user-agent)) + :sync t + :success (cl-function + (lambda (&key data &allow-other-keys) + (message "ok"))))))) + response)) + +(defun musicbrainz-coverart-file-back (mbid) + "Get the MusicBrainz Cover Art back cover file for MBID." + (message "MusicBrainz: cover art (back) for %s" mbid) + (message "url: %s/release/%s/back" musicbrainz-coverart-api-url mbid) + (let ((response + (request-response-data + (request + (url-encode-url + (format "%s/release/%s/back" musicbrainz-coverart-api-url mbid)) + :type "GET" + :header (list `("User-Agent" . ,musicbrainz-user-agent)) + :sync t + :success (cl-function + (lambda (&key data &allow-other-keys) + (message "ok"))))))) + response)) + + (provide 'musicbrainz) ;;; musicbrainz.el ends here