Spotify Collage Builder

By: Michael Dunphy
June 2020

Introduction

There are several spoitfy collage makers out there, but many only allow you to create an image from your most listened to tracks and have limited options on customization. I decided to create my own collage builder that creates an album collage from a given playlist url and allows the user more freedom in creating the image.

Some of the features this collage builder provides:

  • Creates an album collage from a playlist
  • User provides an aspect ratio which the program will use to match the most optimal number of rows and columns
  • Optional limit on the number of album images included in the collage (max is 100 given Spotify's API)
  • Optional number of rows
  • Optional number of columns
  • Option to show a grided collage or not
  • Removes duplicate album images
  • Randomizes the order of the albums to create a unique collage everytime

The website that makes use of this code can be accessed at spotify-collage.anvil.app. The website uses Anvil to create a client side web app with a python script very similar to this jupyter file for the server side code.

Examples

Here are a few examples of collages created making use of this playlist:

Aspect Ratios

1:1
3:2
16:9
9:19.5

The above collages show the results from the given aspect ratios while having none for all other features and false for showing the grid.

  • 1:1 is commonly used for instagram photos.
  • 3:2 is commonly used for laptop wallpapers, specifically the macbook pro.
  • 16:9 is used for wide screen displays.
  • 9:19.5 is the aspect ratio for phones including the iPhone 11 pro.

The website has a link to check your device's aspect ratio which can be accessed here.

The program minimizes the number of images missed and tries to get as close as possible to the requested aspect ratio.

Worth noting for the first collage, even though the aspect ratio is 1:1, the program had a higher error with a 6 x 6 collage instead of a 6 x 7 collage due to the missing images. You can always change the weights of the errors in the code to prioritize aspect ratio over missing images if you wish.

Album Limits

2
20
40
50

Each collage is given a 1:1 aspect ratio and the given album limit. Album limits are optional in this program so the user could leave this feature blank on the web app and the program will just work with the number of images taken from the spotify API.

The playlist used for these collages has 49 songs total. If the limit is larger than the number of album images after duplicates are removed, then the program will just use the original number of images taken from the spotify API.

Rows

1
3
5
20

Columns

1
3
5
20

Each collage is again given a 1:1 apsect ratio and a row/column number.

The given number of rows/columns will be prioritized over the goal aspect ratio as seen with the collages given 20 for both rows and columns.

Grid

No Grid
Grid

The grid has no color and can be changed based on the background. You can always make the grid color static in the code.

Code

The program makes use of several libraries including:

  • Spotipy to access the Spotify API
  • Pandas to remove duplicate images
  • Matplotlib to create the collage
  • Random to randomize the order of the albums
  • Math to get the absolute value for the error function
  • URLLib to pull the images from the Spotify API
  • PIL to open album images
In [1]:
%%capture
!pip install spotipy
import spotipy
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import random
import math
import urllib.request
from PIL import Image
from spotipy.oauth2 import SpotifyClientCredentials #To access authorised Spotify data

To access the Spotify API, you will need a client id and secret id. To get those, you can sign up here.

In [2]:
client_id = "your client id"                        #spotify API client id
client_secret = "your secret id"                    #spotify API secret id

client_credentials_manager = SpotifyClientCredentials(client_id=client_id, client_secret=client_secret)

sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) #spotify object to access API

The function below is used to find the optimal number of rows and columns for the collage. The function is given the number of images n, the aspect ratio x and y, and the number of rows and columns the user is looking for if they gave one.

The function compares error rates for different row and column combos. The error rate is determined by the number of missing album images plus the pixel error which is simple the difference in aspect ratios of the resulting image from the row/column pair and the goal aspect ratio.

The weight places on pixel_error is 1000 and was determined simply by trial and error, but can be changed to prioritize getting closer to the right aspect ratio at the cost of leaving images out and vice versa.

In [3]:
#function that finds the best number of columns and rows for the collage
def find_cols_rows(n, x, y, r1, c1): #given number of images, image size x & y, number of given rows and/or columns 
    mi = 100000                                               #min value
    cols = 0                                                  #initial cols value
    rows = 0                                                  #initial rows value
    total_error = 100000                                      #initial total error value
    pixel_ratio = x / y
    
    #print(n)

    if c1 == None:
        for c in range(1,n):                                  #traverse through possible column values up to n
            if r1 == None:
                r = int(n / c)                                #create temp row value
            else:
                r = r1
                
            if r * c <= n:
                imgs_error = abs(n - (r * c))                 #error is how many images are missing

                temp_ratio = c / r
                pixel_error = abs(pixel_ratio - temp_ratio)   #error is difference in given aspect ratios

                temp_error = total_error
                total_error = imgs_error + (1000 * pixel_error) #combine both errors

                if temp_error < total_error:                  #when error starts increasing, global min
                    break;                                    #has already been reached and loop can stop

                mi = min(total_error, mi)                     #to minimize error

                if mi==total_error:                           #store column values with minimum error
                    cols = c
            
    else:
        cols = c1                                             #if given a columns value, that number is stored
        
    mi = 100000                                               #reset values used to minimze error
    pixel_error = 100000
    
    if r1 == None:
        for r in range(1, int(n / cols) + 1):                 #traverse through possible row values                       
            temp_ratio = cols / r                             
            temp_error = pixel_error
            pixel_error = abs(pixel_ratio - temp_ratio)       #error is difference in given aspect ratios

            if temp_error < pixel_error:                      #when error starts increasing, global min
                break;                                        #has already been reached and loop can stop

            mi = min(pixel_error, mi)                         #to minimize error

            if mi==pixel_error:                               #store row value with minimum error
                rows = r
    
    else:
        rows = r1                                             #if given row value, that number is stored
        
    return rows, cols

This function is what creates the collage and calls finds_cols_rows to get the number of rows and columns needed. Collage_maker is given the url of the playlist playlist_uid, the aspect ratio x and y, the ppi (pixels per inch), the limit on the number of images num, the given row/column r and c, and whether to show the grid or not.

In [4]:
#function to produce collage
def collage_maker(playlist_uid, x, y, my_dpi, num, r, c, layout):
    playlist_i = playlist_uid.split('https://open.spotify.com/playlist/')[1] #convert uid to just playlist id
    playlist_id = playlist_i.split('?si=')[0]
    
    p = sp.playlist_tracks(playlist_id)             #retrieve playlist from api
    playlist = pd.DataFrame(p['items'])             #convert list of tracks of playlist to dataframe

    image_urls = []
    images = []

    for i in range(len(playlist.index)):            #retrieves image urls from each track
        image_urls.append(playlist['track'][i]['album']['images'][0]['url'])

    no_dups = pd.DataFrame(image_urls).drop_duplicates()   #remove duplicate images


    for i in no_dups.index:                         #open images and store in images list
        images.append(Image.open(urllib.request.urlopen(no_dups[0][i])))
        
    img_w = 640                                     #x pixel size of spotify image
    img_h = 640                                     #y pixel size of spotify image
    
    
    if num == None or len(images) < num:
        n = len(images)
    else: 
        n = num
    
    rows, cols = find_cols_rows(n, x, y, r, c)

    random.shuffle(images)                          #randomize the list of images

    fig = plt.figure(figsize=(((cols * img_w) / my_dpi), ((rows * img_h) / my_dpi)), 
                     dpi=my_dpi, constrained_layout=True, facecolor='black')  #create figure
    
    if layout == False:                             #to show no grid
        fig.set_constrained_layout_pads(hspace=-0.02, wspace=-0.02)
    else:                                           #to show the grid
        fig.set_constrained_layout_pads(hspace=-0.0, wspace=-0.0)
        
    f = gridspec.GridSpec(nrows=rows,ncols=cols, figure=fig) #create grid
    
    i = 0                                           #to traverse through images
    for x in range(rows):                           #traverse through rows
        for y in range(cols):                       #traverse through columns
            ax = fig.add_subplot(f[x, y])           #create new subplot, add to grid
            ax.imshow(images[i], resample="True")   #store album image in added subplot
            ax.axis('off')                          #turn axis labels off
            i+=1     
    
    return plt

Here is the main function. Given the url, aspect ratio, album limit number, rows, columns, and layout type.

Separates the aspect ratio string, provides a ppi and calls the collage_maker function.

In [5]:
#main function
def main_collage(playlist_uid, ratio, num, r, c, layout): #given playlist url and selected device
    
    x = float(ratio.split(':')[0])                  #separates the aspect ratio into x and y
    y = float(ratio.split(':')[1])
    ppi = 120                                       #can change ppi, but 120 is pretty good for most devices
    
    return collage_maker(playlist_uid, x, y, ppi, num, r, c, layout)    

Call to the main function and resulting collage.

There are some playlists you can test with as well with the number of songs in each playlist.

In [6]:
#Some playlists to test with if you wish
#June playlist https://open.spotify.com/playlist/4YnJcfGsw3odjP2YiHeXw0?si=_x2gf_i3Qaq9wUpOVt4Ztw 49 songs
#May playlist https://open.spotify.com/playlist/4KYLmJDy3WtxS8tJBdggml?si=gM-Q5oQNRiW9L4QLEOAyzA 51 songs
#April Playlist https://open.spotify.com/playlist/5P9iX96k8U85nnM6MMgUK1?si=KCmDTspqSRKfF_cS-6z9Qg 43 songs
#March Playlist https://open.spotify.com/playlist/1VgmJlP1aK72bLf84DTQ1r?si=VVpVMNUmS2KOqSUdjh5i1Q 36 songs
#February Playlist https://open.spotify.com/playlist/2iwVG9QskQQZ7hchIqnojx?si=Nibu7W1dTp-pvYdk7HUEVQ 35 songs
#January Playlist https://open.spotify.com/playlist/4ZSEVCrvjm6yqiJWIrlBvU?si=gjWqKgebTOCrQgmivBMGjw 34 songs

#Note: the number of songs isn't necessarly equal to the number of album images since there may be duplicate  
#albums that are removed 

#call main function example
main_collage('https://open.spotify.com/playlist/4YnJcfGsw3odjP2YiHeXw0?si=_x2gf_i3Qaq9wUpOVt4Ztw', "1:1",
                 None, None, None, False)
Out[6]:
<module 'matplotlib.pyplot' from '/opt/conda/lib/python3.7/site-packages/matplotlib/pyplot.py'>

Hope you enjoy the collage builder and have fun on your coding adventures.