self-hosted · one listener · est. 2012 in the data

A private pressing of everything you play.

An acetate is the one-off reference disc a mastering engineer cuts before an album goes to press. Acetate does the same for a listening life: every play since 2012, archived on hardware I own, analyzed by neural networks that have listened to the entire library, and turned back into playlists, maps, and answers.

155,831
real plays archived
14
years of history
24,163
tracks the models listened to
0
cloud services involved

What it is

archive

Append-only backups

Playlists and liked songs are content-hashed and snapshotted on change. Deletions and quietly un-liked songs leave a trace forever.

history

Every play, kept

The GDPR export reaches back to 2012; a 15-minute poller keeps it current with no gaps. A canonicalization pass collapses duplicate track IDs so your favorites don't split their counts.

stats

A private Wrapped

Pick a year, quarter, month or week and see top tracks, artists, genres, discovery rate, and listening clock. In July, without asking anyone.

robots

Playlists that build themselves

Monthly liked archives, top-played rotations, lost gems. Proposed and maintained by robots that can never touch a playlist a human made.

ears

Models that listened

Every track is embedded from its audio in two spaces: a genre-shaped space (discogs-effnet) and a description-shaped one (CLAP). Genre stops being metadata and becomes a property of the sound.

map

Hyperspace

UMAP projects the embeddings into a 3D point cloud; HDBSCAN finds the clusters. Taste has geography.

Why it exists

In late 2024, Spotify turned off the APIs that made this hobby possible for new apps. I started computing the missing analysis from my listening history, open data, and the audio itself.

So acetate does. It runs on a small box at home. Spotify can't delete it, redesign it, or decide the feature I use isn't worth maintaining.

Inside

The acetate home page: 14 years of listening in numbers, listening hours by year
the front page: live status bar, hero stats, plays by year
The Wrapped page: a year in review, computed locally
Wrapped for any period, computed locally
Hyperspace: the library as a 3D point cloud, colored by cluster
Hyperspace, colored by clusters the algorithm found on its own

The architecture

Everything sits on one SQLite file. At personal scale, a single WAL-mode file outruns any database you'd deploy, backs up with cp, and will still open in thirty years.

Sources

Spotify APIrecently-played poll · 15 min GDPR exportevery play since 2012 Deezer30s preview audio Last.fmtags · co-listening MusicBrainzrecording MBIDs

one sqlite file

play_eventsthe listening log *_snapshotsappend-only backups recording_statscanonical per-song rollup embeddingsfloat32 blobs, 1280-d recording_stylesdiscogs400 top-8

Consumers

dashboardstats · wrapped · gems playlist robotsmonthly · gems · smart chat agentSQL + similarity tools Hyperspacethe 3D map

The map

Hyperspace colored by dominant Discogs style
Fourteen years of taste, in three dimensions.
a curated cut of the map is live on this site. fly it yourself →

Run it yourself

acetate is a uv-managed Python app: FastAPI + HTMX dashboard, APScheduler jobs, one SQLite file, and Docker Compose on whatever box you own. The app works with any library; mine is the dataset used during development.

The open-source repo is coming soon. The code was built in the open with my own listening data wired all the way through. Separating the instrument from the specimen deserves more than a Friday-night force-push. Until then, the journey post is the documentation: the architecture, the model choices, and the graveyard of things I tried and dropped.

The full story

How a playlist-backup script became a lab notebook: the genre saga (four acts, one heartbreak), teaching machines to hear and to read, the dependency-hell trophies, and the graveyard of roads not taken. It includes actual spectrograms, cosine matrices, and numbers from the database.

How I made acetate →