[vlc-commits] misc: webservices: add musicbrainz
Francois Cartegnie
git at videolan.org
Sat Jun 1 00:23:43 CEST 2019
vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | 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
More information about the vlc-commits
mailing list