An album art collage from a Spotify playlist

In January this year, I wrote a retrospective about the music I enjoyed listening to in 2016. Since I use Spotify almost exclusively, I made a playlist of my favourite songs and wrote a Perl script that downloads all the album covers from Spotify and stitches them into a montage. The script was hurriedly written for my specific need but since other people showed interest in it, I decided to polish it up a bit and release in on github:

https://github.com/deepakg/spotify

The rest of this article annotates interesting bits of the script.

Before you can use the script, you’ll need to set up an application on Spotify. This will give you a client_id and client_secret. For a personal script I’d hardcode them into the script but for something I intend to publish on github, I’d rather not as I’d inadvertently end up pushing it one day. So instead, I create an .ini file in my home directory called .api_keys where I store them:

[spotify]
client_id = <your client_id>
client_secret = <your client_secret>

We use the Config::INI::Reader module from CPAN to parse the entire contents of an .ini file into a dictionary:

my $config = $ENV{HOME} . '/.api_keys';
my $config_hash = Config::INI::Reader->read_file($config);

The client_id and client_secret can now be accessed as: $config_hash->{spotify}{client_id} and $config_hash->{spotify}{client_secret}

We cannot use the client id and client secret directly to make an API call. Instead, we need to exchange them for a “bearer token” that can be used for making the API calls. The get_bearer_token subroutine does exactly that. Unlike client id and client secret, bearer token expires and has to be renewed. Since it typically last an hour, we won’t need to renew it in this script (and to keep things simple nor will we try to reuse the bearer token between two runs of the script).

Once we have the bearer token, our next step is to use it to make a call to the Spotify’s playlist API to retrieve meta-data (that includes album art urls) about all the tracks in the playlist. But for which playlist? The script includes a hardcoded playlist with 8 songs as a demo, but you can ask it to use yours by passing it through the --playlist command line argument.

Right-click a playlist in the Spotify Desktop client and select the “Copy Playlist Link” option from the menu. This link is what the --playlist argument expects:

perl spotify-collage.pl --playlist https://open.spotify.com/user/deepak.gulati/playlist/6VIJPqtY1rsRH9uxEj0c0o

The script extracts the user and playlist id from this URL and calls the Spotify’s playlist tracks API with it:

my ($user_id, $playlist_id) = $playlist_url =~ m|^https://open\.spotify\.com/user/(.*?)/playlist/(.*)$|;
my $api_url = "https://api.spotify.com/v1/users/$user_id/playlists/$playlist_id/tracks"; #this is the endpoint we call

The call returns a lot of meta-data including information about the album a track is from. The album section also contains an array with url of the album art in three sizes (640x640, 300x300 and 64x64). We collect the urls of the 640x640 images into an array and then extract the unique urls. We do this because a playlist might have multiple tracks from the same album and we don’t want to repeat album covers in our collage.

The next step is to download the images to a temporary directory. Instead of hardcoding a folder in /tmp, I use the tempdir function from File::Temp to do the right thing. As a bonus, you can tell the function to cleanup the directory automatically once the script is done with it. The mirror method from HTTP::Tiny makes it really simple to download a url to a local file.

sub download_images {
my $images = shift;
my $dir = tempdir(
CLEANUP => 1,
DIR => File::Spec->tmpdir,
template => 'spotify-montage-XXXX',
);
say STDERR $dir;
my $count = 1;
my $total = scalar(@$images);
my $ua = HTTP::Tiny->new;
for my $image (@$images) {
# $image is the full url - split it on '/' and get the name
# of the file
my $file = $count . '-' . (split m|/|, $image)[-1] . '.jpg';
        # download the file
say STDERR "Downloading $count of $total";
$ua->mirror($image, $dir . "/" . $file);
$count++;
}
return $dir;
}

The data for tracks is returned in the same order as they were added to the playlist. The album art urls are opaque. So while we could use our $images arrayref to track the order, I decided to prefix the files with a number to capture the order in which they were downloaded.

The work of creating the montage is handled by Imager::Montage which greatly simplifies the job. All it needs is a list of file paths and the number of rows and columns we want in our grid. It can also resize images before putting them in the grid. We use the 640x640 size and size it down to 200x200 before arranging it in the grid. The no. of rows and columns are dynamically generated based on the number of images we have. We try to fit images in a 4:3 ratio grid and this might exclude some albums. You could always modify the script to accept the number of rows and columns from command line.

That’s all.

Like what you read? Give Deepak Gulati a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.