Handle artist display names

This commit is contained in:
emeric
2023-10-14 19:22:13 +02:00
parent 76290768bb
commit d2956df4cc
20 changed files with 389 additions and 309 deletions
+3
View File
@@ -14,6 +14,7 @@ The following extra fields are implemented:
* `musicBrainzId` * `musicBrainzId`
* `genres` * `genres`
* `artists` * `artists`
* `displayArtist`
* `releaseTypes` * `releaseTypes`
* `moods` * `moods`
* `originalReleaseDate` * `originalReleaseDate`
@@ -23,7 +24,9 @@ The following extra fields are implemented:
* `musicBrainzId`: note this is actually the recording MBID when this response refers to a song * `musicBrainzId`: note this is actually the recording MBID when this response refers to a song
* `genres` * `genres`
* `artists` * `artists`
* `displayArtist`
* `albumArtists` * `albumArtists`
* `displayAlbumArtist`
* `contributors` * `contributors`
* `moods` * `moods`
* `replayGain` * `replayGain`
-1
View File
@@ -14,7 +14,6 @@
<div class="col"> <div class="col">
<h2>${name}</h2> <h2>${name}</h2>
${<if-has-release-artists>}${artists}${</if-has-release-artists>} ${<if-has-release-artists>}${artists}${</if-has-release-artists>}
${<if-has-various-release-artists>}<div class="text-secondary">${tr:Lms.Explore.various-artists}</div>${</if-has-various-release-artists>}
<div class="small text-muted">${<if-has-year>}${year} · ${</if-has-year>}${duration}</div> <div class="small text-muted">${<if-has-year>}${year} · ${</if-has-year>}${duration}</div>
${clusters class="mb-2"} ${clusters class="mb-2"}
<div class="btn-group"> <div class="btn-group">
+2 -3
View File
@@ -40,12 +40,11 @@
<message id="Lms.Explore.Releases.template.entry-grid"> <message id="Lms.Explore.Releases.template.entry-grid">
<div class="col h-100 Lms-bg-dark-hover rounded"> <div class="col h-100 Lms-bg-dark-hover rounded">
<div class="p-2"> <div class="p-2">
<div class="Lms-responsive-square Lms-cover-container"> <div class="Lms-responsive-square Lms-cover-container mb-1">
${cover class="shadow-sm"} ${cover class="shadow-sm"}
</div> </div>
${release-name class="d-block text-truncate text-nowrap text-decoration-none link-success"} ${release-name class="d-block text-truncate text-nowrap text-decoration-none link-success"}
${<if-has-artist>}${artist-name class="d-block text-truncate text-nowrap text-decoration-none link-secondary"}${</if-has-artist>} ${<if-has-artist>}${artist-name class="d-block text-truncate text-nowrap"}${</if-has-artist>}
${<if-has-various-artists>}<div class="text-truncate text-nowrap text-secondary">${tr:Lms.Explore.various-artists}</div>${</if-has-various-artists>}
${<if-has-year>}<div class="small text-muted">${year}</div>${</if-has-year>} ${<if-has-year>}<div class="small text-muted">${year}</div>${</if-has-year>}
</div> </div>
</div> </div>
+3
View File
@@ -210,6 +210,7 @@ getRelease(const TagMap& tags)
release.emplace(); release.emplace();
release->name = std::move(*releaseName); release->name = std::move(*releaseName);
release->artistDisplayName = getPropertyValueAs<std::string_view>(tags, "ALBUMARTIST").value_or("");
release->mbid = getPropertyValueFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"}); release->mbid = getPropertyValueFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"});
release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"}); release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
release->mediumCount = getPropertyValueAs<std::size_t>(tags, "DISCTOTAL"); release->mediumCount = getPropertyValueAs<std::size_t>(tags, "DISCTOTAL");
@@ -353,6 +354,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
track.copyrightURL = value; track.copyrightURL = value;
else if (tag == "REPLAYGAIN_TRACK_GAIN") else if (tag == "REPLAYGAIN_TRACK_GAIN")
track.replayGain = StringUtils::readAs<float>(value); track.replayGain = StringUtils::readAs<float>(value);
else if (tag == "ARTIST")
track.artistDisplayName = value;
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{ {
std::set<std::string> clusterNames; std::set<std::string> clusterNames;
@@ -79,6 +79,7 @@ namespace MetaData
std::optional<UUID> mbid; std::optional<UUID> mbid;
std::string name; std::string name;
std::string artistDisplayName;
std::vector<Artist> artists; std::vector<Artist> artists;
std::optional<std::size_t> mediumCount; std::optional<std::size_t> mediumCount;
std::optional<PrimaryType> primaryType; std::optional<PrimaryType> primaryType;
@@ -117,6 +118,7 @@ namespace MetaData
std::string copyright; std::string copyright;
std::string copyrightURL; std::string copyrightURL;
std::optional<float> replayGain; std::optional<float> replayGain;
std::string artistDisplayName;
std::vector<Artist> artists; std::vector<Artist> artists;
std::vector<Artist> conductorArtists; std::vector<Artist> conductorArtists;
std::vector<Artist> composerArtists; std::vector<Artist> composerArtists;
@@ -665,6 +665,18 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
static
void
migrateFromV40(Session& session)
{
// add artist_display_name in Release and Track
session.getDboSession().execute("ALTER TABLE release ADD artist_display_name TEXT");
session.getDboSession().execute("ALTER TABLE track ADD artist_display_name TEXT");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion();
}
void void
doDbMigration(Session& session) doDbMigration(Session& session)
{ {
@@ -711,6 +723,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
{37, migrateFromV37}, {37, migrateFromV37},
{38, migrateFromV38}, {38, migrateFromV38},
{39, migrateFromV39}, {39, migrateFromV39},
{40, migrateFromV40},
}; };
while (1) while (1)
@@ -26,7 +26,7 @@ namespace Database
class Session; class Session;
using Version = std::size_t; using Version = std::size_t;
static constexpr Version LMS_DATABASE_VERSION {40}; static constexpr Version LMS_DATABASE_VERSION {41};
class VersionInfo class VersionInfo
{ {
public: public:
@@ -113,6 +113,7 @@ namespace Database
Wt::WDateTime getLastWritten() const; Wt::WDateTime getLastWritten() const;
std::optional<ReleaseTypePrimary> getPrimaryType() const { return _primaryType; } std::optional<ReleaseTypePrimary> getPrimaryType() const { return _primaryType; }
EnumSet<ReleaseTypeSecondary> getSecondaryTypes() const { return _secondaryTypes; } EnumSet<ReleaseTypeSecondary> getSecondaryTypes() const { return _secondaryTypes; }
std::string_view getArtistDisplayName() const { return _artistDisplayName; }
std::size_t getTracksCount() const; std::size_t getTracksCount() const;
// Setters // Setters
@@ -121,6 +122,7 @@ namespace Database
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; } void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
void setPrimaryType(std::optional<ReleaseTypePrimary> type) { _primaryType = type; } void setPrimaryType(std::optional<ReleaseTypePrimary> type) { _primaryType = type; }
void setSecondaryTypes(EnumSet<ReleaseTypeSecondary> types) { _secondaryTypes = types; } void setSecondaryTypes(EnumSet<ReleaseTypeSecondary> types) { _secondaryTypes = types; }
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
// Get the artists of this release // Get the artists of this release
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const; std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
@@ -137,7 +139,7 @@ namespace Database
Wt::Dbo::field(a, _totalDisc, "total_disc"); Wt::Dbo::field(a, _totalDisc, "total_disc");
Wt::Dbo::field(a, _primaryType, "primary_type"); Wt::Dbo::field(a, _primaryType, "primary_type");
Wt::Dbo::field(a, _secondaryTypes, "secondary_types"); Wt::Dbo::field(a, _secondaryTypes, "secondary_types");
Wt::Dbo::field(a, _artistDisplayName, "artist_display_name");
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release"); Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
} }
@@ -155,6 +157,7 @@ namespace Database
std::optional<int> _totalDisc{}; std::optional<int> _totalDisc{};
std::optional<ReleaseTypePrimary> _primaryType; std::optional<ReleaseTypePrimary> _primaryType;
EnumSet<ReleaseTypeSecondary> _secondaryTypes; EnumSet<ReleaseTypeSecondary> _secondaryTypes;
std::string _artistDisplayName;
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
}; };
@@ -138,6 +138,7 @@ class Track final : public Object<Track, TrackId>
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); } void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; } void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc! void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc!
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
void clearArtistLinks(); void clearArtistLinks();
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink); void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); } void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
@@ -163,6 +164,7 @@ class Track final : public Object<Track, TrackId>
std::optional<std::string> getCopyrightURL() const; std::optional<std::string> getCopyrightURL() const;
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; } std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; } std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
std::string_view getArtistDisplayName() const { return _artistDisplayName; }
// no artistLinkTypes means get all // no artistLinkTypes means get all
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
@@ -195,6 +197,7 @@ class Track final : public Object<Track, TrackId>
Wt::Dbo::field(a, _copyrightURL, "copyright_url"); Wt::Dbo::field(a, _copyrightURL, "copyright_url");
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain"); Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain");
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?) Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?)
Wt::Dbo::field(a, _artistDisplayName, "artist_display_name");
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track"); Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
@@ -228,6 +231,7 @@ class Track final : public Object<Track, TrackId>
std::string _copyrightURL; std::string _copyrightURL;
std::optional<float> _trackReplayGain; std::optional<float> _trackReplayGain;
std::optional<float> _releaseReplayGain; std::optional<float> _releaseReplayGain;
std::string _artistDisplayName;
Wt::Dbo::ptr<Release> _release; Wt::Dbo::ptr<Release> _release;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks; Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
@@ -192,6 +192,8 @@ namespace
const EnumSet<ReleaseTypeSecondary> secondaryTypes{ convertReleaseTypesSecondary(releaseInfo.secondaryTypes) }; const EnumSet<ReleaseTypeSecondary> secondaryTypes{ convertReleaseTypesSecondary(releaseInfo.secondaryTypes) };
if (release->getSecondaryTypes() != secondaryTypes) if (release->getSecondaryTypes() != secondaryTypes)
release.modify()->setSecondaryTypes(secondaryTypes); release.modify()->setSecondaryTypes(secondaryTypes);
if (release->getArtistDisplayName() != releaseInfo.artistDisplayName)
release.modify()->setArtistDisplayName(releaseInfo.artistDisplayName);
} }
Release::pointer Release::pointer
@@ -525,5 +527,6 @@ namespace Scanner
track.modify()->setCopyright(trackInfo->copyright); track.modify()->setCopyright(trackInfo->copyright);
track.modify()->setCopyrightURL(trackInfo->copyrightURL); track.modify()->setCopyrightURL(trackInfo->copyrightURL);
track.modify()->setTrackReplayGain(trackInfo->replayGain); track.modify()->setTrackReplayGain(trackInfo->replayGain);
track.modify()->setArtistDisplayName(trackInfo->artistDisplayName);
} }
} }
@@ -186,6 +186,8 @@ namespace API::Subsonic
for (const Artist::pointer& artist : release->getReleaseArtists()) for (const Artist::pointer& artist : release->getReleaseArtists())
albumNode.addArrayChild("artists", createArtistNode(artist)); albumNode.addArrayChild("artists", createArtistNode(artist));
albumNode.setAttribute("displayArtist", release->getArtistDisplayName());
{ {
const Wt::WDate originalReleaseDate{ release->getOriginalReleaseDate() }; const Wt::WDate originalReleaseDate{ release->getOriginalReleaseDate() };
albumNode.setAttribute("originalReleaseDate", originalReleaseDate.isValid() ? StringUtils::toISO8601String(originalReleaseDate) : ""); albumNode.setAttribute("originalReleaseDate", originalReleaseDate.isValid() ? StringUtils::toISO8601String(originalReleaseDate) : "");
+9 -4
View File
@@ -134,11 +134,12 @@ namespace API::Subsonic
trackResponse.setAttribute("artistId", idToString(artists.front()->getId())); trackResponse.setAttribute("artistId", idToString(artists.front()->getId()));
} }
if (track->getRelease()) Release::pointer release{ track->getRelease() };
if (release)
{ {
trackResponse.setAttribute("album", track->getRelease()->getName()); trackResponse.setAttribute("album", release->getName());
trackResponse.setAttribute("albumId", idToString(track->getRelease()->getId())); trackResponse.setAttribute("albumId", idToString(release->getId()));
trackResponse.setAttribute("parent", idToString(track->getRelease()->getId())); trackResponse.setAttribute("parent", idToString(release->getId()));
} }
trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count()); trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count());
@@ -195,7 +196,11 @@ namespace API::Subsonic
} }; } };
addArtistLinks("artists", TrackArtistLinkType::Artist); addArtistLinks("artists", TrackArtistLinkType::Artist);
trackResponse.setAttribute("displayArtist", track->getArtistDisplayName());
addArtistLinks("albumartists", TrackArtistLinkType::ReleaseArtist); addArtistLinks("albumartists", TrackArtistLinkType::ReleaseArtist);
if (release)
trackResponse.setAttribute("displayAlbumArtist", release->getArtistDisplayName());
auto addClusters{ [&](std::string_view field, std::string_view clusterTypeName) auto addClusters{ [&](std::string_view field, std::string_view clusterTypeName)
{ {
+2 -2
View File
@@ -535,8 +535,8 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
if (!artists.empty()) if (!artists.empty())
{ {
entry->setCondition("if-has-artists", true); entry->setCondition("if-has-artists", true);
entry->bindWidget("artists", Utils::createArtistContainer(artists)); entry->bindWidget("artists", Utils::createArtistAnchorList(artists));
entry->bindWidget("artists-md", Utils::createArtistContainer(artists)); entry->bindWidget("artists-md", Utils::createArtistAnchorList(artists));
} }
const auto release {track->getRelease()}; const auto release {track->getRelease()};
+88 -23
View File
@@ -38,8 +38,7 @@
namespace UserInterface::Utils namespace UserInterface::Utils
{ {
std::string std::string durationToString(std::chrono::milliseconds msDuration)
durationToString(std::chrono::milliseconds msDuration)
{ {
const std::chrono::seconds duration{ std::chrono::duration_cast<std::chrono::seconds>(msDuration) }; const std::chrono::seconds duration{ std::chrono::duration_cast<std::chrono::seconds>(msDuration) };
@@ -64,8 +63,7 @@ namespace UserInterface::Utils
} }
std::unique_ptr<Wt::WImage> std::unique_ptr<Wt::WImage> createCover(Database::ReleaseId releaseId, CoverResource::Size size)
createCover(Database::ReleaseId releaseId, CoverResource::Size size)
{ {
auto cover{ std::make_unique<Wt::WImage>() }; auto cover{ std::make_unique<Wt::WImage>() };
cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(releaseId, size)); cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(releaseId, size));
@@ -74,8 +72,7 @@ namespace UserInterface::Utils
return cover; return cover;
} }
std::unique_ptr<Wt::WImage> std::unique_ptr<Wt::WImage> createCover(Database::TrackId trackId, CoverResource::Size size)
createCover(Database::TrackId trackId, CoverResource::Size size)
{ {
auto cover{ std::make_unique<Wt::WImage>() }; auto cover{ std::make_unique<Wt::WImage>() };
cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(trackId, size)); cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(trackId, size));
@@ -84,8 +81,7 @@ namespace UserInterface::Utils
return cover; return cover;
} }
std::unique_ptr<Wt::WInteractWidget> std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete)
createCluster(Database::ClusterId clusterId, bool canDelete)
{ {
auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
@@ -120,8 +116,7 @@ namespace UserInterface::Utils
return res; return res;
} }
std::unique_ptr<Wt::WContainerWidget> std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass)
createArtistContainer(const std::vector<Database::ArtistId>& artistIds)
{ {
using namespace Database; using namespace Database;
@@ -140,7 +135,8 @@ namespace UserInterface::Utils
artistContainer->addNew<Wt::WText>(" · "); artistContainer->addNew<Wt::WText>(" · ");
auto anchor{ createArtistAnchor(artist) }; auto anchor{ createArtistAnchor(artist) };
anchor->addStyleClass("link-success text-decoration-none"); // hack anchor->addStyleClass("text-decoration-none"); // hack
anchor->addStyleClass(std::string{ cssAnchorClass });
artistContainer->addWidget(std::move(anchor)); artistContainer->addWidget(std::move(anchor));
firstArtist = false; firstArtist = false;
} }
@@ -148,8 +144,82 @@ namespace UserInterface::Utils
return artistContainer; return artistContainer;
} }
Wt::WLink std::unique_ptr<Wt::WContainerWidget> createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass)
createArtistLink(Database::Artist::pointer artist) {
using namespace Database;
std::size_t matchCount{};
std::string_view::size_type currentOffset{};
auto result{ std::make_unique<Wt::WContainerWidget>() };
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
// consider order is guaranteed + we will likely succeed
for (const ArtistId artistId : artistIds)
{
const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
if (!artist)
break;
const auto pos{ displayName.find(artist->getName(), currentOffset) };
if (pos == std::string_view::npos)
break;
assert(pos >= currentOffset);
if (pos != currentOffset)
result->addNew<Wt::WText>(std::string{ displayName.substr(currentOffset, pos - currentOffset) }, Wt::TextFormat::Plain);
auto anchor{ createArtistAnchor(artist) };
anchor->addStyleClass("text-decoration-none"); // hack
anchor->addStyleClass(std::string{ cssAnchorClass }); // hack
result->addWidget(std::move(anchor));
currentOffset = pos + artist->getName().size();
matchCount += 1;
}
if (matchCount != artistIds.size())
return createArtistAnchorList(artistIds, cssAnchorClass);
return result;
}
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchorsForRelease(Database::ObjectPtr<Database::Release> release, Database::ArtistId omitIfMatchThisArtist, std::string_view cssAnchorClass)
{
using namespace Database;
Artist::FindParameters params;
params.setRelease(release->getId());
params.setLinkType(TrackArtistLinkType::ReleaseArtist);
if (const auto releaseArtists{ Artist::find(LmsApp->getDbSession(), params) }; !releaseArtists.results.empty())
{
if (releaseArtists.results.size() == 1 && releaseArtists.results.front() == omitIfMatchThisArtist)
return {};
return Utils::createArtistDisplayNameWithAnchors(release->getArtistDisplayName(), releaseArtists.results, cssAnchorClass);
}
params.setLinkType(TrackArtistLinkType::Artist);
const auto artists{ Artist::find(LmsApp->getDbSession(), params) };
if (artists.results.size() == 1)
{
if (artists.results.front() == omitIfMatchThisArtist)
return {};
return Utils::createArtistAnchorList({ artists.results.front() }, cssAnchorClass);
}
if (artists.results.size() > 1)
{
auto res{ std::make_unique<Wt::WContainerWidget>() };
res->addNew<Wt::WText>(Wt::WString::tr("Lms.Explore.various-artists"));
return res;
}
return {};
}
Wt::WLink createArtistLink(Database::Artist::pointer artist)
{ {
if (const auto mbid{ artist->getMBID() }) if (const auto mbid{ artist->getMBID() })
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()} }; return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()} };
@@ -157,8 +227,7 @@ namespace UserInterface::Utils
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() }; return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() };
} }
std::unique_ptr<Wt::WAnchor> std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::Artist::pointer artist, bool setText)
createArtistAnchor(Database::Artist::pointer artist, bool setText)
{ {
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist)); auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
@@ -172,8 +241,7 @@ namespace UserInterface::Utils
return res; return res;
} }
Wt::WLink Wt::WLink createReleaseLink(Database::Release::pointer release)
createReleaseLink(Database::Release::pointer release)
{ {
if (const auto mbid{ release->getMBID() }) if (const auto mbid{ release->getMBID() })
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()} }; return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()} };
@@ -181,8 +249,7 @@ namespace UserInterface::Utils
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() }; return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() };
} }
std::unique_ptr<Wt::WAnchor> std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::Release::pointer release, bool setText)
createReleaseAnchor(Database::Release::pointer release, bool setText)
{ {
auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release)); auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release));
@@ -196,8 +263,7 @@ namespace UserInterface::Utils
return res; return res;
} }
std::unique_ptr<Wt::WAnchor> std::unique_ptr<Wt::WAnchor> createTrackListAnchor(Database::TrackList::pointer trackList, bool setText)
createTrackListAnchor(Database::TrackList::pointer trackList, bool setText)
{ {
Wt::WLink link{ Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString() }; Wt::WLink link{ Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString() };
auto res{ std::make_unique<Wt::WAnchor>(link) }; auto res{ std::make_unique<Wt::WAnchor>(link) };
@@ -213,8 +279,7 @@ namespace UserInterface::Utils
return res; return res;
} }
std::unique_ptr<Wt::WContainerWidget> std::unique_ptr<Wt::WContainerWidget> createClustersForTrack(Database::Track::pointer track, Filters& filters)
createClustersForTrack(Database::Track::pointer track, Filters& filters)
{ {
using namespace Database; using namespace Database;
+4 -1
View File
@@ -21,6 +21,7 @@
#include <chrono> #include <chrono>
#include <string> #include <string>
#include <string_view>
#include <Wt/WContainerWidget.h> #include <Wt/WContainerWidget.h>
#include <Wt/WImage.h> #include <Wt/WImage.h>
@@ -57,7 +58,9 @@ namespace UserInterface::Utils
std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete = false); std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete = false);
std::unique_ptr<Wt::WContainerWidget> createArtistContainer(const std::vector<Database::ArtistId>& artists); std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
std::unique_ptr<Wt::WContainerWidget> createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchorsForRelease(Database::ObjectPtr<Database::Release> release, Database::ArtistId omitIfMatchThisArtist = {}, std::string_view cssAnchorClass = "link-success");
Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist); Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool setText = true); std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool setText = true);
+11 -23
View File
@@ -32,9 +32,9 @@ using namespace Database;
namespace UserInterface::ReleaseListHelpers namespace UserInterface::ReleaseListHelpers
{ {
static namespace
std::unique_ptr<Wt::WTemplate> {
createEntryInternal(const Release::pointer& release, const std::string& templateKey, const Artist::pointer& artist, const bool showYear) std::unique_ptr<Wt::WTemplate> createEntryInternal(const Release::pointer& release, const std::string& templateKey, const Artist::pointer& artist, const bool showYear)
{ {
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr(templateKey)) }; auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr(templateKey)) };
@@ -48,20 +48,11 @@ namespace UserInterface::ReleaseListHelpers
anchor->setImage(std::move(cover)); anchor->setImage(std::move(cover));
} }
auto artists {release->getReleaseArtists()}; auto artistAnchors{ Utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : ArtistId{}, "link-secondary") };
if (artists.empty()) if (artistAnchors)
artists = release->getArtists();
const bool isSameArtist {(std::find(std::cbegin(artists), std::cend(artists), artist) != artists.end())};
if (artists.size() > 1)
{
entry->setCondition("if-has-various-artists", true);
}
else if (artists.size() == 1 && !isSameArtist)
{ {
entry->setCondition("if-has-artist", true); entry->setCondition("if-has-artist", true);
entry->bindWidget("artist-name", Utils::createArtistAnchor(artists.front())); entry->bindWidget("artist-name", std::move(artistAnchors));
} }
if (showYear) if (showYear)
@@ -76,21 +67,19 @@ namespace UserInterface::ReleaseListHelpers
return entry; return entry;
} }
}
std::unique_ptr<Wt::WTemplate> std::unique_ptr<Wt::WTemplate> createEntry(const Release::pointer& release, const Artist::pointer& artist, bool showYear)
createEntry(const Release::pointer& release, const Artist::pointer& artist, bool showYear)
{ {
return createEntryInternal(release, "Lms.Explore.Releases.template.entry-grid", artist, showYear); return createEntryInternal(release, "Lms.Explore.Releases.template.entry-grid", artist, showYear);
} }
std::unique_ptr<Wt::WTemplate> std::unique_ptr<Wt::WTemplate> createEntry(const Release::pointer& release)
createEntry(const Release::pointer& release)
{ {
return createEntry(release, Artist::pointer{}, false /*year*/); return createEntry(release, Artist::pointer{}, false /*year*/);
} }
std::unique_ptr<Wt::WTemplate> std::unique_ptr<Wt::WTemplate> createEntryForArtist(const Database::Release::pointer& release, const Database::Artist::pointer& artist)
createEntryForArtist(const Database::Release::pointer& release, const Database::Artist::pointer& artist)
{ {
return createEntry(release, artist, true); return createEntry(release, artist, true);
} }
@@ -98,8 +87,7 @@ namespace UserInterface::ReleaseListHelpers
namespace UserInterface::ReleaseHelpers namespace UserInterface::ReleaseHelpers
{ {
Wt::WString Wt::WString buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet<ReleaseTypeSecondary> secondaryTypes)
buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet<ReleaseTypeSecondary> secondaryTypes)
{ {
Wt::WString res; Wt::WString res;
+6 -24
View File
@@ -128,7 +128,7 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
for (const auto& [role, artistIds] : artistMap) for (const auto& [role, artistIds] : artistMap)
{ {
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))}; std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistAnchorList(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))}; auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
artistsEntry->bindString("type", role); artistsEntry->bindString("type", role);
artistsEntry->bindWidget("artist-container", std::move(artistContainer)); artistsEntry->bindWidget("artist-container", std::move(artistContainer));
@@ -400,8 +400,8 @@ Release::refreshView()
if (variousArtists && !artists.empty()) if (variousArtists && !artists.empty())
{ {
entry->setCondition("if-has-artists", true); entry->setCondition("if-has-artists", true);
entry->bindWidget("artists", Utils::createArtistContainer(artists)); entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
entry->bindWidget("artists-md", Utils::createArtistContainer(artists)); entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(),artists));
} }
auto trackNumber {track->getTrackNumber()}; auto trackNumber {track->getTrackNumber()};
@@ -480,29 +480,11 @@ Release::refreshView()
void void
Release::refreshReleaseArtists(const Database::Release::pointer& release) Release::refreshReleaseArtists(const Database::Release::pointer& release)
{ {
std::vector<ObjectPtr<Artist>> artists; auto container{ Utils::createArtistsAnchorsForRelease(release) };
if (container)
artists = release->getReleaseArtists();
if (artists.empty())
{
artists = release->getArtists(TrackArtistLinkType::Artist);
if (artists.size() > 1)
{
setCondition("if-has-various-release-artists", true);
return;
}
}
if (!artists.empty())
{ {
setCondition("if-has-release-artists", true); setCondition("if-has-release-artists", true);
bindWidget("artists", std::move(container));
Wt::WContainerWidget* artistsContainer {bindNew<Wt::WContainerWidget>("artists")};
for (const auto& artist : artists)
{
Wt::WTemplate* artistTemplate {artistsContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-release-artist"))};
artistTemplate->bindWidget("artist", Utils::createArtistAnchor(artist));
}
} }
} }
+3 -3
View File
@@ -117,7 +117,7 @@ namespace UserInterface::TrackListHelpers
for (const auto& [role, artistIds] : artistMap) for (const auto& [role, artistIds] : artistMap)
{ {
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))}; std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistAnchorList(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))}; auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
artistsEntry->bindString("type", role); artistsEntry->bindString("type", role);
artistsEntry->bindWidget("artist-container", std::move(artistContainer)); artistsEntry->bindWidget("artist-container", std::move(artistContainer));
@@ -171,8 +171,8 @@ namespace UserInterface::TrackListHelpers
if (!artists.empty()) if (!artists.empty())
{ {
entry->setCondition("if-has-artists", true); entry->setCondition("if-has-artists", true);
entry->bindWidget("artists", Utils::createArtistContainer(artists)); entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
entry->bindWidget("artists-md", Utils::createArtistContainer(artists)); entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
} }
if (track->getRelease()) if (track->getRelease())
+6
View File
@@ -96,6 +96,9 @@ operator<<(std::ostream& os, const MetaData::Release& release)
if (release.mediumCount) if (release.mediumCount)
std::cout << "\tMediumCount: " << *release.mediumCount << std::endl; std::cout << "\tMediumCount: " << *release.mediumCount << std::endl;
if (!release.artistDisplayName.empty())
std::cout << "\tDisplay artist: " << release.artistDisplayName << std::endl;
for (const MetaData::Artist& artist : release.artists) for (const MetaData::Artist& artist : release.artists)
std::cout << "\tRelease artist: " << artist << std::endl; std::cout << "\tRelease artist: " << artist << std::endl;
@@ -154,6 +157,9 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
std::cout << "Parsed metadata:" << std::endl; std::cout << "Parsed metadata:" << std::endl;
if (!track->artistDisplayName.empty())
std::cout << "Display artist: " << track->artistDisplayName << std::endl;
for (const Artist& artist : track->artists) for (const Artist& artist : track->artists)
std::cout << "Artist: " << artist << std::endl; std::cout << "Artist: " << artist << std::endl;