Vinyl Scrobbling macOS App
Like many techies on their day off, I completely abandoned my planned tasks and embarked on an entirely unplanned project: creating a macOS application. Well, to be more precise, I decided to prompt one using Claude 3.5 Sonnet.
With Anthropic’s recent updates to their AI model, I figured it was the perfect opportunity to test its capabilities while solving a personal need.
The Catalyst: My New Tuneshine Device
Recently, I acquired a Tuneshine - a clever device that arrived last week. It works wonderfully with my streaming services of choice, including Apple Music and Spotify, and offers integration with LastFM.
For those unfamiliar with the device, Techmoan provides an excellent overview in their review:
For those that know me, you will know that the Tuneshine ticks a lot of geeky boxes ✅.
The Challenge
Given the Tuneshine’s LastFM support, I assumed I could continue using OpenScrobbler to log my vinyl listening sessions and have the album artwork display on the device. I was mistaken - but not due to any fault in either the Tuneshine or OpenScrobbler.
The issue lies in how these services interact with LastFM. The Tuneshine is designed for online players and monitors tracks that are “Scrobbling now” through LastFM’s API. In contrast, OpenScrobbler manually logs tracks after they’ve been played, which means they don’t trigger the “now playing” status that the Tuneshine looks for.
After searching for an alternative - specifically, an application that would run in the background and send real-time “now playing” requests to the LastFM API - I came up empty-handed. That’s when I decided to prompt my own solution.
Where to start?
The place to start was prompting a quick MVP, could I get Clade to give me something that
Claude returned the following code (I filled in the track details at the bottom):
import pylastimport timefrom datetime import datetimeimport loggingimport jsonimport os
class LastFMScrobbler: def __init__(self, config_path='config.json'): self.load_config(config_path) self.setup_logging() self.initialize_network()
def load_config(self, config_path): """Load configuration from JSON file""" try: with open(config_path, 'r') as f: config = json.load(f) self.api_key = config['api_key'] self.api_secret = config['api_secret'] self.username = config['username'] self.password_hash = config['password_hash'] except FileNotFoundError: raise Exception("Config file not found. Please create a config.json file.")
def setup_logging(self): """Setup logging configuration""" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('scrobbler.log'), logging.StreamHandler() ] ) self.logger = logging.getLogger(__name__)
def initialize_network(self): """Initialize Last.FM network connection""" try: self.network = pylast.LastFMNetwork( api_key=self.api_key, api_secret=self.api_secret, username=self.username, password_hash=self.password_hash ) self.logger.info("Successfully connected to Last.FM network") except Exception as e: self.logger.error(f"Failed to initialize Last.FM network: {str(e)}") raise
def scrobble_track(self, artist, title, album=""): """Scrobble a track to Last.FM""" timestamp = int(time.time()) try: self.network.scrobble( artist=artist, title=title, timestamp=timestamp, album=album ) self.logger.info(f"Scrobbled: {artist} - {title}") return True except Exception as e: self.logger.error(f"Failed to scrobble track: {str(e)}") return False
def update_now_playing(self, artist, title, album=""): """Update now playing status""" try: self.network.update_now_playing( artist=artist, title=title, album=album ) self.logger.info(f"Updated now playing: {artist} - {title}") return True except Exception as e: self.logger.error(f"Failed to update now playing: {str(e)}") return False
def main(): # Example configuration file format (config.json): # { # "api_key": "your_api_key", # "api_secret": "your_api_secret", # "username": "your_username", # "password_hash": "your_password_hash" # }
scrobbler = LastFMScrobbler()
# Example usage: artist = "Tori Amos" title = "God" album = "Under The Pink"
# Update now playing scrobbler.update_now_playing(artist, title, album)
# Scrobble the track scrobbler.scrobble_track(artist, title, album)
if __name__ == "__main__": main()
After installing the pylast module using the command below:
pip install pylast
I ran the code and saw that “God” by Tori Amos from the album Under The Pink had appeared in LastFM and glancing over at the Tuneshine I saw the following …
Result !!! Now that I proved it could work I could start prompting away.
Prompting the full application
I won’t fill this post with every prompt, because there were a lot of them, but after a few hours I had a Python powered stand-alone macOS application. The key fetaures I prompted were …
- Search albums using their Discogs ID
- Automatic track duration detection (with fallback options)
- Real-time “Now Playing” status with a timer
- Automatic track progression
- Clean configuration management as I didn’t want to hard code any of my keys or credentials into the application
- Detailed logging system
Its uses the rumps to do the bulk of heavy lifting around being a status bar application and then py2app to take the rumps powered status bar application and make it standalone,
Seeing it in action
It is a really basic application so lets dive straight in scrobble Damned Damned Damned by The Damned- first off we need the Discogs ID which is “24589202”. Now we have that we can open the application and search for it:
Checking the logs at ~/.vinyl-scrobbler/vinyl_scrobbler.log
I can see the following:
tail -f ~/.vinyl-scrobbler/vinyl_scrobbler.log2024-10-28 16:45:46,156 - INFO - No Discogs duration for I Feel Alright, trying Last.fm...2024-10-28 16:45:46,156 - INFO - Fetching duration from Last.fm for: The Damned - I Feel Alright2024-10-28 16:45:46,156 - INFO - track.getInfo2024-10-28 16:45:46,570 - INFO - HTTP Request: POST https://ws.audioscrobbler.com/2.0/ "HTTP/1.1 200 OK"2024-10-28 16:45:46,573 - INFO - Found duration on Last.fm: 4:272024-10-28 16:45:46,573 - INFO - Menu updated successfully2024-10-28 16:46:04,573 - INFO - Loaded album: Damned Damned Damned2024-10-28 16:46:54,528 - INFO - track.updateNowPlaying2024-10-28 16:46:54,773 - INFO - HTTP Request: POST https://ws.audioscrobbler.com/2.0/ "HTTP/1.1 200 OK"2024-10-28 16:46:54,774 - INFO - Updated now playing: The Damned - Neat Neat Neat2024-10-28 16:49:36,780 - INFO - track.scrobble2024-10-28 16:49:36,941 - INFO - HTTP Request: POST https://ws.audioscrobbler.com/2.0/ "HTTP/1.1 200 OK"2024-10-28 16:49:36,943 - INFO - Scrobbled: The Damned - Neat Neat Neat2024-10-28 16:49:36,944 - INFO - track.updateNowPlaying2024-10-28 16:49:37,093 - INFO - HTTP Request: POST https://ws.audioscrobbler.com/2.0/ "HTTP/1.1 200 OK"2024-10-28 16:49:37,095 - INFO - Updated now playing: The Damned - Fan Club
Finally, checking the Tuneshine we can see:
Summary
What started as an off the cuff idea after waking up turned into a successful experiment in AI-assisted development using Claude 3.5 Sonnet. The challenge was to create a solution that would enable real-time LastFM scrobbling for vinyl records to work with the Tuneshine device. Through iterative prompting and development, I was were able to create a functional macOS status bar application that not only solves the original problem but includes several useful features.
The project demonstrates the potential of modern AI tools in practical application development. By leveraging Claude 3.5 Sonnet’s capabilities alongside libraries like rumps and py2app, I were was to transform a simple Python script into a fully-functional macOS application with next to no knowlege of either developing Python or native macOS applications.
For those interested in trying it out or building upon this project, the complete source code is available on GitHub at:
Now its not perfect, so I should probably add the following:
But this adhoc project served as an example of how AI can be effectively used to rapidly prototype and develop solutions for specific use cases, even when working with multiple APIs and system-level integration requirements.
Audio Summary
Related Posts

Personal Project Updates and AI Editors
About that time I wrote and published an App to the Apple App Store without knowing how to code

Running Flux on macOS
“Learn how to install and use MFLUX to run FLUX.1 models on macOS. This guide explores generating high-quality AI images, comparing [Schnell] and [Dev] models, and enhancing outputs with LoRAs for custom styles.”

How to Install Ansible on a Mac: A Modern Approach
A guide to installing and managing Ansible on macOS using Conda, with tips for handling collections and dependencies.