Musicbrainz API – Parsing Results

Musicbrainz logoIn the previous post I explained how Musicbrainz is structured, and our objective to use the API to obtain metadata for an artist and title value pair, retrieved from the Shoutcast server which is playing an audio track submitted from the MIXXX client workstation playing the MP3 file.
At this point, we have obtained some first level results to our query for an artist and title.

Visual Representation of Data sets

The Results Array

Musicbrainz returns a slew of information when it finds a match, which has to be to parsed through to obtain the earliest (first) release from our request. Let’s take second here and break it down, so you’ll have a better understanding of the relations and how we’ll use them to gather even more metadata relative to our song, artist, release as well as other cool stuff.

We have our args set, and artist and title, now it’s time to dig into MusicBrainz API and find the results “FIRST” release. To do that, we have to go through all the releases returned, after sorting them by the release years, and score, and stop when the score drops below 99. The PHP class has several quick methods for getting our data, and saves a lot of time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
try {

    $recordings = $brainz->search(new RecordingFilter($args));
    $releases   = [];
    $out        = new stdClass(); // our output object
    $lastScore  = null; // initialized variable.
   
    foreach($recordings as $recording){

        // If recording score is lower than what we have now, we want 100, but have found
        // 99 is acceptable in most cases to properly match on the original first recording
        // for this release.

         if ( null != $lastScore && $lastScore >= 99 ) {
            break;
         }      
         
        $lastScore        = $recording->getScore();
        $releaseDates     = $recording->getReleaseDates();
        $oldestReleaseKey = key($releaseDates);
       
        // Only compare the year of the release so we format the date field to just the year is compared.
        // Once we have the oldest year, we will save our results to our $firstRecording array object.

        if( $releaseDates[$oldestReleaseKey]->format('Y') < $firstRecording['releaseDate']->format('Y')){

            $firstRecording = array(
                'query'       => $args,
                'release'     => $recording->releases[$oldestReleaseKey],
                'releaseDate' => $recording->releases[$oldestReleaseKey]->getReleaseDate(),
                'release-count' => count($recording->releases),
                'recording'   => $recording,            
                'artist'      => $recording->getArtist(),
                'recordingId' => $recording->getId(),
                'trackLength' => $recording->getLength(),
                'execution'   => new stdClass(), // used for debugging
                );
               
        }
    }

At this point, we have our firstRecording object which contains a base of information about the release, the artist, and the recording.

Our next step is to get the following information:

Getting The Label

How we query MusicBrainz to obtain our release label, and add that to our firstRecording object, else set it to ‘none’. Here we send our release id along with the include ‘labels’ to lookup the label associated with our release.

1
2
3
4
 $releaseLabel = $brainz->lookup('release', $firstRecording['release']->id, array('labels'));
    foreach($releaseLabel['label-info'] as $label){
        $firstRecording['release']->label = $label['label']['name'] ? $label['label']['name'] : 'none';
    }

About Artist

To get our metadata from wikipedia, we must query Musicbrainz in a special request to include the artist relations data. We also grab some additional data, which we may want to use at a later date, but for now we’ll just store it in our mongoDB so we know it’s there.

1
2
3
4
5
 $artistUrlRels = $brainz->lookup('artist', $firstRecording['artist']->id, array('url-rels','annotation'));
 $wikidataKey   = recursive_array_search('wikidata', $artistUrlRels['relations']);
 $artistQID     = $wikidataKey ? end( explode('/',  
parse_url($artistUrlRels['relations'][$wikidataKey]['url']['resource'], PHP_URL_PATH) ) ): null;
  $firstRecording['artist']->wiki    = $artistQID ? wikiExtract($artistQID) : null;

This is the wikiExtract function we use to get wikipedia extract content for artist, release, and anything else we want.
A simple curl of the wikipedia api and we get our json data, or if not, we return false.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function wikiExtract($qid){
    $url = "https://www.wikidata.org/w/api.php?" .
            "action=wbgetentities&format=xml&props=sitelinks&ids=$qid&sitefilter=enwiki&format=json";

    $curl = curl_init();
    // Set some options - we are passing in a useragent too here
    curl_setopt_array($curl, [
        CURLOPT_RETURNTRANSFER  => 1,
        CURLOPT_URL             => $url,
        CURLOPT_USERAGENT       => 'Hawkwynd Radio 1.0'
    ]);

    // Send the request & save response to $resp
    $resp = curl_exec($curl);

    // Close request to clear up some resources
    curl_close($curl);

    $data = json_decode($resp);
    $title = rawurlencode($data->entities->$qid->sitelinks->enwiki->title); // ie ZZ Top (band)
    $wikiUrl = "https://en.wikipedia.org/w/api.php?".
               "format=json&action=query&prop=extracts&exlimit=1&explaintext&exintro&titles=$title";

    $curl = curl_init();
    curl_setopt_array($curl, [
        CURLOPT_RETURNTRANSFER  => 1,
        CURLOPT_URL             => $wikiUrl,
        CURLOPT_USERAGENT       => 'Hawkwynd Radio 1.0'
    ]);

    $wikiResponse = json_decode(curl_exec($curl));

    if(!property_exists($wikiResponse,'query')) return false; // no wiki data found

    // init output array
    $output = array(
        'qid'           => null,
        'title'         => null,
        'extract'       => null,
        'pageid'        => null,
        'pageUrl'       => null
    );

    // iterate response build output
    foreach($wikiResponse->query->pages as $page){
        $output['qid']              = $qid;
        $output['title']            = $page->title;
        $output['extract']          = $page->extract; // this is the content we display.
        $output['pageid']           = $page->pageid;
        $output['pageUrl']          = "https://en.wikipedia.org/wiki/".$page->title;
    }

    // Close request to clear up some resources
    curl_close($curl);

    return $output; // array data out as array
}

About Release

Now, we issue a request to Musicbrainz to get any wikidata from our release. Similar in fashion to how we got our artist’s wikidata, using the wikiExtract function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 // Build release-group object
    $releaseGroup = $brainz->lookup('release', $firstRecording['release']->id, array('release-groups','labels'));
    $firstRecording['release-group'] = $releaseGroup['release-group'];
    $firstRecording['release-group']['musicbrainz'] = "https://musicbrainz.org/release-group/" .
                                                      $releaseGroup['release-group']['id'];
    $firstRecording['release-group']['url-rels'] = "https://musicbrainz.org/ws/2/release-group/" .
                                                   $releaseGroup['release-group']['id']."?inc=url-rels&fmt=json";
   
    //  wikidata for release-group
    $rgUrlRels = $brainz->lookup('release-group', $releaseGroup['release-group']['id'], array('url-rels'));
    $wikidataKey         = recursive_array_search('wikidata', $rgUrlRels['relations']);

    $releaseGroupQID     = $wikidataKey ? end( explode('/',  
                                          parse_url($rgUrlRels['relations'][$wikidataKey]['url']['resource'],
                                          PHP_URL_PATH) ) ): null;    

    $firstRecording['release-group']['wiki'] =  $releaseGroupQID ? wikiExtract($releaseGroupQID) : null;

Track List

Sample Track List

To obtain the list of songs on our release, we simply execute another request to Musicbrainz. We make this call after we’ve already made our firstRecording request, because it’s a quick response function, and we don’t need to store this information in our MongoDB, and, we only need to make this call once for the duration of the song’s play. Our jQuery makes the call and our PHP returns json back to populate the container.


1
2
3
4
5
6
7
8
9
10
11
12
// javascript
$.getJSON('browseRecordings.php', {
            releaseId: relid
}).done(function(results){
  if(results.length > 0){
    $('.recording-list-container').html('<div class="header">Track List</div><ol></ol>');                  
      $.each(results.slice(0,20), function(idx, recording){
       tracklen = recording.length != null ? millisToMinutesAndSeconds(recording.length) : '';
       $('.recording-list-container ol').append('<li>' + recording.title + ' ' + tracklen + '</li>');
     });
   }
});

And here’s browseRecordings.php script which returns results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$brainz = new MusicBrainz(new GuzzleHttpAdapter(new Client()));
$brainz->setUserAgent('hawkwyndRadio', '1.0', 'http://stream.hawkwynd.com');
$pkg = new stdClass();
$out = [];

try{
    $recordings = (object) $brainz->lookup('release', $releaseId, array('recordings'));  
   
    foreach($recordings->media as $m){
        foreach($m['tracks'] as $track){
            array_push($out, $track);
        }
    }

    echo json_encode($out);

} catch(Exception $e) {
    echo json_encode( $e->getMessage() );
}

Album Cover art

Cover Art

To get our coverart, or the image associated with the release, we have to perform the following request. Using the release group ID, we can query the API to retrieve the url to the associated cover image. We use the large thumbnail here.


1
2
3
4
5
6
7
8
9
10
// coverart for release-group
$ReleaseGroupImage  = json_decode(
                       @file_get_contents(
                         "https://coverartarchive.org/release-group/".$releaseGroup['release-group']['id']
                        )
                       );
$firstRecording['release-group']['coverart'] =
      $ReleaseGroupImage != false ?
      $ReleaseGroupImage->images[0]->thumbnails->large :
      null;

Artist Discography

Artist Discography

We use a combination of PHP and Javascript to populate the artist discography. The javascript calls the script passing the artist ID value to our php code, which the accesses the API to return a json list of releases for our artist, sorted in order of their release date. The javascript then interates the json array, and renders our listing.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function releaseHistory(arid){

   $.getJSON('browseReleases.php', {
            artistId: arid
        }).done(function(list){

        var artistName = $('.artist-name').html();
        var listing = '';

        $.each(list, function(idx, val){
            listing += '<div class="listing">' + val.date + ' '+ val.title +'</div>';
        });

        // Render discography listing
        $('.wrap-collapsible-releases').html(
            '<input id="release-collapsible" class="toggle" type="checkbox">' +
            '<label for="release-collapsible" class="lbl-toggle">'+artistName+ ' discography</label>'+
            '<div class="collapsible-content">'+
              '<div class="content-inner">' + listing + '</div>' +
            '</div>'
        );
    });
}

browseReleases.php code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// List all release-groups of an artist in order by release date oldest first

error_reporting(E_STRICT);
ini_set('display_errors', 1);

use Guzzle\Http\Client;
use MusicBrainz\HttpAdapters\GuzzleHttpAdapter;
use MusicBrainz\MusicBrainz;
require dirname(__DIR__) . '/stream/musicbrainz/vendor/autoload.php';

$artistId = $_GET['artistId'];

// Create new MusicBrainz object
$brainz = new MusicBrainz(new GuzzleHttpAdapter(new Client()));
$brainz->setUserAgent('hawkwyndRadio', '1.0', 'http://stream.hawkwynd.com');
$pkg = new stdClass();
$out =[];

try{
    $releases = (object) $brainz->lookup('artist', $artistId, array('release-groups','releases'));  

 // sort releases order of date in ascending order
 array_multisort(
    array_column(
         $releases->{'release-groups'}, "first-release-date"
    ),
    SORT_ASC,
    $releases->{'release-groups'}
 );
 
foreach($releases->{'release-groups'} as $release){
       
 if($release['first-release-date']) {
      $pkg          = new stdClass();
      $pkg->title   = $release['title'];
      $pkg->date    = date('Y' , strtotime($release['first-release-date']));
      $pkg->id      = $release['id'];
       array_push($out, $pkg);
  }
         
}

// spew result
echo json_encode($out);

} catch(Exception $e) {
    print $e->getMessage();
}
exit;