1 ;;; musicbrainz.el --- MusicBrainz API interface -*- coding: utf-8; lexical-binding: t -*-
5 ;; Author: nik gaffney <nik@fo.am>
8 ;; Package-Requires: ((emacs "27.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/>.
30 ;; - basic MusicBrainz interface
31 ;; - partial & incomplete
48 (defcustom musicbrainz-api-url "https://musicbrainz.org/ws/2"
49 "URL for musicbrainz API.
50 Documentation available at https://musicbrainz.org/doc/MusicBrainz_API"
54 (defcustom musicbrainz-api-token ""
55 "An auth token is required for some functions."
62 ;; https://musicbrainz.org/doc/MusicBrainz_API#Browse
64 ;; On each entity resource, you can perform three different GET requests:
65 ;; lookup: /<ENTITY_TYPE>/<MBID>?inc=<INC>
66 ;; browse: /<RESULT_ENTITY_TYPE>?<BROWSING_ENTITY_TYPE>=<MBID>&limit=<LIMIT>&offset=<OFFSET>&inc=<INC>
67 ;; search: /<ENTITY_TYPE>?query=<QUERY>&limit=<LIMIT>&offset=<OFFSET>
69 ;; Note: Keep in mind only the search request is available without an MBID
70 ;; (or, in specific cases, a disc ID, ISRC or ISWC). If all you have is the
71 ;; name of an artist or album, for example, you'll need to make a search and
72 ;; pick the right result to get its MBID; only then will you able to use it
73 ;; in a lookup or browse request.
75 ;; On the genre resource, we support an "all" sub-resource to fetch all genres,
76 ;; paginated, in alphabetical order:
78 ;; all: /genre/all?limit=<LIMIT>&offset=<OFFSET>
82 (defconst musicbrainz-entities-core
83 (list "area" "artist" "event" "genre" "instrument" "label" "place"
84 "recording" "release" "release-group" "series" "work" "url")
85 "API resources which represent core entities in the MusicBrainz database.")
87 (defconst musicbrainz-entities-non-core
88 (list "rating" "tag" "collection")
89 "API resources which represent non-core entities in the MusicBrainz database.")
91 (defconst musicbrainz-entities-uids
92 (list "discid" "isrc" "iswc")
93 "API resources based on other unique identifiers in the MusicBrainz database.")
95 (defconst musicbrainz-entities-linked
96 (list "area" "artist" "collection" "event" "instrument" "label" "place"
97 "recording" "release" "release-group" "series" "work" "url")
98 "API resources for linked entites in the MusicBrainz database.")
103 (defun musicbrainz-linked-entity-p (entity)
104 "Check if ENTITY can be used in a browse request (incomplete).
106 The following list shows which linked entities you can use in a browse request:
108 /ws/2/area collection
109 /ws/2/artist area, collection, recording, release, release-group, work
110 /ws/2/collection area, artist, editor, event, label, place, recording,
111 release, release-group, work
112 /ws/2/event area, artist, collection, place
113 /ws/2/instrument collection
114 /ws/2/label area, collection, release
115 /ws/2/place area, collection
116 /ws/2/recording artist, collection, release, work
117 /ws/2/release area, artist, collection, label, track, track_artist,
118 recording, release-group
119 /ws/2/release-group artist, collection, release
120 /ws/2/series collection
121 /ws/2/work artist, collection
124 (if (member entity musicbrainz-entities-linked) t nil))
129 (defun musicbrainz-mbid-p (mbid)
130 "Check (permissive) if MBID is valid and/or well formatted.
131 An MBID is a 36 character Universally Unique Identifier, see https://musicbrainz.org/doc/MusicBrainz_Identifier for details."
132 (if (and (length= mbid 36)
134 (rx (repeat 8 hex) ;; [A-F0-9]{8}
135 "-" (repeat 4 hex) ;; -[A-F0-9]{4}
136 "-4" (repeat 3 hex) ;; -4[A-F0-9]{3}
137 "-" (repeat 4 hex) ;; -[89AB][A-F0-9]{3}
138 "-" (repeat 12 hex)) ;; -[A-F0-9]{12}
143 ;;; ;; ;; ; ; ; ; ; ;
146 ;; https://musicbrainz.org/doc/MusicBrainz_API/Search
152 (defun musicbrainz-search (entity query &optional limit)
153 "Search the MusicBrainz database for ENTITY matching QUERY.
154 Optionally return only LIMIT number of results.
156 The QUERY field supports the full Lucene Search syntax, some details
157 can be found near https://musicbrainz.org/doc/MusicBrainz_API/Search
158 or in the Lucene docs."
160 (message "musicbrainz: searching %s=%s" entity query)
161 (let* ((max (if limit limit 1))
163 (request-response-data
166 (format "%s/%s?query=%s&fmt=json&limit=%s"
167 musicbrainz-api-url entity query max))
171 :success (cl-function
172 (lambda (&key data &allow-other-keys)
173 (if (eq t (assoc-default 'valid data))
174 (message "Token is valid for user: %s"
175 (assoc-default 'user_name data))
176 (message "Not a valid user token"))))))))
180 ;; various specific searches
183 (defun musicbrainz-search-artist (artist &optional limit)
184 "Search for an ARTIST and show matches.
185 Optionally return LIMIT number of results."
186 (let ((data (musicbrainz-search "artist" artist limit)))
193 (format "%s | %s |\n" .name .id)
194 (format "%s | %s | %s |\n"
200 (defun musicbrainz-artist-to-mbid (artist)
201 "Find an MBID for ARTIST (with 100% match).
202 See `musicbrainz-disambiguate-artist' if there are multiple matches."
203 (let ((data (musicbrainz-search "artist" artist)))
205 (car (remove nil (seq-map
214 (defun musicbrainz-disambiguate-artist (artist &optional limit)
215 "More ARTIST data. less ambiguity (with optional LIMIT).
216 Outputs an `org-mode' table with descriptions and MBID link to artists pages."
217 (let ((data (musicbrainz-search "artist" artist limit)))
219 (cons (format "| Artist: %s| MBID |\n" artist)
223 (format "%s | %s, %s | [[https://musicbrainz.org/artist/%s][%s]] |\n"
224 .score .name .disambiguation .id .id)))
229 (defun musicbrainz-search-label (label &optional limit)
230 "Search for a LABEL and show matches.
231 Optionally return LIMIT number of results."
232 (let ((data (musicbrainz-search "label" label limit)))
239 (format "%s | %s |\n" .name .id)
240 (format "%s | %s | %s (%s%s) | %s |\n"
242 (if .disambiguation .disambiguation "")
244 (format "%s " .life-span.begin) "")
246 (format "—%s" .life-span.end)
253 ;;;;;; ; ; ;; ; ; ; ; ; ;; ;
256 ;; https://musicbrainz.org/doc/MusicBrainz_API#Browse
260 ;; Browse requests are a direct lookup of all the entities directly linked
261 ;; to another entity ("directly linked" here meaning it does not include
262 ;; entities linked by a relationship). For example, you may want to see all
263 ;; releases on the label ubiktune:
265 ;; /ws/2/release?label=47e718e1-7ee4-460c-b1cc-1192a841c6e5
267 ;; Note that browse requests are not searches: in order to browse all the releases
268 ;; on the ubiktune label you will need to know the MBID of ubiktune.
270 ;; The order of the results depends on what linked entity you are browsing
271 ;; by (however it will always be consistent). If you need to sort the entities,
272 ;; you will have to fetch all entities and sort them yourself.
274 ;; Keep in mind only the search request is available without an MBID (or, in
275 ;; specific cases, a disc ID, ISRC or ISWC). If all you have is the name of an
276 ;; artist or album, for example, you'll need to make a search and pick the right
277 ;; result to get its MBID to use it in a lookup or browse request.
281 (defun musicbrainz-browse (entity link lookup &optional type)
282 "Search the MusicBrainz database for ENTITY with LINK matching LOOKUP.
283 Optionally limit the search to TYPE results for ENTITY."
284 (message "musicbrainz: browsing %s linked to %s" entity link)
285 (message "url: %s/%s?%s=%s&type=%s&fmt=json" musicbrainz-api-url entity link lookup type)
287 (request-response-data
290 (format "%s/%s?%s=%s&type=%s&fmt=json" musicbrainz-api-url entity link lookup type))
294 :success (cl-function
295 (lambda (&key data &allow-other-keys)
302 (provide 'musicbrainz)
304 ;;; musicbrainz.el ends here