Make the audio similarity engine the default one for new installs, if available
This commit is contained in:
@@ -42,8 +42,8 @@ _LMS_ provides several ways to help you find the music you like:
|
|||||||
* ...
|
* ...
|
||||||
|
|
||||||
__Note__: the recommendation engine supports two modes:
|
__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`.
|
* **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
|
## 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).
|
_LMS_ primarily relies on tags to organize your music collection but also supports browsing by directory using the [Subsonic/OpenSubsonic API](SUBSONIC.md).
|
||||||
|
|||||||
@@ -180,17 +180,15 @@ namespace lms::db
|
|||||||
|
|
||||||
bool Session::migrateSchemaIfNeeded()
|
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() };
|
auto uniqueTransaction{ createWriteTransaction() };
|
||||||
|
|
||||||
if (!ScanSettings::find(*this))
|
if (!ScanSettings::find(*this))
|
||||||
create<ScanSettings>();
|
create<ScanSettings>().modify()->setRecommendationEngineType(defaultRecommendationEngineType);
|
||||||
}
|
|
||||||
|
|
||||||
return migrationPerformed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::createIndexesIfNeeded()
|
void Session::createIndexesIfNeeded()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "database/Transaction.hpp"
|
#include "database/Transaction.hpp"
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
|
|
||||||
namespace lms::db
|
namespace lms::db
|
||||||
{
|
{
|
||||||
@@ -58,6 +59,7 @@ namespace lms::db
|
|||||||
|
|
||||||
void prepareTablesIfNeeded(); // need to run only once at startup
|
void prepareTablesIfNeeded(); // need to run only once at startup
|
||||||
bool migrateSchemaIfNeeded(); // returns true if migration was performed
|
bool migrateSchemaIfNeeded(); // returns true if migration was performed
|
||||||
|
void createScanSettingsIfNeeded(RecommendationEngineType defaultRecommendationEngineType = RecommendationEngineType::Clusters);
|
||||||
void createIndexesIfNeeded();
|
void createIndexesIfNeeded();
|
||||||
void vacuumIfNeeded();
|
void vacuumIfNeeded();
|
||||||
void vacuum();
|
void vacuum();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include "database/IdType.hpp"
|
#include "database/IdType.hpp"
|
||||||
#include "database/Object.hpp"
|
#include "database/Object.hpp"
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
|
|
||||||
LMS_DECLARE_IDTYPE(ScanSettingsId)
|
LMS_DECLARE_IDTYPE(ScanSettingsId)
|
||||||
|
|
||||||
@@ -49,14 +50,6 @@ namespace lms::db
|
|||||||
Hourly,
|
Hourly,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Do not modify values (just add)
|
|
||||||
enum class RecommendationEngineType
|
|
||||||
{
|
|
||||||
Clusters = 0,
|
|
||||||
None = 2,
|
|
||||||
AudioSimilarity = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
ScanSettings() = default;
|
ScanSettings() = default;
|
||||||
|
|
||||||
static pointer find(Session& session, std::string_view name = "");
|
static pointer find(Session& session, std::string_view name = "");
|
||||||
|
|||||||
@@ -114,6 +114,13 @@ namespace lms::db
|
|||||||
PubDateDesc,
|
PubDateDesc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class RecommendationEngineType
|
||||||
|
{
|
||||||
|
Clusters = 0,
|
||||||
|
None = 2,
|
||||||
|
AudioSimilarity = 3,
|
||||||
|
};
|
||||||
|
|
||||||
enum class ReleaseArtistLinkSortMethod
|
enum class ReleaseArtistLinkSortMethod
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -344,6 +344,7 @@ VALUES
|
|||||||
|
|
||||||
// Now perform full migration
|
// Now perform full migration
|
||||||
db.getTLSSession().migrateSchemaIfNeeded();
|
db.getTLSSession().migrateSchemaIfNeeded();
|
||||||
|
db.getTLSSession().createScanSettingsIfNeeded();
|
||||||
|
|
||||||
// Now perform some dummy finds to ensure all fields are correctly mapped
|
// Now perform some dummy finds to ensure all fields are correctly mapped
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,35 +32,35 @@ namespace lms::recommendation
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
db::ScanSettings::RecommendationEngineType getRecommendationEngineType(db::Session& session)
|
db::RecommendationEngineType getRecommendationEngineType(db::Session& session)
|
||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
return db::ScanSettings::find(session)->getRecommendationEngineType();
|
return db::ScanSettings::find(session)->getRecommendationEngineType();
|
||||||
}
|
}
|
||||||
|
|
||||||
EngineType toEngineType(db::ScanSettings::RecommendationEngineType type)
|
EngineType toEngineType(db::RecommendationEngineType type)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case db::ScanSettings::RecommendationEngineType::None:
|
case db::RecommendationEngineType::None:
|
||||||
return EngineType::None;
|
return EngineType::None;
|
||||||
case db::ScanSettings::RecommendationEngineType::Clusters:
|
case db::RecommendationEngineType::Clusters:
|
||||||
return EngineType::Clusters;
|
return EngineType::Clusters;
|
||||||
case db::ScanSettings::RecommendationEngineType::AudioSimilarity:
|
case db::RecommendationEngineType::AudioSimilarity:
|
||||||
return EngineType::AudioSimilarity;
|
return EngineType::AudioSimilarity;
|
||||||
}
|
}
|
||||||
return EngineType::None;
|
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)
|
switch (type)
|
||||||
{
|
{
|
||||||
case db::ScanSettings::RecommendationEngineType::Clusters:
|
case db::RecommendationEngineType::Clusters:
|
||||||
return std::make_unique<TagsEngine>(db);
|
return std::make_unique<TagsEngine>(db);
|
||||||
case db::ScanSettings::RecommendationEngineType::AudioSimilarity:
|
case db::RecommendationEngineType::AudioSimilarity:
|
||||||
return std::make_unique<MusicNNEmbeddingEngine>(db);
|
return std::make_unique<MusicNNEmbeddingEngine>(db);
|
||||||
case db::ScanSettings::RecommendationEngineType::None:
|
case db::RecommendationEngineType::None:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -139,7 +139,7 @@ namespace lms::recommendation
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
db::ScanSettings::RecommendationEngineType RecommendationService::prepareReload()
|
db::RecommendationEngineType RecommendationService::prepareReload()
|
||||||
{
|
{
|
||||||
const auto type{ getRecommendationEngineType(_db.getTLSSession()) };
|
const auto type{ getRecommendationEngineType(_db.getTLSSession()) };
|
||||||
std::unique_lock lock{ _mutex };
|
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;
|
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;
|
TrackResults findTrackSimilarityPath(db::TrackId startTrackId, db::TrackId endTrackId, std::size_t maxCount) const override;
|
||||||
|
|
||||||
db::ScanSettings::RecommendationEngineType prepareReload();
|
db::RecommendationEngineType prepareReload();
|
||||||
|
|
||||||
db::IDb& _db;
|
db::IDb& _db;
|
||||||
mutable std::shared_mutex _mutex;
|
mutable std::shared_mutex _mutex;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ namespace lms::scanner
|
|||||||
settings->allowArtistMBIDFallback = scanSettings->getAllowMBIDArtistMerge();
|
settings->allowArtistMBIDFallback = scanSettings->getAllowMBIDArtistMerge();
|
||||||
settings->artistImageFallbackToRelease = scanSettings->getArtistImageFallbackToRelease();
|
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->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);
|
settings->musicnnMaxPatchCountPerTrack = core::Service<core::IConfig>::get()->getULong("musicnn-max-patch-count-per-track", 20);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "core/SystemPaths.hpp"
|
#include "core/SystemPaths.hpp"
|
||||||
|
|
||||||
#include "audio/IAudioOutput.hpp"
|
#include "audio/IAudioOutput.hpp"
|
||||||
|
#include "audio/IMusicNNEmbeddingExtractor.hpp"
|
||||||
#include "database/IDb.hpp"
|
#include "database/IDb.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/profiling/IQueryProfiler.hpp"
|
#include "database/profiling/IQueryProfiler.hpp"
|
||||||
@@ -436,6 +437,7 @@ namespace lms
|
|||||||
db::Session session{ *database };
|
db::Session session{ *database };
|
||||||
session.prepareTablesIfNeeded();
|
session.prepareTablesIfNeeded();
|
||||||
bool migrationPerformed{ session.migrateSchemaIfNeeded() };
|
bool migrationPerformed{ session.migrateSchemaIfNeeded() };
|
||||||
|
session.createScanSettingsIfNeeded(audio::canExtractMusicNNEmbeddings() ? db::RecommendationEngineType::AudioSimilarity : db::RecommendationEngineType::Clusters);
|
||||||
session.createIndexesIfNeeded();
|
session.createIndexesIfNeeded();
|
||||||
|
|
||||||
// As this may be quite long, we only do it during startup
|
// As this may be quite long, we only do it during startup
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace lms::ui
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
using RecommendationEngineTypeModel = ValueStringModel<db::ScanSettings::RecommendationEngineType>;
|
using RecommendationEngineTypeModel = ValueStringModel<db::RecommendationEngineType>;
|
||||||
|
|
||||||
class RecommendationEngineValidator : public Wt::WValidator
|
class RecommendationEngineValidator : public Wt::WValidator
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ namespace lms::ui
|
|||||||
std::string inputStr{ input.toUTF8() };
|
std::string inputStr{ input.toUTF8() };
|
||||||
|
|
||||||
const auto row{ _model->getRowFromString(inputStr) };
|
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))
|
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") };
|
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);
|
_updateStartTimeModel->add(time.toString(), time);
|
||||||
}
|
}
|
||||||
|
|
||||||
_recommendationEngineTypeModel = std::make_shared<ValueStringModel<db::ScanSettings::RecommendationEngineType>>();
|
_recommendationEngineTypeModel = std::make_shared<ValueStringModel<db::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.audio-similarity"), db::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.clusters"), db::RecommendationEngineType::Clusters);
|
||||||
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.none"), db::ScanSettings::RecommendationEngineType::None);
|
_recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.none"), db::RecommendationEngineType::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<UpdatePeriodModel> _updatePeriodModel;
|
std::shared_ptr<UpdatePeriodModel> _updatePeriodModel;
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ int main(int argc, char* argv[])
|
|||||||
db::Session session{ *db };
|
db::Session session{ *db };
|
||||||
session.prepareTablesIfNeeded();
|
session.prepareTablesIfNeeded();
|
||||||
session.migrateSchemaIfNeeded();
|
session.migrateSchemaIfNeeded();
|
||||||
|
session.createScanSettingsIfNeeded();
|
||||||
session.createIndexesIfNeeded();
|
session.createIndexesIfNeeded();
|
||||||
|
|
||||||
std::cout << "Starting generation..." << std::endl;
|
std::cout << "Starting generation..." << std::endl;
|
||||||
|
|||||||
Reference in New Issue
Block a user