]> git.vanrenterghem.biz Git - musicbrainz.git/blob - musicbrainz.el
Once more the fortress of pure numbers
[musicbrainz.git] / musicbrainz.el
1 ;;; musicbrainz.el --- MusicBrainz API interface -*- coding: utf-8; lexical-binding: t -*-
3 ;; Copyright 2023 FoAM
4 ;;
5 ;; Author: nik gaffney <nik@fo.am>
6 ;; Created: 2023-05-05
7 ;; Version: 0.1
8 ;; Package-Requires: ((emacs "28.1") (request "0.3"))
9 ;; Keywords: music, scrobbling, multimedia
10 ;; URL: https://github.com/zzkt/metabrainz
12 ;; This file is not part of GNU Emacs.
14 ;; This program is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
28 ;;; Commentary:
30 ;; An interface to the MusicBrainz "open music encyclopedia" collection
31 ;; of music metadata. The main entry points are `musicbrainz-search' for
32 ;; general searches and `musicbrainz-lookup' for the more specific.
33 ;; There are also some narrower searches such as `musicbrainz-search-artist'
34 ;;
35 ;; Naming follows the MusicBrainz API reasonably closely, so the official API
36 ;; documentation can provide insight into how searching, browsing and lookups
37 ;; are structured. MusicBrainz has it's particular taxonomy and quirks, so
38 ;; some familiarity may be required to get useful results in some cases.
39 ;;
40 ;; https://musicbrainz.org/doc/MusicBrainz_API
42 ;;; Code:
44 (require 'request)
45 (require 'json)
46 (require 'pp)
48 ;;; ;; ;; ;  ; ;   ;  ;      ;
49 ;;
50 ;; API config
51 ;;
52 ;;; ; ;; ;;
54 (defcustom musicbrainz-api-url "https://musicbrainz.org/ws/2"
55   "URL for MusicBrainz API.
56 Documentation available at https://musicbrainz.org/doc/MusicBrainz_API"
57   :type 'string
58   :group 'musicbrainz)
60 (defcustom musicbrainz-coverart-api-url "http://coverartarchive.org"
61   "URL for MusicBrainz Cover Art Archive API.
62 Documentation available at https://musicbrainz.org/doc/Cover_Art_Archive/API"
63   :type 'string
64   :group 'musicbrainz)
66 (defcustom musicbrainz-api-token ""
67   "An auth token is required for some functions."
68   :type 'string
69   :group 'musicbrainz)
71 (defcustom musicbrainz-user-agent "musicbrainz.el/0.1"
72   "A User-Agent header to identify source of API requests.
73 As seen in https://wiki.musicbrainz.org/MusicBrainz_API/Rate_Limiting"
74   :type 'string
75   :group 'musicbrainz)
77 ;;; ; ; ;;;   ;  ;
78 ;;
79 ;; API entities
80 ;;  https://musicbrainz.org/doc/MusicBrainz_API#Browse
81 ;;
82 ;; On each entity resource, you can perform three different GET requests:
83 ;;  lookup:   /<ENTITY_TYPE>/<MBID>?inc=<INC>
84 ;;  browse:   /<RESULT_ENTITY_TYPE>?<BROWSING_ENTITY_TYPE>=<MBID>&limit=<LIMIT>&offset=<OFFSET>&inc=<INC>
85 ;;  search:   /<ENTITY_TYPE>?query=<QUERY>&limit=<LIMIT>&offset=<OFFSET>
86 ;;
87 ;; Note: Keep in mind only the search request is available without an MBID
88 ;; (or, in specific cases, a disc ID, ISRC or ISWC). If all you have is the
89 ;; name of an artist or album, for example, you'll need to make a search and
90 ;; pick the right result to get its MBID; only then will you able to use it
91 ;; in a lookup or browse request.
92 ;;
93 ;; On the genre resource, we support an "all" sub-resource to fetch all genres,
94 ;; paginated, in alphabetical order:
95 ;;
96 ;;  all:      /genre/all?limit=<LIMIT>&offset=<OFFSET>
97 ;;
98 ;; ; ;;; ;
100 (defconst musicbrainz-entities-core
101   (list "area" "artist" "event" "genre" "instrument" "label" "place"
102         "recording" "release" "release-group" "series" "work" "url")
103   "API resources which represent core entities in the MusicBrainz database.")
105 (defconst musicbrainz-entities-non-core
106   (list "rating" "tag" "collection")
107   "API resources which represent non-core entities in the MusicBrainz database.")
109 (defconst musicbrainz-entities-uids
110   (list "discid" "isrc" "iswc")
111   "API resources based on other unique identifiers in the MusicBrainz database.")
113 (defconst musicbrainz-entities-linked
114   (list "area" "artist" "collection" "event" "instrument" "label" "place"
115         "recording" "release" "release-group" "series" "work" "url")
116   "API resources for linked entites in the MusicBrainz database.")
118 (defconst musicbrainz-search-types
119   (list "annotation" "area" "artist" "cdstub" "event" "instrument"
120         "label" "place" "recording" "release" "release-group"
121         "series" "tag" "work" "url")
122   "Valid TYPE parameters for MusicBrainz searches.")
124 (defconst musicbrainz-relationships
125   (list "area-rels" "artist-rels" "event-rels" "instrument-rels"
126         "label-rels" "place-rels" "recording-rels" "release-rels"
127         "release-group-rels" "series-rels" "url-rels" "work-rels")
128   "Valid relationships for lookups.")
131 ;; entity checks
133 (defun musicbrainz-core-entity-p (entity)
134   "Check if ENTITY is a core entity."
135   (if (member entity musicbrainz-entities-core) t nil))
137 (defun musicbrainz-non-core-entity-p (entity)
138   "Check if ENTITY is a non-core entity."
139   (if (member entity musicbrainz-entities-non-core) t nil))
141 (defun musicbrainz-uid-entity-p (entity)
142   "Check if ENTITY is a unique identifier entity."
143   (if (member entity musicbrainz-entities-uids) t nil))
145 (defun musicbrainz-search-type-p (type)
146   "Check if TYPE is a valid search type."
147   (if (member type musicbrainz-search-types) t nil))
150 ;; Linked entities
152 (defun musicbrainz-linked-entity-p (entity)
153   "Check if ENTITY can be used in a browse request (incomplete).
155 The following list shows which linked entities you can use in a browse request:
157  /ws/2/area            collection
158  /ws/2/artist          area, collection, recording, release, release-group, work
159  /ws/2/collection      area, artist, editor, event, label, place, recording,
160                        release, release-group, work
161  /ws/2/event           area, artist, collection, place
162  /ws/2/instrument      collection
163  /ws/2/label           area, collection, release
164  /ws/2/place           area, collection
165  /ws/2/recording       artist, collection, release, work
166  /ws/2/release         area, artist, collection, label, track, track_artist,
167                        recording, release-group
168  /ws/2/release-group   artist, collection, release
169  /ws/2/series          collection
170  /ws/2/work            artist, collection
171  /ws/2/url             resource"
173   (if (member entity musicbrainz-entities-linked) t nil))
176 ;; utils & aux
178 (defun musicbrainz-mbid-p (mbid)
179   "Check (permissive) if MBID is valid and/or well formatted.
180 An MBID is a 36 character Universally Unique Identifier, see https://musicbrainz.org/doc/MusicBrainz_Identifier for details."
182   (if (and (length= mbid 36) ;; length= requires emacs > 28.1
183            (string-match-p
184             (rx (repeat 8 hex)        ;;  [A-F0-9]{8}
185                 "-" (repeat 4 hex)    ;; -[A-F0-9]{4}
186                 "-" (repeat 4 hex)    ;; -[34][A-F0-9]{3}
187                 "-" (repeat 4 hex)    ;; -[89AB][A-F0-9]{3}
188                 "-" (repeat 12 hex))  ;; -[A-F0-9]{12}
189             mbid))
190       t nil))
192 ;; https://lucene.apache.org/core/4_3_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters
193 (defconst musicbrainz-qeury-special-chars
194   (list "+" "-" "&" "|" "!" "(" ")" "{" "}"  "[" "]" "^" "\"" "~" "*" "?" ":" "\\" "/"))
197 (defun musicbrainz-format (response)
198   "Format a generic RESPONSE."
199   (format "%s" (pp response)))
202 (defun musicbrainz--unwrap-0 (entity)
203   "Unwrap (fragile) .artist-credit ENTITY -> .name more or less."
204   (format "%s" (cdar (aref entity 0))))
207 ;;; ;; ;; ;  ; ;   ;  ;      ;
208 ;;
209 ;; Search API
210 ;;  https://musicbrainz.org/doc/MusicBrainz_API/Search
211 ;;
212 ;; The MusicBrainz API search requests provide a way to search for MusicBrainz
213 ;; entities based on different sorts of queries and are provided by a search
214 ;; server built using Lucene technology.
215 ;;
216 ;; Parameters common to all resources
217 ;;
218 ;;  type    Selects the entity index to be searched: annotation, area, artist,
219 ;;          cdstub, event, instrument, label, place, recording, release,
220 ;;          release-group, series, tag, work, url
221 ;;
222 ;;  query   Lucene search query. This is mandatory
223 ;;
224 ;;  limit   An integer value defining how many entries should be returned.
225 ;;          Only values between 1 and 100 (both inclusive) are allowed.
226 ;;          If not given, this defaults to 25.
227 ;;
228 ;;  offset  Return search results starting at a given offset.
229 ;;          Used for paging through more than one page of results.
230 ;;
231 ;;  dismax  If set to "true", switches the Solr query parser from edismax to dismax,
232 ;;          which will escape certain special query syntax characters by default
233 ;;          for ease of use. This is equivalent to switching from the "Indexed search
234 ;;          with advanced query syntax" method to the plain "Indexed search" method
235 ;;          on the website. Defaults to "false".
236 ;;
237 ;; ;; ; ;  ;
239 ;;;###autoload
240 (defun musicbrainz-search (type query &optional limit offset)
241   "Search the MusicBrainz database for TYPE matching QUERY.
242 Optionally return only LIMIT number of results from OFFSET.
244 The QUERY field supports the full Lucene Search syntax, some details
245 can be found near https://musicbrainz.org/doc/MusicBrainz_API/Search
246 or in the Lucene docs."
248   (interactive "sMusicBrainz search type: \nsMusicBrainz search query: ")
249   (message "MusicBrainz: searching %s=%s" type query)
250   ;; queries may need to be escaped
251   (let* ((max (if limit limit 1))
252          (from (if offset offset ""))
253          (response
254            (request-response-data
255             (request
256              (url-encode-url
257               (format "%s/%s?query=%s&fmt=json&limit=%s&offset=%s"
258                       musicbrainz-api-url type query max from))
259              :type "GET"
260              :headers (list `("User-Agent" . ,musicbrainz-user-agent))
261              :parser 'json-read
262              :sync t
263              :sucess (cl-function
264                       (lambda (&key data &allow-other-keys)
265                         (message "ok: %s" data)))))))
266     (if (called-interactively-p 'any)
267         (message "%s" (pp response))
268         response)))
271 ;;;###autoload
272 (defun musicbrainz-find (query &rest extras)
273   "Search MusicBrainz for QUERY (and EXTRAS) or recommend a more specific search.
274 MusicBrainz makes a distinction between `search', `lookup' and `browse' this
275 provides a more general entry point to searching/browsing the database.
277 Heuristics.
278 - not yet
279 - if QUERY is an MBID, check artist, recording, etc
280 - if QUERY is text, search for artists or recordings, etc"
282   (message "MusicBrainz: query %s %s" query (if extras extras ""))
283   (if (musicbrainz-mbid-p query)
284       ;; search (lookup) for things that could have an mbid
285       (let ((mbid query))
286         (message "searching mbid: %s" mbid)
287         ;; search (search/browse/query) for other things
288         (progn
289           (message "searching other: %s" mbid)))))
292 ;; generate search functions
294 (defmacro musicbrainz--defsearch-1 (name format-string format-args)
295   "Generate search function to format a single item.
296 NAME FORMAT-STRING FORMAT-ARGS
297 See listenbrainz--deformatter for details."
298   (let ((f (intern (concat "musicbrainz-search-" name)))
299         (doc (format "Search for %s using QUERY and show matches.
300 Optionally return LIMIT number of results." name)))
301     `(defun ,f (query &optional limit) ,doc
302        (let* ((max (if limit limit 1))
303               (response
304                 (musicbrainz-search ,name query max)))
305          (let-alist response
306                     (format ,format-string ,@format-args))))))
309 (defmacro musicbrainz--defsearch-2 (name format-string format-args alist)
310   "Generate lookup function to format multiple items.
311 NAME FORMAT-STRING FORMAT-ARGS ALIST
312 See listenbrainz--deformatter for details."
313   (let ((f (intern (concat "musicbrainz-search-" name)))
314         (doc (format "Search for %s using QUERY and show matches.
315 Optionally return LIMIT number of results." name)))
316     `(defun ,f (query &optional limit) ,doc
317        (let* ((max (if limit limit 1))
318               (response
319                 (musicbrainz-search ,name query max)))
320          (let-alist response
321                     (seq-map
322                      (lambda (i)
323                        (let-alist i
324                                   (format ,format-string ,@format-args)))
325                      ,alist))))))
327 ;; various specific searches
329 ;; search ->  musicbrainz-search-annotation
330 (musicbrainz--defsearch-2 "annotation"
331                           "%s | %s |  %s | %s | [[https://musicbrainz.org/%s/%s][%s]] |\n"
332                           (.score .type .name .text .type .entity .entity)
333                           .annotations)
335 ;; search ->  musicbrainz-search-area
336 (musicbrainz--defsearch-2 "area"
337                           "%s | [[https://musicbrainz.org/area/%s][%s]] |\n"
338                           (.name .id .id)
339                           .areas)
342 (defun musicbrainz-search-artist (artist &optional limit)
343   "Search for an ARTIST and show matches.
344 Optionally return LIMIT number of results."
345   (let ((data (musicbrainz-search "artist" artist limit)))
346     (let-alist
347      data
348      (seq-map
349       (lambda (i)
350         (let-alist i
351                    (if (not limit)
352                        (format "%s | %s |\n" .name .id)
353                        (format "%s | %s | %s |\n"
354                                .score .name .id))))
355       .artists))))
358 (defun musicbrainz-artist-to-mbid (artist)
359   "Find an MBID for ARTIST (with 100% match).
360 See `musicbrainz-disambiguate-artist' if there are multiple matches."
361   (let ((data (musicbrainz-search "artist" artist)))
362     (let-alist data
363                (car (remove nil (seq-map
364                                  (lambda (i)
365                                    (let-alist i
366                                               (when (= 100 .score)
367                                                 (format "%s" .id))))
368                                  .artists))))))
371 (defun musicbrainz-disambiguate-artist (artist &optional limit)
372   "More ARTIST data. less ambiguity (with optional LIMIT).
373 Outputs an `org-mode' table with descriptions and MBID link to artists pages."
374   (let* ((max (if limit limit 11))
375          (data (musicbrainz-search "artist" artist max)))
376     (let-alist data
377                (cons (format "| Artist: %s| MBID |\n" artist)
378                      (seq-map
379                       (lambda (i)
380                         (let-alist i
381                                    (format "%s | %s, %s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
382                                            .score .name .disambiguation .id .id)))
383                       .artists)))))
386 ;; search ->  musicbrainz-search-event
387 (musicbrainz--defsearch-2 "event"
388                           "%s | [[https://musicbrainz.org/event/%s][%s]] |\n"
389                           (.name .id .id)
390                           .events)
392 ;; search ->  musicbrainz-search-instrument
393 (musicbrainz--defsearch-2 "instrument"
394                           "| %s | %s | [[https://musicbrainz.org/instrument/%s][%s]] |\n"
395                           (.name .type  .id .id)
396                           .instruments)
399 (defun musicbrainz-search-label (label &optional limit)
400   "Search for a LABEL and show matches.
401 Optionally return LIMIT number of results."
402   (let ((data (musicbrainz-search "label" label limit)))
403     (let-alist
404      data
405      (seq-map
406       (lambda (i)
407         (let-alist i
408                    (if (not limit)
409                        (format "%s | [[https://musicbrainz.org/label/%s][%s]] |\n" .name .id .id)
410                        (format "%s | %s | %s (%s%s) |  [[https://musicbrainz.org/label/%s][%s]]  |\n"
411                                .score .name
412                                (if .disambiguation .disambiguation "")
413                                (if .life-span.begin
414                                    (format "%s " .life-span.begin) "")
415                                (if .life-span.end
416                                    (format "—%s" .life-span.end)
417                                    "ongoing")
418                                .id .id))))
419       .labels))))
422 ;; search ->  musicbrainz-search-place
423 (musicbrainz--defsearch-2 "place"
424                           "%s | [[https://musicbrainz.org/place/%s][%s]] |\n"
425                           (.name .id .id)
426                           .places)
428 ;; search ->  musicbrainz-search-recording
429 (musicbrainz--defsearch-2 "recording"
430                           "%s | %s, %s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
431                           (.score .title (musicbrainz--unwrap-0 .artist-credit) .id .id)
432                           .recordings)
434 ;; search ->  musicbrainz-search-release
435 (musicbrainz--defsearch-2 "release"
436                           "%s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
437                           (.score .title (musicbrainz--unwrap-0 .artist-credit) .id .id)
438                           .releases)
440 ;; search ->  musicbrainz-search-release-group
441 (musicbrainz--defsearch-2 "release-group"
442                           "%s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n"
443                           (.first-release-date .title .primary-type .id .id)
444                           .release-groups)
446 ;; search ->  musicbrainz-search-series
447 (musicbrainz--defsearch-2 "series"
448                           "%s | [[https://musicbrainz.org/series/%s][%s]] |\n"
449                           (.name .id .id)
450                           .series)
452 ;; search ->  musicbrainz-search-work
453 (musicbrainz--defsearch-2 "work"
454                           "%s | %s | [[https://musicbrainz.org/work/%s][%s]] |\n"
455                           (.score .title .id .id)
456                           .works)
458 ;; search ->  musicbrainz-search-url
459 (musicbrainz--defsearch-2 "url"
460                           "%s | [[%s][%s]] | [[https://musicbrainz.org/url/%s][%s]] |\n"
461                           (.score .resource .resource .id .id)
462                           .urls)
465 ;;; ;; ;; ;  ; ;   ;  ;      ;
466 ;;
467 ;; Lookups
468 ;;  https://musicbrainz.org/doc/MusicBrainz_API#Lookups
469 ;;
470 ;;; ;; ;;   ; ;
472 ;;;###autoload
473 (defun musicbrainz-lookup (entity mbid &optional inc)
474   "Search (lookup not browse) MusicBrainz for ENTITY with MBID.
475 Optionally add an INC list.
477 Subqueries
478  /ws/2/area
479  /ws/2/artist          recordings, releases, release-groups, works
480  /ws/2/collection      user-collections (requires authentication)
481  /ws/2/event
482  /ws/2/genre
483  /ws/2/instrument
484  /ws/2/label           releases
485  /ws/2/place
486  /ws/2/recording       artists, releases, isrcs, url-rels
487  /ws/2/release         artists, collections, labels, recordings, release-groups
488  /ws/2/release-group   artists, releases
489  /ws/2/series
490  /ws/2/work
491  /ws/2/url"
493   (interactive "sMusicBrainz entity type: \nsMusicBrainz MBID for entity: ")
494   (message "MusicBrainz: lookup: %s/%s" entity mbid)
495   (if (and (musicbrainz-core-entity-p entity)
496            (musicbrainz-mbid-p mbid))
497       (let* ((add (if inc inc ""))
498              (response
499                (request-response-data
500                 (request
501                  (url-encode-url
502                   (format "%s/%s/%s?inc=%s&fmt=json"
503                           musicbrainz-api-url entity mbid add))
504                  :type "GET"
505                  :parser 'json-read
506                  :sync t
507                  :success (cl-function
508                            (lambda (&key data &allow-other-keys)
509                              (when data
510                                (message "%s data: %s" entity mbid))))))))
511         (if (called-interactively-p 'any)
512             (message "%s" (pp response))
513             response))
514       (user-error "MusicBrainz: search requires a valid MBID and entity (i.e. one of %s)"
515                   musicbrainz-entities-core)))
517 ;; relationship lookups
519 (defun musicbrainz-relations (entity relation mbid)
520   "Lookup relationships of type RELATION to ENTITY with MBID."
521   ;; no sanity and/or error checks
522   (musicbrainz-lookup entity mbid (format "%s-rels" relation)))
525 ;; specific MBID lookup requests & subrequests (limited to 25 results?)
527 (defmacro musicbrainz--deflookup-1 (name format-string format-args)
528   "Generate lookup function to format a single item.
529 NAME FORMAT-STRING FORMAT-ARGS
530 See listenbrainz--deformatter for details."
531   (let ((f (intern (concat "musicbrainz-lookup-" name)))
532         (doc "MusicBrainz lookup.")
533         (prompt (format "sMusicBrainz lookup %s by MBID: " name)))
534     `(defun ,f (mbid) ,doc
535        (interactive ,prompt)
536        (let ((response
537                (musicbrainz-lookup ,name mbid)))
538          (let-alist response
539                     (format ,format-string ,@format-args))))))
542 (defmacro musicbrainz--deflookup-2 (query subquery format-string format-args alist)
543   "Generate lookup function to format multiple items.
544 QUERY SUBQUERY FORMAT-STRING FORMAT-ARGS ALIST
545 See listenbrainz--deformatter for details."
546   (let ((f (intern (format "musicbrainz-lookup-%s-%s" query subquery)))
547         (doc "MusicBrainz lookup.")
548         (prompt (format "sMusicBrainz lookup %s %s by MBID: " query subquery)))
549     `(defun ,f (mbid) ,doc
550        (interactive ,prompt)
551        (let ((response
552                (musicbrainz-lookup ,query mbid ,subquery)))
553          (let-alist response
554                     (seq-map
555                      (lambda (i)
556                        (let-alist i
557                                   (format ,format-string ,@format-args)))
558                      ,alist))))))
561 ;; (defun musicbrainz-lookup-artist (mbid)
562 ;;   "MusicBrainz lookup for artist with MBID."
563 ;;   (let ((response
564 ;;           (musicbrainz-lookup "artist" mbid)))
565 ;;     (let-alist response
566 ;;                (format "| %s | %s | %s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
567 ;;                        .name .disambiguation .type .id .id))))
570 ;; (defun musicbrainz-lookup-artist-recordings (mbid)
571 ;;   "MusicBrainz lookup for recordings from artist with MBID."
572 ;;   (let ((response
573 ;;           (musicbrainz-lookup "artist" mbid "recordings")))
574 ;;     (let-alist response
575 ;;                (seq-map
576 ;;                 (lambda (i)
577 ;;                   (let-alist i
578 ;;                              (format "%s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
579 ;;                                      .title .id .id)))
580 ;;                 .recordings))))
583 ;; lookup ->  musicbrainz-lookup-area
584 (musicbrainz--deflookup-1 "area"
585                           "| %s | [[https://musicbrainz.org/area/%s][%s]] |\n"
586                           (.name .id .id))
588 ;; lookup ->  musicbrainz-lookup-artist
589 (musicbrainz--deflookup-1 "artist"
590                           "| %s | %s | %s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
591                           (.name .disambiguation .type .id .id))
593 ;; lookup ->  musicbrainz-lookup-artist-recordings
594 (musicbrainz--deflookup-2 "artist" "recordings"
595                           "%s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
596                           (.title .id .id)
597                           .recordings)
599 ;; lookup ->  musicbrainz-lookup-artist-releases
600 (musicbrainz--deflookup-2 "artist" "releases"
601                           "%s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
602                           (.date .title .packaging .id .id)
603                           .releases)
605 ;; lookup ->  musicbrainz-lookup-artist-release-groups
606 (musicbrainz--deflookup-2 "artist" "release-groups"
607                           "%s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n"
608                           (.first-release-date .title .primary-type .id .id)
609                           .release-groups)
611 ;; lookup ->  musicbrainz-lookup-artist-works
612 (musicbrainz--deflookup-2 "artist" "works"
613                           " %s | [[https://musicbrainz.org/work/%s][%s]] |\n"
614                           (.title .id .id)
615                           .works)
617 ;; lookup ->  musicbrainz-lookup-collection
618 (musicbrainz--deflookup-1 "collection"
619                           "| %s | [[https://musicbrainz.org/collection/%s][%s]] |\n"
620                           (.name .id .id))
622 ;; lookup ->  musicbrainz-lookup-collection-user-collections (requires authentication)
623 (musicbrainz--deflookup-2 "collection" "user-collections"
624                           " %s | [[https://musicbrainz.org/collection/%s][%s]] |\n"
625                           (.name .id .id)
626                           .collection)
628 ;; lookup ->  musicbrainz-lookup-event
629 (musicbrainz--deflookup-1 "event"
630                           "| %s | [[https://musicbrainz.org/event/%s][%s]] |\n"
631                           (.name .id .id))
633 ;; lookup ->  musicbrainz-lookup-genre
634 (musicbrainz--deflookup-1 "genre"
635                           "| %s | [[https://musicbrainz.org/genre/%s][%s]] |\n"
636                           (.name .id .id))
638 ;; lookup ->  musicbrainz-lookup-instrument
639 (musicbrainz--deflookup-1 "instrument"
640                           "| %s | %s | [[https://musicbrainz.org/instrument/%s][%s]] |\n"
641                           (.name .type  .id .id))
643 ;; lookup ->  musicbrainz-lookup-label
644 (musicbrainz--deflookup-1 "label"
645                           "| %s | %s | [[https://musicbrainz.org/label/%s][%s]] |\n"
646                           (.name .disambiguation .id .id))
649 ;; lookup ->  musicbrainz-lookup-label-releases
650 (musicbrainz--deflookup-2 "label" "releases"
651                           "%s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
652                           (.date .title .id .id)
653                           .releases)
655 ;; lookup ->  musicbrainz-lookup-place
656 (musicbrainz--deflookup-1 "place"
657                           "| %s | [[https://musicbrainz.org/place/%s][%s]] |\n"
658                           (.name .id .id))
660 ;; lookup ->  musicbrainz-lookup-recording
661 (musicbrainz--deflookup-1 "recording"
662                           "| %s | %s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
663                           (.first-release-date .title .id .id))
666 ;; lookup ->  musicbrainz-lookup-recording-artists
667 (musicbrainz--deflookup-2 "recording" "artists"
668                           "%s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
669                           (.artist.name .artist.id .artist.id)
670                           .artist-credit)
672 ;; lookup ->  musicbrainz-lookup-recording-releases
673 (musicbrainz--deflookup-2 "recording" "releases"
674                           "%s | %s |  %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
675                           (.date .title .packaging .id .id)
676                           .releases)
678 ;; lookup ->  musicbrainz-lookup-recording-isrcs
679 (musicbrainz--deflookup-2 "recording" "isrcs"
680                           "%s | [[https://musicbrainz.org/isrc/%s][%s]] |\n"
681                           (.name .id .id)
682                           .isrcs)
684 ;; lookup ->  musicbrainz-lookup-recording-url-rels
685 (musicbrainz--deflookup-2 "recording" "url-rels"
686                           "%s | [[https://musicbrainz.org/recording/%s][%s]] |\n"
687                           (.name .id .id)
688                           .relations)
690 ;; lookup ->  musicbrainz-lookup-release
691 (musicbrainz--deflookup-1 "release"
692                           "| %s | %s | %s | [[https://musicbrainz.org/release/%s][%s]] |\n"
693                           (.date .title .packaging .id .id))
695 ;; lookup ->  musicbrainz-lookup-release-artists
696 (musicbrainz--deflookup-2 "release" "artists"
697                           "%s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
698                           (.artist.name .artist.id .artist.id)
699                           .artist-credit)
701 ;; lookup ->  musicbrainz-lookup-release-collections
703 ;; lookup ->  musicbrainz-lookup-release-labels
705 ;; lookup ->  musicbrainz-lookup-release-recordings
707 ;; lookup ->  musicbrainz-lookup-release-release-groups
709 ;; lookup ->  musicbrainz-lookup-release-group
710 (musicbrainz--deflookup-1 "release-group"
711                           "| %s | %s | %s | [[https://musicbrainz.org/release-group/%s][%s]] |\n"
712                           (.first-release-date .title .primary-type .id .id))
714 ;; lookup ->  musicbrainz-lookup-release-group-artists
715 (musicbrainz--deflookup-2 "release-group" "artists"
716                           "%s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
717                           (.artist.name .artist.id .artist.id)
718                           .artist-credit)
720 ;; lookup ->  musicbrainz-lookup-release-group-releases
722 ;; lookup ->  musicbrainz-lookup-series
723 (musicbrainz--deflookup-1 "series"
724                           "| %s | [[https://musicbrainz.org/series/%s][%s]] |\n"
725                           (.title .id .id))
727 ;; lookup ->  musicbrainz-lookup-work
728 (musicbrainz--deflookup-1 "work"
729                           "| %s | [[https://musicbrainz.org/work/%s][%s]] |\n"
730                           (.title .id .id))
732 ;; lookup ->  musicbrainz-lookup-url
733 (musicbrainz--deflookup-1 "url"
734                           "| %s | [[https://musicbrainz.org/url/%s][%s]] |\n"
735                           (.name .id .id))
739 ;;;;;; ; ; ;; ;   ;     ;  ; ; ;;   ;
740 ;;
741 ;; Browse API
742 ;;  https://musicbrainz.org/doc/MusicBrainz_API#Browse
743 ;;
744 ;;;; ; ; ; ; ;
746 ;; Browse requests are a direct lookup of all the entities directly linked
747 ;; to another entity ("directly linked" here meaning it does not include
748 ;; entities linked by a relationship). For example, you may want to see all
749 ;; releases on the label ubiktune:
751 ;; /ws/2/release?label=47e718e1-7ee4-460c-b1cc-1192a841c6e5
753 ;; Note that browse requests are not searches: in order to browse all the releases
754 ;; on the ubiktune label you will need to know the MBID of ubiktune.
756 ;; The order of the results depends on what linked entity you are browsing
757 ;; by (however it will always be consistent). If you need to sort the entities,
758 ;; you will have to fetch all entities and sort them yourself.
760 ;; Keep in mind only the search request is available without an MBID (or, in
761 ;; specific cases, a disc ID, ISRC or ISWC). If all you have is the name of an
762 ;; artist or album, for example, you'll need to make a search and pick the right
763 ;; result to get its MBID to use it in a lookup or browse request.
766 ;;;###autoload
767 (defun musicbrainz-browse (entity link query &optional type)
768   "Search the MusicBrainz database for ENTITY with LINK matching QUERY.
769 Optionally limit the search to TYPE results for ENTITY."
770   (message "MusicBrainz: browsing %s linked to %s" entity link)
771   (message "url: %s/%s?%s=%s&type=%s&fmt=json" musicbrainz-api-url entity link query type)
772   (let ((response
773           (request-response-data
774            (request
775             (url-encode-url
776              (format "%s/%s?%s=%s&type=%s&fmt=json" musicbrainz-api-url entity link query type))
777             :type "GET"
778             :header (list `("User-Agent" . ,musicbrainz-user-agent))
779             :parser 'json-read
780             :sync t
781             :success (cl-function
782                       (lambda (&key data &allow-other-keys)
783                         (message "ok: %s" (if data data ""))))))))
784     response))
788 ;;;;;; ; ; ;; ;   ;     ;  ; ; ;;   ;
789 ;;
790 ;; Cover Art Archive API
791 ;;  https://musicbrainz.org/doc/Cover_Art_Archive/API
792 ;;
793 ;;;; ; ; ; ; ;
795 ;; /release/{mbid}/
796 ;; /release/{mbid}/front
797 ;; /release/{mbid}/back
798 ;; /release/{mbid}/{id}
799 ;; /release/{mbid}/({id}|front|back)-(250|500|1200)
800 ;;
801 ;; /release-group/{mbid}/
802 ;; /release-group/{mbid}/front[-(250|500|1200)]
804 ;;;###autoload
805 (defun musicbrainz-coverart (mbid &optional release-group)
806   "Search MusicBrainz Cover Art Archive for release MBID.
807 When RELEASE-GROUP is non-nil MBID is for a release group, rather than release."
808   (message "MusicBrainz: cover art for %s" mbid)
809   (message "url: %s/release/%s" musicbrainz-coverart-api-url mbid)
810   (let ((response
811           (request-response-data
812            (request
813             (url-encode-url
814              (format "%s/release%s/%s"
815                      musicbrainz-coverart-api-url
816                      (if release-group "-group" "")
817                      mbid))
818             :type "GET"
819             :header (list `("User-Agent" . ,musicbrainz-user-agent))
820             :parser 'json-read
821             :sync t
822             :success (cl-function
823                       (lambda (&key data &allow-other-keys)
824                         (message "ok: %s" (if data data ""))))))))
825     response))
827 (defun musicbrainz-coverart-file-front (mbid)
828   "Get the MusicBrainz Cover Art front cover file for MBID."
829   (message "MusicBrainz: cover art (front) for %s" mbid)
830   (message "url: %s/release/%s/front" musicbrainz-coverart-api-url mbid)
831   (let ((response
832           (request-response-data
833            (request
834             (url-encode-url
835              (format "%s/release/%s/front" musicbrainz-coverart-api-url mbid))
836             :type "GET"
837             :header (list `("User-Agent" . ,musicbrainz-user-agent))
838             :sync t
839             :success (cl-function
840                       (lambda (&key data &allow-other-keys)
841                         (message "ok: %s" (if data data ""))))))))
842     response))
844 (defun musicbrainz-coverart-file-back (mbid)
845   "Get the MusicBrainz Cover Art back cover file for MBID."
846   (message "MusicBrainz: cover art (back) for %s" mbid)
847   (message "url: %s/release/%s/back" musicbrainz-coverart-api-url mbid)
848   (let ((response
849           (request-response-data
850            (request
851             (url-encode-url
852              (format "%s/release/%s/back" musicbrainz-coverart-api-url mbid))
853             :type "GET"
854             :header (list `("User-Agent" . ,musicbrainz-user-agent))
855             :sync t
856             :success (cl-function
857                       (lambda (&key data &allow-other-keys)
858                         (message "ok: %s" (if data data ""))))))))
859     response))
862 (provide 'musicbrainz)
864 ;;; musicbrainz.el ends here