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
-
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
-
Scan Album A first, then Album B (or both together in the same rescan).
-
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:
- 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.
- 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
Describe the bug
When a track has a joined-display
ALBUMARTIST(e.g.Martin Garrix & Alesso) together with a multi-valueMUSICBRAINZ_ALBUMARTIST_IDcontaining 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
ALBUMARTISTis just that one individual (e.g.Martin Garrix) gets matched to the pre-existing, mis-named contributor via the MBID lookup inSlim::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
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):
(
ALBUMARTISTis a single display string;ALBUMARTISTSandMUSICBRAINZ_ALBUMARTISTIDare multi-value and positionally aligned, per Picard convention.)Album B (same first individual, solo):
Scan Album A first, then Album B (or both together in the same rescan).
Query LMS via JSON-RPC:
Expected behavior
Two contributor rows:
Martin Garrix(MBID3e1f2ee4-...) andMartin Garrix & Alesso(no MBID, or a composite-credit MBID if one exists). Album B should display with album artistMartin Garrix. Album A should display with album artistMartin Garrix & Alesso.Actual behavior
name='Martin Garrix & Alesso'andmusicbrainz_id='3e1f2ee4-...'(Martin Garrix's MBID, bound to the composite name).ALBUMARTISTrole and display asMartin Garrix & Alessodespite the file tags saying otherwise.name='Martin Garrix'is ever created.Root cause
Slim/Schema/Contributor.pm:241-326splits the incomingALBUMARTISTstring viasplitTag()(default split char;) and the incomingMUSICBRAINZ_ALBUMARTIST_IDmulti-value list independently, then zips them positionally:Two problems compound:
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.:267-286). On later scans, any track whose MBID matches the poisoned contributor resolves to that row regardless of the incomingALBUMARTISTstring. The storednameis not updated (:298-323updatenamesortandextidbut nevername).In our library, Red Rocks (a B2B set, scanned first) poisons the
Martin GarrixMBID by binding it toMartin Garrix & Alesso. When AMF 2024 (Martin Garrix solo) is processed next, itsMartin GarrixALBUMARTIST 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:
ALBUMARTISTvsALBUMARTISTS): https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.htmlPicard's own default writes
ALBUMARTISTas a single joined display string andALBUMARTISTS+MUSICBRAINZ_ALBUMARTISTIDas positionally-aligned multi-value tags. The joined string uses the MusicBrainz artist-credit join phrase (&,feat.,,, etc.), not;, so the defaultsplitListof;cannot recover the individual artists fromALBUMARTISTalone. 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::addafter@brainzIDListis populated, drop the MBIDs when the lengths don't match. Better to lose a composite-album MBID binding than to bind the wrong one:Zero risk of cross-album poisoning. One-file, ~5-line change.
Fix 2: correct, prefer the multi-value
ALBUMARTISTStag when present. In the formats layer (Slim/Formats/*.pm) andSlim/Schema.pm::_mergeAndCreateContributors, if the track carries anALBUMARTISTStag (plural, multi-value), use it as the source of individual contributors and zip it withMUSICBRAINZ_ALBUMARTIST_ID. KeepALBUMARTIST(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 currentsplitList-based behaviour whenALBUMARTISTSis 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::addaround:267-286, when an existing contributor is found by MBID but its storednamediffers from the incomingname, 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 splitMartin Garrix & Alessointo 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
\\hyperv\Data\...); files are Ogg/Opus tagged by a Picard-convention taggerAdditional context
Minimal repro attached as
lyrion-albumartist-mbid-repro.zip(two 2-second silent Opus files with the tag layout described above, plus themake_repro.pyscript 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:
After the rescan:
Happy to test any patches against a live library with known-bad tag layouts.
lyrion-albumartist-mbid-repro.zip