Recreating Spotify Wrapped In PHP

I quite like the end of the year report from Spotify that they call "Wrapped". This is a little application in which they tell you what your favorite artist was and what sort of genres you listened to the most during the year. It shouldn't come as a surprise to me that I listened to this or that, but since I listen to Spotify around 30-40 hours a week I certainly lose track of what my favorite artist was that year.

This years report got me thinking about how difficult it would be for me to generate my own Spotify report, whenever I wanted. Maybe to see who I had listened to the most during the week, or what sort of genres I was into at the moment.

I knew Spotify had an API, but had never really made use of it. So I decided to put something together in PHP to generate a weekly wrapped report.

Connecting To The Spotify API

The Spotify Web API is a RESTful JSON service that uses OAuth as the authentication mechanism. As all of the calls to the API that return data need to be authenticated you need to get an OAuth token before you can make use of it.

To get this working I needed to create an application in Spotify and then use this to authenticate my personal Spotify account to that application. The creation of applications is pretty simple, and you will a client ID and client secret that you can use to generate the connection to Spotify.

A screenshot of the Spotify developer dashboard, showing the hashbangcode application..

Spotify allows you to create an application in "development mode", which basically has a smaller usage quote and will only allow you to authenticate up to 25 users. This is more than enough for my purposes.

I then looked around for a PHP library that would allow me to connect to the Spotify Web API. I could have built this myself, but I was sure that a few libraries would exist that your simplify the code I needed to write.

As it happens, there are a few PHP Spotify libraries, and after trying a few out I decided on the jwilsson/spotify-web-api-php library. This library seems to cover all of the Spotify API, is well maintained, and is written using modern PHP coding techniques (including coding standards and unit testing).

To connect to the API I just needed to write a little bit of code; supplying the client ID and client secret keys that I was given when setting up the application. The following code will create the SpotifyWebAPI\Session object and redirect the user to Spotify to allow them to authenticate against the application. When setting up the authentication redirect I also pass in a set of scopes that I would like to use against that user.

<?php

require '../vendor/autoload.php';

// Start a PHP session.
session_start();

// Create spotify session.
$spotifySession = new SpotifyWebAPI\Session(
  '[client ID]',
  '[client secret]',
  'https://localhost/callback.php'
);

// Redirect to Spotiy.
$options = [
  'scope' => [
    'user-read-email',
    'user-read-recently-played',
  ],
];

header('Location: ' . $spotifySession->getAuthorizeUrl($options));
die();

Once we visit this page we are taken to a screen that looks like this, which verified that we want to connect our personal Spotify account to the application. You can see the scopes presented here, which gives the user more information about what this application will have access to. Note that the user has to be logged into Spotify already to see this dialog.

A screenshot of the Spotify application, showing the permissions being accepted.

Once the user has accepted this then they will be sent back to a "callback" URL that we also stipulated when creating the application. This is just a local address for now, but OAuth will work fine with local addresses.

The callback URL is just another page that takes the "code" parameter from the callback URL and generates an access token for the user and stores this in the PHP session. Once that's complete we show a link to the history.php page, which is where we will do the analysis on the user's history.

This the contents of the callback.php page, which accepts the incoming Spotify authentication.

<?php

require '../vendor/autoload.php';

// Start a PHP session.
session_start();

// Create spotify session.
$spotifySession = new SpotifyWebAPI\Session(
  '[client ID]',
  '[client secret]',
  'https://localhost/callback.php'
);

if (isset($_GET['code'])) {
  $spotifySession->requestAccessToken($_GET['code']);

  $_SESSION['access'] = $spotifySession->getAccessToken();
  $_SESSION['refresh'] = $spotifySession->getRefreshToken();

  echo '<a href="/history.php">History</a>';
}

We could redirect to the history page, but it's sometimes nice to show the user a confirmation page to say "thanks for connecting".

From this point on, all we need to do in order to connect to the Spotify Web API is to run the following code. This will pull the 'access' token out of the PHP session variable and setup the connection to Spotify.

$api = new SpotifyWebAPI\SpotifyWebAPI();
$access = $_SESSION['access'];
$api->setAccessToken($access);

The $api variable is now our connection to Spotify.

This is how OAuth works in general, and the Spotify API is quite a nice introduction to it.

Generating Spotify History

Looking at the Spotify Web API shows that it is possible to get the recently played tracks for a user using the /me/player/recently-played endpoint. I won't go into large amounts of detail as to what is in the API response as he Spotify documentation is actually pretty good. They have a Swagger-like API demonstration that will allow you to see the actual data brought back from your API request.

This endpoint is wrapped by the "getMyRecentTracks" method in the SpotifyWebAPI class, so we just need to call that method to generate the data.

Calling this endpoint generates a long list of tracks; but the API is paginated. We therefore need to loop around and ask for more data from after the current cursor position (which is part of the API).

Before we get the data we need to set up a few arrays that will be used to store the data for later analysis and rendering.

// Set up buckets for our data.
$artists = [];
$genres = [];
$tracks = [];

Then, we can loop over getMyRecentTracks to get all of the history data.

// Grab the data from Spotify.
$recent = null;
while (true) {
  try {
    if ($recent === null) {
      // Get the most recently played tracks.
      $recent = $api->getMyRecentTracks();
    } else {
      // If we have a $recent object, then look for the results in the before
      // cursor.
      $options = ['before' => $recent->cursors->before];
      $recent = $api->getMyRecentTracks($options);
    }
  } catch (\SpotifyWebAPI\SpotifyWebAPIException $e) {
    // There was a problem with the OAuth token, redirect the user back to the
    // index.php page to re-generate tokens.
    header('Location: index.php');
    die();
  }

  // Loop through our results and extract the needed data.
  foreach ($recent->items as $id => $item) {
    // Get the artist data.
    $apiArtists = getArtistWithCache($api, $item->track->artists[0]->id);

    // Perform genre count.
    foreach ($apiArtists->genres as $genre) {
      if (isset($genres[$genre])) {
        $genres[$genre]++;
      } else {
        $genres[$genre] = 0;
      }
    }

    // Perform artist count and add the duration of tracks listened to.
    if (isset($artists[$item->track->artists[0]->name])) {
      $artists[$item->track->artists[0]->name] += $item->track->duration_ms;
    } else {
      $artists[$item->track->artists[0]->name] = $item->track->duration_ms;
    }

    // Store the track data for later rendering.
    $tracks[] = [
      'time' => $item->played_at,
      'artist' => $item->track->artists[0]->name,
      'album' => $item->track->album->name,
      'track' => $item->track->name,
      'image' => $item->track->album->images[2],
    ];
  }

  if ($recent->cursors === null) {
    // If there is no cursors present then break out of the loop.
    break;
  }
  $before = $recent->cursors->before;
}

After this code has run our arrays will be full of data.

I found out quite quickly from running this that Spotify will only allow you to grab the last 50 tracks you listened to using this API endpoint. There aren't any other API endpoints that you can use to grab your Spotify history so this is pretty much it. I've heard that you can make a request to Spotify for older data, but these requests can take a little while to produce.

The getArtistWithCache function in this loop is a custom function that prevents the same endpoint being asked for the same data over and over again within the loop. It uses a static variable to populate a cache of artist data and will simply return one of the cached item if we ask for the same artist again during this request.

function getArtistWithCache($api, $id) {
  static $artistCache = [];

  if (isset($artistCache[$id])) {
    return $artistCache[$id];
  }

  $apiArtist = $api->getArtist($id);

  $artistCache[$id] = $apiArtist;

  return $artistCache[$id];
}

Now we just need to generate the statistics for the different data arrays we collected.

To generate the artist statistics we just sort and loop over the array, printing out the artist name and converting the milliseconds duration time into minutes.

// Generate artist stats.
arsort($artists);
echo '<table>';
foreach ($artists as $artist => $duration) {
  $duration = number_format($duration/1000/60, 2);
  echo '<tr>';
  echo '<td>' . $artist . '</td><td>' . $duration . ' minutes</td>';
  echo '</tr>';
}
echo '</table>';

To generate the genre stats we just loop over the items and print out the genre and how many times we came across this genre in our analysis.

// Generate genre stats.
arsort($genres);
echo '<table>';
foreach ($genres as $genere => $number) {
  echo '<tr>';
  echo '<td>' . $genere . '</td><td>' . $number . '</td>';
  echo '</tr>';
}
echo '</table>';

You can optionally generate a list of tracks with the thumbnail image for the album cover, which is nice to see.

echo '<ul>';
foreach ($tracks as $track) {
  echo '<li>';
  echo '<img src="' . $track['image']->url . '" height="' . $track['image']->height . '">';
  echo $track['artist'] . ': ' . $track['track'] . ' <br>';
  echo '<span style="font-size: small">' . $track['time'] . '</span>';
  echo '</li>';
}
echo '</ul>';

This generates an ugly but serviceable list of tracks.

My Statistics

Running this code on my own profile produces the following results for artists.

Noctule67.99 minutes
Bones of Minerva61.97 minutes
The Machinist39.97 minutes
Konvent31.87 minutes
Blood Incantation18.19 minutes
Point Mort11.16 minutes

I was listening to Notcule whilst writing this post out, which is why it is in there for over an hour. I'm quite into the Spanish band Bones of Minerva at the moment, and this shows me listening to them quite a lot today. The Mechinist and Blood Incantation have been in heavy rotation for the last couple of years.

The genres generated from the tracks are as follows.

latin rock14
epic black metal12
deathcore10
melodic deathcore10
nu-metalcore10
danish death metal5
danish metal5
cavernous death metal2
cosmic death metal2
death metal2
deathgrind2
denver metal2
new wave of osdm2
technical death metal2

The genres in Spotify are absolutely all over the place and appear just tangentially connected to the artists they represent. I have absolutely no idea what track produced the genre "cavernous death metal", but it sounds like a genre I can get behind!

Conclusion

I set out to generate a few statistics of my listening habits, but with only 50 tracks maximum being returned by this API endpoint there isn't a lot to look at. What statistics I was able to generate were interesting, but since I had listened to those tracks just today it just ended up telling me what I already know.

As I had a limited amount of data to play with I didn't feel it necessary to delve into creating graphs and special graphics. Hence me just creating a couple of tables and leaving it at that.

It is possible to extend this code to generate a longer report, but that would mean polling the Spotify API every couple of hours to keep an up to date list of the tracks. I might attempt to do that in the future, but for now I am satisfied with the results. There may be a part two if I feel like this application warrants more detail.

Feel free to use the code above in your own applications.

I did some looking around and found a service that does this already. So, if you are interested in generating a longer report using your Spotify data then take a look at https://www.statsforspotify.com/. I have not tried this site out, but it has a good privacy policy so it's worth a shot.

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
10 + 9 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.