${tr:Lms.Explore.duration}
diff --git a/src/libs/services/database/impl/Artist.cpp b/src/libs/services/database/impl/Artist.cpp
index b3588fb5..1bbfbfeb 100644
--- a/src/libs/services/database/impl/Artist.cpp
+++ b/src/libs/services/database/impl/Artist.cpp
@@ -28,6 +28,7 @@
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
+#include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp"
namespace Database
diff --git a/src/libs/services/database/impl/EnumSetTraits.hpp b/src/libs/services/database/impl/EnumSetTraits.hpp
new file mode 100644
index 00000000..7aef83af
--- /dev/null
+++ b/src/libs/services/database/impl/EnumSetTraits.hpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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 .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "utils/EnumSet.hpp"
+
+namespace Wt::Dbo
+{
+
+ template
+ struct sql_value_traits, void> : public sql_value_traits
+ {
+ using ValueType = typename EnumSet::ValueType;
+ static_assert(sizeof(long long) > sizeof(ValueType));
+
+ static void bind(EnumSet v, SqlStatement *statement, int column, int size)
+ {
+ sql_value_traits::bind(static_cast(v.getBitfield()), statement, column, size);
+ }
+
+ static bool read(EnumSet& v, SqlStatement *statement, int column, int size)
+ {
+ long long val;
+ if (sql_value_traits::read(val, statement, column, size))
+ {
+ v.setBitfield(val);
+ return true;
+ }
+ v.clear();
+ return false;
+ }
+ };
+}
+
diff --git a/src/libs/services/database/impl/Migration.cpp b/src/libs/services/database/impl/Migration.cpp
index 45561592..28b22dd8 100644
--- a/src/libs/services/database/impl/Migration.cpp
+++ b/src/libs/services/database/impl/Migration.cpp
@@ -653,6 +653,18 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
ScanSettings::get(session).modify()->incScanVersion();
}
+ static
+ void
+ migrateFromV39(Session& session)
+ {
+ // add release type
+ session.getDboSession().execute("ALTER TABLE release ADD primary_type INTEGER");
+ session.getDboSession().execute("ALTER TABLE release ADD secondary_types INTEGER");
+
+ // Just increment the scan version of the settings to make the next scheduled scan rescan everything
+ ScanSettings::get(session).modify()->incScanVersion();
+ }
+
void
doDbMigration(Session& session)
{
@@ -698,6 +710,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
{36, migrateFromV36},
{37, migrateFromV37},
{38, migrateFromV38},
+ {39, migrateFromV39},
};
while (1)
diff --git a/src/libs/services/database/impl/Migration.hpp b/src/libs/services/database/impl/Migration.hpp
index ed87913b..2c4f1896 100644
--- a/src/libs/services/database/impl/Migration.hpp
+++ b/src/libs/services/database/impl/Migration.hpp
@@ -26,7 +26,7 @@ namespace Database
class Session;
using Version = std::size_t;
- static constexpr Version LMS_DATABASE_VERSION {39};
+ static constexpr Version LMS_DATABASE_VERSION {40};
class VersionInfo
{
public:
diff --git a/src/libs/services/database/impl/Release.cpp b/src/libs/services/database/impl/Release.cpp
index fb09c963..93b6dfd3 100644
--- a/src/libs/services/database/impl/Release.cpp
+++ b/src/libs/services/database/impl/Release.cpp
@@ -28,6 +28,7 @@
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
+#include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
diff --git a/src/libs/services/database/impl/Session.cpp b/src/libs/services/database/impl/Session.cpp
index a1ff5149..0aa5d551 100644
--- a/src/libs/services/database/impl/Session.cpp
+++ b/src/libs/services/database/impl/Session.cpp
@@ -40,6 +40,7 @@
#include "services/database/TrackList.hpp"
#include "services/database/TrackFeatures.hpp"
#include "services/database/User.hpp"
+#include "EnumSetTraits.hpp"
#include "Migration.hpp"
namespace Database
diff --git a/src/libs/services/database/include/services/database/Release.hpp b/src/libs/services/database/include/services/database/Release.hpp
index 27932db0..f985a448 100644
--- a/src/libs/services/database/include/services/database/Release.hpp
+++ b/src/libs/services/database/include/services/database/Release.hpp
@@ -109,11 +109,15 @@ class Release : public Object
std::size_t getDiscCount() const; // may not be total disc (if incomplete for example)
std::chrono::milliseconds getDuration() const;
Wt::WDateTime getLastWritten() const;
+ std::optional getPrimaryType() const { return _primaryType; }
+ EnumSet getSecondaryTypes() const { return _secondaryTypes; }
// Setters
void setName(std::string_view name) { _name = name; }
void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
void setTotalDisc(std::optional totalDisc) { _totalDisc = totalDisc; }
+ void setPrimaryType(std::optional type) { _primaryType = type; }
+ void setSecondaryTypes(EnumSet types) { _secondaryTypes = types; }
// Get the artists of this release
std::vector> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
@@ -128,6 +132,8 @@ class Release : public Object
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _MBID, "mbid");
Wt::Dbo::field(a, _totalDisc, "total_disc");
+ Wt::Dbo::field(a, _primaryType, "primary_type");
+ Wt::Dbo::field(a, _secondaryTypes, "secondary_types");
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
}
@@ -139,9 +145,11 @@ class Release : public Object
static constexpr std::size_t _maxNameLength {128};
- std::string _name;
- std::string _MBID;
- std::optional _totalDisc {};
+ std::string _name;
+ std::string _MBID;
+ std::optional _totalDisc {};
+ std::optional _primaryType;
+ EnumSet _secondaryTypes;
Wt::Dbo::collection> _tracks; // Tracks in the release
};
diff --git a/src/libs/services/database/include/services/database/Types.hpp b/src/libs/services/database/include/services/database/Types.hpp
index 691d040c..57085732 100644
--- a/src/libs/services/database/include/services/database/Types.hpp
+++ b/src/libs/services/database/include/services/database/Types.hpp
@@ -191,5 +191,30 @@ namespace Database
Playlist, // user controlled playlists
Internal, // internal usage (current playqueue, history, ...)
};
+
+ // as defined in https://musicbrainz.org/doc/Release_Group/Type
+ enum class ReleaseTypePrimary
+ {
+ Album,
+ Single,
+ EP,
+ Broadcast,
+ Other,
+ };
+
+ enum class ReleaseTypeSecondary
+ {
+ Compilation,
+ Soundtrack,
+ Spokenword,
+ Interview,
+ Audiobook,
+ AudioDrama,
+ Live,
+ Remix,
+ DJMix,
+ Mixtape_Street,
+ Demo,
+ };
}
diff --git a/src/libs/services/database/test/Release.cpp b/src/libs/services/database/test/Release.cpp
index f9697eea..f0d90f70 100644
--- a/src/libs/services/database/test/Release.cpp
+++ b/src/libs/services/database/test/Release.cpp
@@ -507,3 +507,26 @@ TEST_F(DatabaseFixture, Release_getDiscCount)
EXPECT_EQ(release.get()->getDiscCount(), 2);
}
}
+
+TEST_F(DatabaseFixture, Release_releaseType)
+{
+ ScopedRelease release {session, "MyRelease"};
+
+ {
+ auto transaction {session.createSharedTransaction()};
+ EXPECT_EQ(release.get()->getPrimaryType(), std::nullopt);
+ EXPECT_EQ(release.get()->getSecondaryTypes(), EnumSet {});
+ }
+
+ {
+ auto transaction {session.createUniqueTransaction()};
+ release.get().modify()->setPrimaryType({ ReleaseTypePrimary::Album });
+ release.get().modify()->setSecondaryTypes({ ReleaseTypeSecondary::Compilation });
+ }
+
+ {
+ auto transaction {session.createSharedTransaction()};
+ EXPECT_EQ(release.get()->getPrimaryType(), ReleaseTypePrimary::Album);
+ EXPECT_TRUE(release.get()->getSecondaryTypes().contains(ReleaseTypeSecondary::Compilation));
+ }
+}
diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp
index 011fea52..f1b9b5d9 100644
--- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp
+++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp
@@ -115,6 +115,67 @@ namespace
return artists;
}
+ ReleaseTypePrimary convertReleaseTypePrimary(MetaData::Release::PrimaryType type)
+ {
+ switch (type)
+ {
+ case MetaData::Release::PrimaryType::Album: return ReleaseTypePrimary::Album;
+ case MetaData::Release::PrimaryType::Single: return ReleaseTypePrimary::Single;
+ case MetaData::Release::PrimaryType::EP: return ReleaseTypePrimary::EP;
+ case MetaData::Release::PrimaryType::Broadcast: return ReleaseTypePrimary::Broadcast;
+ case MetaData::Release::PrimaryType::Other: return ReleaseTypePrimary::Other;
+ }
+
+ return ReleaseTypePrimary::Other;
+ }
+
+ EnumSet convertReleaseTypesSecondary(EnumSet types)
+ {
+ EnumSet res;
+
+ for (MetaData::Release::SecondaryType type : types)
+ {
+ switch (type)
+ {
+ case MetaData::Release::SecondaryType::Compilation:
+ res.insert(ReleaseTypeSecondary::Compilation);
+ break;
+ case MetaData::Release::SecondaryType::Soundtrack:
+ res.insert(ReleaseTypeSecondary::Soundtrack);
+ break;
+ case MetaData::Release::SecondaryType::Spokenword:
+ res.insert(ReleaseTypeSecondary::Spokenword);
+ break;
+ case MetaData::Release::SecondaryType::Interview:
+ res.insert(ReleaseTypeSecondary::Interview);
+ break;
+ case MetaData::Release::SecondaryType::Audiobook:
+ res.insert(ReleaseTypeSecondary::Audiobook);
+ break;
+ case MetaData::Release::SecondaryType::AudioDrama:
+ res.insert(ReleaseTypeSecondary::AudioDrama);
+ break;
+ case MetaData::Release::SecondaryType::Live:
+ res.insert(ReleaseTypeSecondary::Live);
+ break;
+ case MetaData::Release::SecondaryType::Remix:
+ res.insert(ReleaseTypeSecondary::Remix);
+ break;
+ case MetaData::Release::SecondaryType::DJMix:
+ res.insert(ReleaseTypeSecondary::DJMix);
+ break;
+ case MetaData::Release::SecondaryType::Mixtape_Street:
+ res.insert(ReleaseTypeSecondary::Mixtape_Street);
+ break;
+ case MetaData::Release::SecondaryType::Demo:
+ res.insert(ReleaseTypeSecondary::Demo);
+ break;
+ }
+ }
+
+ return res;
+ }
+
void
updateReleaseIfNeeded(Release::pointer release, const MetaData::Release& releaseInfo)
{
@@ -122,6 +183,15 @@ namespace
release.modify()->setName(releaseInfo.name);
if (release->getTotalDisc() != releaseInfo.mediumCount)
release.modify()->setTotalDisc(releaseInfo.mediumCount);
+ if (releaseInfo.primaryType)
+ {
+ const ReleaseTypePrimary primaryType {convertReleaseTypePrimary(*releaseInfo.primaryType)};
+ if (release->getPrimaryType() != primaryType)
+ release.modify()->setPrimaryType(primaryType);
+ }
+ const EnumSet secondaryTypes {convertReleaseTypesSecondary(releaseInfo.secondaryTypes)};
+ if (release->getSecondaryTypes() != secondaryTypes)
+ release.modify()->setSecondaryTypes(secondaryTypes);
}
Release::pointer
diff --git a/src/libs/utils/include/utils/EnumSet.hpp b/src/libs/utils/include/utils/EnumSet.hpp
index f09ae1e2..997d81bd 100644
--- a/src/libs/utils/include/utils/EnumSet.hpp
+++ b/src/libs/utils/include/utils/EnumSet.hpp
@@ -31,9 +31,11 @@ class EnumSet
static_assert(std::is_enum::value);
static_assert(std::is_same::value || std::is_same::value);
- using index_type = std::uint_fast8_t;
+ using IndexType = std::uint_fast8_t;
public:
+ using ValueType = underlying_type;
+
EnumSet() = default;
constexpr EnumSet(std::initializer_list values)
{
@@ -112,14 +114,14 @@ class EnumSet
private:
friend class EnumSet;
- constexpr iterator(const EnumSet& _container, index_type _index)
+ constexpr iterator(const EnumSet& _container, IndexType _index)
: _container {_container}
, _index {_index}
{
}
const EnumSet& _container;
- index_type _index;
+ IndexType _index;
};
constexpr iterator begin() const
@@ -132,25 +134,45 @@ class EnumSet
return iterator {*this, npos};
}
- private:
- static_assert(std::numeric_limits::max() >= sizeof(underlying_type) * 8);
- enum : index_type { npos = sizeof(underlying_type) * 8 };
+ constexpr underlying_type getBitfield() const
+ {
+ return _bitfield;
+ }
- constexpr index_type getFirstBitSetIndex(index_type start = {}) const
+ constexpr void setBitfield(underlying_type bitfield)
+ {
+ _bitfield = bitfield;
+ }
+
+ constexpr bool operator==(const EnumSet other) const
+ {
+ return _bitfield == other._bitfield;
+ }
+
+ constexpr bool operator!=(const EnumSet other) const
+ {
+ return _bitfield != other._bitfield;
+ }
+
+ private:
+ static_assert(std::numeric_limits::max() >= sizeof(underlying_type) * 8);
+ enum : IndexType { npos = sizeof(underlying_type) * 8 };
+
+ constexpr IndexType getFirstBitSetIndex(IndexType start = {}) const
{
assert(start < npos);
// return npos if no bit found
- index_type res {countTrailingZero(_bitfield >> start)};
+ IndexType res {countTrailingZero(_bitfield >> start)};
if (res == npos)
return res;
return res + start;
}
- static constexpr index_type countTrailingZero(underlying_type bitField)
+ static constexpr IndexType countTrailingZero(underlying_type bitField)
{
- index_type res {};
+ IndexType res {};
while (res < (sizeof(underlying_type) * 8) && (bitField & 1) == 0)
{
@@ -166,5 +188,3 @@ class EnumSet
underlying_type _bitfield{};
};
-
-
diff --git a/src/libs/utils/test/CMakeLists.txt b/src/libs/utils/test/CMakeLists.txt
index 9f33170b..48da8343 100644
--- a/src/libs/utils/test/CMakeLists.txt
+++ b/src/libs/utils/test/CMakeLists.txt
@@ -1,8 +1,9 @@
include(GoogleTest)
add_executable(test-utils
- String.cpp
+ EnumSet.cpp
RecursiveSharedMutex.cpp
+ String.cpp
Utils.cpp
)
diff --git a/src/libs/utils/test/EnumSet.cpp b/src/libs/utils/test/EnumSet.cpp
new file mode 100644
index 00000000..b5902aa8
--- /dev/null
+++ b/src/libs/utils/test/EnumSet.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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 .
+ */
+
+#include
+
+#include "utils/EnumSet.hpp"
+
+TEST(EnumSet, ctr)
+{
+ enum class Foo
+ {
+ One,
+ Two,
+ };
+
+ {
+ constexpr EnumSet test {Foo::One};
+
+ static_assert(!test.empty());
+ static_assert(test.contains(Foo::One));
+ static_assert(!test.contains(Foo::Two));
+
+ EXPECT_TRUE(!test.empty());
+ EXPECT_TRUE(test.contains(Foo::One));
+ EXPECT_FALSE(test.contains(Foo::Two));
+
+ static_assert(test.getBitfield() != 0);
+ }
+
+ {
+ constexpr EnumSet test {Foo::One, Foo::Two};
+ constexpr auto bitfield {test.getBitfield()};
+
+ EnumSet test2;
+ EXPECT_FALSE(test2.contains(Foo::One));
+ EXPECT_FALSE(test2.contains(Foo::Two));
+
+ test2.setBitfield(bitfield);
+
+ EXPECT_TRUE(test2.contains(Foo::One));
+ EXPECT_TRUE(test2.contains(Foo::Two));
+ EXPECT_EQ(test, test2);
+ }
+}
diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp
index 0a7c36ac..194410e7 100644
--- a/src/lms/ui/explore/ReleaseView.cpp
+++ b/src/lms/ui/explore/ReleaseView.cpp
@@ -52,6 +52,43 @@ using namespace Database;
namespace UserInterface {
+Wt::WString
+buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet secondaryTypes)
+{
+ Wt::WString res;
+
+ switch (primaryType)
+ {
+ case ReleaseTypePrimary::Album: res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); break;
+ case ReleaseTypePrimary::Broadcast: res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); break;
+ case ReleaseTypePrimary::EP: res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); break;
+ case ReleaseTypePrimary::Single: res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); break;
+ case ReleaseTypePrimary::Other: res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); break;
+ }
+
+ for (ReleaseTypeSecondary secondaryType : secondaryTypes)
+ {
+ res += Wt::WString {" · "};
+
+ switch (secondaryType)
+ {
+ case ReleaseTypeSecondary::Compilation: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); break;
+ case ReleaseTypeSecondary::Spokenword: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); break;
+ case ReleaseTypeSecondary::Soundtrack: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); break;
+ case ReleaseTypeSecondary::Interview: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); break;
+ case ReleaseTypeSecondary::Audiobook: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); break;
+ case ReleaseTypeSecondary::AudioDrama: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); break;
+ case ReleaseTypeSecondary::Live: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); break;
+ case ReleaseTypeSecondary::Remix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); break;
+ case ReleaseTypeSecondary::DJMix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); break;
+ case ReleaseTypeSecondary::Mixtape_Street: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); break;
+ case ReleaseTypeSecondary::Demo: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); break;
+ }
+ }
+
+ return res;
+}
+
void
showReleaseInfoModal(Database::ReleaseId releaseId)
{
@@ -65,7 +102,12 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
Wt::WWidget* releaseInfoPtr {releaseInfo.get()};
releaseInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
- Wt::WContainerWidget* artistTable {releaseInfo->bindNew("artist-table")};
+ if (auto primaryReleaseType {release->getPrimaryType()})
+ {
+ releaseInfo->setCondition("if-has-release-type", true);
+ releaseInfo->bindString("release-type", buildReleaseTypeString(*primaryReleaseType, release->getSecondaryTypes()));
+ }
+
std::map> artistMap;
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
@@ -116,13 +158,19 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
artistMap.erase(itRolelessPerformers);
}
- for (const auto& [role, artistIds] : artistMap)
+ if (!artistMap.empty())
{
- std::unique_ptr artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
- auto artistsEntry {std::make_unique(Wt::WString::tr("Lms.Explore.template.info.artists"))};
- artistsEntry->bindString("type", role);
- artistsEntry->bindWidget("artist-container", std::move(artistContainer));
- artistTable->addWidget(std::move(artistsEntry));
+ releaseInfo->setCondition("if-has-artist", true);
+ Wt::WContainerWidget* artistTable {releaseInfo->bindNew("artist-table")};
+
+ for (const auto& [role, artistIds] : artistMap)
+ {
+ std::unique_ptr artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
+ auto artistsEntry {std::make_unique(Wt::WString::tr("Lms.Explore.template.info.artists"))};
+ artistsEntry->bindString("type", role);
+ artistsEntry->bindWidget("artist-container", std::move(artistContainer));
+ artistTable->addWidget(std::move(artistsEntry));
+ }
}
// TODO: save in DB and mean all this
diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp
index 039ca242..132a2bbf 100644
--- a/src/lms/ui/explore/TrackListHelpers.cpp
+++ b/src/lms/ui/explore/TrackListHelpers.cpp
@@ -60,7 +60,6 @@ namespace UserInterface::TrackListHelpers
Wt::WWidget* trackInfoPtr {trackInfo.get()};
trackInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
- Wt::WContainerWidget* artistTable {trackInfo->bindNew("artist-table")};
std::map> artistMap;
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
@@ -111,13 +110,19 @@ namespace UserInterface::TrackListHelpers
artistMap.erase(itRolelessPerformers);
}
- for (const auto& [role, artistIds] : artistMap)
+ if (!artistMap.empty())
{
- std::unique_ptr artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
- auto artistsEntry {std::make_unique(Wt::WString::tr("Lms.Explore.template.info.artists"))};
- artistsEntry->bindString("type", role);
- artistsEntry->bindWidget("artist-container", std::move(artistContainer));
- artistTable->addWidget(std::move(artistsEntry));
+ trackInfo->setCondition("if-has-artist", true);
+ Wt::WContainerWidget* artistTable {trackInfo->bindNew("artist-table")};
+
+ for (const auto& [role, artistIds] : artistMap)
+ {
+ std::unique_ptr artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
+ auto artistsEntry {std::make_unique(Wt::WString::tr("Lms.Explore.template.info.artists"))};
+ artistsEntry->bindString("type", role);
+ artistsEntry->bindWidget("artist-container", std::move(artistContainer));
+ artistTable->addWidget(std::move(artistsEntry));
+ }
}
if (const auto audioFile {Av::parseAudioFile(track->getPath())})