Skip to content

JakebGutierrez/crates

Repository files navigation

crates

A private, offline playlist generator for music you already own.

Scans your local library, enriches each track with Last.fm metadata (listener counts, genre tags), stores everything in a local SQLite database, and generates .m3u playlists — ranked by popularity, filtered by genre and decade — that import directly into Swinsian on macOS.

No streaming service. No internet after the initial scan. No recommendation engine. Just your files.


What it does

Scan (run once, ~80 min for 25k tracks): walks your music folder, reads file tags with mutagen, and asks Last.fm two things per track — how popular it is and what genre tags people have applied. Stores everything locally. Incremental: only scans new files on subsequent runs.

Generate (instant, fully offline): queries the local DB to write .m3u playlists. Filter by genre tag, year range, and minimum listener count. Rank by Last.fm listener count.


Setup

git clone https://github.com/yourname/crates
cd crates
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp config.example.py config.py
# edit config.py — set your Last.fm API key, MUSIC_DIR, and paths

Get a free Last.fm API key at last.fm/api/account/create. Any app name works.


Usage

# One-time scan (test on a small subfolder first)
python crates.py scan

# Library stats and match rate
python crates.py stats

# See what tags Last.fm actually assigned to your library
python crates.py tags

# Generate all preset playlists at once
python crates.py presets

# Artist playlists
python crates.py artist "Radiohead"
python crates.py artist "Radiohead" --limit 30 --deep-cuts
python crates.py radio "Radiohead"

# Custom genre playlist
python crates.py genre "Late 90s Rap" --tags "hip-hop" "hip hop" "rap" --years 1995 1999

# Best of a decade, optionally filtered by genre
python crates.py decade 1990
python crates.py decade 2000 --tags "electronic"

# Remove DB entries for files that have been deleted or moved
python crates.py clean          # dry run — shows what would be removed
python crates.py clean --yes    # actually deletes

How it works

crates.py     CLI. Subcommands above. Holds the PRESETS dict.
scanner.py    File walker + tag reader (mutagen) + Last.fm enrichment. Writes to SQLite.
playlists.py  All query/generation logic. No network calls. Reads SQLite, writes M3U.
config.py     Your paths and API key. Gitignored — see config.example.py.

The scan is resume-safe: Ctrl+C anytime and re-run to continue. Failed tracks (network errors) are retried automatically. Permanent no-matches (track absent from Last.fm) are skipped on future runs.

Each track gets a status in the DB:

status meaning
matched found on Last.fm, has listener count and tags
no_match not found — permanent, won't retry
failed network error — retried on next scan
pending not yet processed

Presets

Edit the PRESETS dict in crates.py to define your own named playlists. Run python crates.py tags after your first scan to see which tags actually exist in your library — Last.fm's tag vocabulary is user-generated and varies.

PRESETS = {
    "My Playlist": {
        "tags":          ["tag1", "tag2"],   # OR logic — any one tag matches
        "year_range":    (1990, 1999),        # optional
        "min_listeners": 50_000,
        "limit":         50,
    },
}

Data quality notes

  • Match rate depends on how well-documented your artists are on Last.fm. Expect 95–100% for mainstream western music, lower for niche/local/non-English artists.
  • Tag coverage is sparse for many tracks even when matched — Last.fm only returns tags that users have applied. Listener counts are more reliable than tags.
  • Popularity ranking uses Last.fm global listener count. This reflects a track's global reach, not personal taste — use --deep-cuts to invert it.
  • The quality ceiling is the source data. If a track isn't tagged on Last.fm, no query reaches it.

Requirements

  • Python 3.12+
  • macOS (output paths and Swinsian import are macOS-native; M3U files are otherwise portable)
  • Last.fm API key (free)
  • mutagen, requests — see requirements.txt

About

Local music library playlist generator.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages