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 <curl/curl.h>
20 #include <libavformat/avformat.h>
21 #include <libavutil/base64.h>
22
23 #include "coverart.h"
24 #include "cyanrip_log.h"
25
26 #define COVERART_DB_URL_BASE "http://coverartarchive.org/release"
27
crip_save_art(cyanrip_ctx * ctx,CRIPArt * art,const cyanrip_out_fmt * fmt)28 int crip_save_art(cyanrip_ctx *ctx, CRIPArt *art, const cyanrip_out_fmt *fmt)
29 {
30 int ret = 0;
31
32 if (!art->pkt) {
33 cyanrip_log(ctx, 0, "Cover art has no packet!\n");
34 return 0;
35 }
36
37 char *filepath = crip_get_path(ctx, CRIP_PATH_COVERART, 1, fmt, art);
38 if (!filepath)
39 return 0;
40
41 AVFormatContext *avf = NULL;
42 ret = avformat_alloc_output_context2(&avf, NULL, NULL, filepath);
43 if (ret < 0) {
44 cyanrip_log(ctx, 0, "Unable to init lavf context: %s!\n", av_err2str(ret));
45 goto fail;
46 }
47
48 AVStream *st = avformat_new_stream(avf, NULL);
49 if (!st) {
50 cyanrip_log(ctx, 0, "Unable to alloc stream!\n");
51 ret = AVERROR(ENOMEM);
52 goto fail;
53 }
54
55 memcpy(st->codecpar, art->params, sizeof(AVCodecParameters));
56 st->time_base = (AVRational){ 1, 25 };
57 avf->oformat->video_codec = st->codecpar->codec_id;
58 av_dict_copy(&st->metadata, art->meta, 0);
59 av_dict_copy(&avf->metadata, art->meta, 0);
60
61 /* Open for writing */
62 ret = avio_open(&avf->pb, filepath, AVIO_FLAG_WRITE);
63 if (ret < 0) {
64 cyanrip_log(ctx, 0, "Couldn't open %s for writing: %s!\n", filepath, av_err2str(ret));
65 goto fail;
66 }
67 av_freep(&filepath);
68
69 /* Write header */
70 ret = avformat_write_header(avf, NULL);
71 if (ret < 0) {
72 cyanrip_log(ctx, 0, "Couldn't write header: %s!\n", av_err2str(ret));
73 goto fail;
74 }
75
76 AVPacket *pkt = av_packet_clone(art->pkt);
77 pkt->stream_index = st->index;
78 if ((ret = av_interleaved_write_frame(avf, pkt)) < 0) {
79 cyanrip_log(ctx, 0, "Error writing picture packet: %s!\n", av_err2str(ret));
80 goto fail;
81 }
82 av_packet_free(&pkt);
83
84 if ((ret = av_write_trailer(avf)) < 0) {
85 cyanrip_log(ctx, 0, "Error writing trailer: %s!\n", av_err2str(ret));
86 goto fail;
87 }
88
89 fail:
90 if (avf)
91 avio_closep(&avf->pb);
92 avformat_free_context(avf);
93
94 return ret;
95 }
96
crip_free_art(CRIPArt * art)97 void crip_free_art(CRIPArt *art)
98 {
99 av_packet_free(&art->pkt);
100 av_freep(&art->params);
101
102 av_freep(&art->data);
103 art->size = 0;
104
105 av_freep(&art->source_url);
106 av_freep(&art->extension);
107
108 av_dict_free(&art->meta);
109 }
110
receive_image(void * buffer,size_t size,size_t nb,void * opaque)111 static size_t receive_image(void *buffer, size_t size, size_t nb, void *opaque)
112 {
113 CRIPArt *art = opaque;
114
115 uint8_t *new_data = av_realloc(art->data, art->size + (size * nb));
116 if (!new_data)
117 return 0;
118
119 memcpy(new_data + art->size, buffer, size * nb);
120
121 art->data = new_data;
122 art->size += size * nb;
123
124 return size * nb;
125 }
126
fetch_image(cyanrip_ctx * ctx,CURL * curl_ctx,CRIPArt * art,const char * release_id,const char * type,int info_only,const char * own_url)127 static int fetch_image(cyanrip_ctx *ctx, CURL *curl_ctx, CRIPArt *art,
128 const char *release_id, const char *type, int info_only,
129 const char *own_url)
130 {
131 int ret;
132 char errbuf[CURL_ERROR_SIZE];
133
134 char temp[4096];
135 if (!own_url) {
136 snprintf(temp, sizeof(temp), "%s/%s/%s",
137 COVERART_DB_URL_BASE, release_id, type);
138 curl_easy_setopt(curl_ctx, CURLOPT_URL, temp);
139 } else {
140 curl_easy_setopt(curl_ctx, CURLOPT_URL, own_url);
141 }
142
143 char user_agent[256] = { 0 };
144 sprintf(user_agent, "cyanrip/%s ( https://github.com/cyanreg/cyanrip )", PROJECT_VERSION_STRING);
145 curl_easy_setopt(curl_ctx, CURLOPT_USERAGENT, user_agent);
146
147 curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, receive_image);
148 curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, art);
149
150 curl_easy_setopt(curl_ctx, CURLOPT_ERRORBUFFER, errbuf);
151 curl_easy_setopt(curl_ctx, CURLOPT_FAILONERROR, 1L); /* Explode on errors */
152
153 if (!info_only) {
154 cyanrip_log(ctx, 0, "Downloading %s cover art...\n", type);
155 curl_easy_setopt(curl_ctx, CURLOPT_FOLLOWLOCATION, 1L);
156 }
157
158 CURLcode res = curl_easy_perform(curl_ctx);
159 if (res != CURLE_OK) {
160 /* Filter out the majority of 404 errors here */
161 if (res == CURLE_HTTP_RETURNED_ERROR) {
162 cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": not found!\n", type);
163 crip_free_art(art);
164 ret = own_url ? AVERROR(EINVAL) : 0;
165 goto end;
166 }
167
168 /* Different error */
169 size_t len = strlen(errbuf);
170 if (len)
171 cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s%s!\n",
172 type, errbuf, ((errbuf[len - 1] != '\n') ? "\n" : ""));
173 else
174 cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n",
175 type, curl_easy_strerror(res));
176 ret = AVERROR(EINVAL);
177 goto end;
178 }
179
180 /* Get content type, if not possible we probably have an error somewhere */
181 char *content_type = NULL;
182 res = curl_easy_getinfo(curl_ctx, CURLINFO_CONTENT_TYPE, &content_type);
183 if (res != CURLE_OK) {
184 cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n",
185 type, curl_easy_strerror(res));
186 ret = AVERROR(EINVAL);
187 goto end;
188 }
189
190 /* Response code */
191 long response_code = 0;
192 res = curl_easy_getinfo(curl_ctx, CURLINFO_RESPONSE_CODE, &response_code);
193 if (res != CURLE_OK) {
194 cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n",
195 type, curl_easy_strerror(res));
196 ret = AVERROR(EINVAL);
197 goto end;
198 }
199
200 /* Get the URL */
201 char *final_url = NULL;
202 res = curl_easy_getinfo(curl_ctx, CURLINFO_EFFECTIVE_URL, &final_url);
203 if (res != CURLE_OK) {
204 cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n",
205 type, curl_easy_strerror(res));
206 ret = AVERROR(EINVAL);
207 goto end;
208 }
209
210 ret = art->size;
211
212 if (info_only) {
213 av_freep(&art->data);
214 art->size = 0;
215 } else {
216 char header[99];
217 snprintf(header, sizeof(header), "data:%s;base64,", content_type);
218
219 size_t data_len = AV_BASE64_SIZE(art->size);
220 char *data = av_mallocz(strlen(header) + data_len);
221 memcpy(data, header, strlen(header));
222
223 av_base64_encode(data + strlen(header), data_len, art->data, art->size);
224
225 av_free(art->data);
226 art->data = data;
227 art->size = data_len;
228 }
229
230 art->source_url = av_strdup(final_url);
231
232 end:
233 if (res < 0)
234 crip_free_art(art);
235
236 return ret;
237 }
238
demux_image(cyanrip_ctx * ctx,CRIPArt * art,int info_only)239 static int demux_image(cyanrip_ctx *ctx, CRIPArt *art, int info_only)
240 {
241 int ret = 0;
242 AVFormatContext *avf = NULL;
243 const char *in_url = art->data ? (const char *)art->data : art->source_url;
244
245 ret = avformat_open_input(&avf, in_url, NULL, NULL);
246 if (ret < 0) {
247 cyanrip_log(ctx, 0, "Unable to open \"%s\": %s!\n", art->data ? "(data)" : in_url,
248 av_err2str(ret));
249 goto end;
250 }
251
252 ret = avformat_find_stream_info(avf, NULL);
253 if (ret < 0) {
254 cyanrip_log(ctx, 0, "Unable to get cover image info: %s!\n", av_err2str(ret));
255 goto end;
256 }
257
258 art->params = av_calloc(1, sizeof(AVCodecParameters));
259 if (!art->params)
260 goto end;
261
262 memcpy(art->params, avf->streams[0]->codecpar, sizeof(AVCodecParameters));
263
264 /* Output extension guesswork */
265 av_freep(&art->extension);
266 if (art->params->codec_id == AV_CODEC_ID_MJPEG) {
267 art->extension = av_strdup("jpg");
268 } else if (art->params->codec_id == AV_CODEC_ID_PNG) {
269 art->extension = av_strdup("png");
270 } else if (art->params->codec_id == AV_CODEC_ID_BMP) {
271 art->extension = av_strdup("bmp");
272 } else if (art->params->codec_id == AV_CODEC_ID_TIFF) {
273 art->extension = av_strdup("tiff");
274 } else if (art->params->codec_id == AV_CODEC_ID_AV1) {
275 art->extension = av_strdup("avif");
276 } else if (art->params->codec_id == AV_CODEC_ID_HEVC) {
277 art->extension = av_strdup("heif");
278 } else if (art->params->codec_id == AV_CODEC_ID_WEBP) {
279 art->extension = av_strdup("webp");
280 } else if (art->params->codec_id != AV_CODEC_ID_NONE) {
281 art->extension = av_strdup(avcodec_get_name(art->params->codec_id));
282 } else {
283 ret = AVERROR(EINVAL);
284 cyanrip_log(ctx, 0, "Error demuxing cover image: %s!\n", av_err2str(ret));
285 goto end;
286 }
287
288 if (info_only)
289 goto end;
290
291 art->pkt = av_packet_alloc();
292 if (!art->pkt) {
293 ret = AVERROR(ENOMEM);
294 goto end;
295 }
296
297 ret = av_read_frame(avf, art->pkt);
298 if (ret < 0) {
299 cyanrip_log(ctx, 0, "Error demuxing cover image: %s!\n", av_err2str(ret));
300 goto end;
301 }
302
303 avformat_close_input(&avf);
304
305 av_freep(&art->data);
306 art->size = 0;
307
308 return 0;
309
310 end:
311 avformat_close_input(&avf);
312
313 return ret;
314 }
315
string_is_url(const char * src)316 static int string_is_url(const char *src)
317 {
318 return !strncmp(src, "http://", 4) ||
319 !strncmp(src, "https://", 4) ||
320 !strncmp(src, "ftp://", 4) ||
321 !strncmp(src, "ftps://", 4) ||
322 !strncmp(src, "sftp://", 4) ||
323 !strncmp(src, "tftp://", 4) ||
324 !strncmp(src, "gopher://", 4) ||
325 !strncmp(src, "telnet://", 4);
326 }
327
crip_fill_coverart(cyanrip_ctx * ctx,int info_only)328 int crip_fill_coverart(cyanrip_ctx *ctx, int info_only)
329 {
330 int err = 0;
331
332 int have_front = 0;
333 int have_back = 0;
334 for (int i = 0; i < ctx->nb_cover_arts; i++) {
335 const char *title = dict_get(ctx->cover_arts[i].meta, "title");
336 have_front |= !strcmp(title, "Front");
337 have_back |= !strcmp(title, "Back");
338 }
339
340 CURL *curl_ctx = curl_easy_init();
341
342 if (!have_front || !have_back) {
343 const char *release_id = dict_get(ctx->meta, "release_id");
344 if (!release_id && !ctx->settings.disable_coverart_db) {
345 cyanrip_log(ctx, 0, "Release ID unavailable, cannot search Cover Art DB!\n");
346 } else if (!ctx->settings.disable_coverart_db) {
347 int has_err = 0;
348 if (!have_front) {
349 has_err = fetch_image(ctx, curl_ctx, &ctx->cover_arts[ctx->nb_cover_arts], release_id, "front", info_only, NULL);
350 if (has_err > 0) {
351 av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "title", "Front", 0);
352 av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "source", "Cover Art DB", 0);
353 ctx->cover_arts[ctx->nb_cover_arts].extension = av_strdup("jpg");
354 ctx->nb_cover_arts++;
355 }
356 }
357 if (!have_back && (has_err > 0)) {
358 has_err = fetch_image(ctx, curl_ctx, &ctx->cover_arts[ctx->nb_cover_arts], release_id, "back", info_only, NULL);
359 if (has_err > 0) {
360 av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "title", "Back", 0);
361 av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "source", "Cover Art DB", 0);
362 ctx->cover_arts[ctx->nb_cover_arts].extension = av_strdup("jpg");
363 ctx->nb_cover_arts++;
364 }
365 }
366 }
367 }
368
369 for (int i = 0; i < ctx->nb_cover_arts; i++) {
370 char *source_url = ctx->cover_arts[i].source_url;
371 const char *title = dict_get(ctx->cover_arts[i].meta, "title");
372 int is_url = string_is_url(source_url);
373
374 /* If its a url, we haven't downloaded it from CADB and we're not info printing */
375 if (is_url && !ctx->cover_arts[i].data && !info_only) {
376 err = fetch_image(ctx, curl_ctx, &ctx->cover_arts[i], NULL, title, 0, source_url);
377 if (err < 0)
378 goto end;
379 }
380
381 /* If we don't have to download it, or we have data */
382 if (!is_url || !info_only) {
383 if ((err = demux_image(ctx, &ctx->cover_arts[i], info_only)) < 0)
384 goto end;
385 }
386 }
387
388 end:
389 curl_easy_cleanup(curl_ctx);
390
391 return err;
392 }
393
crip_fill_track_coverart(cyanrip_ctx * ctx,int info_only)394 int crip_fill_track_coverart(cyanrip_ctx *ctx, int info_only)
395 {
396 int err = 0;
397 CURL *curl_ctx = curl_easy_init();
398
399 for (int i = 0; i < ctx->nb_tracks; i++) {
400 if (!ctx->tracks[i].art.source_url)
401 continue;
402
403 char *source_url = ctx->tracks[i].art.source_url;
404 int is_url = string_is_url(source_url);
405
406 /* If its a url, we haven't downloaded it from CADB and we're not info printing */
407 if (is_url && !ctx->tracks[i].art.data && !info_only) {
408 err = fetch_image(ctx, curl_ctx, &ctx->tracks[i].art, NULL, "track", 0, source_url);
409 if (err < 0)
410 goto end;
411 }
412
413 /* If we don't have to download it, or we have data */
414 if (!is_url || !info_only) {
415 if ((err = demux_image(ctx, &ctx->tracks[i].art, info_only)) < 0)
416 goto end;
417 }
418 }
419
420 end:
421 curl_easy_cleanup(curl_ctx);
422
423 return err;
424 }
425