mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-27 15:45:56 -05:00
[spotify] Scan saved albums and playlist using the spotify web api
This commit is contained in:
parent
1672b67040
commit
0bea83cafa
@ -1415,7 +1415,7 @@ cache_daap_threshold(void)
|
||||
* @return 0 if successful, -1 if an error occurred
|
||||
*/
|
||||
void
|
||||
cache_artwork_ping(char *path, time_t mtime, int del)
|
||||
cache_artwork_ping(const char *path, time_t mtime, int del)
|
||||
{
|
||||
struct cache_arg *cmdarg;
|
||||
|
||||
|
@ -31,7 +31,7 @@ cache_daap_threshold(void);
|
||||
#define CACHE_ARTWORK_INDIVIDUAL 1
|
||||
|
||||
void
|
||||
cache_artwork_ping(char *path, time_t mtime, int del);
|
||||
cache_artwork_ping(const char *path, time_t mtime, int del);
|
||||
|
||||
int
|
||||
cache_artwork_delete_by_path(char *path);
|
||||
|
262
src/spotify.c
262
src/spotify.c
@ -156,8 +156,8 @@ static void *g_libhandle;
|
||||
static enum spotify_state g_state;
|
||||
// The base playlist id for all Spotify playlists in the db
|
||||
static int spotify_base_plid;
|
||||
// The base playlist id for Spotify saved tracks in the db
|
||||
static int spotify_saved_plid;
|
||||
// Flag telling us if access to the web api was granted
|
||||
static bool spotify_access_token_valid;
|
||||
|
||||
// Audio fifo
|
||||
static audio_fifo_t *g_audio_fifo;
|
||||
@ -707,7 +707,7 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
|
||||
|
||||
// DPRINTF(E_DBG, L_SPOTIFY, "Saving track '%s': '%s' by %s (%s)\n", url, mfi.title, mfi.artist, mfi.album);
|
||||
|
||||
library_process_media(url, time(NULL), 0, DATA_KIND_SPOTIFY, 0, 0, &mfi, dir_id);
|
||||
library_process_media(url, time(NULL), 0, DATA_KIND_SPOTIFY, 0, false, &mfi, dir_id);
|
||||
|
||||
free_mfi(&mfi, 1);
|
||||
|
||||
@ -910,7 +910,7 @@ spotify_playlist_save(sp_playlist *pl)
|
||||
// told which track it was...). Note that this function will result in a ref
|
||||
// count on the sp_link, which the caller must decrease with sp_link_release.
|
||||
static enum command_state
|
||||
spotify_uri_register(void *arg, int *retval)
|
||||
uri_register(void *arg, int *retval)
|
||||
{
|
||||
sp_link *link;
|
||||
sp_track *track;
|
||||
@ -963,7 +963,8 @@ static void playlist_update_in_progress(sp_playlist *pl, bool done, void *userda
|
||||
{
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "Playlist update (status %d): %s\n", done, fptr_sp_playlist_name(pl));
|
||||
|
||||
spotify_playlist_save(pl);
|
||||
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
|
||||
spotify_playlist_save(pl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -971,7 +972,8 @@ static void playlist_metadata_updated(sp_playlist *pl, void *userdata)
|
||||
{
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "Playlist metadata updated: %s\n", fptr_sp_playlist_name(pl));
|
||||
|
||||
spotify_playlist_save(pl);
|
||||
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
|
||||
spotify_playlist_save(pl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1001,7 +1003,8 @@ static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
|
||||
|
||||
fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
|
||||
|
||||
spotify_playlist_save(pl);
|
||||
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
|
||||
spotify_playlist_save(pl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1027,6 +1030,9 @@ playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *
|
||||
|
||||
fptr_sp_playlist_remove_callbacks(pl, &pl_callbacks, NULL);
|
||||
|
||||
if (spotify_access_token_valid) // TODO Trigger update of library on pl change
|
||||
return;
|
||||
|
||||
link = fptr_sp_link_create_from_playlist(pl);
|
||||
if (!link)
|
||||
{
|
||||
@ -1545,11 +1551,8 @@ artwork_get(void *arg, int *retval)
|
||||
static void
|
||||
logged_in(sp_session *sess, sp_error error)
|
||||
{
|
||||
cfg_t *spotify_cfg;
|
||||
sp_playlist *pl;
|
||||
sp_playlistcontainer *pc;
|
||||
struct playlist_info pli;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (SP_ERROR_OK != error)
|
||||
@ -1565,24 +1568,6 @@ logged_in(sp_session *sess, sp_error error)
|
||||
pl = fptr_sp_session_starred_create(sess);
|
||||
fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
|
||||
|
||||
spotify_cfg = cfg_getsec(cfg, "spotify");
|
||||
if (! cfg_getbool(spotify_cfg, "base_playlist_disable"))
|
||||
{
|
||||
memset(&pli, 0, sizeof(struct playlist_info));
|
||||
pli.title = "Spotify";
|
||||
pli.type = PL_FOLDER;
|
||||
pli.path = "spotify:playlistfolder";
|
||||
|
||||
ret = db_pl_add(&pli, &spotify_base_plid);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
spotify_base_plid = 0;
|
||||
|
||||
pc = fptr_sp_session_playlistcontainer(sess);
|
||||
|
||||
fptr_sp_playlistcontainer_add_callbacks(pc, &pc_callbacks, NULL);
|
||||
@ -2005,6 +1990,9 @@ spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const ch
|
||||
return;
|
||||
}
|
||||
|
||||
// Received a valid access token
|
||||
spotify_access_token_valid = true;
|
||||
|
||||
// TODO Trigger init-scan after successful access to spotifywebapi
|
||||
|
||||
evbuffer_add_printf(evbuf, "ok, all done</p>\n");
|
||||
@ -2012,6 +2000,15 @@ spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const ch
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
spotify_uri_register(const char *uri)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
tmp = strdup(uri);
|
||||
commands_exec_async(cmdbase, uri_register, tmp);
|
||||
}
|
||||
|
||||
/* Thread: library */
|
||||
void
|
||||
spotify_login(char *path)
|
||||
@ -2055,9 +2052,6 @@ spotify_login(char *path)
|
||||
|
||||
if (path)
|
||||
{
|
||||
db_spotify_purge();
|
||||
spotify_saved_plid = 0;
|
||||
|
||||
ret = spotify_file_read(path, &username, &password);
|
||||
if (ret < 0)
|
||||
return;
|
||||
@ -2068,9 +2062,6 @@ spotify_login(char *path)
|
||||
}
|
||||
else
|
||||
{
|
||||
db_spotify_purge();
|
||||
spotify_saved_plid = 0;
|
||||
|
||||
err = fptr_sp_session_relogin(g_sess);
|
||||
}
|
||||
|
||||
@ -2081,11 +2072,212 @@ spotify_login(char *path)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
map_track_to_mfi(const struct spotify_track *track, struct media_file_info* mfi)
|
||||
{
|
||||
mfi->title = safe_strdup(track->name);
|
||||
mfi->album = safe_strdup(track->album);
|
||||
mfi->artist = safe_strdup(track->artist);
|
||||
mfi->album_artist = safe_strdup(track->album_artist);
|
||||
mfi->disc = track->disc_number;
|
||||
mfi->song_length = track->duration_ms;
|
||||
mfi->track = track->track_number;
|
||||
mfi->compilation = track->is_compilation;
|
||||
}
|
||||
|
||||
static void
|
||||
map_album_to_mfi(const struct spotify_album *album, struct media_file_info* mfi)
|
||||
{
|
||||
mfi->album = safe_strdup(album->name);
|
||||
mfi->album_artist = safe_strdup(album->artist);
|
||||
mfi->genre = safe_strdup(album->genre);
|
||||
mfi->compilation = album->is_compilation;
|
||||
}
|
||||
|
||||
/* Thread: library */
|
||||
static int
|
||||
scan_saved_albums()
|
||||
{
|
||||
struct spotify_request request;
|
||||
json_object *jsontracks;
|
||||
int track_count;
|
||||
struct spotify_album album;
|
||||
struct spotify_track track;
|
||||
struct media_file_info mfi;
|
||||
int dir_id;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
db_transaction_begin();
|
||||
|
||||
memset(&request, 0, sizeof(struct spotify_request));
|
||||
|
||||
while (0 == spotifywebapi_request_next(&request, SPOTIFY_WEBAPI_SAVED_ALBUMS))
|
||||
{
|
||||
while (0 == spotifywebapi_saved_albums_fetch(&request, &jsontracks, &track_count, &album))
|
||||
{
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "Got saved album: '%s' - '%s' (%s) - track-count: %d\n",
|
||||
album.artist, album.name, album.uri, track_count);
|
||||
|
||||
dir_id = prepare_directories(album.artist, album.name);
|
||||
ret = 0;
|
||||
for (i = 0; i < track_count && ret == 0; i++)
|
||||
{
|
||||
ret = spotifywebapi_album_track_fetch(jsontracks, i, &track);
|
||||
if (ret == 0 && track.uri)
|
||||
{
|
||||
memset(&mfi, 0, sizeof(struct media_file_info));
|
||||
map_track_to_mfi(&track, &mfi);
|
||||
map_album_to_mfi(&album, &mfi);
|
||||
|
||||
library_process_media(track.uri, album.mtime, 0, DATA_KIND_SPOTIFY, 0, album.is_compilation, &mfi, dir_id);
|
||||
spotify_uri_register(track.uri);
|
||||
|
||||
cache_artwork_ping(track.uri, album.mtime, 0);
|
||||
|
||||
free_mfi(&mfi, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spotifywebapi_request_end(&request);
|
||||
|
||||
db_transaction_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Thread: library */
|
||||
static int
|
||||
scan_playlisttracks(struct spotify_playlist *playlist, int plid)
|
||||
{
|
||||
struct spotify_request request;
|
||||
struct spotify_track track;
|
||||
struct media_file_info mfi;
|
||||
int dir_id;
|
||||
|
||||
memset(&request, 0, sizeof(struct spotify_request));
|
||||
|
||||
while (0 == spotifywebapi_request_next(&request, playlist->tracks_href))
|
||||
{
|
||||
// DPRINTF(E_DBG, L_SPOTIFY, "Playlist tracks\n%s\n", request.response_body);
|
||||
while (0 == spotifywebapi_playlisttracks_fetch(&request, &track))
|
||||
{
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "Got playlist track: '%s' (%s) \n", track.name, track.uri);
|
||||
|
||||
if (track.uri)
|
||||
{
|
||||
dir_id = prepare_directories(track.album_artist, track.album);
|
||||
|
||||
memset(&mfi, 0, sizeof(struct media_file_info));
|
||||
map_track_to_mfi(&track, &mfi);
|
||||
|
||||
library_process_media(track.uri, 1 /* TODO passing one prevents overwriting existing entries */, 0, DATA_KIND_SPOTIFY, 0, track.is_compilation, &mfi, dir_id);
|
||||
spotify_uri_register(track.uri);
|
||||
|
||||
cache_artwork_ping(track.uri, 1, 0);
|
||||
|
||||
free_mfi(&mfi, 1);
|
||||
|
||||
db_pl_add_item_bypath(plid, track.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spotifywebapi_request_end(&request);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Thread: library */
|
||||
static int
|
||||
scan_playlists()
|
||||
{
|
||||
struct spotify_request request;
|
||||
struct spotify_playlist playlist;
|
||||
char virtual_path[PATH_MAX];
|
||||
int plid;
|
||||
|
||||
db_transaction_begin();
|
||||
|
||||
memset(&request, 0, sizeof(struct spotify_request));
|
||||
|
||||
while (0 == spotifywebapi_request_next(&request, SPOTIFY_WEBAPI_SAVED_PLAYLISTS))
|
||||
{
|
||||
while (0 == spotifywebapi_playlists_fetch(&request, &playlist))
|
||||
{
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "Got playlist: '%s' (%s) \n", playlist.name, playlist.uri);
|
||||
|
||||
if (playlist.owner)
|
||||
{
|
||||
snprintf(virtual_path, PATH_MAX, "/spotify:/%s (%s)", playlist.name, playlist.owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", playlist.name);
|
||||
}
|
||||
|
||||
plid = library_add_playlist_info(playlist.uri, playlist.name, virtual_path, PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
|
||||
|
||||
if (plid)
|
||||
scan_playlisttracks(&playlist, plid);
|
||||
}
|
||||
}
|
||||
|
||||
spotifywebapi_request_end(&request);
|
||||
db_transaction_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Thread: library */
|
||||
static int
|
||||
spotify_initscan()
|
||||
{
|
||||
cfg_t *spotify_cfg;
|
||||
int ret;
|
||||
|
||||
/* Refresh access token for the spotify webapi */
|
||||
spotify_access_token_valid = (0 == spotifywebapi_token_refresh());
|
||||
if (!spotify_access_token_valid)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Spotify webapi token refresh failed. "
|
||||
"In order to use the web api, authorize forked-daapd to access "
|
||||
"your saved tracks by visiting http://forked-daapd.local:3689/oauth\n");
|
||||
|
||||
db_spotify_purge();
|
||||
}
|
||||
|
||||
/*
|
||||
* Add playlist folder for all spotify playlists
|
||||
*/
|
||||
spotify_base_plid = 0;
|
||||
spotify_cfg = cfg_getsec(cfg, "spotify");
|
||||
if (! cfg_getbool(spotify_cfg, "base_playlist_disable"))
|
||||
{
|
||||
ret = library_add_playlist_info("spotify:playlistfolder", "Spotify", NULL, PL_FOLDER, 0, 0);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
|
||||
else
|
||||
spotify_base_plid = ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Login to spotify needs to be done before scanning tracks from the web api.
|
||||
* (Scanned tracks need to be registered with libspotify for playback)
|
||||
*/
|
||||
spotify_login(NULL);
|
||||
|
||||
/*
|
||||
* Scan saved tracks from the web api
|
||||
*/
|
||||
if (spotify_access_token_valid)
|
||||
{
|
||||
scan_saved_albums();
|
||||
scan_playlists();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2112,6 +2304,8 @@ spotify_init(void)
|
||||
sp_error err;
|
||||
int ret;
|
||||
|
||||
spotify_access_token_valid = false;
|
||||
|
||||
/* Initialize libspotify */
|
||||
g_libhandle = dlopen("libspotify.so", RTLD_LAZY);
|
||||
if (!g_libhandle)
|
||||
|
@ -392,14 +392,14 @@ spotifywebapi_request_uri(struct spotify_request *request, const char *uri)
|
||||
snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_access_token);
|
||||
if (keyval_add(request->ctx->output_headers, "Authorization", bearer_token) < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed");
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = http_client_request(request->ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed");
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -409,10 +409,12 @@ spotifywebapi_request_uri(struct spotify_request *request, const char *uri)
|
||||
request->response_body = (char *) evbuffer_pullup(request->ctx->input_body, -1);
|
||||
if (!request->response_body || (strlen(request->response_body) == 0))
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed, response was empty");
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed, response was empty\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
//DPRINTF(E_DBG, L_SPOTIFY, "Wep api response for '%s'\n%s\n", uri, request->response_body);
|
||||
|
||||
request->haystack = json_tokener_parse(request->response_body);
|
||||
if (!request->haystack)
|
||||
{
|
||||
@ -498,6 +500,8 @@ track_metadata(json_object* jsontrack, struct spotify_track* track)
|
||||
track->artist = jparse_artist_from_obj(jsonartists);
|
||||
}
|
||||
track->disc_number = jparse_int_from_obj(jsontrack, "disc_number");
|
||||
track->album_type = jparse_str_from_obj(jsonalbum, "album_type");
|
||||
track->is_compilation = (track->album_type && 0 == strcmp(track->album_type, "compilation"));
|
||||
track->duration_ms = jparse_int_from_obj(jsontrack, "duration_ms");
|
||||
track->name = jparse_str_from_obj(jsontrack, "name");
|
||||
track->track_number = jparse_int_from_obj(jsontrack, "track_number");
|
||||
@ -586,9 +590,13 @@ album_metadata(json_object *jsonalbum, struct spotify_album *album)
|
||||
album->id = jparse_str_from_obj(jsonalbum, "id");
|
||||
|
||||
album->album_type = jparse_str_from_obj(jsonalbum, "album_type");
|
||||
album->genre = jparse_str_from_obj(jsonalbum, "genre"); // FIXME Genre is an array of strings ('genres'), but it seems to be always empty
|
||||
album->is_compilation = (album->album_type && 0 == strcmp(album->album_type, "compilation"));
|
||||
|
||||
album->label = jparse_str_from_obj(jsonalbum, "label");
|
||||
album->release_date = jparse_str_from_obj(jsonalbum, "release_date");
|
||||
|
||||
// TODO Genre is an array of strings ('genres'), but it is always empty (https://github.com/spotify/web-api/issues/157)
|
||||
//album->genre = jparse_str_from_obj(jsonalbum, "genre");
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -22,15 +22,14 @@
|
||||
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#ifdef HAVE_JSON_C_OLD
|
||||
# include <json/json.h>
|
||||
#else
|
||||
# include <json-c/json.h>
|
||||
#endif
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#define SPOTIFY_WEBAPI_SAVED_TRACKS "https://api.spotify.com/v1/me/tracks?limit=50"
|
||||
#define SPOTIFY_WEBAPI_TRACKS "https://api.spotify.com/v1/tracks/"
|
||||
@ -43,6 +42,7 @@ struct spotify_album
|
||||
time_t mtime;
|
||||
|
||||
const char *album_type;
|
||||
bool is_compilation;
|
||||
const char *artist;
|
||||
const char *genre;
|
||||
const char *id;
|
||||
@ -61,6 +61,8 @@ struct spotify_track
|
||||
const char *album_artist;
|
||||
const char *artist;
|
||||
int disc_number;
|
||||
const char *album_type;
|
||||
bool is_compilation;
|
||||
int duration_ms;
|
||||
const char *id;
|
||||
const char *name;
|
||||
|
Loading…
Reference in New Issue
Block a user