Skip to content

Scanner binds unaligned MusicBrainz album-artist IDs to joined-display contributor, poisoning later albums #1555

@Rouzax

Description

@Rouzax

Describe the bug

When a track has a joined-display ALBUMARTIST (e.g. Martin Garrix & Alesso) together with a multi-value MUSICBRAINZ_ALBUMARTIST_ID containing one MBID per individual (per the MusicBrainz Picard tagging convention), the scanner silently binds the first MBID in the list to the single "joined" contributor and discards the rest. This permanently associates one individual's MBID with a contributor whose name is a different string.

On subsequent scans, any track whose ALBUMARTIST is just that one individual (e.g. Martin Garrix) gets matched to the pre-existing, mis-named contributor via the MBID lookup in Slim::Schema::Contributor::add (Slim/Schema/Contributor.pm:268), inherits the wrong name, and the expected standalone artist entry is never created.

Result: the individual artist does not appear in the artist list; their solo album is shown under a composite name they did not tag it with.

To Reproduce

  1. Tag two albums per the MusicBrainz Picard convention (the default output of Picard, beets, and any modern tagger that follows MB):

    Album A (collaboration, tagged by Picard / equivalent):

    ALBUMARTIST=Martin Garrix & Alesso
    ALBUMARTISTS=Martin Garrix
    ALBUMARTISTS=Alesso
    MUSICBRAINZ_ALBUMARTISTID=3e1f2ee4-16be-4406-bf18-6173840cf2b1
    MUSICBRAINZ_ALBUMARTISTID=f0ebe867-e784-4f02-b5fd-00b27a0b3f00
    

    (ALBUMARTIST is a single display string; ALBUMARTISTS and MUSICBRAINZ_ALBUMARTISTID are multi-value and positionally aligned, per Picard convention.)

    Album B (same first individual, solo):

    ALBUMARTIST=Martin Garrix
    ALBUMARTISTS=Martin Garrix
    MUSICBRAINZ_ALBUMARTISTID=3e1f2ee4-16be-4406-bf18-6173840cf2b1
    
  2. Scan Album A first, then Album B (or both together in the same rescan).

  3. Query LMS via JSON-RPC:

    {"method":"slim.request","params":["",["artists","0","50","search:Martin Garrix"]]}
    

Expected behavior

Two contributor rows: Martin Garrix (MBID 3e1f2ee4-...) and Martin Garrix & Alesso (no MBID, or a composite-credit MBID if one exists). Album B should display with album artist Martin Garrix. Album A should display with album artist Martin Garrix & Alesso.

Actual behavior

  • Only one contributor exists with name='Martin Garrix & Alesso' and musicbrainz_id='3e1f2ee4-...' (Martin Garrix's MBID, bound to the composite name).
  • Album B's tracks reuse this row for their ALBUMARTIST role and display as Martin Garrix & Alesso despite the file tags saying otherwise.
  • No contributor row with name='Martin Garrix' is ever created.

Root cause

Slim/Schema/Contributor.pm:241-326 splits the incoming ALBUMARTIST string via splitTag() (default split char ;) and the incoming MUSICBRAINZ_ALBUMARTIST_ID multi-value list independently, then zips them positionally:

my @artistList   = Slim::Music::Info::splitTag($artist);     # ["Martin Garrix & Alesso"]  (len 1)
my @brainzIDList = Slim::Music::Info::splitTag($brainzID);   # ["mbid_a","mbid_b"]         (len 2)

for (my $i = 0; $i < scalar @artistList; $i++) {
    my $name = $artistList[$i];
    my $mbid = $brainzIDList[$i];     # always index-0; index-1 silently dropped
    ...
    # Musicbrainz ID matched first (:267-286)
    if ($mbid) {
        SELECT id FROM contributors WHERE musicbrainz_id = ?
        ...
    }
    if (!$id) {
        INSERT INTO contributors (name, ..., musicbrainz_id)
        # name = "Martin Garrix & Alesso", mbid = Martin Garrix's MBID
    }
}

Two problems compound:

  1. Length mismatch silently swallowed. No check that scalar @artistList == scalar @brainzIDList. When the name list is shorter than the MBID list, trailing MBIDs are discarded and the first MBID is bound to a name that does not correspond to it.
  2. MBID-first match wins over name match (:267-286). On later scans, any track whose MBID matches the poisoned contributor resolves to that row regardless of the incoming ALBUMARTIST string. The stored name is not updated (:298-323 update namesort and extid but never name).

In our library, Red Rocks (a B2B set, scanned first) poisons the Martin Garrix MBID by binding it to Martin Garrix & Alesso. When AMF 2024 (Martin Garrix solo) is processed next, its Martin Garrix ALBUMARTIST never gets its own row.

Standards context

The multi-value tag layout above is the MusicBrainz Picard tagging convention, which is what modern taggers emit and what modern servers (Jellyfin, Plex, Navidrome, Roon, Kodi via TagLib, MusicBee, foobar2000) read:

Picard's own default writes ALBUMARTIST as a single joined display string and ALBUMARTISTS + MUSICBRAINZ_ALBUMARTISTID as positionally-aligned multi-value tags. The joined string uses the MusicBrainz artist-credit join phrase (&, feat., ,, etc.), not ;, so the default splitList of ; cannot recover the individual artists from ALBUMARTIST alone. Files tagged by any Picard-conformant tool will trigger this bug.

Proposed fixes

Three options, in order of increasing scope:

Fix 1: minimal safety net, reject unaligned MBID lists. In Slim/Schema/Contributor.pm::add after @brainzIDList is populated, drop the MBIDs when the lengths don't match. Better to lose a composite-album MBID binding than to bind the wrong one:

if ($brainzID) {
    @brainzIDList = Slim::Music::Info::splitTag($brainzID);
    if (@brainzIDList && @brainzIDList != @artistList) {
        main::INFOLOG && $log->info(
            "ALBUMARTIST name count (" . scalar(@artistList) .
            ") does not match MBID count (" . scalar(@brainzIDList) .
            "). Ignoring MBIDs for this track to avoid false binding."
        );
        @brainzIDList = ();
    }
}

Zero risk of cross-album poisoning. One-file, ~5-line change.

Fix 2: correct, prefer the multi-value ALBUMARTISTS tag when present. In the formats layer (Slim/Formats/*.pm) and Slim/Schema.pm::_mergeAndCreateContributors, if the track carries an ALBUMARTISTS tag (plural, multi-value), use it as the source of individual contributors and zip it with MUSICBRAINZ_ALBUMARTIST_ID. Keep ALBUMARTIST (singular) as the display string on the album record but do not use it to derive contributor rows when the plural form is available. Fall back to the current splitList-based behaviour when ALBUMARTISTS is absent. This matches Picard and what other modern servers do, and needs no user configuration.

Fix 3: defensive, don't let MBID-match override name mismatch. In Slim/Schema/Contributor.pm::add around :267-286, when an existing contributor is found by MBID but its stored name differs from the incoming name, prefer creating a new contributor over reusing the mismatched row. Independently valuable, can ship alongside Fix 1 or Fix 2.

Workaround for users

Set the Artist Tag Split preference (splitList) to include & (or whatever join phrase their tagger uses). This causes LMS to split Martin Garrix & Alesso into two names and zip with the two MBIDs correctly. Downside: names that legitimately contain & (Simon & Garfunkel, Earth, Wind & Fire, Hall & Oates) will also be split, which is usually undesirable for non-B2B libraries.

System Information

  • OS on which you're running LMS: Windows Server (running as a Windows service)
  • Hardware: x86_64
  • Web skin used: Default
  • Browser: Firefox
  • LMS Version: 9.1.0, build 1771315634 (2026-02-19), Perl 5.32.1
  • Player(s) involved: N/A (scanner bug, reproducible without any player)
  • Library on CIFS (\\hyperv\Data\...); files are Ogg/Opus tagged by a Picard-convention tagger

Additional context

Minimal repro attached as lyrion-albumartist-mbid-repro.zip (two 2-second silent Opus files with the tag layout described above, plus the make_repro.py script that generated them). Drop the two album folders into an LMS-scanned library root, rescan, and observe the poisoned contributor.

Scanner log snippet showing the two problematic tracks being read with aligned multi-value ALBUMARTIST MBIDs:

[26-04-15 09:28:06.9858] Slim::Formats::sanitizeTagValues . ALBUMARTIST : Martin Garrix & Alesso
[26-04-15 09:28:06.9909] Slim::Formats::sanitizeTagValues . MUSICBRAINZ_ALBUMARTIST_ID : ARRAY(0x51bd5a0)
...
[26-04-15 09:28:08.3632] Slim::Formats::sanitizeTagValues . ALBUMARTIST : Martin Garrix
[26-04-15 09:28:08.3703] Slim::Formats::sanitizeTagValues . MUSICBRAINZ_ALBUMARTIST_ID : ARRAY(0x7cc8790)

After the rescan:

$ curl -s http://lms.home.lan:9000/jsonrpc.js -d '{"method":"slim.request","params":["",["artists","0","5","search:Martin Garrix"]]}'
-> "artist":"Martin Garrix & Alesso","id":13269   # no standalone "Martin Garrix" row

Happy to test any patches against a live library with known-bad tag layouts.

lyrion-albumartist-mbid-repro.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions