committed by
Hugo Beauzée-Luyssen
2 changed files with 422 additions and 0 deletions
@ -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; |
|||
} |
|||
@ -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 |
|||
Loading…
Reference in new issue