vlc | branch: master | Francois Cartegnie <[email protected]> | Mon May 27 17:14:49 2019 +0200| [e8f5c617dfcf6dc571701eaa8c5d41b5a23d57a5] | committer: Francois Cartegnie
misc: webservices: add musicbrainz > http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=e8f5c617dfcf6dc571701eaa8c5d41b5a23d57a5 --- modules/misc/webservices/musicbrainz.c | 349 +++++++++++++++++++++++++++++++++ modules/misc/webservices/musicbrainz.h | 73 +++++++ 2 files changed, 422 insertions(+) diff --git a/modules/misc/webservices/musicbrainz.c b/modules/misc/webservices/musicbrainz.c new file mode 100644 index 0000000000..2393a7320a --- /dev/null +++ b/modules/misc/webservices/musicbrainz.c @@ -0,0 +1,349 @@ +/***************************************************************************** + * musicbrainz.c : Musicbrainz API lookup + ***************************************************************************** + * Copyright (C) 2019 VideoLabs, VLC authors and VideoLAN + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <string.h> +#include <limits.h> + +#include "json_helper.h" +#include "musicbrainz.h" + +typedef struct +{ + json_value *root; +} musicbrainz_lookup_t; + +static void musicbrainz_lookup_release(musicbrainz_lookup_t *p) +{ + if(p && p->root) + json_value_free(p->root); + free(p); +} + +static musicbrainz_lookup_t * musicbrainz_lookup_new(void) +{ + return calloc(1, sizeof(musicbrainz_lookup_t)); +} + +static musicbrainz_lookup_t * musicbrainz_lookup(vlc_object_t *p_obj, const char *psz_url) +{ + msg_Dbg(p_obj, "Querying MB for %s", psz_url); + void *p_buffer = json_retrieve_document(p_obj, psz_url); + if(!p_buffer) + return NULL; + + musicbrainz_lookup_t *p_lookup = musicbrainz_lookup_new(); + if(p_lookup) + { + p_lookup->root = json_parse_document(p_obj, p_buffer); + if (!p_lookup->root) + msg_Dbg(p_obj, "No results"); + } + free(p_buffer); + return p_lookup; +} + +static bool musicbrainz_fill_track(const json_value *tracknode, musicbrainz_track_t *t) +{ + t->psz_title = json_dupstring(tracknode, "title"); + + const json_value *node = json_getbyname(tracknode, "artist-credit"); + if (node && node->type == json_array && node->u.array.length) + t->psz_artist = json_dupstring(node->u.array.values[0], "name"); + + node = json_getbyname(tracknode, "position"); + if (node && node->type == json_integer) + t->i_index = node->u.integer; + + return true; +} + +static bool musicbrainz_has_cover_in_releasegroup(json_value ** const p_nodes, + size_t i_nodes, + const char *psz_group_id) +{ + for(size_t i=0; i<i_nodes; i++) + { + const json_value *rgnode = json_getbyname(p_nodes[i], "release-group"); + if(rgnode) + { + const char *psz_id = jsongetstring(rgnode, "id"); + if(!psz_id || strcmp(psz_id, psz_group_id)) + continue; + + const json_value *node = json_getbyname(p_nodes[i], "cover-art-archive"); + if(!node) + continue; + + node = json_getbyname(node, "front"); + if(!node || node->type != json_boolean || !node->u.boolean) + continue; + + return true; + } + } + + return false; +} + +static char *musicbrainz_fill_artists(const json_value *arraynode) +{ + char *psz = NULL; + if(arraynode->type != json_array || arraynode->u.array.length < 1) + return psz; + + size_t i_total = 1; + for(size_t i=0; i<arraynode->u.array.length; i++) + { + const json_value *name = json_getbyname(arraynode->u.array.values[i], "name"); + if(name->type != json_string) + continue; + + if(psz == NULL) + { + psz = strdup(name->u.string.ptr); + i_total = name->u.string.length + 1; + } + else + { + char *p = realloc(psz, i_total + name->u.string.length + 2); + if(p) + { + psz = p; + psz = strcat(psz, ", "); + psz = strncat(psz, name->u.string.ptr, name->u.string.length); + i_total += name->u.string.length + 2; + } + } + } + + return psz; +} + +static bool musicbrainz_fill_release(const json_value *releasenode, musicbrainz_release_t *r) +{ + const json_value *media = json_getbyname(releasenode, "media"); + if(!media || media->type != json_array || + media->u.array.length == 0) + return false; + /* we always use first media */ + media = media->u.array.values[0]; + + const json_value *tracks = json_getbyname(media, "tracks"); + if(!tracks || tracks->type != json_array || + tracks->u.array.length == 0) + return false; + + r->p_tracks = calloc(tracks->u.array.length, sizeof(*r->p_tracks)); + if(!r->p_tracks) + return false; + + for(size_t i=0; i<tracks->u.array.length; i++) + { + if(musicbrainz_fill_track(tracks->u.array.values[i], &r->p_tracks[r->i_tracks])) + r->i_tracks++; + } + + r->psz_title = json_dupstring(releasenode, "title"); + r->psz_id = json_dupstring(releasenode, "id"); + + const json_value *rgnode = json_getbyname(releasenode, "release-group"); + if(rgnode) + { + r->psz_date = json_dupstring(rgnode, "first-release-date"); + r->psz_group_id = json_dupstring(rgnode, "id"); + + const json_value *node = json_getbyname(rgnode, "artist-credit"); + if(node) + r->psz_artist = musicbrainz_fill_artists(node); + } + else + { + const json_value *node = json_getbyname(releasenode, "artist-credit"); + if(node) + r->psz_artist = musicbrainz_fill_artists(node); + + node = json_getbyname(releasenode, "release-events"); + if(node && node->type == json_array && node->u.array.length) + r->psz_date = json_dupstring(node->u.array.values[0], "date"); + } + + + return true; +} + +void musicbrainz_recording_release(musicbrainz_recording_t *mbr) +{ + for(size_t i=0; i<mbr->i_release; i++) + { + free(mbr->p_releases[i].psz_id); + free(mbr->p_releases[i].psz_group_id); + free(mbr->p_releases[i].psz_artist); + free(mbr->p_releases[i].psz_title); + free(mbr->p_releases[i].psz_date); + free(mbr->p_releases[i].psz_coverart_url); + for(size_t j=0; j<mbr->p_releases[i].i_tracks; j++) + { + free(mbr->p_releases[i].p_tracks[j].psz_title); + free(mbr->p_releases[i].p_tracks[j].psz_artist); + } + free(mbr->p_releases[i].p_tracks); + } + free(mbr->p_releases); + free(mbr); +} + +static musicbrainz_recording_t *musicbrainz_lookup_recording_by_apiurl(vlc_object_t *obj, + const char *psz_url) +{ + musicbrainz_recording_t *r = calloc(1, sizeof(*r)); + if(!r) + return NULL; + + musicbrainz_lookup_t *lookup = musicbrainz_lookup(obj, psz_url); + if(!lookup) + { + free(r); + return NULL; + } + + const json_value *releases = json_getbyname(lookup->root, "releases"); + if (releases && releases->type == json_array && + releases->u.array.length) + { + r->p_releases = calloc(releases->u.array.length, sizeof(*r->p_releases)); + if(r->p_releases) + { + for(unsigned i=0; i<releases->u.array.length; i++) + { + json_value *node = releases->u.array.values[i]; + musicbrainz_release_t *p_mbrel = &r->p_releases[r->i_release]; + if (!node || node->type != json_object || + !musicbrainz_fill_release(node, p_mbrel)) + continue; + + /* Try to find cover from other releases from the same group */ + if(p_mbrel->psz_group_id && !p_mbrel->psz_coverart_url && + musicbrainz_has_cover_in_releasegroup(releases->u.array.values, + releases->u.array.length, + p_mbrel->psz_group_id)) + { + char *psz_art = coverartarchive_make_releasegroup_arturl( + COVERARTARCHIVE_DEFAULT_SERVER, + p_mbrel->psz_group_id ); + if(psz_art) + p_mbrel->psz_coverart_url = psz_art; + } + + r->i_release++; + } + } + } + + musicbrainz_lookup_release(lookup); + + return r; +} + +static char *musicbrainz_build_discid_json_url(const char *psz_server, + const char *psz_disc_id, + const char *psz_tail) +{ + char *psz_url; + if(asprintf(&psz_url, + "https://%s/ws/2/discid/%s?" + "fmt=json" + "&inc=artist-credits+recordings+release-groups" + "&cdstubs=no" + "%s%s", + psz_server ? psz_server : MUSICBRAINZ_DEFAULT_SERVER, + psz_disc_id, + psz_tail ? "&" : "", + psz_tail ? psz_tail : "" ) > -1 ) + { + return psz_url; + } + return NULL; +} + +musicbrainz_recording_t *musicbrainz_lookup_recording_by_toc(musicbrainz_config_t *cfg, + const char *psz_toc) +{ + char *psz_url = musicbrainz_build_discid_json_url(cfg->psz_mb_server, "-", psz_toc); + if(!psz_url) + return NULL; + + musicbrainz_recording_t *r = musicbrainz_lookup_recording_by_apiurl(cfg->obj, psz_url); + free(psz_url); + return r; +} + +musicbrainz_recording_t *musicbrainz_lookup_recording_by_discid(musicbrainz_config_t *cfg, + const char *psz_disc_id) +{ + char *psz_url = musicbrainz_build_discid_json_url(cfg->psz_mb_server, psz_disc_id, NULL); + if(!psz_url) + return NULL; + + musicbrainz_recording_t *r = musicbrainz_lookup_recording_by_apiurl(cfg->obj, psz_url); + free(psz_url); + return r; +} + +char * coverartarchive_make_releasegroup_arturl(const char *psz_server, const char *psz_group_id) +{ + char *psz_art; + if(-1 < asprintf(&psz_art, "https://%s/release-group/%s/front", + psz_server ? psz_server : COVERARTARCHIVE_DEFAULT_SERVER, + psz_group_id)) + return psz_art; + return NULL; +} + +void musicbrainz_release_covert_art(coverartarchive_t *c) +{ + free(c); +} + +coverartarchive_t * coverartarchive_lookup_releasegroup(musicbrainz_config_t *cfg, const char *psz_id) +{ + coverartarchive_t *c = calloc(1, sizeof(*c)); + if(!c) + return NULL; + + char *psz_url; + if(0 < asprintf(&psz_url, "https://%s/releasegroup/%s", cfg->psz_coverart_server, psz_id )) + { + return NULL; + } + + musicbrainz_lookup_t *p_lookup = musicbrainz_lookup(cfg->obj, psz_url); + free(psz_url); + + if(!p_lookup) + { + free(c); + return NULL; + } + + return c; +} diff --git a/modules/misc/webservices/musicbrainz.h b/modules/misc/webservices/musicbrainz.h new file mode 100644 index 0000000000..d30be92d7f --- /dev/null +++ b/modules/misc/webservices/musicbrainz.h @@ -0,0 +1,73 @@ +/***************************************************************************** + * musicbrainz.h : Musicbrainz API lookup + ***************************************************************************** + * Copyright (C) 2019 VideoLabs, VLC authors and VideoLAN + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifndef VLC_MUSICBRAINZ_H_ +#define VLC_MUSICBRAINZ_H_ + +#define MUSICBRAINZ_DEFAULT_SERVER "musicbrainz.org" +#define COVERARTARCHIVE_DEFAULT_SERVER "coverartarchive.org" + +typedef struct +{ + unsigned i_index; + char *psz_title; + char *psz_artist; +} musicbrainz_track_t; + +typedef struct +{ + char *psz_id; + char *psz_group_id; + char *psz_title; + char *psz_artist; + /* https://github.com/metabrainz/mmd-schema/blob/master/schema/musicbrainz_mmd-2.0.rng + "def_incomplete-date" [0-9]{4}(-[0-9]{2})?(-[0-9]{2})? */ + char *psz_date; + char *psz_coverart_url; + size_t i_tracks; + musicbrainz_track_t *p_tracks; +} musicbrainz_release_t; + +typedef struct +{ + size_t i_release; + musicbrainz_release_t *p_releases; +} musicbrainz_recording_t; + +typedef struct +{ + vlc_object_t *obj; + char *psz_mb_server; + char *psz_coverart_server; +} musicbrainz_config_t; + +void musicbrainz_recording_release(musicbrainz_recording_t *); +musicbrainz_recording_t * musicbrainz_lookup_recording_by_toc(musicbrainz_config_t *, const char *); +musicbrainz_recording_t * musicbrainz_lookup_recording_by_discid(musicbrainz_config_t *, const char *); + +typedef struct +{ + char *psz_url; +} coverartarchive_t; + +void musicbrainz_release_covert_art(coverartarchive_t *c); +coverartarchive_t * coverartarchive_lookup_releasegroup(musicbrainz_config_t *, const char *); +char * coverartarchive_make_releasegroup_arturl(const char *, const char *); + +#endif _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
