• v2026.08.06 — your genres were never that messy; the scanner was breaking them
    test-go / test (push) Successful in 57s
    test-go / integration (push) Successful in 4m57s
    release / Build signed APK (tag releases only) (push) Successful in 4m23s
    release / Build + push container image (push) Successful in 1m39s
    Stable

    bvandeusen released this 2026-08-05 22:19:42 -04:00 | 0 commits to main since this release

    Per-day CalVer. Server-only, with schema migration 0054. Merged via PR #120; main at 7e4727fc, 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                                        1
    

    About 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 \0 Rock.

    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 — 17 means Rock, 40 means AlternRock — and those went unresolved before being welded together. 4017 was never a genre. It was AlternRock and Rock. 526617 was 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 ffprobe on 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 ffprobe per 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 — Rock and rock remain 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

    https://claude.ai/code/session_01N6vZoJ4Se5YyaqdtGVkap5

    Downloads