1 /*
2 * This file is part of cyanrip.
3 *
4 * cyanrip is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * cyanrip is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with cyanrip; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include "musicbrainz.h"
20 #include "cyanrip_log.h"
21
22 #include <musicbrainz5/mb5_c.h>
23 #include <libavutil/crc.h>
24
25 #define READ_MB(FUNC, MBCTX, DICT, KEY) \
26 do { \
27 if (!MBCTX) \
28 break; \
29 int len = FUNC(MBCTX, NULL, 0) + 1; \
30 char *str = av_mallocz(4*len); \
31 FUNC(MBCTX, str, len); \
32 if (str[0] == '\0') \
33 av_free(str); \
34 else \
35 av_dict_set(&DICT, KEY, str, AV_DICT_DONT_STRDUP_VAL | AV_DICT_APPEND); \
36 } while (0)
37
mb_credit(Mb5ArtistCredit credit,AVDictionary * dict,const char * key)38 static void mb_credit(Mb5ArtistCredit credit, AVDictionary *dict, const char *key)
39 {
40 Mb5NameCreditList namecredit_list = mb5_artistcredit_get_namecreditlist(credit);
41 for (int i = 0; i < mb5_namecredit_list_size(namecredit_list); i++) {
42 Mb5NameCredit namecredit = mb5_namecredit_list_item(namecredit_list, i);
43
44 if (mb5_namecredit_get_name(namecredit, NULL, 0)) {
45 READ_MB(mb5_namecredit_get_name, namecredit, dict, key);
46 } else {
47 Mb5Artist artist = mb5_namecredit_get_artist(namecredit);
48 if (artist)
49 READ_MB(mb5_artist_get_name, artist, dict, key);
50 }
51
52 READ_MB(mb5_namecredit_get_joinphrase, namecredit, dict, key);
53 }
54 }
55
56 /* This is pretty awful but I blame musicbrainz entirely */
crc_medium(Mb5Medium medium)57 static uint32_t crc_medium(Mb5Medium medium)
58 {
59 uint32_t crc = UINT32_MAX;
60 const AVCRC *crc_tab = av_crc_get_table(AV_CRC_32_IEEE_LE);
61
62 Mb5TrackList track_list = mb5_medium_get_tracklist(medium);
63 if (!track_list)
64 return 0;
65
66 for (int i = 0; i < mb5_track_list_size(track_list); i++) {
67 AVDictionary *tmp_dict = NULL;
68 Mb5Track track = mb5_track_list_item(track_list, i);
69 Mb5Recording recording = mb5_track_get_recording(track);
70
71 READ_MB(mb5_recording_get_id, recording, tmp_dict, "mbid");
72
73 Mb5ArtistCredit credit;
74 if (recording) {
75 READ_MB(mb5_recording_get_title, recording, tmp_dict, "title");
76 credit = mb5_recording_get_artistcredit(recording);
77 } else {
78 READ_MB(mb5_track_get_title, track, tmp_dict, "title");
79 credit = mb5_track_get_artistcredit(track);
80 }
81 if (credit)
82 mb_credit(credit, tmp_dict, "artist");
83
84 if (dict_get(tmp_dict, "mbid"))
85 crc = av_crc(crc_tab, crc, dict_get(tmp_dict, "mbid"), strlen(dict_get(tmp_dict, "mbid")));
86 if (dict_get(tmp_dict, "artist"))
87 crc = av_crc(crc_tab, crc, dict_get(tmp_dict, "artist"), strlen(dict_get(tmp_dict, "artist")));
88 if (dict_get(tmp_dict, "title"))
89 crc = av_crc(crc_tab, crc, dict_get(tmp_dict, "title"), strlen(dict_get(tmp_dict, "title")));
90
91 av_dict_free(&tmp_dict);
92 crc ^= mb5_track_get_length(track);
93 crc ^= i;
94 }
95
96 return crc;
97 }
98
mb_tracks(cyanrip_ctx * ctx,Mb5Release release,const char * discid,int discnumber)99 static int mb_tracks(cyanrip_ctx *ctx, Mb5Release release, const char *discid, int discnumber)
100 {
101 /* Set totaldiscs if possible */
102 Mb5MediumList medium_list_extra = NULL;
103 Mb5MediumList medium_full_list = mb5_release_get_mediumlist(release);
104 int num_cds = mb5_medium_list_size(medium_full_list);
105 av_dict_set_int(&ctx->meta, "totaldiscs", num_cds, 0);
106
107 if (num_cds == 1 && !discnumber)
108 av_dict_set_int(&ctx->meta, "disc", 1, 0);
109
110 Mb5Medium medium = NULL;
111 if (discnumber) {
112 if (discnumber < 1 || discnumber > num_cds) {
113 cyanrip_log(ctx, 0, "Invalid disc number %i, release only has %i CDs\n", discnumber, num_cds);
114 return 1;
115 }
116 medium = mb5_medium_list_item(medium_full_list, discnumber - 1);
117 if (!medium) {
118 cyanrip_log(ctx, 0, "Got empty medium list.\n");
119 return 1;
120 }
121 } else {
122 medium_list_extra = mb5_release_media_matching_discid(release, discid);
123 if (!medium_list_extra) {
124 cyanrip_log(ctx, 0, "No mediums match DiscID!\n");
125 return 0;
126 }
127
128 medium = mb5_medium_list_item(medium_list_extra, 0);
129 if (!medium) {
130 cyanrip_log(ctx, 0, "Got empty medium list.\n");
131 mb5_medium_list_delete(medium_list_extra);
132 return 1;
133 }
134
135 if (num_cds > 1) {
136 uint32_t medium_crc = crc_medium(medium);
137 for (int i = 0; i < num_cds; i++) {
138 Mb5Medium tmp_medium = mb5_medium_list_item(medium_full_list, i);
139 if (medium_crc == crc_medium(tmp_medium)) {
140 av_dict_set_int(&ctx->meta, "disc", i + 1, 0);
141 break;
142 }
143 }
144 }
145 }
146
147 READ_MB(mb5_medium_get_title, medium, ctx->meta, "discname");
148 READ_MB(mb5_medium_get_format, medium, ctx->meta, "format");
149
150 Mb5TrackList track_list = mb5_medium_get_tracklist(medium);
151 if (!track_list) {
152 cyanrip_log(ctx, 0, "Medium has no track list.\n");
153 if (medium_list_extra)
154 mb5_medium_list_delete(medium_list_extra);
155 return 0;
156 }
157
158 for (int i = 0; i < mb5_track_list_size(track_list); i++) {
159 if (i >= ctx->nb_cd_tracks)
160 break;
161 Mb5Track track = mb5_track_list_item(track_list, i);
162 Mb5Recording recording = mb5_track_get_recording(track);
163
164 READ_MB(mb5_recording_get_id, recording, ctx->tracks[i].meta, "mbid");
165
166 Mb5ArtistCredit credit;
167 if (recording) {
168 READ_MB(mb5_recording_get_title, recording, ctx->tracks[i].meta, "title");
169 credit = mb5_recording_get_artistcredit(recording);
170 } else {
171 READ_MB(mb5_track_get_title, track, ctx->tracks[i].meta, "title");
172 credit = mb5_track_get_artistcredit(track);
173 }
174 if (credit)
175 mb_credit(credit, ctx->tracks[i].meta, "artist");
176 }
177
178 if (medium_list_extra)
179 mb5_medium_list_delete(medium_list_extra);
180
181 return 0;
182 }
183
mb_metadata(cyanrip_ctx * ctx,int manual_metadata_specified,int release_idx,char * release_str,int discnumber)184 static int mb_metadata(cyanrip_ctx *ctx, int manual_metadata_specified, int release_idx, char *release_str, int discnumber)
185 {
186 int ret = 0;
187 const char *ua = "cyanrip/" PROJECT_VERSION_STRING " ( https://github.com/cyanreg/cyanrip )";
188 Mb5Query query = mb5_query_new(ua, NULL, 0);
189 if (!query) {
190 cyanrip_log(ctx, 0, "Could not connect to MusicBrainz.\n");
191 return 1;
192 }
193
194 char *names[] = { "inc" };
195 char *values[] = { "recordings artist-credits" };
196 const char *discid = dict_get(ctx->meta, "discid");
197 if (!discid) {
198 cyanrip_log(ctx, 0, "Missing DiscID!\n");
199 return 0;
200 }
201
202 Mb5Metadata metadata = mb5_query_query(query, "discid", discid, 0, 1, names, values);
203 if (!metadata) {
204 tQueryResult res = mb5_query_get_lastresult(query);
205 if (res != eQuery_ResourceNotFound) {
206 int chars = mb5_query_get_lasterrormessage(query, NULL, 0) + 1;
207 char *msg = av_mallocz(chars*sizeof(*msg));
208 mb5_query_get_lasterrormessage(query, msg, chars);
209 cyanrip_log(ctx, 0, "MusicBrainz lookup failed: %s\n", msg);
210 av_freep(&msg);
211 }
212
213 switch(res) {
214 case eQuery_Timeout:
215 case eQuery_ConnectionError:
216 cyanrip_log(ctx, 0, "Connection failed, try again? Or disable via -N\n");
217 break;
218 case eQuery_AuthenticationError:
219 case eQuery_FetchError:
220 case eQuery_RequestError:
221 cyanrip_log(ctx, 0, "Error fetching/requesting/auth, this shouldn't happen.\n");
222 break;
223 case eQuery_ResourceNotFound:
224 if (manual_metadata_specified) {
225 cyanrip_log(ctx, 0, "Unable to find metadata for this CD, but "
226 "metadata has been manually specified, continuing.\n");
227 cyanrip_log(ctx, 0, "Please help improve the MusicBrainz DB by "
228 "submitting the disc info to the following URL:\n%s\n", ctx->mb_submission_url);
229 goto end;
230 } else {
231 cyanrip_log(ctx, 0, "Unable to find release info for this CD, "
232 "and metadata hasn't been manually added!\n");
233 cyanrip_log(ctx, 0, "Please help improve the MusicBrainz DB by "
234 "submitting the disc info to the following URL:\n%s\n", ctx->mb_submission_url);
235 cyanrip_log(ctx, 0, "To continue add metadata via -a or -t, or ignore via -N!\n");
236 }
237 break;
238 default:
239 break;
240 }
241
242 ret = 1;
243 goto end;
244 }
245
246 Mb5ReleaseList release_list = NULL;
247 Mb5Disc disc = mb5_metadata_get_disc(metadata);
248 if (!disc) {
249 cyanrip_log(ctx, 0, "DiscID not found in MusicBrainz\n");
250 goto end_meta;
251 }
252
253 release_list = mb5_disc_get_releaselist(disc);
254 if (!release_list) {
255 cyanrip_log(ctx, 0, "DiscID has no associated releases.\n");
256 goto end_meta;
257 }
258
259 Mb5Release release = NULL;
260 int num_releases = mb5_release_list_size(release_list);
261 if (!num_releases) {
262 cyanrip_log(ctx, 0, "No releases found for DiscID.\n");
263 goto end_meta;
264 } else if (num_releases > 1 && ((release_idx < 0) && !release_str)) {
265 cyanrip_log(ctx, 0, "Multiple releases found in database for DiscID %s:\n", discid);
266 for (int i = 0; i < num_releases; i++) {
267 release = mb5_release_list_item(release_list, i);
268 AVDictionary *tmp_dict = NULL;
269 READ_MB(mb5_release_get_date, release, tmp_dict, "date");
270 READ_MB(mb5_release_get_title, release, tmp_dict, "album");
271 READ_MB(mb5_release_get_id, release, tmp_dict, "id");
272 READ_MB(mb5_release_get_disambiguation, release, tmp_dict, "disambiguation");
273 READ_MB(mb5_release_get_country, release, tmp_dict, "country");
274
275 Mb5MediumList medium_list = mb5_release_get_mediumlist(release);
276 int num_cds = mb5_medium_list_size(medium_list);
277 if (num_cds > 1)
278 av_dict_set_int(&tmp_dict, "num_cds", num_cds, 0);
279
280 #define PROP(key, postamble) \
281 (!!dict_get(tmp_dict, key)) ? " (" : "", \
282 (!!dict_get(tmp_dict, key)) ? dict_get(tmp_dict, key) : "", \
283 (!!dict_get(tmp_dict, key)) ? postamble : "", \
284 (!!dict_get(tmp_dict, key)) ? ")" : ""
285
286 cyanrip_log(ctx, 0, " %i (ID: %s): %s" "%s%s%s%s" "%s%s%s%s" "%s%s%s%s" "%s%s%s%s" "%s", i + 1,
287 dict_get(tmp_dict, "id") ? dict_get(tmp_dict, "id") : "unknown id",
288 dict_get(tmp_dict, "album") ? dict_get(tmp_dict, "album") : "unknown album",
289 PROP("disambiguation", ""),
290 PROP("country", ""),
291 PROP("num_cds", " CDs"),
292 PROP("date", ""),
293 "\n");
294
295 #undef PROP
296
297 av_dict_free(&tmp_dict);
298 }
299 cyanrip_log(ctx, 0, "\n");
300 cyanrip_log(ctx, 0, "Please specify which release to use by adding the -R argument with an index or ID.\n");
301 ret = 1;
302 goto end_meta;
303 } else if (release_idx >= 0) { /* Release index specified */
304 if ((release_idx < 1) || (release_idx > num_releases)) {
305 cyanrip_log(ctx, 0, "Invalid release index %i specified, only have %i releases!\n", release_idx, num_releases);
306 ret = 1;
307 goto end_meta;
308 }
309 release = mb5_release_list_item(release_list, release_idx - 1);
310 } else if (release_str) { /* Release ID specified */
311 int i = 0;
312 for (; i < num_releases; i++) {
313 release = mb5_release_list_item(release_list, i);
314 AVDictionary *tmp_dict = NULL;
315 READ_MB(mb5_release_get_id, release, tmp_dict, "id");
316 if (dict_get(tmp_dict, "id") && !strcmp(release_str, dict_get(tmp_dict, "id"))) {
317 av_dict_free(&tmp_dict);
318 break;
319 }
320 av_dict_free(&tmp_dict);
321 }
322 if (i == num_releases) {
323 cyanrip_log(ctx, 0, "Release ID %s not found in release list for DiscID %s!\n", release_str, discid);
324 ret = 1;
325 goto end_meta;
326 }
327 } else {
328 release = mb5_release_list_item(release_list, 0);
329 }
330
331 READ_MB(mb5_release_get_id, release, ctx->meta, "release_id");
332 READ_MB(mb5_release_get_disambiguation, release, ctx->meta, "releasecomment");
333 READ_MB(mb5_release_get_date, release, ctx->meta, "date");
334 READ_MB(mb5_release_get_title, release, ctx->meta, "album");
335 READ_MB(mb5_release_get_barcode, release, ctx->meta, "barcode");
336 READ_MB(mb5_release_get_packaging, release, ctx->meta, "packaging");
337 READ_MB(mb5_release_get_country, release, ctx->meta, "country");
338 READ_MB(mb5_release_get_status, release, ctx->meta, "status");
339
340 /* Label info */
341 Mb5LabelInfoList *labelinfolist = mb5_release_get_labelinfolist(release);
342 if (mb5_labelinfo_list_size(labelinfolist) == 1) {
343 Mb5LabelInfo *labelinfo = mb5_label_list_item(labelinfolist, 0);
344 READ_MB(mb5_labelinfo_get_catalognumber, labelinfo, ctx->meta, "catalog");
345
346 Mb5Label *label = mb5_labelinfo_get_label(labelinfo);
347 READ_MB(mb5_label_get_name, label, ctx->meta, "label");
348 }
349
350 Mb5ArtistCredit artistcredit = mb5_release_get_artistcredit(release);
351 if (artistcredit)
352 mb_credit(artistcredit, ctx->meta, "album_artist");
353
354 cyanrip_log(ctx, 0, "Found MusicBrainz release: %s - %s\n",
355 dict_get(ctx->meta, "album"), dict_get(ctx->meta, "album_artist"));
356
357 /* Read track metadata */
358 mb_tracks(ctx, release, discid, discnumber);
359
360 end_meta:
361 mb5_metadata_delete(metadata); /* This frees _all_ metadata */
362
363 end:
364 mb5_query_delete(query);
365 return ret;
366 }
367
crip_fill_metadata(cyanrip_ctx * ctx,int manual_metadata_specified,int release_idx,char * release_str,int discnumber)368 int crip_fill_metadata(cyanrip_ctx *ctx, int manual_metadata_specified,
369 int release_idx, char *release_str, int discnumber)
370 {
371 /* Get musicbrainz tags */
372 if (!ctx->settings.disable_mb)
373 return mb_metadata(ctx, manual_metadata_specified, release_idx, release_str, discnumber);
374
375 return 0;
376 }
377