Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spotify JavaScript API - getting the genre of a song in a playlist

I'm currently using the very cool app Exportify (https://github.com/watsonbox/exportify) to export Spotify playlists to CSV.

My javascript knowledge is terrible but I'm currently trying to include the genre of each track, and I'm having trouble retrieving it.

I'm assuming that:

  • I have to get the genre through the artist.
  • The playlist API returns a simple artist object that can only get me the ID, not the genre (https://developer.spotify.com/web-api/get-playlists-tracks/)
  • That to get the genre I'd have to do a further lookup of the artist using the artist ID (https://developer.spotify.com/web-api/object-model/#artist-object-full)

I can get the artist ID by adding

item.track.artists.map(function (artist) {
  return artist.id
}).join(', '),

to the code below in the exportify.js file.

var tracks = responses.map(function (response) {
  return response.items.map(function (item) {
    return [
      item.track.uri,
      item.track.id,
      item.track.name,
      item.track.artists.map(function (artist) {
        return artist.name
      }).join(', '),
      item.track.artists.map(function (artist) {
        return artist.id
      }).join(', '),
      item.track.album.name,
      item.track.disc_number,
      item.track.track_number,
      item.track.duration_ms,
      item.added_by == null ? '' : item.added_by.uri,
      item.added_at
    ].map(function (track) {
      return '"' + track + '"';
    })
  });
});

Can anyone tell me how I can then get the artist genre based on the artist ID and add it as another column?

like image 451
Chris Avatar asked Oct 24 '25 11:10

Chris


2 Answers

Use the "Get an Artist" endpoint in Spotify:

https://developer.spotify.com/documentation/web-api/reference/get-an-artist

Calling this endpoint will get you the genres of an artist given his artist ID.

like image 54
Potray Avatar answered Oct 27 '25 00:10

Potray


I've implemented it!

Below is the function from https://github.com/pavelkomarov/exportify/blob/master/exportify.js that makes all the requisite API queries. My apiCall function is also in that file. It's using fetch.

  1. First I send off requests for chunks of songs from the playlist.
  2. Then I agglomerate those messages in to a data table and set of artists.
  3. Then I use the set of artists to query for genre information.
  4. Then I join that to the data and write out to csv.

Because it's all happening over the network, each new step has to be wrapped in a .then() which is dependent upon previous objects getting resolved. Thankfully, in recent years JavaScript has gotten more elegant at this. https://eloquentjavascript.net/11_async.html

The live app lives here. I've also created a notebook to analyze the output.

csvData(access_token, playlist) {
    // Make asynchronous API calls for 100 songs at a time, and put the results (all Promises) in a list.
    let requests = [];
    for (let offset = 0; offset < playlist.tracks.total; offset = offset + 100) {
        requests.push(utils.apiCall(playlist.tracks.href.split('?')[0] + '?offset=' + offset + '&limit=100',
                access_token));
    }

    // "returns a single Promise that resolves when all of the promises passed as an iterable have resolved"
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
    let artist_hrefs = new Set();
    let data_promise = Promise.all(requests).then(responses => {
        return responses.map(response => { // apply to all responses
            return response.items.map(song => { // appy to all songs in each response
                song.track.artists.forEach(a => { artist_hrefs.add(a.href) });
                return [song.track.uri, '"'+song.track.name.replace(/"/g,'')+'"', '"'+song.track.album.name.replace(/"/g,'')+'"',
                    song.track.duration_ms, song.track.popularity, song.track.album.release_date,
                    '"'+song.track.artists.map(artist => { return artist.name }).join(',')+'"',
                    song.added_by.uri, song.added_at]
            });
        });
    });

    // Make queries on all the artists, because this json is where genre information lives. Unfortunately this
    // means a second wave of traffic.
    let genre_promise = data_promise.then(() => {
        let artists_promises = Array.from(artist_hrefs).map(href => utils.apiCall(href, access_token));
        return Promise.all(artists_promises).then(responses => {
          let artist_genres = {};
          responses.forEach(artist => { artist_genres[artist.name] = artist.genres.join(','); });
          return artist_genres;
        });
    });

    // join genres to the table, label the columns, and put all data in a single csv string
    return Promise.all([data_promise, genre_promise]).then(values => {
        [data, artist_genres] = values;

        data = data.flat();
        data.forEach(row => {
            artists = row[6].substring(1, row[6].length-1).split(','); // strip the quotes
            deduplicated_genres = new Set(artists.map(a => artist_genres[a]).join(",").split(",")); // join and split and take set
            row.push('"'+Array.from(deduplicated_genres).filter(x => x != "").join(",")+'"'); // remove empty strings
        });
        data.unshift(["Spotify URI", "Track Name", "Album Name", "Duration (ms)",
            "Popularity", "Release Date", "Artist Name(s)", "Added By", "Added At", "Genres"]);

        csv = '';
        data.forEach(row => { csv += row.join(",") + "\n" });
        return csv;
    });
},
like image 27
Pavel Komarov Avatar answered Oct 27 '25 01:10

Pavel Komarov