Added a way to set artists that must not be split, fixes #522
This commit is contained in:
@@ -39,6 +39,15 @@
|
||||
</label>
|
||||
${artist-tag-delimiter-container class="row gy-3"}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="${id:artists-to-not-split}">
|
||||
${tr:Lms.Admin.Database.artists-to-not-split}
|
||||
</label>
|
||||
${artists-to-not-split class="form-control"}
|
||||
<div class="invalid-feedback">
|
||||
${artists-to-not-split-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">
|
||||
${tr:Lms.Admin.Database.default-tag-delimiters}
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<!--Scan settings-->
|
||||
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Allow merging artists without an MBID to those with one</message>
|
||||
<message id="Lms.Admin.Database.artist-tag-delimiters">Delimiters to be used for splitting artist tags</message>
|
||||
<message id="Lms.Admin.Database.artists-to-not-split">Artists to not split using the delimiters (one artist per line)</message>
|
||||
<message id="Lms.Admin.Database.daily">Daily</message>
|
||||
<message id="Lms.Admin.Database.default-tag-delimiters">Delimiters to be used for splitting other tags</message>
|
||||
<message id="Lms.Admin.Database.extra-tags-to-scan">Extra tags to scan</message>
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<!--Scan settings-->
|
||||
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Permettre la fusion des artistes sans MBID avec ceux qui en ont un</message>
|
||||
<message id="Lms.Admin.Database.artist-tag-delimiters">Délimiteurs à utiliser pour séparer les tags d'artistes</message>
|
||||
<message id="Lms.Admin.Database.artists-to-not-split">Artistes à ne pas séparer en utilisant les délimiteurs (un artiste par ligne)</message>
|
||||
<message id="Lms.Admin.Database.daily">Tous les jours</message>
|
||||
<message id="Lms.Admin.Database.default-tag-delimiters">Délimiteurs à utiliser pour séparer les autres tags</message>
|
||||
<message id="Lms.Admin.Database.extra-tags-to-scan">Tags supplémentaires à scanner</message>
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<!--Scan settings-->
|
||||
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Consentire la fusione degli artisti senza MBID con quelli che ne hanno uno</message>
|
||||
<message id="Lms.Admin.Database.artist-tag-delimiters">Delimitatori da utilizzare per separare i tag degli artisti</message>
|
||||
<message id="Lms.Admin.Database.artists-to-not-split">Artisti da non suddividere utilizzando i delimitatori (un artista per riga)</message>
|
||||
<message id="Lms.Admin.Database.daily">Giornaliera</message>
|
||||
<message id="Lms.Admin.Database.default-tag-delimiters">Delimitatori da utilizzare per separare gli altri tag</message>
|
||||
<message id="Lms.Admin.Database.extra-tags-to-scan">Tag aggiuntivi da scansionare</message>
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<!--Scan settings-->
|
||||
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Pozwól na łączenie artystów bez MBID z tymi, którzy go mają</message>
|
||||
<message id="Lms.Admin.Database.artist-tag-delimiters">Znaki rozdzielające artystów</message>
|
||||
<message id="Lms.Admin.Database.artists-to-not-split">Artyści, których nie należy dzielić przy użyciu separatorów (jeden artysta na linię)</message>
|
||||
<message id="Lms.Admin.Database.daily">Codziennie</message>
|
||||
<message id="Lms.Admin.Database.default-tag-delimiters">Znaki rozdzielające inne oznaczenia</message>
|
||||
<message id="Lms.Admin.Database.extra-tags-to-scan">Szukaj dodatkowych znaczników</message>
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 87 };
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 88 };
|
||||
}
|
||||
|
||||
VersionInfo::VersionInfo()
|
||||
@@ -1184,6 +1184,11 @@ FROM tracklist)");
|
||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings RENAME COLUMN scan_version TO audio_scan_version");
|
||||
}
|
||||
|
||||
void migrateFromV87(Session& session)
|
||||
{
|
||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings ADD COLUMN artists_to_not_split TEXT NON NULL DEFAULT('')");
|
||||
}
|
||||
|
||||
bool doDbMigration(Session& session)
|
||||
{
|
||||
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||
@@ -1247,6 +1252,7 @@ FROM tracklist)");
|
||||
{ 84, migrateFromV84 },
|
||||
{ 85, migrateFromV85 },
|
||||
{ 86, migrateFromV86 },
|
||||
{ 87, migrateFromV87 },
|
||||
};
|
||||
|
||||
bool migrationPerformed{};
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "database/Session.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -40,7 +41,14 @@ namespace lms::db
|
||||
return session.getDboSession()->add(std::unique_ptr<ScanSettings>(new ScanSettings{ name }));
|
||||
}
|
||||
|
||||
ScanSettings::pointer ScanSettings::get(Session& session, std::string_view name)
|
||||
ScanSettings::pointer ScanSettings::find(Session& session, ScanSettingsId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<ScanSettings>>("SELECT s_s from scan_settings s_s").where("s_s.id = ?").bind(id));
|
||||
}
|
||||
|
||||
ScanSettings::pointer ScanSettings::find(Session& session, std::string_view name)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -66,13 +74,19 @@ namespace lms::db
|
||||
return core::stringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\');
|
||||
}
|
||||
|
||||
std::vector<std::string> ScanSettings::getArtistsToNotSplit() const
|
||||
{
|
||||
return core::stringUtils::splitEscapedStrings(_artistsToNotSplit, ';', '\\');
|
||||
}
|
||||
|
||||
void ScanSettings::setExtraTagsToScan(std::span<const std::string_view> extraTags)
|
||||
{
|
||||
std::string newTagsToScan{ core::stringUtils::joinStrings(extraTags, ";") };
|
||||
if (newTagsToScan != _extraTagsToScan)
|
||||
{
|
||||
_extraTagsToScan.swap(newTagsToScan);
|
||||
incAudioScanVersion();
|
||||
|
||||
_extraTagsToScan = std::move(newTagsToScan);
|
||||
}
|
||||
}
|
||||
|
||||
void ScanSettings::setArtistTagDelimiters(std::span<const std::string_view> delimiters)
|
||||
@@ -85,6 +99,16 @@ namespace lms::db
|
||||
}
|
||||
}
|
||||
|
||||
void ScanSettings::setArtistsToNotSplit(std::span<const std::string_view> artists)
|
||||
{
|
||||
std::string artistsToNotSplit{ core::stringUtils::escapeAndJoinStrings(artists, ';', '\\') };
|
||||
if (artistsToNotSplit != _artistsToNotSplit)
|
||||
{
|
||||
_artistsToNotSplit.swap(artistsToNotSplit);
|
||||
incAudioScanVersion();
|
||||
}
|
||||
}
|
||||
|
||||
void ScanSettings::setDefaultTagDelimiters(std::span<const std::string_view> delimiters)
|
||||
{
|
||||
std::string tagDelimiters{ core::stringUtils::escapeAndJoinStrings(delimiters, ';', '\\') };
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace lms::db
|
||||
{
|
||||
auto uniqueTransaction{ createWriteTransaction() };
|
||||
|
||||
if (!ScanSettings::get(*this))
|
||||
if (!ScanSettings::find(*this))
|
||||
create<ScanSettings>();
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ namespace lms::db
|
||||
|
||||
ScanSettings() = default;
|
||||
|
||||
static pointer get(Session& session, std::string_view name = "");
|
||||
static pointer find(Session& session, std::string_view name = "");
|
||||
static pointer find(Session& session, ScanSettingsId id);
|
||||
|
||||
// Getters
|
||||
std::size_t getAudioScanVersion() const { return _audioScanVersion; }
|
||||
@@ -69,6 +70,7 @@ namespace lms::db
|
||||
SimilarityEngineType getSimilarityEngineType() const { return _similarityEngineType; }
|
||||
std::vector<std::string> getArtistTagDelimiters() const;
|
||||
std::vector<std::string> getDefaultTagDelimiters() const;
|
||||
std::vector<std::string> getArtistsToNotSplit() const;
|
||||
bool getSkipSingleReleasePlayLists() const { return _skipSingleReleasePlayLists; }
|
||||
bool getAllowMBIDArtistMerge() const { return _allowMBIDArtistMerge; }
|
||||
|
||||
@@ -78,6 +80,7 @@ namespace lms::db
|
||||
void setExtraTagsToScan(std::span<const std::string_view> extraTags);
|
||||
void setSimilarityEngineType(SimilarityEngineType type) { _similarityEngineType = type; }
|
||||
void setArtistTagDelimiters(std::span<const std::string_view> delimiters);
|
||||
void setArtistsToNotSplit(std::span<const std::string_view> artists);
|
||||
void setDefaultTagDelimiters(std::span<const std::string_view> delimiters);
|
||||
void setSkipSingleReleasePlayLists(bool value);
|
||||
void setAllowMBIDArtistMerge(bool value);
|
||||
@@ -92,6 +95,7 @@ namespace lms::db
|
||||
Wt::Dbo::field(a, _similarityEngineType, "similarity_engine_type");
|
||||
Wt::Dbo::field(a, _extraTagsToScan, "extra_tags_to_scan");
|
||||
Wt::Dbo::field(a, _artistTagDelimiters, "artist_tag_delimiters");
|
||||
Wt::Dbo::field(a, _artistsToNotSplit, "artists_to_not_split");
|
||||
Wt::Dbo::field(a, _defaultTagDelimiters, "default_tag_delimiters");
|
||||
Wt::Dbo::field(a, _skipSingleReleasePlayLists, "skip_single_release_playlists");
|
||||
Wt::Dbo::field(a, _allowMBIDArtistMerge, "allow_mbid_artist_merge");
|
||||
@@ -112,6 +116,7 @@ namespace lms::db
|
||||
SimilarityEngineType _similarityEngineType{ SimilarityEngineType::Clusters };
|
||||
std::string _extraTagsToScan;
|
||||
std::string _artistTagDelimiters;
|
||||
std::string _artistsToNotSplit;
|
||||
std::string _defaultTagDelimiters;
|
||||
bool _skipSingleReleasePlayLists{};
|
||||
bool _allowMBIDArtistMerge{};
|
||||
|
||||
@@ -15,6 +15,7 @@ add_executable(test-database
|
||||
RatedRelease.cpp
|
||||
RatedTrack.cpp
|
||||
Release.cpp
|
||||
ScanSettings.cpp
|
||||
StarredArtist.cpp
|
||||
StarredRelease.cpp
|
||||
StarredTrack.cpp
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "database/RatedArtist.hpp"
|
||||
#include "database/RatedRelease.hpp"
|
||||
#include "database/RatedTrack.hpp"
|
||||
#include "database/ScanSettings.hpp"
|
||||
#include "database/StarredArtist.hpp"
|
||||
#include "database/StarredRelease.hpp"
|
||||
#include "database/StarredTrack.hpp"
|
||||
@@ -362,6 +363,7 @@ VALUES
|
||||
EXPECT_FALSE(RatedTrack::find(session, RatedTrackId{}));
|
||||
EXPECT_FALSE(Release::find(session, ReleaseId{}));
|
||||
EXPECT_FALSE(ReleaseType::find(session, ReleaseTypeId{}));
|
||||
EXPECT_FALSE(ScanSettings::find(session, ScanSettingsId{}));
|
||||
EXPECT_FALSE(StarredArtist::find(session, StarredArtistId{}));
|
||||
EXPECT_FALSE(StarredRelease::find(session, StarredReleaseId{}));
|
||||
EXPECT_FALSE(StarredTrack::find(session, StarredTrackId{}));
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/ScanSettings.hpp"
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
using ScopedScanSettings = ScopedEntity<db::ScanSettings>;
|
||||
|
||||
TEST_F(DatabaseFixture, ScanSettings)
|
||||
{
|
||||
ScopedScanSettings settings{ session, "test" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto artists{ settings.get()->getArtistsToNotSplit() };
|
||||
ASSERT_EQ(artists.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
settings.get().modify()->setArtistsToNotSplit(std::initializer_list<std::string_view>{ "AC/DC", "My/Artist" });
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto artists{ settings.get()->getArtistsToNotSplit() };
|
||||
ASSERT_EQ(artists.size(), 2);
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
@@ -16,7 +16,6 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define STBI_ONLY_JPEG
|
||||
|
||||
@@ -62,36 +62,41 @@ namespace lms::metadata
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> getTagValuesFirstMatchAs(const ITagReader& tagReader, std::initializer_list<TagType> tagTypes, std::span<const std::string> tagDelimiters)
|
||||
void addTagIfNonEmpty(std::vector<T>& res, std::string_view tag)
|
||||
{
|
||||
tag = core::stringUtils::stringTrim(tag);
|
||||
if (tag.empty())
|
||||
return;
|
||||
|
||||
if (std::optional<T> val{ core::stringUtils::readAs<T>(tag) })
|
||||
res.emplace_back(std::move(*val));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> getTagValuesFirstMatchAs(const ITagReader& tagReader, std::initializer_list<TagType> tagTypes, std::span<const std::string> tagDelimiters, const WhiteList* whitelist = nullptr)
|
||||
{
|
||||
std::vector<T> res;
|
||||
|
||||
for (const TagType tagType : tagTypes)
|
||||
{
|
||||
auto addTagIfNonEmpty{ [&res](std::string_view tag) {
|
||||
tag = core::stringUtils::stringTrim(tag);
|
||||
if (!tag.empty())
|
||||
{
|
||||
std::optional<T> val{ core::stringUtils::readAs<T>(tag) };
|
||||
if (val)
|
||||
res.emplace_back(std::move(*val));
|
||||
}
|
||||
} };
|
||||
|
||||
tagReader.visitTagValues(tagType, [&](std::string_view value) {
|
||||
for (std::string_view tagDelimiter : tagDelimiters)
|
||||
value = core::stringUtils::stringTrim(value);
|
||||
if (!whitelist || !whitelist->contains(value))
|
||||
{
|
||||
if (value.find(tagDelimiter) != std::string_view::npos)
|
||||
for (std::string_view tagDelimiter : tagDelimiters)
|
||||
{
|
||||
for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiters))
|
||||
addTagIfNonEmpty(splitTag);
|
||||
if (value.find(tagDelimiter) != std::string_view::npos)
|
||||
{
|
||||
for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiters))
|
||||
addTagIfNonEmpty(res, splitTag);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no delimiter found, or no delimiter to be used
|
||||
addTagIfNonEmpty(value);
|
||||
addTagIfNonEmpty(res, value);
|
||||
});
|
||||
|
||||
if (!res.empty())
|
||||
@@ -153,11 +158,11 @@ namespace lms::metadata
|
||||
std::initializer_list<TagType> artistMBIDTagNames,
|
||||
const AudioFileParserParameters& params)
|
||||
{
|
||||
std::vector<std::string> artistNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistTagNames, params.artistTagDelimiters) };
|
||||
std::vector<std::string> artistNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistTagNames, params.artistTagDelimiters, ¶ms.artistsToNotSplit) };
|
||||
if (artistNames.empty())
|
||||
return {};
|
||||
|
||||
std::vector<std::string> artistSortNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistSortTagNames, params.artistTagDelimiters) };
|
||||
std::vector<std::string> artistSortNames{ getTagValuesFirstMatchAs<std::string>(tagReader, artistSortTagNames, params.artistTagDelimiters, ¶ms.artistsToNotSplit) };
|
||||
std::vector<core::UUID> artistMBIDs{ getTagValuesFirstMatchAs<core::UUID>(tagReader, artistMBIDTagNames, params.defaultTagDelimiters) };
|
||||
|
||||
std::vector<Artist> artists;
|
||||
@@ -238,6 +243,7 @@ namespace lms::metadata
|
||||
// Otherwise, we reconstruct the string using a standard, hardcoded, join
|
||||
if (artistTag && strIsMatchingArtistNames(*artistTag, artistNames))
|
||||
{
|
||||
// Limitation: this test does not take the whitelist into account
|
||||
if (!strIsContainingAny(*artistTag, artistTagDelimiters))
|
||||
artistDisplayName = *artistTag;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "core/PartialDateTime.hpp"
|
||||
@@ -213,11 +214,18 @@ namespace lms::metadata
|
||||
Accurate,
|
||||
};
|
||||
|
||||
struct WhiteListHash : std::hash<std::string>, std::hash<std::string_view>
|
||||
{
|
||||
using is_transparent = void;
|
||||
};
|
||||
|
||||
using WhiteList = std::unordered_set<std::string, WhiteListHash, std::equal_to<>>;
|
||||
struct AudioFileParserParameters
|
||||
{
|
||||
ParserBackend backend{ ParserBackend::TagLib };
|
||||
ParserReadStyle readStyle{ ParserReadStyle::Average };
|
||||
std::vector<std::string> artistTagDelimiters;
|
||||
WhiteList artistsToNotSplit;
|
||||
std::vector<std::string> defaultTagDelimiters;
|
||||
std::vector<std::string> userExtraTags;
|
||||
bool debug{};
|
||||
|
||||
@@ -263,9 +263,9 @@ namespace lms::metadata
|
||||
const TestTagReader testTags{
|
||||
{
|
||||
{ TagType::Album, { "MyAlbum" } },
|
||||
{ TagType::AlbumArtist, { "AlbumArtist1 / AlbumArtist2" } },
|
||||
{ TagType::AlbumArtist, { "AlbumArtist1 / AlbumArtist2" } },
|
||||
{ TagType::Artist, { " Artist1 / Artist2 feat. Artist3 " } },
|
||||
{ TagType::Genre, { "Genre1 ; Genre2" } },
|
||||
{ TagType::Genre, { "Genre1 ; Genre2" } },
|
||||
{ TagType::Language, { " Lang1/Lang2 / Lang3" } },
|
||||
}
|
||||
};
|
||||
@@ -295,11 +295,60 @@ namespace lms::metadata
|
||||
// Release
|
||||
ASSERT_TRUE(track->medium->release.has_value());
|
||||
EXPECT_EQ(track->medium->release->name, "MyAlbum");
|
||||
ASSERT_EQ(track->medium->release->artists.size(), 2);
|
||||
EXPECT_EQ(track->medium->release->artists[0].name, "AlbumArtist1");
|
||||
EXPECT_EQ(track->medium->release->artists[1].name, "AlbumArtist2");
|
||||
EXPECT_EQ(track->medium->release->artistDisplayName, "AlbumArtist1, AlbumArtist2");
|
||||
}
|
||||
|
||||
TEST(AudioFileParser, customArtistDelimiters_whitelist)
|
||||
{
|
||||
const TestTagReader testTags{
|
||||
{
|
||||
{ TagType::Album, { "MyAlbum" } },
|
||||
{ TagType::AlbumArtist, { " AC/DC " } },
|
||||
{ TagType::Artist, { "AC/DC " } },
|
||||
}
|
||||
};
|
||||
|
||||
AudioFileParserParameters params;
|
||||
params.artistTagDelimiters = { "/" };
|
||||
params.artistsToNotSplit = { "AC/DC" };
|
||||
TestAudioFileParser parser{ params };
|
||||
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
|
||||
|
||||
ASSERT_EQ(track->artists.size(), 1);
|
||||
EXPECT_EQ(track->artists[0].name, "AC/DC");
|
||||
EXPECT_EQ(track->artistDisplayName, "AC/DC");
|
||||
ASSERT_TRUE(track->medium.has_value());
|
||||
ASSERT_TRUE(track->medium->release.has_value());
|
||||
EXPECT_EQ(track->medium->release->name, "MyAlbum");
|
||||
ASSERT_EQ(track->medium->release->artists.size(), 1);
|
||||
EXPECT_EQ(track->medium->release->artists[0].name, "AC/DC");
|
||||
EXPECT_EQ(track->medium->release->artistDisplayName, "AC/DC");
|
||||
}
|
||||
|
||||
TEST(AudioFileParser, customArtistDelimiters_whitelist_multi)
|
||||
{
|
||||
const TestTagReader testTags{
|
||||
{
|
||||
{ TagType::Artist, { "AC/DC and MyArtist" } },
|
||||
{ TagType::Artists, { "AC/DC", "MyArtist" } },
|
||||
}
|
||||
};
|
||||
|
||||
AudioFileParserParameters params;
|
||||
params.artistTagDelimiters = { "/" };
|
||||
params.artistsToNotSplit = { "AC/DC" };
|
||||
TestAudioFileParser parser{ params };
|
||||
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
|
||||
|
||||
ASSERT_EQ(track->artists.size(), 2);
|
||||
EXPECT_EQ(track->artists[0].name, "AC/DC");
|
||||
EXPECT_EQ(track->artists[1].name, "MyArtist");
|
||||
EXPECT_EQ(track->artistDisplayName, "AC/DC, MyArtist"); // Reconstructed since this use case is not handled
|
||||
}
|
||||
|
||||
TEST(AudioFileParser, customDelimiters_foundInArtist)
|
||||
{
|
||||
const TestTagReader testTags{
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace lms::recommendation
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
return db::ScanSettings::get(session)->getSimilarityEngineType();
|
||||
return db::ScanSettings::find(session)->getSimilarityEngineType();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace lms::scanner
|
||||
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const ScanSettings::pointer scanSettings{ ScanSettings::get(session, name) };
|
||||
const ScanSettings::pointer scanSettings{ ScanSettings::find(session, name) };
|
||||
if (!scanSettings)
|
||||
return settings;
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace lms::scanner
|
||||
|
||||
settings->artistTagDelimiters = scanSettings->getArtistTagDelimiters();
|
||||
settings->defaultTagDelimiters = scanSettings->getDefaultTagDelimiters();
|
||||
settings->artistsToNotSplit = scanSettings->getArtistsToNotSplit();
|
||||
|
||||
settings->skipSingleReleasePlayLists = scanSettings->getSkipSingleReleasePlayLists();
|
||||
settings->allowArtistMBIDFallback = scanSettings->getAllowMBIDArtistMerge();
|
||||
@@ -125,7 +126,7 @@ namespace lms::scanner
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
ScanSettings::pointer scanSettings{ ScanSettings::get(session, name) };
|
||||
ScanSettings::pointer scanSettings{ ScanSettings::find(session, name) };
|
||||
if (!scanSettings)
|
||||
scanSettings = session.create<ScanSettings>(name);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace lms::scanner
|
||||
bool skipDuplicateTrackMBID{};
|
||||
std::vector<std::string> extraTags;
|
||||
std::vector<std::string> artistTagDelimiters;
|
||||
std::vector<std::string> artistsToNotSplit;
|
||||
std::vector<std::string> defaultTagDelimiters;
|
||||
bool skipSingleReleasePlayLists{};
|
||||
bool allowArtistMBIDFallback{ true }; // TODO false?
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace lms::scanner
|
||||
params.userExtraTags = settings.extraTags;
|
||||
params.artistTagDelimiters = settings.artistTagDelimiters;
|
||||
params.defaultTagDelimiters = settings.defaultTagDelimiters;
|
||||
params.artistsToNotSplit.insert(settings.artistsToNotSplit.cbegin(), settings.artistsToNotSplit.end());
|
||||
params.backend = metadata::ParserBackend::TagLib;
|
||||
params.readStyle = getParserReadStyle();
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WString.h>
|
||||
#include <Wt/WTemplateFormView.h>
|
||||
#include <Wt/WTextArea.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "database/ScanSettings.hpp"
|
||||
@@ -72,6 +72,7 @@ namespace lms::ui
|
||||
static inline constexpr Field SimilarityEngineTypeField{ "similarity-engine-type" };
|
||||
static inline constexpr Field SkipSingleReleasePlayListsField{ "skip-single-release-playlists" };
|
||||
static inline constexpr Field AllowMBIDArtistMergeField{ "allow-mbid-artist-merge" };
|
||||
static inline constexpr Field ArtistsToNotSplitField{ "artists-to-not-split" };
|
||||
|
||||
using UpdatePeriodModel = ValueStringModel<ScanSettings::UpdatePeriod>;
|
||||
|
||||
@@ -84,6 +85,7 @@ namespace lms::ui
|
||||
addField(SimilarityEngineTypeField);
|
||||
addField(SkipSingleReleasePlayListsField);
|
||||
addField(AllowMBIDArtistMergeField);
|
||||
addField(ArtistsToNotSplitField);
|
||||
|
||||
setValidator(UpdatePeriodField, createMandatoryValidator());
|
||||
setValidator(UpdateStartTimeField, createMandatoryValidator());
|
||||
@@ -100,7 +102,7 @@ namespace lms::ui
|
||||
{
|
||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||
|
||||
const ScanSettings::pointer scanSettings{ ScanSettings::get(LmsApp->getDbSession()) };
|
||||
const ScanSettings::pointer scanSettings{ ScanSettings::find(LmsApp->getDbSession()) };
|
||||
|
||||
auto periodRow{ _updatePeriodModel->getRowFromValue(scanSettings->getUpdatePeriod()) };
|
||||
if (periodRow)
|
||||
@@ -113,7 +115,7 @@ namespace lms::ui
|
||||
if (scanSettings->getUpdatePeriod() == ScanSettings::UpdatePeriod::Hourly
|
||||
|| scanSettings->getUpdatePeriod() == ScanSettings::UpdatePeriod::Never)
|
||||
{
|
||||
setReadOnly(DatabaseSettingsModel::UpdateStartTimeField, true);
|
||||
setReadOnly(UpdateStartTimeField, true);
|
||||
}
|
||||
|
||||
setValue(SkipSingleReleasePlayListsField, scanSettings->getSkipSingleReleasePlayLists());
|
||||
@@ -128,13 +130,20 @@ namespace lms::ui
|
||||
std::transform(std::cbegin(extraTags), std::cend(extraTags), std::back_inserter(extraTagsToScan), [](std::string_view extraTag) { return std::string{ extraTag }; });
|
||||
artistDelimiters = scanSettings->getArtistTagDelimiters();
|
||||
defaultDelimiters = scanSettings->getDefaultTagDelimiters();
|
||||
|
||||
{
|
||||
std::string artists{ core::stringUtils::joinStrings(scanSettings->getArtistsToNotSplit(), '\n') };
|
||||
setValue(ArtistsToNotSplitField, Wt::WString::fromUTF8(std::move(artists)));
|
||||
if (artistDelimiters.empty())
|
||||
setReadOnly(ArtistsToNotSplitField, true);
|
||||
}
|
||||
}
|
||||
|
||||
void saveData(std::span<const std::string_view> extraTagsToScan, std::span<const std::string_view> artistDelimiters, std::span<const std::string_view> defaultDelimiters)
|
||||
{
|
||||
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
|
||||
|
||||
ScanSettings::pointer scanSettings{ ScanSettings::get(LmsApp->getDbSession()) };
|
||||
ScanSettings::pointer scanSettings{ ScanSettings::find(LmsApp->getDbSession()) };
|
||||
|
||||
{
|
||||
const auto updatePeriodRow{ _updatePeriodModel->getRowFromString(valueText(UpdatePeriodField)) };
|
||||
@@ -167,10 +176,17 @@ namespace lms::ui
|
||||
scanSettings.modify()->setExtraTagsToScan(extraTagsToScan);
|
||||
scanSettings.modify()->setArtistTagDelimiters(artistDelimiters);
|
||||
scanSettings.modify()->setDefaultTagDelimiters(defaultDelimiters);
|
||||
|
||||
{
|
||||
const std::string artists{ valueText(ArtistsToNotSplitField).toUTF8() };
|
||||
std::vector<std::string_view> artistsToNotSplit{ core::stringUtils::splitString(artists, '\n') };
|
||||
scanSettings.modify()->setArtistsToNotSplit(artistsToNotSplit);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void initializeModels()
|
||||
void
|
||||
initializeModels()
|
||||
{
|
||||
_updatePeriodModel = std::make_shared<ValueStringModel<ScanSettings::UpdatePeriod>>();
|
||||
_updatePeriodModel->add(Wt::WString::tr("Lms.Admin.Database.never"), ScanSettings::UpdatePeriod::Never);
|
||||
@@ -246,13 +262,18 @@ namespace lms::ui
|
||||
LineEditContainerWidget(std::shared_ptr<Wt::WValidator> validator)
|
||||
: _validator{ validator } {}
|
||||
|
||||
Wt::Signal<std::size_t> sizeChanged;
|
||||
|
||||
void add(const Wt::WString& value = "")
|
||||
{
|
||||
auto* entry{ addNew<LineEditEntryWidget>(value, _validator) };
|
||||
|
||||
entry->deleted.connect(this, [=, this] {
|
||||
removeWidget(entry);
|
||||
sizeChanged.emit(count());
|
||||
});
|
||||
|
||||
sizeChanged.emit(count());
|
||||
}
|
||||
|
||||
bool validate()
|
||||
@@ -325,7 +346,7 @@ namespace lms::ui
|
||||
|
||||
clear();
|
||||
|
||||
auto t{ addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.Database.template")) };
|
||||
auto* t{ addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.Database.template")) };
|
||||
auto model{ std::make_shared<DatabaseSettingsModel>() };
|
||||
|
||||
// Update Period
|
||||
@@ -378,6 +399,12 @@ namespace lms::ui
|
||||
});
|
||||
}
|
||||
|
||||
t->setFormWidget(DatabaseSettingsModel::ArtistsToNotSplitField, std::make_unique<Wt::WTextArea>());
|
||||
artistTagDelimiters->sizeChanged.connect(this, [=](std::size_t newSize) {
|
||||
model->setReadOnly(DatabaseSettingsModel::ArtistsToNotSplitField, newSize == 0);
|
||||
t->updateView(model.get());
|
||||
});
|
||||
|
||||
// Default tag delimiter
|
||||
auto* defaultTagDelimiters{ t->bindNew<LineEditContainerWidget>("default-tag-delimiter-container", tagDelimiterValidator) };
|
||||
{
|
||||
@@ -424,7 +451,7 @@ namespace lms::ui
|
||||
|
||||
extraTagsToScan->clear();
|
||||
for (const std::string& extraTag : extraTags)
|
||||
extraTagsToScan->add(Wt::WString::fromUTF8(std::string{ extraTag }));
|
||||
extraTagsToScan->add(Wt::WString::fromUTF8(extraTag));
|
||||
|
||||
artistTagDelimiters->clear();
|
||||
for (const std::string& artistDelimiter : artistDelimiters)
|
||||
|
||||
Reference in New Issue
Block a user