{"id":49766242,"url":"https://github.com/little-big-data/mp3-enricher","last_synced_at":"2026-05-11T10:51:08.652Z","repository":{"id":354071609,"uuid":"1220340939","full_name":"little-big-data/mp3-enricher","owner":"little-big-data","description":"Enrich ID3 tags for your MP3 library","archived":false,"fork":false,"pushed_at":"2026-04-24T19:55:42.000Z","size":160,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T03:07:24.053Z","etag":null,"topics":["discogs","discogs-api","id3","mp3","mp3-tagger","mp3-tags","musicbrainz","musicbrainz-api","self-hosted"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/little-big-data.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T19:52:46.000Z","updated_at":"2026-04-26T20:41:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/little-big-data/mp3-enricher","commit_stats":null,"previous_names":["little-big-data/mp3-enricher"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/little-big-data/mp3-enricher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/little-big-data%2Fmp3-enricher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/little-big-data%2Fmp3-enricher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/little-big-data%2Fmp3-enricher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/little-big-data%2Fmp3-enricher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/little-big-data","download_url":"https://codeload.github.com/little-big-data/mp3-enricher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/little-big-data%2Fmp3-enricher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32891965,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-10T13:40:02.631Z","status":"online","status_checked_at":"2026-05-11T02:00:05.975Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["discogs","discogs-api","id3","mp3","mp3-tagger","mp3-tags","musicbrainz","musicbrainz-api","self-hosted"],"created_at":"2026-05-11T10:51:07.762Z","updated_at":"2026-05-11T10:51:08.646Z","avatar_url":"https://github.com/little-big-data.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mp3-enricher\n\n[![CI](https://github.com/jschloman/mp3-enricher/actions/workflows/ci.yml/badge.svg)](https://github.com/jschloman/mp3-enricher/actions/workflows/ci.yml)\n\nA parallelised, idempotent CLI pipeline that enriches ID3 tags on MP3 libraries using Discogs, Wikipedia, and MusicBrainz.\n\nThe tool is **non-destructive** — it never moves, renames, or deletes your files. It only writes metadata into the MP3 ID3 tags.\n\n---\n\n## Quick Start\n\n```bash\ngit clone https://github.com/jschloman/mp3-enricher.git \u0026\u0026 cd mp3-enricher\npython -m venv .venv \u0026\u0026 source .venv/bin/activate   # Windows: .venv\\Scripts\\activate\npip install -e \".[dev]\"\ncp .env.example .env                                 # add your DISCOGS_TOKEN\npython tagger/mp3_tagger.py enrich \"/path/to/music\" --db-path library.db\npython tagger/mp3_tagger.py write  --db-path library.db\n```\n\nSee [Step-by-step usage](#step-by-step-usage) for a full walkthrough including manual review.\n\n---\n\n## Table of Contents\n\n- [How it works](#how-it-works)\n- [What gets written](#what-gets-written)\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [Step-by-step usage](#step-by-step-usage)\n  - [Step 1 — Enrich a folder](#step-1--enrich-a-folder)\n  - [Step 2 — Write tags to disk](#step-2--write-tags-to-disk)\n  - [Step 3 — Handle albums that weren't matched](#step-3--handle-albums-that-werent-matched)\n- [Command reference](#command-reference)\n- [Tips and troubleshooting](#tips-and-troubleshooting)\n\n---\n\n## How it works\n\nEach album folder is processed in three stages:\n\n```\nenrich command\n  1. Scan         — finds all .mp3 files, reads existing tags and folder name\n  2. Discogs      — searches for the album, selects the oldest original release\n  3. Wikipedia    — fetches the artist's biography for context\n  4. MusicBrainz  — looks up collective/band memberships\n  5. Heuristics   — infers origin, gender, genre, label, etc. from all sources\n  6. DB            — saves everything to a local SQLite database\n\nwrite command\n  7. Write        — reads the database and applies tags to the .mp3 files\n```\n\nState is stored in a local SQLite database (`tester.db` by default). Every step is idempotent — re-running a command skips work that is already done.\n\n---\n\n## What gets written\n\nThe following ID3 tags are written to each MP3 file:\n\n| Tag | Frame | Example |\n|---|---|---|\n| Title | `TIT2` | `Head Like A Hole` |\n| Artist | `TPE1` | `Nine Inch Nails` |\n| Album Artist | `TPE2` | `Nine Inch Nails` |\n| Album | `TALB` | `Pretty Hate Machine` |\n| Genre | `TCON` | `Industrial` |\n| Track number | `TRCK` | `01/10` |\n| Year | `TDRC` / `TYER` | `1989` |\n| Grouping | `TIT1` | `Origin:Cleveland, US \\| Gender:Male \\| Label:TVT Records` |\n\nThe **Grouping** tag (`TIT1`) is a structured metadata string that carries everything the heuristic enricher inferred, in a pipe-delimited format:\n\n```\nOrigin:Cleveland, US | Gender:Male | Race:White | Subgenre:Industrial Rock | Label:TVT Records | link:Nine Inch Nails | Holiday:Halloween\n```\n\nThe `link:` segment lists artist collectives or band affiliations found via MusicBrainz. Multiple links are comma-separated (`link:Wu-Tang Clan, Gravediggaz`). The segment is omitted when no links are found.\n\n---\n\n## Installation\n\n```bash\ngit clone https://github.com/jschloman/mp3-enricher\ncd mp3-enricher\npython -m venv venv\nsource venv/bin/activate        # Windows: venv\\Scripts\\activate\npip install -e \".[dev]\"\n```\n\n---\n\n## Configuration\n\nCreate a `.env` file in the project root:\n\n```bash\ncp .env.example .env\n```\n\nOpen `.env` and fill in your Discogs token (the only required value):\n\n```ini\nDISCOGS_TOKEN=your_personal_access_token_here\n```\n\nYou can get a free Discogs Personal Access Token from:\n**Settings → Developers → Generate new token** at [discogs.com](https://www.discogs.com/settings/developers)\n\nOptional settings:\n\n```ini\nDISCOGS_FUZZY_THRESHOLD=85   # Minimum match score (0–100). Lower = more permissive.\nID3_VERSION=2.3              # 2.3 (default, widest compatibility) or 2.4\nWORKERS=4                    # Thread count for parallel processing\n```\n\n---\n\n## Step-by-step usage\n\n### Step 1 — Enrich a folder\n\nThe `enrich` command does everything except writing to the MP3 files themselves. It scans the folder, looks up the album on Discogs, scrapes Wikipedia, and saves all enriched metadata to the local database.\n\n```bash\npython -m tagger.mp3_tagger enrich \"D:\\Music\\Nine Inch Nails - Pretty Hate Machine\"\n```\n\n**The folder name matters.** The tool parses it to guess the artist and album. The expected format is:\n\n```\nArtist - Album\n```\n\nFor example:\n- `Nine Inch Nails - Pretty Hate Machine` → artist: *Nine Inch Nails*, album: *Pretty Hate Machine*\n- `The Cure - Disintegration` → artist: *The Cure*, album: *Disintegration*\n\nIf the folder name doesn't follow this format the tool will still try, but the Discogs match will be less reliable. You can always fix a failed match via the manual review workflow (see Step 3).\n\n**What you'll see:**\n\n```\n[*] Initializing test database at tester.db...\n[*] Scanning folder: D:\\Music\\Nine Inch Nails - Pretty Hate Machine...\n[+] Found 10 MP3 files.\n[+] Guessed Artist: Nine Inch Nails\n[+] Guessed Album:  Pretty Hate Machine\n[+] Folder and tracks inserted into database.\n[*] Starting Discogs enrichment...\n[+] Best Match Found: Nine Inch Nails - Pretty Hate Machine (ID: 75544, Year: 1989)\n[+] Master ID: 207276\n[*] Fetching full release details...\n[+] Release Title: Pretty Hate Machine\n[+] Artists: Nine Inch Nails\n[+] Tracks found in Discogs: 10\n[*] Fetching Wikipedia context...\n[+] Fetched 843 characters of Wikipedia context.\n[*] Applying heuristic enrichment...\n[+] Fetched Discogs artist profile for: Nine Inch Nails\n[*] Querying MusicBrainz for collective memberships...\n[!] No MusicBrainz collectives found.\n[+] Guessed Gender: Male\n[+] Guessed Origin: Cleveland, Ohio, US\n[+] Guessed Genre:  Industrial\n[+] Guessed Subgenres: Industrial Rock, Synth-pop\n[+] Canonical Album Artist: Nine Inch Nails\n[+] GRP1 Sample: Origin:Cleveland, Ohio, US | Gender:Male | Subgenre:Industrial Rock, Synth-pop | Label:TVT Records\n[*] Downloading album art from: https://img.discogs.com/...\n[+] Album art saved to: tagger_workdir/art/75544.jpg\n\n[SUCCESS] Enrichment test completed.\n```\n\nIf Discogs **cannot find a match**, the album is added to the manual review queue instead and you'll see:\n\n```\n[-] No suitable Discogs release found — added to manual review.\n```\n\nSee [Step 3](#step-3--handle-albums-that-werent-matched) for how to handle this.\n\n**Options:**\n\n```\n--db-path PATH    Path to the SQLite database. Default: tester.db\n--token TEXT      Discogs token (overrides DISCOGS_TOKEN in .env)\n```\n\n---\n\n### Step 2 — Write tags to disk\n\nOnce enrichment is done, run `write` to apply the saved metadata to your MP3 files:\n\n```bash\npython -m tagger.mp3_tagger write\n```\n\nThis reads all tracks in the database with `enrichment_status=found` and `written_status=pending` and writes their tags to disk. It skips anything already written, so it's safe to run repeatedly.\n\n**What you'll see:**\n\n```\n[+] Written: 10 | Errors: 0\n```\n\n**Preview before committing** — use `--dry-run` to see what would happen without touching any files:\n\n```bash\npython -m tagger.mp3_tagger write --dry-run\n```\n\n```\n[*] Dry-run mode — no files will be modified.\n[+] Written: 10 | Errors: 0\n```\n\n**Re-write already-written tracks** — if you've updated the enrichment data and need to re-apply:\n\n```bash\npython -m tagger.mp3_tagger write --force\n```\n\n**Speed up large libraries** — process tracks in parallel:\n\n```bash\npython -m tagger.mp3_tagger write --workers 4\n```\n\n**Limit to one folder** — write only a single artist or album directory:\n\n```bash\npython -m tagger.mp3_tagger write --folder \"/path/to/music/Nine Inch Nails\"\npython -m tagger.mp3_tagger write --folder \"/path/to/music/Nine Inch Nails/Pretty Hate Machine\"\n```\n\n**Options:**\n\n```\n--db-path PATH            SQLite database to read from. Default: tester.db\n--dry-run                 Preview writes without modifying any files\n--force                   Re-write tracks already marked as done\n--workers INTEGER         Number of parallel write threads. Default: 1\n--id3-version [2.3|2.4]   ID3 version to write. Default: 2.3\n--folder PATH             Limit writes to tracks whose file path starts with PATH\n```\n\n\u003e **ID3 version note:** `2.3` is the default and has the widest software compatibility (iTunes, Windows Explorer, most car stereos). Use `2.4` if your player specifically requires it — it stores the year in `TDRC` instead of `TYER`.\n\n---\n\n### Step 3 — Handle albums that weren't matched\n\nWhen Discogs can't automatically find an album — usually because the folder name is ambiguous, the release is rare, or the artist name is spelled differently — the album is flagged for manual review.\n\n#### 3a. Export the review list to CSV\n\n```bash\npython -m tagger.mp3_tagger export-manual\n```\n\nThis creates `manual_review.csv` with one row per unmatched album:\n\n```csv\nalbum_id,folder_path,artist_guess,album_guess,reason,user_discogs_url\n12,D:\\Music\\NIN - PHM,NIN,PHM,No Discogs match,\n47,D:\\Music\\Unknown Artist - Untitled,Unknown Artist,Untitled,No Discogs match,\n```\n\n**Options:**\n\n```\n--db-path PATH       Database to read from. Default: tester.db\n--csv-path PATH      Output file. Default: manual_review.csv\n```\n\n#### 3b. Fill in the Discogs URLs\n\nOpen `manual_review.csv` in Excel, Numbers, or any text editor.\n\nFor each album you can identify, find it on [discogs.com](https://www.discogs.com) and paste the Discogs URL into the `user_discogs_url` column. Leave rows blank that you can't identify.\n\n```csv\nalbum_id,folder_path,artist_guess,album_guess,reason,user_discogs_url\n12,D:\\Music\\NIN - PHM,NIN,PHM,No Discogs match,https://www.discogs.com/Nine-Inch-Nails-Pretty-Hate-Machine/release/75544\n13,D:\\Music\\Portishead\\Dummy,Portishead,Dummy,No Discogs version with 11 tracks,https://www.discogs.com/master/22702-Portishead-Dummy\n47,D:\\Music\\Unknown Artist - Untitled,Unknown Artist,Untitled,No Discogs match,\n```\n\nBoth **release URLs** and **master URLs** are accepted:\n\n| URL format | Example | Behaviour |\n|---|---|---|\n| `/release/N` | `https://www.discogs.com/release/75544` | Uses that exact release |\n| `/Artist-Album/release/N` | `.../Nine-Inch-Nails.../release/75544` | Uses that exact release |\n| `/master/N` | `https://www.discogs.com/master/22702` | Fetches all versions of the master and uses the oldest audio release |\n| `/master/N-Slug` | `.../master/22702-Portishead-Dummy` | Same as above — the slug after the ID is ignored |\n\nWhen you paste a master URL the tool automatically resolves it to the oldest original audio release (skipping video formats such as DVD and VHS), so you don't need to hunt for a specific pressing.\n\n#### 3c. Process the corrections\n\n```bash\npython -m tagger.mp3_tagger process-manual\n```\n\nThis reads every row with a `user_discogs_url`, fetches that release from Discogs, runs the full heuristic enrichment, and saves the results to the database — identical to what `enrich` does automatically.\n\n```\n[+] Processing 1 correction(s)...\n  [*] album_id=12 (NIN — PHM)\n  [+] Done — Pretty Hate Machine | GRP1: Origin:Cleveland, US | Gender:Male | Label:TVT Records\n\n[SUCCESS] Manual processing complete.\n```\n\nAfter this, run `write` to apply the newly enriched tags to disk:\n\n```bash\npython -m tagger.mp3_tagger write\n```\n\n**Options:**\n\n```\n--db-path PATH    Database to read from. Default: tester.db\n--csv-path PATH   CSV file to process. Default: manual_review.csv\n--token TEXT      Discogs token (overrides DISCOGS_TOKEN in .env)\n```\n\n---\n\n## Command reference\n\n### Core pipeline\n\n| Command | What it does |\n|---|---|\n| `enrich \u003cfolder\u003e` | Scan a folder, look up Discogs/Wikipedia/MusicBrainz, save results to DB |\n| `write` | Apply enriched tags from DB to MP3 files on disk |\n| `export-manual` | Export unmatched albums to `manual_review.csv` for human review |\n| `process-manual` | Re-run enrichment using Discogs URLs you filled in to the CSV |\n| `fix \u003cfolder\u003e` | Re-enrich already-matched albums (e.g. after a bug fix) and reset written status |\n| `fix-track-numbers \u003clibrary\u003e` | Repair TRCK/TPOS tags from filename prefixes (`01 - Song.mp3`) |\n\n### Operational utilities\n\n| Command | What it does |\n|---|---|\n| `scan-integrity \u003clibrary\u003e` | Scan for tag/folder mismatches; write CSV + DB |\n| `retry-not-found` | Re-run enrichment on all `not_found` albums in DB |\n| `prefill-master-urls --csv \u003cfile\u003e` | Pre-fill Discogs URLs in a manual-review CSV |\n| `enrich-missing --csv \u003cfile\u003e` | Scan artist dirs missing from DB (run before `process-manual`) |\n\nAll commands share `--db-path` to point at a specific database file. The default is `tester.db` in the current directory.\n\n#### `scan-integrity` — detect tag problems before they cause bad enrichment\n\n```bash\npython tagger/mp3_tagger.py scan-integrity \"/path/to/your/music\" \\\n    --db-path library.db \\\n    --out _tag_issues.csv \\\n    --threshold 75\n```\n\nWalks the library tree and flags:\n- **AlbumArtist** (`TPE2`) or **Album** (`TALB`) tag that doesn't match the folder name (fuzzy threshold, default 75)\n- **Inconsistent** tags across tracks in the same album folder\n- **All-untitled** albums (≥80% of tracks have blank/generic titles like \"Track 1\")\n- **Compilation artist mismatch** — real AlbumArtist but TPE1=\"Various Artists\" in a catch-all folder\n\nFindings are written to `_tag_issues.csv` **and** to the `tag_issues` table in the database. Re-running is idempotent — duplicates are silently ignored.\n\n```\nOptions:\n  --db-path PATH       Database (default: tester.db)\n  --out PATH           Output CSV (default: \u003clibrary\u003e/_tag_issues.csv)\n  --threshold INT      Fuzzy-match threshold 0–100 (default: 75)\n  --no-db              Write CSV only — skip the database write\n```\n\n#### `retry-not-found` — retry automatic enrichment on failed albums\n\n```bash\npython tagger/mp3_tagger.py retry-not-found --db-path library.db\n```\n\nQueries the database for albums with `enrichment_status=not_found`, groups them by artist directory, and re-runs the full enrichment pipeline directly (no subprocess). Reports `Newly found: N | Still not_found: M` at the end.\n\n```\nOptions:\n  --db-path PATH       Database (default: tester.db)\n  --token TEXT         Discogs token (overrides .env)\n  --starts-with TEXT   Only retry albums whose artist folder begins with this prefix\n  --dry-run            Show what would be retried without running enrichment\n```\n\n#### `prefill-master-urls` — reduce manual-review workload before human review\n\n```bash\npython tagger/mp3_tagger.py prefill-master-urls \\\n    --csv manual_review.csv \\\n    --db-path library.db\n```\n\nRuns two search passes on your manual-review CSV and fills `user_discogs_url` automatically where confident:\n\n- **Pass 1** — rows where reason = \"No Discogs version with N tracks\": re-searches Discogs and writes the master URL\n- **Pass 2** — rows where reason = \"No Discogs match\": checks whether the per-track Artist tag differs from the folder artist; if so, tries a search with the track artist (catches artist aliases and side projects)\n\nThe CSV is updated in place. Rows that already have a URL are untouched.\n\n```\nOptions:\n  --csv PATH           Manual review CSV to pre-fill (required)\n  --db-path PATH       Database (default: tester.db)\n  --token TEXT         Discogs token (overrides .env)\n```\n\n#### `enrich-missing` — populate DB for albums already identified by a reviewer\n\n```bash\npython tagger/mp3_tagger.py enrich-missing \\\n    --csv reviewed_manual.csv \\\n    --db-path library.db\n```\n\nUse this when `process-manual` reports \"folder_path not in DB\" for albums that clearly exist on disk. It reads the CSV, finds `folder_path` entries not yet in the database, and scans the parent artist directories to add them. Then run `process-manual` as normal to apply the reviewer-supplied Discogs URLs.\n\n```\nOptions:\n  --csv PATH           Reviewed manual review CSV (required)\n  --db-path PATH       Database (default: tester.db)\n  --token TEXT         Discogs token (overrides .env)\n```\n\n---\n\n## Tips and troubleshooting\n\n**The wrong Discogs release was selected**\n\nThe tool picks the oldest release across all master versions using fuzzy matching. If it picks the wrong one, use the manual review workflow to supply the exact Discogs URL for the correct release.\n\n**A track wasn't updated because its position didn't match**\n\nTrack matching uses the Discogs `position` field (e.g. `\"1\"`, `\"2\"`) against the `track_number` read from the existing ID3 `TRCK` tag. If your files don't have track number tags, or the positions are in a different format (e.g. `\"A1\"` for vinyl sides), the track won't be enriched. Add a `TRCK` tag manually with a tool like Mp3tag and re-run.\n\n**Discogs rate limiting**\n\nDiscogs allows 60 requests per minute for authenticated users. The tool doesn't enforce rate limiting by default in the tester CLI. If you're running large batches and hit 429 errors, wait a minute and re-run — the idempotent design means already-enriched albums are skipped.\n\n**Re-enriching an already-processed album**\n\nThe `enrich` command uses an upsert — re-running it on a folder that's already in the database will overwrite the existing record and re-run the full lookup. This is safe.\n\n**Checking what's in the database**\n\nYou can inspect `tester.db` directly with any SQLite viewer (e.g. [DB Browser for SQLite](https://sqlitebrowser.org/)):\n\n```sql\n-- See all albums and their status\nSELECT folder_path, artist_guess, album_guess, enrichment_status FROM albums;\n\n-- See tracks pending a write\nSELECT file_path, title, artist, genre, grouping\nFROM tracks\nWHERE enrichment_status = 'found' AND written_status = 'pending';\n\n-- See what's in the manual review queue\nSELECT * FROM manual_reviews WHERE status = 'pending';\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flittle-big-data%2Fmp3-enricher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flittle-big-data%2Fmp3-enricher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flittle-big-data%2Fmp3-enricher/lists"}