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

/*****************************************************************************
* 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;
}