Added a dedicated table for release artists + handle display names in UI/Subsonic API, ref #731
This commit is contained in:
@@ -109,7 +109,7 @@ namespace lms::api::subsonic
|
||||
{
|
||||
// API says: "Returns a random collection of songs from the given artist and similar artists"
|
||||
const std::size_t similarArtistCount{ count / 5 };
|
||||
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, similarArtistCount) };
|
||||
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, { TrackArtistLinkType::Artist }, similarArtistCount) };
|
||||
artistIds.push_back(artistId);
|
||||
|
||||
const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 };
|
||||
@@ -392,16 +392,16 @@ namespace lms::api::subsonic
|
||||
case SubsonicArtistListMode::AllArtists:
|
||||
break;
|
||||
case SubsonicArtistListMode::ReleaseArtists:
|
||||
parameters.setLinkType(TrackArtistLinkType::ReleaseArtist);
|
||||
parameters.setReleaseArtistsOnly(true);
|
||||
break;
|
||||
case SubsonicArtistListMode::TrackArtists:
|
||||
parameters.setLinkType(TrackArtistLinkType::Artist);
|
||||
parameters.setTrackArtistLinkType(TrackArtistLinkType::Artist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
parameters.filters.setMediaLibrary(mediaLibrary);
|
||||
|
||||
// This endpoint does not scale: make sort lived transactions in order not to block the whole application
|
||||
// This endpoint does not scale: make short lived transactions in order not to block the whole application
|
||||
|
||||
// first pass: dispatch the artists by first letter
|
||||
LMS_LOG(API_SUBSONIC, DEBUG, "GetArtists: fetching all artists...");
|
||||
@@ -419,12 +419,7 @@ namespace lms::api::subsonic
|
||||
{
|
||||
std::string_view sortName{ artist->getSortName() };
|
||||
|
||||
char sortChar;
|
||||
if (sortName.empty() || !std::isalpha(sortName[0]))
|
||||
sortChar = '#';
|
||||
else
|
||||
sortChar = std::toupper(sortName[0]);
|
||||
|
||||
const char sortChar{ (sortName.empty() || !std::isalpha(sortName[0])) ? '#' : static_cast<char>(std::toupper(sortName[0])) };
|
||||
artistsSortedByFirstChar[sortChar].push_back(artist->getId());
|
||||
}
|
||||
|
||||
@@ -465,9 +460,18 @@ namespace lms::api::subsonic
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
Response::Node artistNode{ createArtistNode(context, artist) };
|
||||
|
||||
const auto releases{ Release::find(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
|
||||
for (const Release::pointer& release : releases.results)
|
||||
auto addRelease{ [&](const Release::pointer& release) {
|
||||
artistNode.addArrayChild("album", createAlbumNode(context, release, true /* id3 */));
|
||||
} };
|
||||
|
||||
Release::find(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId()), [&](const db::Release::pointer& release) {
|
||||
addRelease(release);
|
||||
});
|
||||
|
||||
Release::find(context.getDbSession(), Release::FindParameters{}.setTrackArtist(artist->getId()), [&](const db::Release::pointer& release) {
|
||||
if (!release->hasArtist(id))
|
||||
addRelease(release);
|
||||
});
|
||||
|
||||
response.addNode("artist", std::move(artistNode));
|
||||
|
||||
@@ -561,7 +565,7 @@ namespace lms::api::subsonic
|
||||
});
|
||||
}
|
||||
|
||||
auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, count) };
|
||||
auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist }, count) };
|
||||
|
||||
{
|
||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
@@ -99,23 +100,38 @@ namespace lms::api::subsonic
|
||||
else if (const auto year{ release->getYear() })
|
||||
albumNode.setAttribute("year", *year);
|
||||
|
||||
auto artists{ release->getReleaseArtists() };
|
||||
if (artists.empty())
|
||||
artists = release->getArtists();
|
||||
|
||||
if (!artists.empty())
|
||||
struct Artist
|
||||
{
|
||||
if (!release->getArtistDisplayName().empty())
|
||||
albumNode.setAttribute("artist", release->getArtistDisplayName());
|
||||
else
|
||||
albumNode.setAttribute("artist", utils::joinArtistNames(artists));
|
||||
std::string name;
|
||||
std::string id;
|
||||
};
|
||||
std::optional<Artist> artist;
|
||||
|
||||
if (artists.size() == 1)
|
||||
const auto artistLinks{ release->getArtistLinks() };
|
||||
if (!artistLinks.empty())
|
||||
{
|
||||
artist = Artist{ .name = std::string{ release->getArtistDisplayName() }, .id = {} };
|
||||
if (artistLinks.size() == 1)
|
||||
artist->id = idToString(artistLinks.front()->getArtistId());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const auto trackArtists{ release->getTrackArtists(TrackArtistLinkType::Artist) }; !trackArtists.empty())
|
||||
{
|
||||
albumNode.setAttribute("artistId", idToString(artists.front()->getId()));
|
||||
if (trackArtists.size() > 1)
|
||||
artist = Artist{ .name = "Various Artists", .id = {} };
|
||||
else if (trackArtists.size() == 1)
|
||||
artist = Artist{ .name = trackArtists.front()->getName(), .id = idToString(trackArtists.front()->getId()) };
|
||||
}
|
||||
}
|
||||
|
||||
if (artist)
|
||||
{
|
||||
albumNode.setAttribute("artist", artist->name);
|
||||
if (!artist->id.empty())
|
||||
albumNode.setAttribute("artistId", artist->id);
|
||||
}
|
||||
|
||||
albumNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.getUser()->getId(), release->getId()));
|
||||
|
||||
// Report the first GENRE for this track
|
||||
@@ -182,16 +198,16 @@ namespace lms::api::subsonic
|
||||
if (id3)
|
||||
{
|
||||
albumNode.createEmptyArrayChild("artists");
|
||||
for (const Artist::pointer& artist : release->getReleaseArtists())
|
||||
albumNode.addArrayChild("artists", createArtistNode(artist));
|
||||
for (const db::ReleaseArtistLink::pointer& artistLink : artistLinks)
|
||||
albumNode.addArrayChild("artists", createMinimalArtistNode(artistLink));
|
||||
|
||||
albumNode.setAttribute("displayArtist", release->getArtistDisplayName());
|
||||
}
|
||||
else
|
||||
{
|
||||
albumNode.createEmptyArrayChild("albumArtists");
|
||||
for (const Artist::pointer& artist : release->getReleaseArtists())
|
||||
albumNode.addArrayChild("albumArtists", createArtistNode(artist));
|
||||
for (const db::ReleaseArtistLink::pointer& artistLink : artistLinks)
|
||||
albumNode.addArrayChild("albumArtists", createMinimalArtistNode(artistLink));
|
||||
|
||||
albumNode.setAttribute("displayAlbumArtist", release->getArtistDisplayName());
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
@@ -66,18 +67,25 @@ namespace lms::api::subsonic
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateArtist");
|
||||
|
||||
Response::Node artistNode{ createArtistNode(artist) };
|
||||
Response::Node artistNode{ createMinimalArtistNode(artist) };
|
||||
|
||||
artistNode.setAttribute("id", idToString(artist->getId()));
|
||||
artistNode.setAttribute("name", artist->getName());
|
||||
if (const auto artwork{ artist->getPreferredArtwork() })
|
||||
{
|
||||
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
|
||||
artistNode.setAttribute("coverArt", idToString(coverArtId));
|
||||
}
|
||||
|
||||
const std::size_t count{ Release::getCount(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
|
||||
artistNode.setAttribute("albumCount", count);
|
||||
{
|
||||
std::size_t count{ Release::getCount(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
|
||||
|
||||
// TODO: not very efficient
|
||||
Release::find(context.getDbSession(), Release::FindParameters{}.setTrackArtist(artist->getId()), [&](const db::Release::pointer& release) {
|
||||
if (!release->hasArtist(artist->getId()))
|
||||
count++;
|
||||
});
|
||||
|
||||
artistNode.setAttribute("albumCount", count);
|
||||
}
|
||||
|
||||
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.getUser()->getId(), artist->getId()) }; dateTime.isValid())
|
||||
artistNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
|
||||
@@ -107,7 +115,7 @@ namespace lms::api::subsonic
|
||||
return artistNode;
|
||||
}
|
||||
|
||||
Response::Node createArtistNode(const Artist::pointer& artist)
|
||||
Response::Node createMinimalArtistNode(const Artist::pointer& artist)
|
||||
{
|
||||
Response::Node artistNode;
|
||||
|
||||
@@ -116,4 +124,24 @@ namespace lms::api::subsonic
|
||||
|
||||
return artistNode;
|
||||
}
|
||||
|
||||
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::ReleaseArtistLink>& artistLink)
|
||||
{
|
||||
Response::Node artistNode;
|
||||
|
||||
artistNode.setAttribute("id", idToString(artistLink->getArtistId()));
|
||||
artistNode.setAttribute("name", artistLink->getArtistName());
|
||||
|
||||
return artistNode;
|
||||
}
|
||||
|
||||
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::TrackArtistLink>& artistLink)
|
||||
{
|
||||
Response::Node artistNode;
|
||||
|
||||
artistNode.setAttribute("id", idToString(artistLink->getArtistId()));
|
||||
artistNode.setAttribute("name", artistLink->getArtistName());
|
||||
|
||||
return artistNode;
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -30,6 +30,8 @@
|
||||
namespace lms::db
|
||||
{
|
||||
class Artist;
|
||||
class ReleaseArtistLink;
|
||||
class TrackArtistLink;
|
||||
class User;
|
||||
class Session;
|
||||
} // namespace lms::db
|
||||
@@ -45,5 +47,7 @@ namespace lms::api::subsonic
|
||||
} // namespace utils
|
||||
|
||||
Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr<db::Artist>& artist);
|
||||
Response::Node createArtistNode(const db::ObjectPtr<db::Artist>& artist); // only minimal info
|
||||
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::Artist>& artist); // only minimal info
|
||||
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::ReleaseArtistLink>& artistLink); // only minimal info
|
||||
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::TrackArtistLink>& artistLink); // only minimal info
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -27,14 +27,14 @@
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink, const db::ObjectPtr<db::Artist>& artist)
|
||||
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink)
|
||||
{
|
||||
Response::Node contributorNode;
|
||||
|
||||
contributorNode.setAttribute("role", utils::toString(trackArtistLink->getType()));
|
||||
if (!trackArtistLink->getSubType().empty())
|
||||
contributorNode.setAttribute("subRole", trackArtistLink->getSubType());
|
||||
contributorNode.addChild("artist", createArtistNode(artist));
|
||||
contributorNode.addChild("artist", createMinimalArtistNode(trackArtistLink));
|
||||
|
||||
return contributorNode;
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@ namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink, const db::ObjectPtr<db::Artist>& artist);
|
||||
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink);
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "core/MimeTypes.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
@@ -198,19 +200,23 @@ namespace lms::api::subsonic
|
||||
trackResponse.createEmptyArrayChild("artists");
|
||||
trackResponse.createEmptyArrayChild("contributors");
|
||||
|
||||
db::TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const db::TrackArtistLink::pointer& link, const db::Artist::pointer& artist) {
|
||||
switch (link->getType())
|
||||
track->visitArtistLinks([&](const db::TrackArtistLink::pointer& artistLink) {
|
||||
switch (artistLink->getType())
|
||||
{
|
||||
case db::TrackArtistLinkType::Artist:
|
||||
trackResponse.addArrayChild("artists", createArtistNode(artist));
|
||||
break;
|
||||
case db::TrackArtistLinkType::ReleaseArtist:
|
||||
trackResponse.addArrayChild("albumArtists", createArtistNode(artist));
|
||||
trackResponse.addArrayChild("artists", createMinimalArtistNode(artistLink));
|
||||
break;
|
||||
default:
|
||||
trackResponse.addArrayChild("contributors", createContributorNode(link, artist));
|
||||
trackResponse.addArrayChild("contributors", createContributorNode(artistLink));
|
||||
}
|
||||
});
|
||||
|
||||
if (release)
|
||||
{
|
||||
release->visitArtistLinks([&](const db::ReleaseArtistLink::pointer& artistLink) {
|
||||
trackResponse.addArrayChild("albumArtists", createMinimalArtistNode(artistLink));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
trackResponse.setAttribute("displayArtist", track->getArtistDisplayName());
|
||||
|
||||
Reference in New Issue
Block a user