MCPcopy Index your code
hub / github.com/DevMatei/make-a-wrapped

github.com/DevMatei/make-a-wrapped @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
257 symbols 875 edges 28 files 10 documented · 4%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

🎧 Make a Wrapped

Spotify Wrapped-style recap generator for ListenBrainz, Last.fm, and Navidrome. Built with Flask.

[!IMPORTANT] Rebranded to Make a Wrapped (was ListenBrainz Wrapped). Site's back up — if something's broken, open a GitHub issue.

New 2025 templates are out!

Make a Wrapped — shareable stats for your scrobbles

🌐 website

wrapped.devmatei.com - built by devmatei.com

💡 why it's cool

  • pulls from ListenBrainz, Last.fm, Navidrome, MusicBrainz, Cover Art Archive, and Wikidata. all public, no tokens needed (except the optional Last.fm key you'd already have for artwork anyway)
  • artist art tries Last.fm first, falls back to MusicBrainz/Wikidata, and if all else fails there's a built-in editor to upload/zoom/position your own image (saved in local storage, or temporarily on the server for 1 hour)
  • rate limits are in so your server doesn't get nuked
  • there's a live counter of total wraps ever generated (idk it seemed cool)
  • officially listed on the ListenBrainz Enabled Applications page :D

wanna make me slightly richer? (i'm broke lol)

ko-fi

⚡ quickstart

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

copy .env.example to .env and fill in the basics:

FLASK_ENV=production
SECRET_KEY=<something>
LASTFM_API_KEY=<your-lastfm-key>
# set HTTP_PROXY / HTTPS_PROXY if you tunnel through a proxy

run it:

gunicorn -w 4 -b 0.0.0.0:8000 wrapped-fm:app

or locally:

./start.sh <host> <port>

or with Docker (change the port in the compose file first):

sudo docker compose up -d

⚙️ config

core

LISTENBRAINZ_API=https://api.listenbrainz.org/1 MUSICBRAINZ_API, COVER_ART_API LISTENBRAINZ_RANGE=this_year AVERAGE_TRACK_LENGTH_MINUTES, COVER_ART_LOOKUP_LIMIT

integrations

LASTFM_API_KEY - required for Last.fm stats + better artist images LASTFM_API, LASTFM_USER_AGENT

performance

HTTP_TIMEOUT, HTTP_POOL_MAXSIZE, LISTENBRAINZ_CACHE_TTL, LISTENBRAINZ_CACHE_SIZE APP_RATE_LIMIT, APP_STATS_RATE_LIMIT, APP_IMAGE_RATE_LIMIT, APP_MAX_TOP_RESULTS APP_IMAGE_CONCURRENCY, APP_IMAGE_QUEUE_LIMIT, APP_IMAGE_QUEUE_TIMEOUT TEMP_ARTWORK_TTL_SECONDS, TEMP_ARTWORK_MAX_BYTES WRAPPED_COUNT_FILE (defaults to data/wrapped-count.txt) WRAPPED_COUNT_SINCE - label for when you started counting wraps (ISO date string) APP_RATE_LIMIT_SALT, APP_TRUST_PROXY_HEADERS

why it exists

I self-host my music library on Navidrome and don't use Spotify, but all my friends post their Wrapped every year and everyone's like "wait what's that?" when you show them a ListenBrainz stats page. so I built this. same vibe, works with open music platforms.

go flex your scrobbles at wrapped.devmatei.com :)

about me

I'm Matei (DevMatei) - full-stack dev from Moldova. I build random web tools, run a homelab full of self-hosted stuff, and care way too much about my music library. Check out my other projects at devmatei.com or hit me up on socials if you wanna talk projects or self-hosting.

🤝 contributing

See CONTRIBUTING.yml for setup, style notes, and the PR checklist. TL;DR: keep PRs focused, run python -m py_compile wrapped-fm.py, and attach screenshots for any UI changes.

🧩 to-do

  • [x] security improvements
  • [x] faster rendering (capped around 33s by API speeds, not much I can do)
  • [x] modular, readable code (kind of)

originally made for Last.fm only by jeff parla <3

📜 license

AGPL-3.0 - share alike

not affiliated with Spotify, ListenBrainz, Last.fm, Navidrome, or MusicBrainz. just a fan thing built for fun.

Core symbols most depended-on inside this repo

Shape

Function 222
Route 16
Class 10
Method 9

Languages

TypeScript54%
Python46%

Modules by API surface

static/js/main.js67 symbols
wrapped_fm/routes.py34 symbols
wrapped_fm/images.py19 symbols
wrapped_fm/lastfm.py18 symbols
static/js/md5.js15 symbols
static/js/utils.js11 symbols
wrapped_fm/musicbrainz.py10 symbols
wrapped_fm/listenbrainz.py10 symbols
static/js/service-selector.js9 symbols
static/js/canvas-renderer.js8 symbols
wrapped_fm/turnstile.py7 symbols
static/js/navidrome/api.js7 symbols

For agents

$ claude mcp add make-a-wrapped \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page