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