-
released this
2026-08-05 22:19:42 -04:00 | 0 commits to main since this releasePer-day CalVer. Server-only, with schema migration 0054. Merged via PR #120;
mainat7e4727fc, CI green across the Go lane (incl. the real-migration integration suite) and the image build.One fix. It started as a cosmetic complaint about the genre list and turned out to be corrupting recommendations.
What you saw
Yesterday's release added Library → Genres and asked you to look at the raw spread, because the plan was to design a category taxonomy around whatever your library actually contained. You looked. It contained things like this:
Alternative RockRock 420 tracks Boom BapDowntempoHip HopInstrumentalLo-FiLo-Fi Hip Hop… 275 Indie RockRock 235 ElectronicElectronic RockTrap 103 4017 2 17Indie Rock40Britpop 1About two-thirds of the ~700 entries looked like that. The reasonable conclusion was that your tags are a mess and need cleaning up.
They aren't. That was us.
What was actually happening
ID3v2 stores multiple values in one tag field separated by a null byte — a file tagged both Alternative Rock and Rock holds
Alternative Rock\0Rock.The tag-reading library Minstrel used split on that null and rejoined the pieces with nothing between them. Two genres became one nonsense genre. Eight genres became one 70-character nonsense genre.
The bare numbers are the same bug wearing a different hat. ID3 lets a genre be a numeric code —
17means Rock,40means AlternRock — and those went unresolved before being welded together.4017was never a genre. It was AlternRock and Rock.526617was Electronic, New Wave, Rock.Why this mattered more than the list looking bad
Genre isn't just a browse axis. It feeds your taste profile, and through that, recommendations.
- Your taste profile was learning
"Alternative RockRock"as a tag it had never seen anywhere else — a dead-end vocabulary entry instead of two real genres shared with hundreds of other tracks. - The recommendation and Discover queries split genre on
;and,before comparing tracks. There was nothing to split, so they compared welded tokens that matched almost nothing. - Every count was wrong. Rock reported 3413 tracks while silently excluding every track whose Rock tag had been welded to something else.
So the effect was not that a screen looked untidy. Genre similarity — one of the inputs deciding what turns up in your mixes — has been running on a vocabulary of pseudo-tags this whole time.
The fix, and the road not taken
Minstrel already runs
ffprobeon every file for duration, so the obvious move was to ask ffmpeg for the genre too — no new dependency, one line.That was checked rather than assumed, and it was the wrong answer. ffmpeg reads the first value out of a multi-value field and silently throws the rest away. It would have replaced a visible bug with an invisible one: no more nonsense labels, but Alternative Rock / Rock quietly becomes just Alternative Rock, and multi-genre tags are precisely the signal that makes genre similarity useful.
So Minstrel now reads the genre field itself — all three ID3v2 generations, all four text encodings, and the awkward corners of the format. Everything else about your files is still read by the same library as before; only genre changed hands.
Numeric codes are resolved properly now too, including the older parenthesised style.
Your library repairs itself
This is the part worth knowing, because there's a button you might go looking for and won't find.
Minstrel skips files that haven't changed since the last scan — that's what makes scans fast. But re-tagging a file doesn't change the file, so a normal scan would have skipped every already-indexed track and repaired nothing. There was no "force full rescan" anywhere either.
Rather than add one, tracks now record which version of the tag-reading logic last touched them. Everything currently in your library is marked as stale, so the next ordinary scan re-reads it — automatically, once, no action from you. Future tag fixes can reuse the same mechanism.
That repair pass also reuses the duration it already knows instead of re-running
ffprobeper file, since the audio hasn't moved. On a large library that's the difference between minutes and hours.Your taste profile rebuilds on its own schedule and replaces its tag list wholesale, so the polluted entries clear themselves shortly after the scan.
Install
Redeploy the server image. That's the whole install — this release carries migration 0054, which applies automatically on restart. No new APK; the Android app is unchanged.
Then let a scan run, or trigger one from Admin → Library.
What to look at afterwards
- Library → Genres. The welded entries and the bare numbers should be gone. Expect the list to get shorter and the counts to get bigger — Rock in particular should jump well past 3413. That's the fix working, not a new bug.
- Multi-genre albums. An album tagged with several genres should now be reachable from each of them, and its detail page should show several chips instead of one strange one.
- Give recommendations a couple of weeks. Genre similarity is now comparing real tags. Whether that's noticeable is an honest open question — genre is one input among several — but it's the first time it's been fed correct data.
Known and deliberate
Genres are still exposed exactly as your files spell them. No case folding, no synonym mapping —
Rockandrockremain separate rows. That's unchanged from yesterday and still deliberate.The category-taxonomy idea that started all this is on hold on purpose, because it can't be designed against corrupted data. What's underneath the corruption turns out to be a clean, consistent, MusicBrainz-style vocabulary of roughly 250 labels — granular, but tidy. That's a different and much smaller problem than "clean up a mess," and it may not need a taxonomy at all so much as better sorting and search. Worth re-deciding once you've seen the repaired list.
Only MP3s were affected. This particular flaw is specific to how ID3v2 tags were read, so FLAC, OGG and M4A files were never welded. Whether those formats lose secondary genres for a different reason is filed, unproven, and deliberately not built on speculation.
🤖 Generated with Claude Code
Downloads
- Your taste profile was learning