Make the audio similarity engine the default one for new installs, if available

This commit is contained in:
emeric
2026-07-12 15:38:29 +02:00
parent 714b8640da
commit 78c624bde2
12 changed files with 40 additions and 36 deletions
+1 -1
View File
@@ -42,8 +42,8 @@ _LMS_ provides several ways to help you find the music you like:
* ...
__Note__: the recommendation engine supports two modes:
* **Audio similarity** (default): uses MusicNN embeddings. Extraction throughput depends heavily on both the number of scanner threads and the speed of your storage. Typical reported speeds range from ~1k tracks/hour on a Raspberry Pi 4 to ~25k tracks/hour on an Intel Core i5-13500.
* **Tag-based**: uses metadata tags `genre`, `mood`, `grouping`, and `language`.
* **Audio similarity**: uses MusicNN embeddings. Extraction throughput depends heavily on both the number of scanner threads and the speed of your storage. Typical reported speeds range from ~1k tracks/hour on a Raspberry Pi 4 (3 threads) to ~25k tracks/hour on an Intel Core i5-13500 (10 threads). To speed up extraction, consider tweaking `scanner-thread-count` in `lms.conf` (defaults to half the number of logical CPUs).
## About tags
_LMS_ primarily relies on tags to organize your music collection but also supports browsing by directory using the [Subsonic/OpenSubsonic API](SUBSONIC.md).
+4 -6
View File
@@ -180,17 +180,15 @@ namespace lms::db
bool Session::migrateSchemaIfNeeded()
{
const bool migrationPerformed{ Migration::doDbMigration(*this) };
return Migration::doDbMigration(*this);
}
// TODO: move this elsewhere
void Session::createScanSettingsIfNeeded(RecommendationEngineType defaultRecommendationEngineType)
{
auto uniqueTransaction{ createWriteTransaction() };
if (!ScanSettings::find(*this))
create<ScanSettings>();
}
return migrationPerformed;
create<ScanSettings>().modify()->setRecommendationEngineType(defaultRecommendationEngineType);
}
void Session::createIndexesIfNeeded()
@@ -28,6 +28,7 @@
#include "database/Transaction.hpp"
#include "database/Types.hpp"
#include "database/objects/Types.hpp"
namespace lms::db
{
@@ -58,6 +59,7 @@ namespace lms::db
void prepareTablesIfNeeded(); // need to run only once at startup
bool migrateSchemaIfNeeded(); // returns true if migration was performed
void createScanSettingsIfNeeded(RecommendationEngineType defaultRecommendationEngineType = RecommendationEngineType::Clusters);
void createIndexesIfNeeded();
void vacuumIfNeeded();
void vacuum();
@@ -29,6 +29,7 @@
#include "database/IdType.hpp"
#include "database/Object.hpp"
#include "database/objects/Types.hpp"
LMS_DECLARE_IDTYPE(ScanSettingsId)
@@ -49,14 +50,6 @@ namespace lms::db
Hourly,
};
// Do not modify values (just add)
enum class RecommendationEngineType
{
Clusters = 0,
None = 2,
AudioSimilarity = 3,
};
ScanSettings() = default;
static pointer find(Session& session, std::string_view name = "");
@@ -114,6 +114,13 @@ namespace lms::db
PubDateDesc,
};
enum class RecommendationEngineType
{
Clusters = 0,
None = 2,
AudioSimilarity = 3,
};
enum class ReleaseArtistLinkSortMethod
{
None,
+1
View File
@@ -344,6 +344,7 @@ VALUES
// Now perform full migration
db.getTLSSession().migrateSchemaIfNeeded();
db.getTLSSession().createScanSettingsIfNeeded();
// Now perform some dummy finds to ensure all fields are correctly mapped
{
@@ -32,35 +32,35 @@ namespace lms::recommendation
{
namespace
{
db::ScanSettings::RecommendationEngineType getRecommendationEngineType(db::Session& session)
db::RecommendationEngineType getRecommendationEngineType(db::Session& session)
{
auto transaction{ session.createReadTransaction() };
return db::ScanSettings::find(session)->getRecommendationEngineType();
}
EngineType toEngineType(db::ScanSettings::RecommendationEngineType type)
EngineType toEngineType(db::RecommendationEngineType type)
{
switch (type)
{
case db::ScanSettings::RecommendationEngineType::None:
case db::RecommendationEngineType::None:
return EngineType::None;
case db::ScanSettings::RecommendationEngineType::Clusters:
case db::RecommendationEngineType::Clusters:
return EngineType::Clusters;
case db::ScanSettings::RecommendationEngineType::AudioSimilarity:
case db::RecommendationEngineType::AudioSimilarity:
return EngineType::AudioSimilarity;
}
return EngineType::None;
}
std::unique_ptr<IEngine> createEngine(db::ScanSettings::RecommendationEngineType type, db::IDb& db)
std::unique_ptr<IEngine> createEngine(db::RecommendationEngineType type, db::IDb& db)
{
switch (type)
{
case db::ScanSettings::RecommendationEngineType::Clusters:
case db::RecommendationEngineType::Clusters:
return std::make_unique<TagsEngine>(db);
case db::ScanSettings::RecommendationEngineType::AudioSimilarity:
case db::RecommendationEngineType::AudioSimilarity:
return std::make_unique<MusicNNEmbeddingEngine>(db);
case db::ScanSettings::RecommendationEngineType::None:
case db::RecommendationEngineType::None:
return nullptr;
}
return nullptr;
@@ -139,7 +139,7 @@ namespace lms::recommendation
return false;
}
db::ScanSettings::RecommendationEngineType RecommendationService::prepareReload()
db::RecommendationEngineType RecommendationService::prepareReload()
{
const auto type{ getRecommendationEngineType(_db.getTLSSession()) };
std::unique_lock lock{ _mutex };
@@ -59,7 +59,7 @@ namespace lms::recommendation
ArtistResults findSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
TrackResults findTrackSimilarityPath(db::TrackId startTrackId, db::TrackId endTrackId, std::size_t maxCount) const override;
db::ScanSettings::RecommendationEngineType prepareReload();
db::RecommendationEngineType prepareReload();
db::IDb& _db;
mutable std::shared_mutex _mutex;
@@ -126,7 +126,7 @@ namespace lms::scanner
settings->allowArtistMBIDFallback = scanSettings->getAllowMBIDArtistMerge();
settings->artistImageFallbackToRelease = scanSettings->getArtistImageFallbackToRelease();
settings->extractMusicNNEmbeddings = scanSettings->getRecommendationEngineType() == db::ScanSettings::RecommendationEngineType::AudioSimilarity;
settings->extractMusicNNEmbeddings = scanSettings->getRecommendationEngineType() == db::RecommendationEngineType::AudioSimilarity;
settings->musicnnModelPath = core::Service<core::IConfig>::get()->getPath("musicnn-model-path", "/usr/share/lms/models/MSD_musicnn_embedding.onnx");
settings->musicnnMaxPatchCountPerTrack = core::Service<core::IConfig>::get()->getULong("musicnn-max-patch-count-per-track", 20);
+2
View File
@@ -36,6 +36,7 @@
#include "core/SystemPaths.hpp"
#include "audio/IAudioOutput.hpp"
#include "audio/IMusicNNEmbeddingExtractor.hpp"
#include "database/IDb.hpp"
#include "database/Session.hpp"
#include "database/profiling/IQueryProfiler.hpp"
@@ -436,6 +437,7 @@ namespace lms
db::Session session{ *database };
session.prepareTablesIfNeeded();
bool migrationPerformed{ session.migrateSchemaIfNeeded() };
session.createScanSettingsIfNeeded(audio::canExtractMusicNNEmbeddings() ? db::RecommendationEngineType::AudioSimilarity : db::RecommendationEngineType::Clusters);
session.createIndexesIfNeeded();
// As this may be quite long, we only do it during startup
+6 -6
View File
@@ -44,7 +44,7 @@ namespace lms::ui
{
namespace
{
using RecommendationEngineTypeModel = ValueStringModel<db::ScanSettings::RecommendationEngineType>;
using RecommendationEngineTypeModel = ValueStringModel<db::RecommendationEngineType>;
class RecommendationEngineValidator : public Wt::WValidator
{
@@ -63,7 +63,7 @@ namespace lms::ui
std::string inputStr{ input.toUTF8() };
const auto row{ _model->getRowFromString(inputStr) };
if (row && _model->getValue(*row) == db::ScanSettings::RecommendationEngineType::AudioSimilarity)
if (row && _model->getValue(*row) == db::RecommendationEngineType::AudioSimilarity)
{
if (!core::Service<recommendation::IRecommendationService>::get()->isEngineTypeSupported(recommendation::EngineType::AudioSimilarity))
return Wt::WValidator::Result{ Wt::ValidationState::Invalid, Wt::WString::tr("Lms.Admin.Database.recommendation-engine-not-supported") };
@@ -247,10 +247,10 @@ namespace lms::ui
_updateStartTimeModel->add(time.toString(), time);
}
_recommendationEngineTypeModel = std::make_shared<ValueStringModel<db::ScanSettings::RecommendationEngineType>>();
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.audio-similarity"), db::ScanSettings::RecommendationEngineType::AudioSimilarity);
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.clusters"), db::ScanSettings::RecommendationEngineType::Clusters);
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.none"), db::ScanSettings::RecommendationEngineType::None);
_recommendationEngineTypeModel = std::make_shared<ValueStringModel<db::RecommendationEngineType>>();
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.audio-similarity"), db::RecommendationEngineType::AudioSimilarity);
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.clusters"), db::RecommendationEngineType::Clusters);
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.none"), db::RecommendationEngineType::None);
}
std::shared_ptr<UpdatePeriodModel> _updatePeriodModel;
@@ -282,6 +282,7 @@ int main(int argc, char* argv[])
db::Session session{ *db };
session.prepareTablesIfNeeded();
session.migrateSchemaIfNeeded();
session.createScanSettingsIfNeeded();
session.createIndexesIfNeeded();
std::cout << "Starting generation..." << std::endl;