You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
11 KiB
349 lines
11 KiB
/*****************************************************************************
|
|
* 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;
|
|
}
|
|
|