/* * Copyright (C) 2010-2011 Julien BLACHE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "db.h" #include "misc.h" #include "logger.h" #include "conffile.h" #include "cache.h" #include "player.h" #include "http.h" #if LIBAVFORMAT_VERSION_MAJOR >= 53 # include "avio_evbuffer.h" #endif #include "artwork.h" #ifndef HAVE_LIBEVENT2 # define evbuffer_get_length(x) (x)->off #endif #ifdef HAVE_SPOTIFY_H # include "spotify.h" #endif #if LIBAVCODEC_VERSION_MAJOR <= 53 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR <= 34) # define AV_CODEC_ID_MJPEG CODEC_ID_MJPEG # define AV_CODEC_ID_PNG CODEC_ID_PNG # define AV_CODEC_ID_NONE CODEC_ID_NONE #endif static const char *cover_extension[] = { "jpg", "png", }; static int artwork_read(struct evbuffer *evbuf, char *path) { uint8_t buf[4096]; struct stat sb; int fd; int ret; fd = open(path, O_RDONLY); if (fd < 0) { DPRINTF(E_WARN, L_ART, "Could not open artwork file '%s': %s\n", path, strerror(errno)); return -1; } ret = fstat(fd, &sb); if (ret < 0) { DPRINTF(E_WARN, L_ART, "Could not stat() artwork file '%s': %s\n", path, strerror(errno)); goto out_fail; } ret = evbuffer_expand(evbuf, sb.st_size); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Out of memory for artwork\n"); goto out_fail; } while ((ret = read(fd, buf, sizeof(buf))) > 0) evbuffer_add(evbuf, buf, ret); close(fd); return 0; out_fail: close(fd); return -1; } static int rescale_needed(AVCodecContext *src, int max_w, int max_h, int *target_w, int *target_h) { DPRINTF(E_DBG, L_ART, "Original image dimensions: w %d h %d\n", src->width, src->height); *target_w = src->width; *target_h = src->height; if ((src->width == 0) || (src->height == 0)) /* Unknown source size, can't rescale */ return 0; if ((max_w <= 0) || (max_h <= 0)) /* No valid target dimensions, use original */ return 0; if ((src->width <= max_w) && (src->height <= max_h)) /* Smaller than target */ return 0; if (src->width * max_h > src->height * max_w) /* Wider aspect ratio than target */ { *target_w = max_w; *target_h = (double)max_w * ((double)src->height / (double)src->width); } else /* Taller or equal aspect ratio */ { *target_w = (double)max_h * ((double)src->width / (double)src->height); *target_h = max_h; } DPRINTF(E_DBG, L_ART, "Raw destination width %d height %d\n", *target_w, *target_h); if ((*target_h > max_h) && (max_h > 0)) *target_h = max_h; /* PNG prefers even row count */ *target_w += *target_w % 2; if ((*target_w > max_w) && (max_w > 0)) *target_w = max_w - (max_w % 2); DPRINTF(E_DBG, L_ART, "Destination width %d height %d\n", *target_w, *target_h); return 1; } static int artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out_w, int out_h) { uint8_t *buf; uint8_t *outbuf; AVCodecContext *src; AVFormatContext *dst_ctx; AVCodecContext *dst; AVOutputFormat *dst_fmt; AVStream *dst_st; AVCodec *img_decoder; AVCodec *img_encoder; AVFrame *i_frame; AVFrame *o_frame; struct SwsContext *swsctx; AVPacket pkt; int have_frame; int outbuf_len; int ret; src = src_ctx->streams[s]->codec; // Avoids threading issue in both ffmpeg and libav that prevents decoding embedded png's src->thread_count = 1; img_decoder = avcodec_find_decoder(src->codec_id); if (!img_decoder) { DPRINTF(E_LOG, L_ART, "No suitable decoder found for artwork %s\n", src_ctx->filename); return -1; } #if LIBAVCODEC_VERSION_MAJOR >= 54 || (LIBAVCODEC_VERSION_MAJOR == 53 && LIBAVCODEC_VERSION_MINOR >= 6) ret = avcodec_open2(src, img_decoder, NULL); #else ret = avcodec_open(src, img_decoder); #endif if (ret < 0) { DPRINTF(E_LOG, L_ART, "Could not open codec for decoding: %s\n", strerror(AVUNERROR(ret))); return -1; } /* Set up output */ #if LIBAVFORMAT_VERSION_MAJOR >= 53 || (LIBAVFORMAT_VERSION_MAJOR == 52 && LIBAVFORMAT_VERSION_MINOR >= 45) /* FFmpeg 0.6 */ dst_fmt = av_guess_format("image2", NULL, NULL); #else dst_fmt = guess_format("image2", NULL, NULL); #endif if (!dst_fmt) { DPRINTF(E_LOG, L_ART, "ffmpeg image2 muxer not available\n"); ret = -1; goto out_close_src; } dst_fmt->video_codec = AV_CODEC_ID_NONE; /* Try to keep same codec if possible */ if (src->codec_id == AV_CODEC_ID_PNG) dst_fmt->video_codec = AV_CODEC_ID_PNG; else if (src->codec_id == AV_CODEC_ID_MJPEG) dst_fmt->video_codec = AV_CODEC_ID_MJPEG; /* If not possible, select new codec */ if (dst_fmt->video_codec == AV_CODEC_ID_NONE) { dst_fmt->video_codec = AV_CODEC_ID_PNG; } img_encoder = avcodec_find_encoder(dst_fmt->video_codec); if (!img_encoder) { DPRINTF(E_LOG, L_ART, "No suitable encoder found for codec ID %d\n", dst_fmt->video_codec); ret = -1; goto out_close_src; } dst_ctx = avformat_alloc_context(); if (!dst_ctx) { DPRINTF(E_LOG, L_ART, "Out of memory for format context\n"); ret = -1; goto out_close_src; } dst_ctx->oformat = dst_fmt; #if LIBAVFORMAT_VERSION_MAJOR >= 53 dst_fmt->flags &= ~AVFMT_NOFILE; #else ret = snprintf(dst_ctx->filename, sizeof(dst_ctx->filename), "evbuffer:%p", evbuf); if ((ret < 0) || (ret >= sizeof(dst_ctx->filename))) { DPRINTF(E_LOG, L_ART, "Output artwork URL too long\n"); ret = -1; goto out_free_dst_ctx; } #endif #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21) dst_st = avformat_new_stream(dst_ctx, NULL); #else dst_st = av_new_stream(dst_ctx, 0); #endif if (!dst_st) { DPRINTF(E_LOG, L_ART, "Out of memory for new output stream\n"); ret = -1; goto out_free_dst_ctx; } dst = dst_st->codec; #if LIBAVCODEC_VERSION_MAJOR >= 54 || (LIBAVCODEC_VERSION_MAJOR == 53 && LIBAVCODEC_VERSION_MINOR >= 35) avcodec_get_context_defaults3(dst, NULL); #else avcodec_get_context_defaults2(dst, AVMEDIA_TYPE_VIDEO); #endif if (dst_fmt->flags & AVFMT_GLOBALHEADER) dst->flags |= CODEC_FLAG_GLOBAL_HEADER; dst->codec_id = dst_fmt->video_codec; #if LIBAVCODEC_VERSION_MAJOR >= 53 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR >= 64) dst->codec_type = AVMEDIA_TYPE_VIDEO; #else dst->codec_type = CODEC_TYPE_VIDEO; #endif #if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) # ifndef HAVE_FFMPEG dst->pix_fmt = avcodec_find_best_pix_fmt2((enum AVPixelFormat *)img_encoder->pix_fmts, src->pix_fmt, 1, NULL); # else dst->pix_fmt = avcodec_find_best_pix_fmt_of_list((enum AVPixelFormat *)img_encoder->pix_fmts, src->pix_fmt, 1, NULL); # endif #else const enum PixelFormat *pix_fmts; int64_t pix_fmt_mask = 0; pix_fmts = img_encoder->pix_fmts; while (pix_fmts && (*pix_fmts != -1)) { pix_fmt_mask |= (1 << *pix_fmts); pix_fmts++; } dst->pix_fmt = avcodec_find_best_pix_fmt(pix_fmt_mask, src->pix_fmt, 1, NULL); #endif if (dst->pix_fmt < 0) { DPRINTF(E_LOG, L_ART, "Could not determine best pixel format\n"); ret = -1; goto out_free_dst_ctx; } DPRINTF(E_DBG, L_ART, "Selected pixel format: %d\n", dst->pix_fmt); dst->time_base.num = 1; dst->time_base.den = 25; dst->width = out_w; dst->height = out_h; #if LIBAVFORMAT_VERSION_MAJOR <= 52 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR <= 1) ret = av_set_parameters(dst_ctx, NULL); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Invalid parameters for artwork output: %s\n", strerror(AVUNERROR(ret))); ret = -1; goto out_free_dst_ctx; } #endif /* Open encoder */ #if LIBAVCODEC_VERSION_MAJOR >= 54 || (LIBAVCODEC_VERSION_MAJOR == 53 && LIBAVCODEC_VERSION_MINOR >= 6) ret = avcodec_open2(dst, img_encoder, NULL); #else ret = avcodec_open(dst, img_encoder); #endif if (ret < 0) { DPRINTF(E_LOG, L_ART, "Could not open codec for encoding: %s\n", strerror(AVUNERROR(ret))); ret = -1; goto out_free_dst_ctx; } #if LIBAVCODEC_VERSION_MAJOR >= 56 || (LIBAVCODEC_VERSION_MAJOR == 55 && LIBAVCODEC_VERSION_MINOR >= 29) i_frame = av_frame_alloc(); o_frame = av_frame_alloc(); #else i_frame = avcodec_alloc_frame(); o_frame = avcodec_alloc_frame(); #endif if (!i_frame || !o_frame) { DPRINTF(E_LOG, L_ART, "Could not allocate input/output frame\n"); ret = -1; goto out_free_frames; } ret = avpicture_get_size(dst->pix_fmt, src->width, src->height); DPRINTF(E_DBG, L_ART, "Artwork buffer size: %d\n", ret); buf = (uint8_t *)av_malloc(ret); if (!buf) { DPRINTF(E_LOG, L_ART, "Out of memory for artwork buffer\n"); ret = -1; goto out_free_frames; } avpicture_fill((AVPicture *)o_frame, buf, dst->pix_fmt, src->width, src->height); swsctx = sws_getContext(src->width, src->height, src->pix_fmt, dst->width, dst->height, dst->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL); if (!swsctx) { DPRINTF(E_LOG, L_ART, "Could not get SWS context\n"); ret = -1; goto out_free_buf; } /* Get frame */ have_frame = 0; while (av_read_frame(src_ctx, &pkt) == 0) { if (pkt.stream_index != s) { av_free_packet(&pkt); continue; } #if LIBAVCODEC_VERSION_MAJOR >= 53 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR >= 32) /* FFmpeg 0.6 */ avcodec_decode_video2(src, i_frame, &have_frame, &pkt); #else avcodec_decode_video(src, i_frame, &have_frame, pkt.data, pkt.size); #endif break; } if (!have_frame) { DPRINTF(E_LOG, L_ART, "Could not decode artwork\n"); av_free_packet(&pkt); sws_freeContext(swsctx); ret = -1; goto out_free_buf; } /* Scale */ #if LIBSWSCALE_VERSION_MAJOR >= 1 || (LIBSWSCALE_VERSION_MAJOR == 0 && LIBSWSCALE_VERSION_MINOR >= 9) /* FFmpeg 0.6, libav 0.6+ */ sws_scale(swsctx, (const uint8_t * const *)i_frame->data, i_frame->linesize, 0, src->height, o_frame->data, o_frame->linesize); #else sws_scale(swsctx, i_frame->data, i_frame->linesize, 0, src->height, o_frame->data, o_frame->linesize); #endif sws_freeContext(swsctx); av_free_packet(&pkt); /* Open output file */ #if LIBAVFORMAT_VERSION_MAJOR >= 53 dst_ctx->pb = avio_evbuffer_open(evbuf); #else ret = url_fopen(&dst_ctx->pb, dst_ctx->filename, URL_WRONLY); #endif if (ret < 0) { DPRINTF(E_LOG, L_ART, "Could not open artwork destination buffer\n"); ret = -1; goto out_free_buf; } /* Encode frame */ outbuf_len = dst->width * dst->height * 3; if (outbuf_len < FF_MIN_BUFFER_SIZE) outbuf_len = FF_MIN_BUFFER_SIZE; outbuf = (uint8_t *)av_malloc(outbuf_len); if (!outbuf) { DPRINTF(E_LOG, L_ART, "Out of memory for encoded artwork buffer\n"); #if LIBAVFORMAT_VERSION_MAJOR >= 53 avio_evbuffer_close(dst_ctx->pb); #else url_fclose(dst_ctx->pb); #endif ret = -1; goto out_free_buf; } #if LIBAVCODEC_VERSION_MAJOR >= 54 av_init_packet(&pkt); pkt.data = outbuf; pkt.size = outbuf_len; ret = avcodec_encode_video2(dst, &pkt, o_frame, &have_frame); if (!ret && have_frame && dst->coded_frame) { dst->coded_frame->pts = pkt.pts; dst->coded_frame->key_frame = !!(pkt.flags & AV_PKT_FLAG_KEY); } else if (ret < 0) { DPRINTF(E_LOG, L_ART, "Could not encode artwork\n"); ret = -1; goto out_fclose_dst; } #else ret = avcodec_encode_video(dst, outbuf, outbuf_len, o_frame); if (ret <= 0) { DPRINTF(E_LOG, L_ART, "Could not encode artwork\n"); ret = -1; goto out_fclose_dst; } av_init_packet(&pkt); pkt.stream_index = 0; pkt.data = outbuf; pkt.size = ret; #endif #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3) ret = avformat_write_header(dst_ctx, NULL); #else ret = av_write_header(dst_ctx); #endif if (ret != 0) { DPRINTF(E_LOG, L_ART, "Could not write artwork header: %s\n", strerror(AVUNERROR(ret))); ret = -1; goto out_fclose_dst; } ret = av_interleaved_write_frame(dst_ctx, &pkt); if (ret != 0) { DPRINTF(E_LOG, L_ART, "Error writing artwork\n"); ret = -1; goto out_fclose_dst; } ret = av_write_trailer(dst_ctx); if (ret != 0) { DPRINTF(E_LOG, L_ART, "Could not write artwork trailer: %s\n", strerror(AVUNERROR(ret))); ret = -1; goto out_fclose_dst; } switch (dst_fmt->video_codec) { case AV_CODEC_ID_PNG: ret = ART_FMT_PNG; break; case AV_CODEC_ID_MJPEG: ret = ART_FMT_JPEG; break; default: DPRINTF(E_LOG, L_ART, "Unhandled rescale output format\n"); ret = -1; break; } out_fclose_dst: #if LIBAVFORMAT_VERSION_MAJOR >= 53 avio_evbuffer_close(dst_ctx->pb); #else url_fclose(dst_ctx->pb); #endif av_free(outbuf); out_free_buf: av_free(buf); out_free_frames: #if LIBAVCODEC_VERSION_MAJOR >= 56 || (LIBAVCODEC_VERSION_MAJOR == 55 && LIBAVCODEC_VERSION_MINOR >= 29) if (i_frame) av_frame_free(&i_frame); if (o_frame) av_frame_free(&o_frame); #elif LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) if (i_frame) avcodec_free_frame(&i_frame); if (o_frame) avcodec_free_frame(&o_frame); #else if (i_frame) av_free(i_frame); if (o_frame) av_free(o_frame); #endif avcodec_close(dst); out_free_dst_ctx: avformat_free_context(dst_ctx); out_close_src: avcodec_close(src); return ret; } static int artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h) { AVFormatContext *src_ctx; int s; int target_w; int target_h; int format_ok; int ret; DPRINTF(E_DBG, L_ART, "Getting artwork (max destination width %d height %d)\n", max_w, max_h); src_ctx = NULL; #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3) ret = avformat_open_input(&src_ctx, path, NULL, NULL); #else ret = av_open_input_file(&src_ctx, path, NULL, 0, NULL); #endif if (ret < 0) { DPRINTF(E_WARN, L_ART, "Cannot open artwork file '%s': %s\n", path, strerror(AVUNERROR(ret))); return -1; } #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3) ret = avformat_find_stream_info(src_ctx, NULL); #else ret = av_find_stream_info(src_ctx); #endif if (ret < 0) { DPRINTF(E_WARN, L_ART, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret))); #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21) avformat_close_input(&src_ctx); #else av_close_input_file(src_ctx); #endif return -1; } format_ok = 0; for (s = 0; s < src_ctx->nb_streams; s++) { if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_PNG) { format_ok = ART_FMT_PNG; break; } else if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_MJPEG) { format_ok = ART_FMT_JPEG; break; } } if (s == src_ctx->nb_streams) { DPRINTF(E_LOG, L_ART, "Artwork file '%s' not a PNG or JPEG file\n", path); #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21) avformat_close_input(&src_ctx); #else av_close_input_file(src_ctx); #endif return -1; } ret = rescale_needed(src_ctx->streams[s]->codec, max_w, max_h, &target_w, &target_h); /* Fastpath */ if (!ret && format_ok) { ret = artwork_read(evbuf, path); if (ret == 0) ret = format_ok; } else ret = artwork_rescale(evbuf, src_ctx, s, target_w, target_h); #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21) avformat_close_input(&src_ctx); #else av_close_input_file(src_ctx); #endif if (ret < 0) { if (evbuffer_get_length(evbuf) > 0) evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); } return ret; } static int artwork_get_player_image(struct evbuffer *evbuf, char *path) { struct http_client_ctx ctx; struct keyval *kv; const char *content_type; char *url; int len; int ret; DPRINTF(E_DBG, L_ART, "Trying internet stream artwork in %s\n", path); ret = 0; player_icy_artwork_url(&url, path); if (!url) return 0; len = strlen(url); if ((len < 14) || (len > PATH_MAX)) // Can't be shorter than http://a/1.jpg goto out_url; kv = keyval_alloc(); if (!kv) goto out_url; memset(&ctx, 0, sizeof(ctx)); ctx.url = url; ctx.headers = kv; ctx.body = evbuf; if (http_client_request(&ctx) < 0) goto out_kv; content_type = keyval_get(kv, "Content-Type"); if (content_type && (strcmp(content_type, "image/jpeg") == 0)) ret = ART_FMT_JPEG; else if (content_type && (strcmp(content_type, "image/png") == 0)) ret = ART_FMT_PNG; if (ret) DPRINTF(E_DBG, L_ART, "Found internet stream artwork in %s (%s)\n", url, content_type); out_kv: keyval_clear(kv); free(kv); out_url: free(url); return ret; } #if LIBAVFORMAT_VERSION_MAJOR >= 55 || (LIBAVFORMAT_VERSION_MAJOR == 54 && LIBAVFORMAT_VERSION_MINOR >= 6) static int artwork_get_embedded_image(struct evbuffer *evbuf, char *path, int max_w, int max_h) { AVFormatContext *src_ctx; AVStream *src_st; int s; int target_w; int target_h; int format_ok; int ret; DPRINTF(E_SPAM, L_ART, "Trying embedded artwork in %s\n", path); src_ctx = NULL; ret = avformat_open_input(&src_ctx, path, NULL, NULL); if (ret < 0) { DPRINTF(E_WARN, L_ART, "Cannot open media file '%s': %s\n", path, strerror(AVUNERROR(ret))); return -1; } ret = avformat_find_stream_info(src_ctx, NULL); if (ret < 0) { DPRINTF(E_WARN, L_ART, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret))); avformat_close_input(&src_ctx); return -1; } format_ok = 0; for (s = 0; s < src_ctx->nb_streams; s++) { if (src_ctx->streams[s]->disposition & AV_DISPOSITION_ATTACHED_PIC) { if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_PNG) { format_ok = ART_FMT_PNG; break; } else if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_MJPEG) { format_ok = ART_FMT_JPEG; break; } } } if (s == src_ctx->nb_streams) { DPRINTF(E_DBG, L_ART, "Did not find embedded artwork in '%s'\n", path); avformat_close_input(&src_ctx); return -1; } else DPRINTF(E_DBG, L_ART, "Found embedded artwork in '%s'\n", path); src_st = src_ctx->streams[s]; ret = rescale_needed(src_st->codec, max_w, max_h, &target_w, &target_h); /* Fastpath */ if (!ret && format_ok) { DPRINTF(E_DBG, L_ART, "Artwork not too large, using original image\n"); ret = evbuffer_expand(evbuf, src_st->attached_pic.size); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Out of memory for artwork\n"); avformat_close_input(&src_ctx); return -1; } ret = evbuffer_add(evbuf, src_st->attached_pic.data, src_st->attached_pic.size); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Could not add embedded image to event buffer\n"); } else ret = format_ok; } else { DPRINTF(E_DBG, L_ART, "Artwork too large, rescaling image\n"); ret = artwork_rescale(evbuf, src_ctx, s, target_w, target_h); } avformat_close_input(&src_ctx); if (ret < 0) { if (evbuffer_get_length(evbuf) > 0) evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); } return ret; } #endif /* * Looks for basename(in_path).{png,jpg}, so if is in_path is /foo/bar.mp3 it * will look for /foo/bar.png and /foo/bar.jpg * * @param evbuf the event buffer that will contain the (scaled) image * @param in_path path to the item we are getting artwork for * @param max_w maximum image width * @param max_h maximum image height * @param out_path path to artwork, input must be either NULL or char[PATH_MAX] * @return ART_FMT_* on success, 0 on nothing found, -1 on error */ static int artwork_get_own_image(struct evbuffer *evbuf, char *in_path, int max_w, int max_h, char *out_path) { char path[PATH_MAX]; char *ptr; int len; int nextensions; int i; int ret; ret = snprintf(path, sizeof(path), "%s", in_path); if ((ret < 0) || (ret >= sizeof(path))) { DPRINTF(E_LOG, L_ART, "Artwork path exceeds PATH_MAX (%s)\n", in_path); return -1; } ptr = strrchr(path, '.'); if (ptr) *ptr = '\0'; len = strlen(path); nextensions = sizeof(cover_extension) / sizeof(cover_extension[0]); for (i = 0; i < nextensions; i++) { ret = snprintf(path + len, sizeof(path) - len, ".%s", cover_extension[i]); if ((ret < 0) || (ret >= sizeof(path) - len)) { DPRINTF(E_LOG, L_ART, "Artwork path will exceed PATH_MAX (%s)\n", in_path); continue; } DPRINTF(E_SPAM, L_ART, "Trying own artwork file %s\n", path); ret = access(path, F_OK); if (ret < 0) continue; break; } if (i == nextensions) return 0; DPRINTF(E_DBG, L_ART, "Found own artwork file %s\n", path); if (out_path) strcpy(out_path, path); return artwork_get(evbuf, path, max_w, max_h); } /* * Looks for cover files in a directory, so if dir is /foo/bar and the user has * configured the cover file names "cover" and "artwork" it will look for * /foo/bar/cover.{png,jpg}, /foo/bar/artwork.{png,jpg} and also * /foo/bar/bar.{png,jpg} (so called parentdir artwork) * * @param evbuf the event buffer that will contain the (scaled) image * @param dir the directory to search * @param max_w maximum image width * @param max_h maximum image height * @param out_path path to artwork, input must be either NULL or char[PATH_MAX] * @return ART_FMT_* on success, 0 on nothing found, -1 on error */ static int artwork_get_dir_image(struct evbuffer *evbuf, char *dir, int max_w, int max_h, char *out_path) { char path[PATH_MAX]; char parentdir[PATH_MAX]; int i; int j; int len; int ret; cfg_t *lib; int nbasenames; int nextensions; char *ptr; ret = snprintf(path, sizeof(path), "%s", dir); if ((ret < 0) || (ret >= sizeof(path))) { DPRINTF(E_LOG, L_ART, "Artwork path exceeds PATH_MAX (%s)\n", dir); return -1; } len = strlen(path); lib = cfg_getsec(cfg, "library"); nbasenames = cfg_size(lib, "artwork_basenames"); if (nbasenames == 0) return 0; nextensions = sizeof(cover_extension) / sizeof(cover_extension[0]); for (i = 0; i < nbasenames; i++) { for (j = 0; j < nextensions; j++) { ret = snprintf(path + len, sizeof(path) - len, "/%s.%s", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]); if ((ret < 0) || (ret >= sizeof(path) - len)) { DPRINTF(E_LOG, L_ART, "Artwork path will exceed PATH_MAX (%s/%s)\n", dir, cfg_getnstr(lib, "artwork_basenames", i)); continue; } DPRINTF(E_SPAM, L_ART, "Trying directory artwork file %s\n", path); ret = access(path, F_OK); if (ret < 0) continue; // If artwork file exists (ret == 0), exit the loop break; } // In case the previous loop exited early, we found an existing artwork file and exit the outer loop if (j < nextensions) break; } // If the loop for directory artwork did not exit early, look for parent directory artwork if (i == nbasenames) { ptr = strrchr(path, '/'); if (ptr) *ptr = '\0'; ptr = strrchr(path, '/'); if ((!ptr) || (strlen(ptr) <= 1)) { DPRINTF(E_LOG, L_ART, "Could not find parent dir name (%s)\n", path); return -1; } strcpy(parentdir, ptr + 1); len = strlen(path); for (i = 0; i < nextensions; i++) { ret = snprintf(path + len, sizeof(path) - len, "/%s.%s", parentdir, cover_extension[i]); if ((ret < 0) || (ret >= sizeof(path) - len)) { DPRINTF(E_LOG, L_ART, "Artwork path will exceed PATH_MAX (%s)\n", parentdir); continue; } DPRINTF(E_SPAM, L_ART, "Trying parent directory artwork file %s\n", path); ret = access(path, F_OK); if (ret < 0) continue; break; } if (i == nextensions) return 0; } DPRINTF(E_DBG, L_ART, "Found directory artwork file %s\n", path); if (out_path) strcpy(out_path, path); return artwork_get(evbuf, path, max_w, max_h); } /* * Given an artwork type (eg embedded, Spotify, own) this function will direct * to the appropriate handler * * @param evbuf the event buffer that will contain the (scaled) image * @param in_path path to the item we are getting artwork for * @param artwork type of the artwork * @param max_w maximum image width * @param max_h maximum image height * @param out_path path to artwork, input must be either NULL or char[PATH_MAX] * @return ART_FMT_* on success, 0 on nothing found, -1 on error */ static int artwork_get_item_path(struct evbuffer *evbuf, char *in_path, int artwork, int max_w, int max_h, char *out_path) { int ret; ret = 0; if (out_path) strcpy(out_path, in_path); switch (artwork) { case ARTWORK_NONE: break; case ARTWORK_UNKNOWN: case ARTWORK_OWN: if (cfg_getbool(cfg_getsec(cfg, "library"), "artwork_individual")) ret = artwork_get_own_image(evbuf, in_path, max_w, max_h, out_path); break; #ifdef HAVE_SPOTIFY_H case ARTWORK_SPOTIFY: ret = spotify_artwork_get(evbuf, in_path, max_w, max_h); (ret < 0) ? (ret = 0) : (ret = ART_FMT_JPEG); break; #endif #if LIBAVFORMAT_VERSION_MAJOR >= 55 || (LIBAVFORMAT_VERSION_MAJOR == 54 && LIBAVFORMAT_VERSION_MINOR >= 6) case ARTWORK_EMBEDDED: ret = artwork_get_embedded_image(evbuf, in_path, max_w, max_h); break; #endif case ARTWORK_HTTP: ret = artwork_get_player_image(evbuf, in_path); break; } return ret; } /* * Get the artwork for the given media file and the given maxiumum width/height * @param evbuf the event buffer that will contain the (scaled) image * @param mfi the media file structure for the file whose image should be returned * @param max_w maximum image width * @param max_h maximum image height * @return ART_FMT_* on success, 0 on nothing found, -1 on error */ static int artwork_get_item_mfi(struct evbuffer *evbuf, struct media_file_info *mfi, int max_w, int max_h) { char path[PATH_MAX]; int cached; int format; int ret; DPRINTF(E_DBG, L_ART, "Looking for artwork for item with id %d\n", mfi->id); ret = cache_artwork_get(CACHE_ARTWORK_INDIVIDUAL, mfi->id, max_w, max_h, &cached, &format, evbuf); if ((ret == 0) && cached) { DPRINTF(E_DBG, L_ART, "Item %d found in cache with format %d\n", mfi->id, format); return format; } if (mfi->data_kind == 0) { format = artwork_get_item_path(evbuf, mfi->path, mfi->artwork, max_w, max_h, path); if (format > 0) cache_artwork_add(CACHE_ARTWORK_INDIVIDUAL, mfi->id, max_w, max_h, format, path, evbuf); return format; } return -1; } /* * Get the artwork image for the given persistentid and the given maximum width/height * * The function first checks if there is a cache entry, if not it will first look for directory artwork files. * If no directory artwork files are found, it looks for individual artwork (embedded images or images from spotify). * * @param evbuf the event buffer that will contain the (scaled) image * @param persistentid persistent songalbumid or songartistid * @param max_w maximum image width * @param max_h maximum image height * @return ART_FMT_* on success, 0 on nothing found, -1 on error */ static int artwork_get_group_persistentid(struct evbuffer *evbuf, int64_t persistentid, int max_w, int max_h) { struct query_params qp; struct db_media_file_info dbmfi; char path[PATH_MAX]; char *dir; int cached; int format; int ret; int artwork; int got_spotifyitem; uint32_t data_kind; DPRINTF(E_DBG, L_ART, "Looking for artwork for group with persistentid %" PRIi64 "\n", persistentid); got_spotifyitem = 0; /* * First check if the artwork cache has a cached entry for the given persistent id and requested width/height */ ret = cache_artwork_get(CACHE_ARTWORK_GROUP, persistentid, max_w, max_h, &cached, &format, evbuf); if ((ret == 0) && cached) { DPRINTF(E_DBG, L_ART, "Group %" PRIi64 " found in cache with format %d\n", persistentid, format); return format; } /* Image is not in the artwork cache. Try directory artwork first */ memset(&qp, 0, sizeof(struct query_params)); qp.type = Q_GROUP_DIRS; qp.persistentid = persistentid; ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Could not start Q_GROUP_DIRS query\n"); /* Skip to invidual files artwork */ goto files_art; } format = 0; while (((ret = db_query_fetch_string(&qp, &dir)) == 0) && (dir)) { /* The db query may return non-directories (eg if item is an internet stream or Spotify) */ if (access(dir, F_OK) < 0) continue; format = artwork_get_dir_image(evbuf, dir, max_w, max_h, path); if (format > 0) break; } db_query_end(&qp); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Error fetching Q_GROUP_DIRS results\n"); goto files_art; } /* Found artwork, cache it and return */ if (format > 0) { cache_artwork_add(CACHE_ARTWORK_GROUP, persistentid, max_w, max_h, format, path, evbuf); return format; } /* Then try individual files */ files_art: memset(&qp, 0, sizeof(struct query_params)); qp.type = Q_GROUP_ITEMS; qp.persistentid = persistentid; ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Could not start Q_GROUP_ITEMS query\n"); return -1; } format = 0; while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) { if ((safe_atoi32(dbmfi.artwork, &artwork) != 0) && (safe_atou32(dbmfi.data_kind, &data_kind) != 0)) continue; format = artwork_get_item_path(evbuf, dbmfi.path, artwork, max_w, max_h, path); if (artwork == ARTWORK_SPOTIFY) got_spotifyitem = 1; if (format > 0) break; } db_query_end(&qp); if (ret < 0) { DPRINTF(E_LOG, L_ART, "Error fetching Q_GROUP_ITEMS results\n"); return -1; } /* Found artwork, cache it and return */ if (format > 0) { if (artwork != ARTWORK_HTTP) cache_artwork_add(CACHE_ARTWORK_GROUP, persistentid, max_w, max_h, format, path, evbuf); return format; } else if (format < 0) { DPRINTF(E_WARN, L_ART, "Error getting artwork for group %" PRIi64 "\n", persistentid); return -1; } DPRINTF(E_DBG, L_ART, "No artwork found for group %" PRIi64 "\n", persistentid); /* Add cache entry for no artwork available */ if ((artwork != ARTWORK_HTTP) && (!got_spotifyitem)) cache_artwork_add(CACHE_ARTWORK_GROUP, persistentid, max_w, max_h, 0, "", evbuf); return 0; } /* * Get the artwork image for the given item id and the given maximum width/height * * @param evbuf the event buffer that will contain the (scaled) image * @param id the mfi item id * @param max_w maximum image width * @param max_h maximum image height * @return ART_FMT_* on success, -1 on error or no artwork found */ int artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h) { struct media_file_info *mfi; int format; mfi = db_file_fetch_byid(id); if (!mfi) { DPRINTF(E_LOG, L_ART, "Artwork request for item %d, but no such item in database\n", id); return -1; } DPRINTF(E_DBG, L_ART, "Artwork request for item %d (%s)\n", id, mfi->fname); format = 0; if (cfg_getbool(cfg_getsec(cfg, "library"), "artwork_individual")) format = artwork_get_item_mfi(evbuf, mfi, max_w, max_h); /* No individual artwork or individual artwork disabled, try group artwork */ if (format <= 0) format = artwork_get_group_persistentid(evbuf, mfi->songalbumid, max_w, max_h); free_mfi(mfi, 0); if (format <= 0) { DPRINTF(E_DBG, L_ART, "No artwork found for item %d\n", id); return -1; } return format; } /* * Get the artwork image for the given group id and the given maximum width/height * * @param evbuf the event buffer that will contain the (scaled) image * @param id the group id (not the persistent id) * @param max_w maximum image width * @param max_h maximum image height * @return ART_FMT_* on success, -1 on error or no artwork found */ int artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h) { int64_t persistentid; int format; DPRINTF(E_DBG, L_ART, "Artwork request for group %d\n", id); /* Get the persistent id for the given group id */ if (db_group_persistentid_byid(id, &persistentid) < 0) { DPRINTF(E_LOG, L_ART, "Error fetching persistent id for group id %d\n", id); return -1; } /* Load artwork image for the persistent id */ format = artwork_get_group_persistentid(evbuf, persistentid, max_w, max_h); if (format <= 0) { DPRINTF(E_DBG, L_ART, "No artwork found for group %d\n", id); return -1; } return format; } /* Checks if the file is an artwork file */ int artwork_file_is_artwork(const char *filename) { cfg_t *lib; int n; int i; int j; int ret; char artwork[PATH_MAX]; lib = cfg_getsec(cfg, "library"); n = cfg_size(lib, "artwork_basenames"); for (i = 0; i < n; i++) { for (j = 0; j < (sizeof(cover_extension) / sizeof(cover_extension[0])); j++) { ret = snprintf(artwork, sizeof(artwork), "%s.%s", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]); if ((ret < 0) || (ret >= sizeof(artwork))) { DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX (%s.%s)\n", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]); continue; } if (strcmp(artwork, filename) == 0) return 1; } if (j < (sizeof(cover_extension) / sizeof(cover_extension[0]))) break; } return 0; }