[spotify/artwork] Load artwork for spotify through the wep api

This commit is contained in:
chme 2017-07-11 18:14:43 +02:00 committed by ejurgensen
parent 731106276f
commit 997b4da4ad
6 changed files with 278 additions and 291 deletions

View File

@ -42,7 +42,7 @@
#include "artwork.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
# include "spotify_webapi.h"
#endif
/* This artwork module will look for artwork by consulting a set of sources one
@ -218,6 +218,57 @@ static struct artwork_source artwork_item_source[] =
/* -------------------------------- HELPERS -------------------------------- */
/* Reads an artwork file from the given url straight into an evbuf
*
* @out evbuf Image data
* @in url URL for the image
* @return 0 on success, -1 on error
*/
static int
artwork_url_read(struct evbuffer *evbuf, const char *url)
{
struct http_client_ctx client;
struct keyval *kv;
const char *content_type;
int len;
int ret;
DPRINTF(E_SPAM, L_ART, "Trying internet artwork in %s\n", url);
ret = ART_E_NONE;
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(&client, 0, sizeof(struct http_client_ctx));
client.url = url;
client.input_headers = kv;
client.input_body = evbuf;
if (http_client_request(&client) < 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;
else
ret = ART_FMT_JPEG;
out_kv:
keyval_clear(kv);
free(kv);
out_url:
return ret;
}
/* Reads an artwork file from the filesystem straight into an evbuf
* TODO Use evbuffer_add_file or evbuffer_read?
*
@ -727,10 +778,7 @@ source_item_own_get(struct artwork_ctx *ctx)
static int
source_item_stream_get(struct artwork_ctx *ctx)
{
struct http_client_ctx client;
struct db_queue_item *queue_item;
struct keyval *kv;
const char *content_type;
char *url;
char *ext;
int len;
@ -764,34 +812,14 @@ source_item_stream_get(struct artwork_ctx *ctx)
if (ret > 0)
goto out_url;
kv = keyval_alloc();
if (!kv)
goto out_url;
memset(&client, 0, sizeof(struct http_client_ctx));
client.url = url;
client.input_headers = kv;
client.input_body = ctx->evbuf;
if (http_client_request(&client) < 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;
ret = artwork_url_read(ctx->evbuf, url);
if (ret > 0)
{
DPRINTF(E_SPAM, L_ART, "Found internet stream artwork in %s (%s)\n", url, content_type);
DPRINTF(E_SPAM, L_ART, "Found internet stream artwork in %s (%d)\n", url, ret);
cache_artwork_stash(ctx->evbuf, url, ret);
}
out_kv:
keyval_clear(kv);
free(kv);
out_url:
free(url);
@ -804,8 +832,11 @@ source_item_spotify_get(struct artwork_ctx *ctx)
{
struct evbuffer *raw;
struct evbuffer *evbuf;
char *artwork_url;
int content_type;
int ret;
artwork_url = NULL;
raw = evbuffer_new();
evbuf = evbuffer_new();
if (!raw || !evbuf)
@ -814,15 +845,19 @@ source_item_spotify_get(struct artwork_ctx *ctx)
return ART_E_ERROR;
}
ret = spotify_artwork_get(raw, ctx->dbmfi->path, ctx->max_w, ctx->max_h);
if (ret < 0)
artwork_url = spotifywebapi_artwork_get(ctx->dbmfi->path, ctx->max_w, ctx->max_h);
if (!artwork_url)
{
DPRINTF(E_WARN, L_ART, "No artwork from Spotify for %s\n", ctx->dbmfi->path);
evbuffer_free(raw);
evbuffer_free(evbuf);
return ART_E_NONE;
}
ret = artwork_url_read(raw, artwork_url);
if (ret <= 0)
goto out_free_evbuf;
content_type = ret;
// Make a refbuf of raw for ffmpeg image size probing and possibly rescaling.
// We keep raw around in case rescaling is not necessary.
#ifdef HAVE_LIBEVENT2_OLD
@ -858,12 +893,14 @@ source_item_spotify_get(struct artwork_ctx *ctx)
evbuffer_free(evbuf);
evbuffer_free(raw);
free(artwork_url);
return ART_FMT_JPEG;
return content_type;
out_free_evbuf:
evbuffer_free(evbuf);
evbuffer_free(raw);
free(artwork_url);
return ART_E_ERROR;
}

View File

@ -46,7 +46,7 @@
#include "artwork.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
# include "spotify_webapi.h"
#endif
#include "ffmpeg-compat.h"
@ -224,6 +224,57 @@ static struct artwork_source artwork_item_source[] =
/* -------------------------------- HELPERS -------------------------------- */
/* Reads an artwork file from the given url straight into an evbuf
*
* @out evbuf Image data
* @in url URL for the image
* @return 0 on success, -1 on error
*/
static int
artwork_url_read(struct evbuffer *evbuf, const char *url)
{
struct http_client_ctx client;
struct keyval *kv;
const char *content_type;
int len;
int ret;
DPRINTF(E_SPAM, L_ART, "Trying internet artwork in %s\n", url);
ret = ART_E_NONE;
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(&client, 0, sizeof(struct http_client_ctx));
client.url = url;
client.input_headers = kv;
client.input_body = evbuf;
if (http_client_request(&client) < 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;
else
ret = ART_FMT_JPEG;
out_kv:
keyval_clear(kv);
free(kv);
out_url:
return ret;
}
/* Reads an artwork file from the filesystem straight into an evbuf
* TODO Use evbuffer_add_file or evbuffer_read?
*
@ -1103,10 +1154,7 @@ source_item_own_get(struct artwork_ctx *ctx)
static int
source_item_stream_get(struct artwork_ctx *ctx)
{
struct http_client_ctx client;
struct db_queue_item *queue_item;
struct keyval *kv;
const char *content_type;
char *url;
char *ext;
int len;
@ -1140,34 +1188,14 @@ source_item_stream_get(struct artwork_ctx *ctx)
if (ret > 0)
goto out_url;
kv = keyval_alloc();
if (!kv)
goto out_url;
memset(&client, 0, sizeof(struct http_client_ctx));
client.url = url;
client.input_headers = kv;
client.input_body = ctx->evbuf;
if (http_client_request(&client) < 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;
ret = artwork_url_read(ctx->evbuf, url);
if (ret > 0)
{
DPRINTF(E_SPAM, L_ART, "Found internet stream artwork in %s (%s)\n", url, content_type);
DPRINTF(E_SPAM, L_ART, "Found internet stream artwork in %s (%d)\n", url, ret);
cache_artwork_stash(ctx->evbuf, url, ret);
}
out_kv:
keyval_clear(kv);
free(kv);
out_url:
free(url);
@ -1183,10 +1211,13 @@ source_item_spotify_get(struct artwork_ctx *ctx)
AVInputFormat *ifmt;
struct evbuffer *raw;
struct evbuffer *evbuf;
char *artwork_url;
int content_type;
int target_w;
int target_h;
int ret;
artwork_url = NULL;
raw = evbuffer_new();
evbuf = evbuffer_new();
if (!raw || !evbuf)
@ -1195,15 +1226,19 @@ source_item_spotify_get(struct artwork_ctx *ctx)
return ART_E_ERROR;
}
ret = spotify_artwork_get(raw, ctx->dbmfi->path, ctx->max_w, ctx->max_h);
if (ret < 0)
artwork_url = spotifywebapi_artwork_get(ctx->dbmfi->path, ctx->max_w, ctx->max_h);
if (!artwork_url)
{
DPRINTF(E_WARN, L_ART, "No artwork from Spotify for %s\n", ctx->dbmfi->path);
evbuffer_free(raw);
evbuffer_free(evbuf);
return ART_E_NONE;
}
ret = artwork_url_read(raw, artwork_url);
if (ret <= 0)
goto out_free_evbuf;
content_type = ret;
// Make a refbuf of raw for ffmpeg image size probing and possibly rescaling.
// We keep raw around in case rescaling is not necessary.
#ifdef HAVE_LIBEVENT2_OLD
@ -1278,7 +1313,7 @@ source_item_spotify_get(struct artwork_ctx *ctx)
evbuffer_free(evbuf);
evbuffer_free(raw);
return ART_FMT_JPEG;
return content_type;
out_close_input:
avformat_close_input(&src_ctx);

View File

@ -141,9 +141,6 @@ static bool scanning;
static pthread_mutex_t status_lck;
static struct spotify_status_info spotify_status_info;
// Timeout timespec
static struct timespec spotify_artwork_timeout = { SPOTIFY_ARTWORK_TIMEOUT, 0 };
// Audio buffer
static struct evbuffer *spotify_audio_buffer;
@ -891,194 +888,6 @@ playback_eot(void *arg, int *retval)
return COMMAND_END;
}
static void
artwork_loaded_cb(sp_image *image, void *userdata)
{
struct artwork_get_param *artwork;
artwork = userdata;
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&artwork->mutex));
artwork->is_loaded = 1;
CHECK_ERR(L_SPOTIFY, pthread_cond_signal(&artwork->cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&artwork->mutex));
}
static enum command_state
artwork_get_bh(void *arg, int *retval)
{
struct artwork_get_param *artwork;
sp_imageformat imageformat;
sp_error err;
const void *data;
char *path;
size_t data_size;
int ret;
artwork = arg;
sp_image *image = artwork->image;
path = artwork->path;
fptr_sp_image_remove_load_callback(image, artwork_loaded_cb, artwork);
err = fptr_sp_image_error(image);
if (err != SP_ERROR_OK)
{
DPRINTF(E_WARN, L_SPOTIFY, "Getting artwork (%s) failed, Spotify error: %s\n", path, fptr_sp_error_message(err));
goto fail;
}
if (!fptr_sp_image_is_loaded(image))
{
DPRINTF(E_LOG, L_SPOTIFY, "Load callback returned, but no image? Possible bug: %s\n", path);
goto fail;
}
imageformat = fptr_sp_image_format(image);
if (imageformat != SP_IMAGE_FORMAT_JPEG)
{
DPRINTF(E_WARN, L_SPOTIFY, "Getting artwork failed, invalid image format from Spotify: %s\n", path);
goto fail;
}
data_size = 0;
data = fptr_sp_image_data(image, &data_size);
if (!data)
{
DPRINTF(E_LOG, L_SPOTIFY, "Getting artwork failed, no image data from Spotify: %s\n", path);
goto fail;
}
if ((data_size < 200) || (data_size > 20000000))
{
// Sometimes we get strange data size even though fptr_sp_image_data returns success
DPRINTF(E_LOG, L_SPOTIFY, "Skipping artwork, data size is weird (%zu)\n", data_size);
goto fail;
}
ret = evbuffer_expand(artwork->evbuf, data_size);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for artwork (data size requested was %zu)\n", data_size);
goto fail;
}
ret = evbuffer_add(artwork->evbuf, data, data_size);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Could not add Spotify image to event buffer\n");
goto fail;
}
DPRINTF(E_DBG, L_SPOTIFY, "Spotify artwork loaded ok\n");
fptr_sp_image_release(image);
*retval = 0;
return COMMAND_END;
fail:
fptr_sp_image_release(image);
*retval = -1;
return COMMAND_END;
}
static enum command_state
artwork_get(void *arg, int *retval)
{
struct artwork_get_param *artwork;
char *path;
sp_link *link;
sp_track *track;
sp_album *album;
const byte *image_id;
sp_image *image;
sp_image_size image_size;
sp_error err;
artwork = arg;
path = artwork->path;
// Now begins: path -> link -> track -> album -> image_id -> image -> format -> data
link = fptr_sp_link_create_from_string(path);
if (!link)
{
DPRINTF(E_WARN, L_SPOTIFY, "Getting artwork failed, invalid Spotify link: %s\n", path);
goto level1_exit;
}
track = fptr_sp_link_as_track(link);
if (!track)
{
DPRINTF(E_WARN, L_SPOTIFY, "Getting artwork failed, invalid Spotify track: %s\n", path);
goto level2_exit;
}
album = fptr_sp_track_album(track);
if (!album)
{
DPRINTF(E_WARN, L_SPOTIFY, "Getting artwork failed, invalid Spotify album: %s\n", path);
goto level2_exit;
}
// Get an image at least the same size as requested
image_size = SP_IMAGE_SIZE_SMALL; // 64x64
if ((artwork->max_w > 64) || (artwork->max_h > 64))
image_size = SP_IMAGE_SIZE_NORMAL; // 300x300
if ((artwork->max_w > 300) || (artwork->max_h > 300))
image_size = SP_IMAGE_SIZE_LARGE; // 640x640
image_id = fptr_sp_album_cover(album, image_size);
if (!image_id)
{
DPRINTF(E_DBG, L_SPOTIFY, "Getting artwork failed, no Spotify image id: %s\n", path);
goto level2_exit;
}
image = fptr_sp_image_create(g_sess, image_id);
if (!image)
{
DPRINTF(E_DBG, L_SPOTIFY, "Getting artwork failed, no Spotify image: %s\n", path);
goto level2_exit;
}
fptr_sp_link_release(link);
artwork->image = image;
artwork->is_loaded = fptr_sp_image_is_loaded(image);
/* If the image is ready we can return it straight away, otherwise we will
* let the calling thread wait, since the Spotify thread should not wait
*/
if (artwork->is_loaded)
return artwork_get_bh(artwork, retval);
DPRINTF(E_SPAM, L_SPOTIFY, "Will wait for Spotify to call artwork_loaded_cb\n");
/* Async - we will return to spotify_artwork_get which will wait for callback */
err = fptr_sp_image_add_load_callback(image, artwork_loaded_cb, artwork);
if (err != SP_ERROR_OK)
{
DPRINTF(E_WARN, L_SPOTIFY, "Adding artwork cb failed, Spotify error: %s\n", fptr_sp_error_message(err));
*retval = -1;
return COMMAND_END;
}
*retval = 0;
return COMMAND_END;
level2_exit:
fptr_sp_link_release(link);
level1_exit:
*retval = -1;
return COMMAND_END;
}
/* --------------------------- SESSION CALLBACKS ------------------------- */
/**
* This callback is called when an attempt to login has succeeded or failed.
@ -1449,41 +1258,6 @@ spotify_playback_seek(int ms)
return -1;
}
/* Thread: httpd (artwork) and worker */
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
{
struct artwork_get_param artwork;
struct timespec ts;
int ret;
artwork.evbuf = evbuf;
artwork.path = path;
artwork.max_w = max_w;
artwork.max_h = max_h;
CHECK_ERR(L_SPOTIFY, mutex_init(&artwork.mutex));
CHECK_ERR(L_SPOTIFY, pthread_cond_init(&artwork.cond, NULL));
ret = commands_exec_sync(cmdbase, artwork_get, NULL, &artwork);
// Artwork was not ready, wait for callback from libspotify
if (ret == 0)
{
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&artwork.mutex));
ts = timespec_reltoabs(spotify_artwork_timeout);
while ((!artwork.is_loaded) && (ret != ETIMEDOUT))
CHECK_ERR_EXCEPT(L_SPOTIFY, pthread_cond_timedwait(&artwork.cond, &artwork.mutex, &ts), ret, ETIMEDOUT);
if (ret == ETIMEDOUT)
DPRINTF(E_LOG, L_SPOTIFY, "Timeout waiting for artwork from Spotify\n");
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&artwork.mutex));
ret = commands_exec_sync(cmdbase, artwork_get_bh, NULL, &artwork);
}
return ret;
}
/* Thread: httpd */
void
spotify_oauth_interface(struct evbuffer *evbuf, const char *redirect_uri)

View File

@ -39,9 +39,6 @@ spotify_playback_stop_nonblock(void);
int
spotify_playback_seek(int ms);
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);
void
spotify_oauth_interface(struct evbuffer *evbuf, const char *redirect_uri);

View File

@ -49,6 +49,7 @@ static const char *spotify_client_secret = "232af95f39014c9ba218285a5c11a239";
static const char *spotify_auth_uri = "https://accounts.spotify.com/authorize";
static const char *spotify_token_uri = "https://accounts.spotify.com/api/token";
static const char *spotify_playlist_uri = "https://api.spotify.com/v1/users/%s/playlists/%s";
static const char *spotify_track_uri = "https://api.spotify.com/v1/tracks/%s";
static const char *spotify_me_uri = "https://api.spotify.com/v1/me";
@ -421,6 +422,28 @@ get_owner_plid_from_uri(const char *uri, char **owner, char **plid)
return 0;
}
/*
* Extracts the id from a spotify album/artist/track uri
*
* E. g. album-uri has the following format: spotify:album:[id]
* The id must be freed by the caller.
*/
static int
get_id_from_uri(const char *uri, char **id)
{
char *tmp;
tmp = strrchr(uri, ':');
if (!tmp)
{
return -1;
}
tmp++;
*id = strdup(tmp);
return 0;
}
int
spotifywebapi_playlisttracks_fetch(struct spotify_request *request, struct spotify_track *track)
{
@ -491,6 +514,118 @@ spotifywebapi_playlist_start(struct spotify_request *request, const char *path,
return 0;
}
static int
spotifywebapi_track_start(struct spotify_request *request, const char *path, struct spotify_track *track, json_object **jsonimages, int *image_count)
{
char uri[1024];
char *id;
json_object *jsonalbum;
int ret;
memset(track, 0, sizeof(struct spotify_track));
ret = get_id_from_uri(path, &id);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error extracting id from track uri '%s'\n", path);
return -1;
}
ret = snprintf(uri, sizeof(uri), spotify_track_uri, id);
free(id);
if (ret < 0 || ret >= sizeof(uri))
{
DPRINTF(E_LOG, L_SPOTIFY, "Error creating playlist endpoint uri for playlist '%s'\n", path);
return -1;
}
ret = request_uri(request, uri);
if (ret < 0)
{
return -1;
}
request->haystack = json_tokener_parse(request->response_body);
parse_metadata_track(request->haystack, track);
if (jsonimages && image_count)
{
if (json_object_object_get_ex(request->haystack, "album", &jsonalbum))
{
if (json_object_object_get_ex(jsonalbum, "images", jsonimages))
{
*image_count = json_object_array_length(*jsonimages);
}
}
}
return 0;
}
static int
spotifywebapi_image_fetch(json_object *jsonimages, int index, struct spotify_image *image)
{
json_object *jsonimage;
memset(image, 0, sizeof(struct spotify_image));
jsonimage = json_object_array_get_idx(jsonimages, index);
if (!jsonimage)
{
return -1;
}
image->url = jparse_str_from_obj(jsonimage, "url");
image->width = jparse_int_from_obj(jsonimage, "width");
image->height = jparse_int_from_obj(jsonimage, "height");
return 0;
}
char *
spotifywebapi_artwork_get(const char *path, int max_w, int max_h)
{
struct spotify_request request;
struct spotify_track track;
json_object *jsonimages;
struct spotify_image image;
int image_count;
int i;
char *artwork_url;
int ret;
artwork_url = NULL;
image_count = 0;
memset(&request, 0, sizeof(struct spotify_request));
ret = spotifywebapi_track_start(&request, path, &track, &jsonimages, &image_count);
if (ret == 0)
{
DPRINTF(E_DBG, L_SPOTIFY, "Got track: '%s' (%s) \n", track.name, track.uri);
// Get an image at least the same size as requested, images are returned with decreasing size
for (i = (image_count - 1); i >= 0 && ret == 0; i--)
{
ret = spotifywebapi_image_fetch(jsonimages, i, &image);
if (ret < 0 || !image.url)
continue;
free(artwork_url);
DPRINTF(E_DBG, L_SPOTIFY, "Got image for album '%s' - '%s': '%s' (%dx%d)\n", track.album_artist, track.album, image.url, image.height, image.width);
artwork_url = strdup(image.url);
if ((max_w < image.width) || (max_h < image.height))
break;
}
}
spotifywebapi_request_end(&request);
return artwork_url;
}
static int
request_user_info()
{

View File

@ -82,6 +82,13 @@ struct spotify_playlist
int tracks_count;
};
struct spotify_image
{
const char *url;
int width;
int height;
};
struct spotify_request
{
struct http_client_ctx *ctx;
@ -116,5 +123,7 @@ int
spotifywebapi_playlisttracks_fetch(struct spotify_request *request, struct spotify_track *track);
int
spotifywebapi_playlist_start(struct spotify_request *request, const char *path, struct spotify_playlist *playlist);
char *
spotifywebapi_artwork_get(const char *path, int max_w, int max_h);
#endif /* SRC_SPOTIFY_WEBAPI_H_ */