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:
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.
The above collages show the results from the given aspect ratios while having none for all other features and false for showing the grid.
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.
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.
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.
The grid has no color and can be changed based on the background. You can always make the grid color static in the code.
The program makes use of several libraries including:
%%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.
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.
#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.
#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.
#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.
#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)