Downloading Album Art with Jekyll

August 02, 2018

When I added the album of the month side of my website, I wanted to make sure the archive had access to all the cover art of the previously chosen albums without loading a bunch of unnaturally heavy Spotify iframes. After a solid hour of messing around with the Spotify API followed by several hours of grokking of Ruby, here’s what I came up with.

Spotify recently updated their API to allow authorized users to download a whole bunch of stuff from their servers, including everything from playlists to user data to actual tracks to album cover art. However, since there’s no simple authentication client side that does not require the user to access to their own Spotify account, I had to resort to downloading the covers in the Jekyll build phase instead. Naturally, the next step was to figure out how to actually write a Jekyll plugin.

Creating a Plugin

Fortunately, the documentation for Jekyll’s plugin system is really comprehensive, and I was able to determine a generator would best suit my need to download images into the assets folder. I first saved the boilerplate plugin to the _plugins directory:

module Reading
  class Generator < Jekyll::Generator
    def generate(site)
    end
  end
end

Getting Spotify Authorization

The next step was to figure out how to properly authenticate my Spotify authorization ID and secret in order to receive a token I could use to request album metadata. I got the credentials here, and used the following instance method in my Generator to make the request:

def authorize()

  if @token
    return @token
  end

  # Create authorization
  id = '***'
  secret = '***'
  authorization = 'Basic ' + Base64.strict_encode64(id + ':' + secret)

  # Make the token request
  uri = URI.parse('https://accounts.spotify.com/api/token')
  https = Net::HTTP.new(uri.host, uri.port)
  https.use_ssl = true
  request = Net::HTTP::Post.new(uri.path)
  request['Authorization'] = authorization
  request.body = 'grant_type=client_credentials'
  response = https.request(request)

  # Parse and return token
  begin
    @token = JSON.parse(response.body)['access_token']
  rescue JSON::ParserError
    return nil
  end

end

The first thing to note here is that I used the instance variable @token because I wanted to be able to call self.authorize any number of times and only have it make the request to the Spotify API once. The second is that I have never written a single line of Ruby, so please be understanding if I am writing extremely verbose code :’^).

Downloading Cover Art

The next step was to use the token I had downloaded to make the request for the Spotify cover art images for a given album. In the Spotify ecosystem, albums all have a unique ID, and I made sure to bundle this in each document in my albums collection. For example, here is the front-matter for Fugue State:

title: Fugue State
artist: Vulfpeck
spotify: 3DiHrdZ5z0AqW6JTpLAKxG
for: "Most Influential Albums #1"
date: 2018-05-23
layout: album

The following Generator instance method makes the request for the second album cover image in the list provided by Spotify. The second is usually 300px by 300px as far as I can tell, so it seemed the most appropriate.

def download(id)

  # Get the URL
  url = album(id)
  if url == nil
    return nil
  end

  # Setup to download the file
  uri = URI.parse(url)
  https = Net::HTTP.new(uri.host, uri.port)
  https.use_ssl = uri.scheme == 'https'
  request = Net::HTTP::Get.new(uri.path)

  # Download and save
  File.open('assets/albums/' + id, 'wb') do |output|
    output.write https.request(request).body
  end

end

Tying it Together

The last part of the project was the actual generate method, which ties everything together. This function is called by Jekyll once the generator object has been instantiated:

def generate(site)

  # Make sure the directory exists
  FileUtils.mkdir_p 'assets/albums'

  # Download missing albums
  site.collections['albums'].docs.each do |album|
    id = album['spotify']
    if !File.file?('assets/albums/' + id)
      self.download(id)
    end
  end

end

And that’s it! Ruby is quite a strange language, but it is flexible, easy to use, and definitely well-suited to the Jekyll framework. I’m a big fan of how straightforward this was (well, granted I made about 150 Google queries), and it gives me cautious optimism about implementing more complex plugins in the future.