[spotify] Scan spotify podcast subscriptions into library

This commit is contained in:
chme 2021-05-16 06:10:47 +02:00
parent e63aba61c5
commit 24450bc90a
5 changed files with 153 additions and 1 deletions

View File

@ -144,3 +144,14 @@ spotify_status_get(struct spotify_status *status)
backend->status_get(status);
}
bool
spotify_podcast_support()
{
struct spotify_backend *backend = backend_set();
if (!backend)
return false;
return backend->has_podcast_support;
}

View File

@ -21,6 +21,8 @@ struct spotify_backend
int (*relogin)(void);
void (*uri_register)(const char *uri);
void (*status_get)(struct spotify_status *status);
bool has_podcast_support;
};
int
@ -47,4 +49,7 @@ spotify_uri_register(const char *uri);
void
spotify_status_get(struct spotify_status *status);
bool
spotify_podcast_support();
#endif /* !__SPOTIFY_H__ */

View File

@ -749,5 +749,7 @@ struct spotify_backend spotify_librespotc =
.logout = logout,
.relogin = relogin,
.status_get = status_get,
.has_podcast_support = true,
};

View File

@ -129,5 +129,7 @@ struct spotify_backend spotify_libspotify =
.relogin = libspotify_relogin,
.uri_register = libspotify_uri_register,
.status_get = status_get,
.has_podcast_support = false,
};

View File

@ -62,6 +62,7 @@ struct spotify_album
int release_year;
const char *uri;
const char *artwork_url;
const char *type;
};
struct spotify_track
@ -89,6 +90,7 @@ struct spotify_track
bool is_playable;
const char *restrictions;
const char *linked_from_uri;
const char *type;
};
struct spotify_playlist
@ -149,6 +151,8 @@ static const char *spotify_album_tracks_uri = "https://api.spotify.com/v1/albums
static const char *spotify_playlists_uri = "https://api.spotify.com/v1/me/playlists?limit=50";
static const char *spotify_playlist_tracks_uri = "https://api.spotify.com/v1/playlists/%s/tracks";
static const char *spotify_artist_albums_uri = "https://api.spotify.com/v1/artists/%s/albums?include_groups=album,single";
static const char *spotify_shows_uri = "https://api.spotify.com/v1/me/shows?limit=50";
static const char *spotify_shows_episodes_uri = "https://api.spotify.com/v1/shows/%s/episodes";
static void
@ -687,6 +691,7 @@ parse_metadata_track(json_object *jsontrack, struct spotify_track *track, int ma
track->track_number = jparse_int_from_obj(jsontrack, "track_number");
track->uri = jparse_str_from_obj(jsontrack, "uri");
track->id = jparse_str_from_obj(jsontrack, "id");
track->type = jparse_str_from_obj(jsontrack, "type");
// "is_playable" is only returned for a request with a market parameter, default to true if it is not in the response
track->is_playable = true;
@ -731,6 +736,7 @@ parse_metadata_album(json_object *jsonalbum, struct spotify_album *album, int ma
album->name = jparse_str_from_obj(jsonalbum, "name");
album->uri = jparse_str_from_obj(jsonalbum, "uri");
album->id = jparse_str_from_obj(jsonalbum, "id");
album->type = jparse_str_from_obj(jsonalbum, "type");
album->album_type = jparse_str_from_obj(jsonalbum, "album_type");
album->is_compilation = (album->album_type && 0 == strcmp(album->album_type, "compilation"));
@ -772,6 +778,44 @@ parse_metadata_playlist(json_object *jsonplaylist, struct spotify_playlist *play
}
}
static void
parse_metadata_show(json_object *jsonshow, struct spotify_album *show)
{
memset(show, 0, sizeof(struct spotify_album));
show->name = jparse_str_from_obj(jsonshow, "name");
show->artist = jparse_str_from_obj(jsonshow, "publisher");
show->uri = jparse_str_from_obj(jsonshow, "uri");
show->id = jparse_str_from_obj(jsonshow, "id");
show->type = jparse_str_from_obj(jsonshow, "type");
}
static void
parse_metadata_episode(json_object *jsonepisode, struct spotify_track *episode)
{
memset(episode, 0, sizeof(struct spotify_track));
episode->name = jparse_str_from_obj(jsonepisode, "name");
episode->uri = jparse_str_from_obj(jsonepisode, "uri");
episode->id = jparse_str_from_obj(jsonepisode, "id");
episode->type = jparse_str_from_obj(jsonepisode, "type");
episode->duration_ms = jparse_int_from_obj(jsonepisode, "duration_ms");
episode->release_date = jparse_str_from_obj(jsonepisode, "release_date");
episode->release_date_precision = jparse_str_from_obj(jsonepisode, "release_date_precision");
if (episode->release_date_precision && strcmp(episode->release_date_precision, "day") == 0)
episode->release_date_time = jparse_time_from_obj(jsonepisode, "release_date");
episode->release_year = get_year_from_date(episode->release_date);
episode->mtime = episode->release_date_time;
// "is_playable" is only returned for a request with a market parameter, default to true if it is not in the response
episode->is_playable = true;
if (json_object_object_get_ex(jsonepisode, "is_playable", NULL))
{
episode->is_playable = jparse_bool_from_obj(jsonepisode, "is_playable");
}
}
/*
* Creates a new string for the playlist API endpoint for the given playist-uri.
* The returned string needs to be freed by the caller.
@ -1399,7 +1443,10 @@ map_track_to_mfi(struct media_file_info *mfi, const struct spotify_track *track,
mfi->track = track->track_number;
mfi->data_kind = DATA_KIND_SPOTIFY;
mfi->media_kind = MEDIA_KIND_MUSIC;
if (strcmp(track->type, "episode") == 0)
mfi->media_kind = MEDIA_KIND_PODCAST;
else
mfi->media_kind = MEDIA_KIND_MUSIC;
mfi->type = strdup("spotify");
mfi->codectype = strdup("wav");
mfi->description = strdup("Spotify audio");
@ -1433,6 +1480,12 @@ map_track_to_mfi(struct media_file_info *mfi, const struct spotify_track *track,
mfi->compilation = track->is_compilation;
}
if (mfi->media_kind == MEDIA_KIND_PODCAST)
{
// For podcasts we want the tracks/episodes release date
mfi->date_released = track->release_date_time;
mfi->year = track->release_year;
}
snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi->album_artist, mfi->album, mfi->title);
mfi->virtual_path = strdup(virtual_path);
}
@ -1581,6 +1634,83 @@ scan_saved_albums(enum spotify_request_type request_type)
return ret;
}
/*
* Add a saved podcast show to the library
*/
static int
saved_episodes_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg)
{
struct spotify_album *show = arg;
struct spotify_track episode;
int dir_id;
int ret;
DPRINTF(E_DBG, L_SPOTIFY, "saved_episodes_add: %s\n", json_object_to_json_string(item));
// Map episode information
parse_metadata_episode(item, &episode);
// Get or create the directory structure for this album
dir_id = prepare_directories(show->artist, show->name);
ret = track_add(&episode, show, NULL, dir_id, request_type);
if (ret == 0 && spotify_saved_plid)
db_pl_add_item_bypath(spotify_saved_plid, episode.uri);
return 0;
}
/*
* Add a saved podcast show to the library
*/
static int
saved_show_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg)
{
json_object *jsonshow;
struct spotify_album show;
char *endpoint_uri;
DPRINTF(E_DBG, L_SPOTIFY, "saved_show_add: %s\n", json_object_to_json_string(item));
if (!json_object_object_get_ex(item, "show", &jsonshow))
{
DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: Item %d is missing the 'show' field\n", index);
return -1;
}
// Map show information
parse_metadata_show(jsonshow, &show);
show.added_at = jparse_str_from_obj(item, "added_at");
show.mtime = jparse_time_from_obj(item, "added_at");
// Now map the show episodes and insert/update them in the files database
endpoint_uri = safe_asprintf(spotify_shows_episodes_uri, show.id);
request_pagingobject_endpoint(endpoint_uri, saved_episodes_add, transaction_start, transaction_end, true, request_type, &show);
free(endpoint_uri);
if ((index + 1) >= total || ((index + 1) % 10 == 0))
DPRINTF(E_LOG, L_SPOTIFY, "Scanned %d of %d saved albums\n", (index + 1), total);
return 0;
}
/*
* Thread: library
*
* Scan users saved podcast shows into the library
*/
static int
scan_saved_shows(enum spotify_request_type request_type)
{
int ret;
ret = request_pagingobject_endpoint(spotify_shows_uri, saved_show_add, NULL, NULL, true, request_type, NULL);
return ret;
}
/*
* Add a saved playlist tracks to the library
@ -1787,6 +1917,8 @@ scan(enum spotify_request_type request_type)
create_saved_tracks_playlist();
scan_saved_albums(request_type);
scan_playlists(request_type);
if (spotify_podcast_support())
scan_saved_shows(request_type);
scanning = false;
end = time(NULL);