When an artist is referred to using several names, pick first the one set in the artist info file, and fallback on the most recent release, ref #731
This commit is contained in:
@@ -39,55 +39,25 @@ namespace lms::scanner::helpers
|
||||
|
||||
return artist;
|
||||
}
|
||||
|
||||
std::string optionalMBIDAsString(const std::optional<core::UUID>& uuid)
|
||||
{
|
||||
return uuid ? std::string{ uuid->getAsString() } : "<no MBID>";
|
||||
}
|
||||
|
||||
void updateArtistIfNeeded(db::Artist::pointer artist, const Artist& artistInfo)
|
||||
{
|
||||
// MBID may be set
|
||||
if (artist->getMBID() != artistInfo.mbid)
|
||||
artist.modify()->setMBID(artistInfo.mbid);
|
||||
|
||||
// Name may have been updated
|
||||
if (artist->getName() != artistInfo.name)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Artist [" << optionalMBIDAsString(artist->getMBID()) << "], updated name from '" << artist->getName() << "' to '" << artistInfo.name << "'");
|
||||
artist.modify()->setName(artistInfo.name);
|
||||
}
|
||||
|
||||
// Sortname may have been updated
|
||||
// As the sort name is quite often not filled in, we update it only if already set (for now?)
|
||||
if (artistInfo.sortName && *artistInfo.sortName != artist->getSortName())
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Artist [" << optionalMBIDAsString(artist->getMBID()) << "], updated sort name from '" << artist->getSortName() << "' to '" << *artistInfo.sortName << "'");
|
||||
artist.modify()->setSortName(*artistInfo.sortName);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
db::Artist::pointer getOrCreateArtistByMBID(db::Session& session, const Artist& artistInfo, AllowFallbackOnMBIDEntry allowFallbackOnMBIDEntries)
|
||||
{
|
||||
assert(artistInfo.mbid.has_value());
|
||||
|
||||
db::Artist::pointer artist{ db::Artist::find(session, *artistInfo.mbid) };
|
||||
if (artist)
|
||||
{
|
||||
updateArtistIfNeeded(artist, artistInfo);
|
||||
}
|
||||
else
|
||||
if (!artist)
|
||||
{
|
||||
if (allowFallbackOnMBIDEntries.value())
|
||||
{
|
||||
// an artist with the same name may already exist, let's recycle it
|
||||
for (const db::Artist::pointer& artistWithSameName : db::Artist::find(session, artistInfo.name))
|
||||
{
|
||||
assert(artistWithSameName->getMBID() != artistInfo.mbid);
|
||||
if (!artistWithSameName->hasMBID())
|
||||
{
|
||||
artist = artistWithSameName;
|
||||
updateArtistIfNeeded(artist, artistInfo);
|
||||
artist.modify()->setMBID(artistInfo.mbid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -102,6 +72,8 @@ namespace lms::scanner::helpers
|
||||
|
||||
db::Artist::pointer getOrCreateArtistByName(db::Session& session, const Artist& artistInfo, AllowFallbackOnMBIDEntry allowFallbackOnMBIDEntries)
|
||||
{
|
||||
assert(artistInfo.mbid == std::nullopt);
|
||||
|
||||
db::Artist::pointer artist;
|
||||
|
||||
// Here we can have only one artist with no MBID, others all have mbids
|
||||
@@ -112,10 +84,7 @@ namespace lms::scanner::helpers
|
||||
if (!allowFallbackOnMBIDEntries.value() || artistCountWithMBID > 1)
|
||||
{
|
||||
if (itArtistWithoutMBID != std::end(artistsWithSameName))
|
||||
{
|
||||
artist = *itArtistWithoutMBID;
|
||||
updateArtistIfNeeded(artist, artistInfo);
|
||||
}
|
||||
else
|
||||
artist = createArtist(session, artistInfo);
|
||||
}
|
||||
@@ -124,15 +93,9 @@ namespace lms::scanner::helpers
|
||||
const auto itArtistWithMBID{ std::find_if(std::begin(artistsWithSameName), std::end(artistsWithSameName), [](const db::Artist::pointer& artist) { return artist->hasMBID(); }) };
|
||||
|
||||
if (itArtistWithMBID != std::end(artistsWithSameName))
|
||||
{
|
||||
artist = *itArtistWithMBID;
|
||||
// not updating artist here: consider metadata quality is less good
|
||||
}
|
||||
else if (itArtistWithoutMBID != std::end(artistsWithSameName))
|
||||
{
|
||||
artist = *itArtistWithoutMBID;
|
||||
updateArtistIfNeeded(artist, artistInfo);
|
||||
}
|
||||
else
|
||||
artist = createArtist(session, artistInfo);
|
||||
}
|
||||
|
||||
@@ -125,7 +125,21 @@ namespace lms::scanner
|
||||
artistInfo.modify()->setDirectory(utils::getOrCreateDirectory(dbSession, getFilePath().parent_path(), mediaLibrary));
|
||||
|
||||
const Artist artistMetadata{ _parsedArtistInfo->mbid, _parsedArtistInfo->name, _parsedArtistInfo->sortName.empty() ? std::nullopt : std::make_optional<std::string>(_parsedArtistInfo->sortName) };
|
||||
|
||||
db::Artist::pointer artist{ helpers::getOrCreateArtist(dbSession, artistMetadata, helpers::AllowFallbackOnMBIDEntry{ getScannerSettings().allowArtistMBIDFallback }) };
|
||||
// Artist info is the highest priority source for artist name/sort name, so update it as needed
|
||||
if (artist->getName() != artistMetadata.name)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Updated artist name from '" << artist->getName() << "' to '" << artistMetadata.name << "' using artist info file");
|
||||
artist.modify()->setName(artistMetadata.name);
|
||||
}
|
||||
|
||||
if (artist->getSortName() != artistMetadata.sortName)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Updated artist sort name from '" << artist->getSortName() << "' to '" << (artistMetadata.sortName ? *artistMetadata.sortName : "") << "' using artist info file");
|
||||
artist.modify()->setSortName(artistMetadata.sortName ? *artistMetadata.sortName : "");
|
||||
}
|
||||
|
||||
artistInfo.modify()->setArtist(artist);
|
||||
artistInfo.modify()->setMBIDMatched(_parsedArtistInfo->mbid.has_value() && _parsedArtistInfo->mbid == artist->getMBID());
|
||||
|
||||
|
||||
@@ -83,14 +83,17 @@ namespace lms::scanner
|
||||
|
||||
void ScanStepArtistReconciliation::process(ScanContext& context)
|
||||
{
|
||||
// Reconcile artist links
|
||||
// Reconcile artist name differences when MBID was used to match
|
||||
updateArtistPreferredName(context);
|
||||
|
||||
// Reconcile artist links when MBID not used to match
|
||||
{
|
||||
// Order is important
|
||||
updateLinksForArtistNameNoLongerMatch(context);
|
||||
updateLinksWithArtistNameAmbiguity(context);
|
||||
}
|
||||
|
||||
// Reconcile artist info
|
||||
// Reconcile artist info when MBID not used to match
|
||||
{
|
||||
// Order is important
|
||||
updateArtistInfoForArtistNameNoLongerMatch(context);
|
||||
@@ -98,6 +101,97 @@ namespace lms::scanner
|
||||
}
|
||||
}
|
||||
|
||||
void ScanStepArtistReconciliation::updateArtistPreferredName(ScanContext& context)
|
||||
{
|
||||
static constexpr std::size_t batchSize{ 50 };
|
||||
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
|
||||
// List artists that have different names when mbid matched.
|
||||
// Possible reasons:
|
||||
// - artist name changed over time (ex: Rhapsody then Rhapsody of Fire), legit use case
|
||||
// - user renamed the artist
|
||||
// Name to pick in order of priority:
|
||||
// - name specified in artist info (if present)
|
||||
// - name as referenced in the latest release
|
||||
|
||||
struct ArtistToUpdate
|
||||
{
|
||||
db::Artist::pointer artist;
|
||||
std::string newName;
|
||||
std::string newSortName;
|
||||
};
|
||||
std::vector<ArtistToUpdate> artistsToUpdate;
|
||||
auto updateArtists{ [&] {
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
for (auto& artistToUpdate : artistsToUpdate)
|
||||
{
|
||||
artistToUpdate.artist.modify()->setName(artistToUpdate.newName);
|
||||
artistToUpdate.artist.modify()->setSortName(artistToUpdate.newSortName);
|
||||
}
|
||||
} };
|
||||
|
||||
db::ArtistId lastRetrievedArtist;
|
||||
while (!_abortScan)
|
||||
{
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto artists{ db::Artist::findWithMBIDNameVariants(session, lastRetrievedArtist, db::Range{ .offset = 0, .size = batchSize }) };
|
||||
if (artists.results.empty())
|
||||
break;
|
||||
|
||||
for (const db::Artist::pointer& artist : artists.results)
|
||||
{
|
||||
bool hasArtistInfo{};
|
||||
db::ArtistInfo::find(session, artist->getId(), db::Range{ .offset = 0, .size = 1 }, [&](const db::ArtistInfo::pointer&) {
|
||||
hasArtistInfo = true;
|
||||
});
|
||||
|
||||
// Scanning artist info should have updated the name of the artist
|
||||
if (hasArtistInfo)
|
||||
continue;
|
||||
|
||||
std::optional<ArtistToUpdate> artistToUpdate;
|
||||
|
||||
db::TrackArtistLink::FindParameters params;
|
||||
params.setArtist(artist->getId());
|
||||
params.setSortMethod(db::TrackArtistLinkSortMethod::OriginalDateDesc);
|
||||
params.setRange(db::Range{ .offset = 0, .size = 1 });
|
||||
db::TrackArtistLink::find(session, params, [&](const db::TrackArtistLink::pointer& link) {
|
||||
if (link->getArtistName() != artist->getName())
|
||||
{
|
||||
artistToUpdate.emplace();
|
||||
artistToUpdate->artist = artist;
|
||||
artistToUpdate->newName = link->getArtistName();
|
||||
artistToUpdate->newSortName = link->getArtistSortName();
|
||||
}
|
||||
});
|
||||
|
||||
if (artistToUpdate)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Updating artist " << artist << " name to '" << artistToUpdate->newName << "' using most recent release reference");
|
||||
artistsToUpdate.emplace_back(std::move(*artistToUpdate));
|
||||
}
|
||||
}
|
||||
|
||||
if (!artists.moreResults)
|
||||
break;
|
||||
}
|
||||
|
||||
if (artistsToUpdate.size() > batchSize)
|
||||
{
|
||||
updateArtists();
|
||||
artistsToUpdate.clear();
|
||||
}
|
||||
}
|
||||
|
||||
updateArtists();
|
||||
|
||||
_progressCallback(context.currentStepStats);
|
||||
}
|
||||
|
||||
void ScanStepArtistReconciliation::updateArtistInfoForArtistNameNoLongerMatch(ScanContext& context)
|
||||
{
|
||||
static constexpr std::size_t batchSize{ 50 };
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace lms::scanner
|
||||
bool needProcess(const ScanContext& context) const override;
|
||||
void process(ScanContext& context) override;
|
||||
|
||||
void updateArtistPreferredName(ScanContext& context);
|
||||
|
||||
void updateLinksForArtistNameNoLongerMatch(ScanContext& context);
|
||||
void updateLinksWithArtistNameAmbiguity(ScanContext& context);
|
||||
void updateArtistInfoForArtistNameNoLongerMatch(ScanContext& context);
|
||||
|
||||
Reference in New Issue
Block a user