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:
|
||||
* **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).
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user