I have Sonos speakers in my flat. In combination with Spotify, this allows me to play just about whatever music I want, in whichever rooms of the flat, simply by opening the Sonos app on my phone and searching for the artist. The biggest challenge is deciding what to listen to.

I also have a record player in my flat, and a number of vinyl records, in nice sleeves with nice cover art. Even though putting on a record is much less convenient than opening Sonos on my phone, I still get a lot from having them around - primarily the ability to wonder over and flick through a big pile of album art to help decide what I want to listen to: something which is lost by music streaming services.

I had an idea for how I could combine the two; keep the convenience and low cost of music streaming, but also retain a physical presence for my music collection (in my case a pile of records).

The solution is built using a webcam and some Python code (using SimpleCV, SoCo libraries) - take a look at it in action:

Click permalink below to get to the code...

As you'll see below, the code is fairly concise. The basic premise is as follows:

  • Load in a configuration file. This lists the name, album cover and Spotify track/album reference for each record cover.
  • Pull images from a camera (in my case I'm using an old iPhone running ipCam. (When I visit 'http://192.168.1.65/image.jpg' on my network, it returns an image from the camera.)
  • Using the SimpleCV/OpenCV keypoint matching functionality, test the retrieved image to see if there is a match to one of the cover art images.
  • If there is a match, use the SoCo library to play the corresponding referenced track or album.
  • Wait half a second, and try again.

Here's the code:

# In[1]:

from SimpleCV import *
import soco
import json
disp = Display(displaytype='notebook')
sonos = soco.SoCo('192.168.1.82')


# In[15]:

import warnings
warnings.filterwarnings("ignore")


# In[13]:

file_location = r'C:\Users\will\Documents\Projects\Sonart'
camera_location = r'http://192.168.1.65/image.jpg'
timer_duration = 0.5


# In[3]:

# open the config file and read in the images

with open(file_location + r"\config.json", 'r+') as f:
    config = json.load(f) 
    
for artist in config:
    image_location = os.path.join(file_location, 'Covers', artist['cover'])
    artist['img'] = Image(str(image_location))


# In[18]:

from IPython.display import clear_output
while True:
    
    try:
        img = Image(camera_location)
    except:
        print("Cannot acquire image. Quitting")
        break
    
    for artist in config:
        match = img.findKeypointMatch(artist['img'], minDist=0.15)

        if match is not None:
            clear_output()
            print('Recognised {0}'.format(artist['artist']))
            sonos.play_uri(artist['metadata']['uri'], artist['metadata']['metadata'], artist['metadata']['title'])
    
    time.sleep(timer_duration)

I intend to get this cleaned up into a repo soon, including functions to help build the config file. For now though, if you want to try it out, I suggest you start by pulling the sightmachine/simpleCV docker image, and running the code from there.

The configuration file which is loaded includes three attributes for each config item: the name, the name of the cover art file on disk, and a stored reference to the Sonos/Spotify track metadata. I got the reference by putting on the right track using the normal Sonos controller, and then calling 'sonos.get_current_track_info()' and saving it to the config file.