diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index ee61e067..d7906600 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -320,6 +320,11 @@ AND NOT EXISTS ( return ObjectPtr{ _image }; } + ImageId Artist::getImageId() const + { + return _image.id(); + } + RangeResults Artist::findSimilarArtistIds(core::EnumSet artistLinkTypes, std::optional range) const { assert(session()); diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index 4d367881..d14174b8 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -706,6 +706,11 @@ namespace lms::db return ObjectPtr{ _image }; } + ImageId Release::getImageId() const + { + return _image.id(); + } + void Release::clearLabels() { _labels.clear(); diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index d2b24cdf..39ee300d 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -33,6 +33,7 @@ #include "database/ArtistId.hpp" #include "database/ClusterId.hpp" #include "database/Filters.hpp" +#include "database/ImageId.hpp" #include "database/MediaLibraryId.hpp" #include "database/Object.hpp" #include "database/ReleaseId.hpp" @@ -137,6 +138,7 @@ namespace lms::db std::optional getMBID() const; bool hasMBID() const; ObjectPtr getImage() const; + ImageId getImageId() const; void visitLinks(std::function& link)> visitor) const; // No artistLinkTypes means get them all diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index 885b9672..03ed0b8f 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -34,6 +34,7 @@ #include "database/CountryId.hpp" #include "database/DirectoryId.hpp" #include "database/Filters.hpp" +#include "database/ImageId.hpp" #include "database/LabelId.hpp" #include "database/MediaLibraryId.hpp" #include "database/Object.hpp" @@ -292,6 +293,7 @@ namespace lms::db std::string_view getBarcode() const { return _barcode; } std::string_view getComment() const { return _comment; } ObjectPtr getImage() const; + ImageId getImageId() const; // Setters void setName(std::string_view name) { _name = name; } diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index a1b83427..5d34e9d2 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -25,10 +25,13 @@ #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/Utils.hpp" +#include "database/Artist.hpp" #include "database/Db.hpp" #include "database/Image.hpp" +#include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" +#include "database/TrackEmbeddedImage.hpp" #include "image/Exception.hpp" #include "image/IEncodedImage.hpp" #include "image/Image.hpp" @@ -178,6 +181,85 @@ namespace lms::cover return {}; } + ArtworkService::ImageFindResult ArtworkService::findArtistImage(db::ArtistId artistId) + { + db::Session& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + ImageFindResult res; + + if (const db::Artist::pointer artist{ db::Artist::find(session, artistId) }) + { + if (const db::ImageId imageId{ artist->getImageId() }; imageId.isValid()) + res = imageId; + + // TODO fallback on first release? + } + + return res; + } + + ArtworkService::ImageFindResult ArtworkService::findPreferredTrackImage(db::TrackId trackId) + { + db::Session& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + ImageFindResult res; + + db::TrackEmbeddedImage::FindParameters params; + params.setTrack(trackId); + params.setIsPreferred(true); + params.setRange(db::Range{ .offset = 0, .size = 1 }); + + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + res = image->getId(); + }); + + if (res.index() == 0) + { + if (db::Track::pointer track{ db::Track::find(session, trackId) }) + { + if (const db::Release::pointer release{ track->getRelease() }) + { + if (const db::ImageId imageId{ release->getImageId() }; imageId.isValid()) + res = imageId; + } + } + } + + return res; + } + + ArtworkService::ImageFindResult ArtworkService::findReleaseImage(db::ReleaseId releaseId) + { + db::Session& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + ImageFindResult res; + + const db::Release::pointer release{ db::Release::find(session, releaseId) }; + if (release) + { + if (const db::ImageId imageId{ release->getImageId() }; imageId.isValid()) + { + res = imageId; + } + else + { + db::TrackEmbeddedImage::FindParameters params; + params.setRelease(releaseId); + params.setIsPreferred(true); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize); + params.setRange(db::Range{ .offset = 0, .size = 1 }); + + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + res = image->getId(); + }); + } + } + + return res; + } + std::shared_ptr ArtworkService::getImage(db::ImageId imageId, std::optional width) { const ImageCache::EntryDesc cacheEntryDesc{ imageId, width }; diff --git a/src/libs/services/artwork/impl/ArtworkService.hpp b/src/libs/services/artwork/impl/ArtworkService.hpp index addfdca5..91b80177 100644 --- a/src/libs/services/artwork/impl/ArtworkService.hpp +++ b/src/libs/services/artwork/impl/ArtworkService.hpp @@ -47,6 +47,10 @@ namespace lms::cover ArtworkService& operator=(const ArtworkService&) = delete; private: + ImageFindResult findArtistImage(db::ArtistId artistId) override; + ImageFindResult findPreferredTrackImage(db::TrackId trackId) override; + ImageFindResult findReleaseImage(db::ReleaseId releaseId) override; + std::shared_ptr getImage(db::ImageId imageId, std::optional width) override; std::shared_ptr getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional width) override; diff --git a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp index 5da77f4a..d9dbb161 100644 --- a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp +++ b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp @@ -22,9 +22,13 @@ #include #include #include +#include +#include "database/ArtistId.hpp" #include "database/ImageId.hpp" +#include "database/ReleaseId.hpp" #include "database/TrackEmbeddedImageId.hpp" +#include "database/TrackId.hpp" #include "image/IEncodedImage.hpp" namespace lms::db @@ -39,6 +43,13 @@ namespace lms::cover public: virtual ~IArtworkService() = default; + // Helpers to find artworks + using ImageFindResult = std::variant; + virtual ImageFindResult findArtistImage(db::ArtistId artistId) = 0; + virtual ImageFindResult findPreferredTrackImage(db::TrackId trackId) = 0; + virtual ImageFindResult findReleaseImage(db::ReleaseId releaseId) = 0; + + // Image retrieval virtual std::shared_ptr getImage(db::ImageId imageId, std::optional width) = 0; virtual std::shared_ptr getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional width) = 0; diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index cec71030..2ee75e42 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -28,9 +28,9 @@ #include "database/Image.hpp" #include "database/Release.hpp" #include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" #include "database/Types.hpp" #include "database/User.hpp" +#include "services/artwork/IArtworkService.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" @@ -87,23 +87,19 @@ namespace lms::api::subsonic } albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getAddedTime())); - if (const auto image{ release->getImage() }) - { - const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() }; - albumNode.setAttribute("coverArt", idToString(coverArtId)); - } - else - { - db::TrackEmbeddedImage::FindParameters params; - params.setRelease(release->getId()); - params.setIsPreferred(true); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize); - params.setRange(db::Range{ 0, 1 }); - db::TrackEmbeddedImage::find(context.dbSession, params, [&](const db::TrackEmbeddedImage::pointer& image) { - const CoverArtId coverArtId{ image->getId() }; - albumNode.setAttribute("coverArt", idToString(coverArtId)); - }); + { + const auto imageResult{ core::Service::get()->findReleaseImage(release->getId()) }; + if (const db::ImageId * imageId{ std::get_if(&imageResult) }) + { + if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) }) + { + const CoverArtId coverArtId{ *imageId, image->getLastWriteTime().toTime_t() }; + albumNode.setAttribute("coverArt", idToString(coverArtId)); + } + } + else if (const db::TrackEmbeddedImageId * embeddedImageId{ std::get_if(&imageResult) }) + albumNode.setAttribute("coverArt", idToString(*embeddedImageId)); } if (const auto originalYear{ release->getOriginalYear() }) albumNode.setAttribute("year", *originalYear); diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index 632e3548..053b7dcc 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -27,6 +27,7 @@ #include "database/Release.hpp" #include "database/TrackArtistLink.hpp" #include "database/User.hpp" +#include "services/artwork/IArtworkService.hpp" #include "services/feedback/IFeedbackService.hpp" #include "CoverArtId.hpp" @@ -95,10 +96,18 @@ namespace lms::api::subsonic artistNode.setAttribute("id", idToString(artist->getId())); artistNode.setAttribute("name", artist->getName()); - if (const auto image{ artist->getImage() }) { - const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() }; - artistNode.setAttribute("coverArt", idToString(coverArtId)); + const auto imageResult{ core::Service::get()->findArtistImage(artist->getId()) }; + if (const db::ImageId * imageId{ std::get_if(&imageResult) }) + { + if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) }) + { + const CoverArtId coverArtId{ *imageId, image->getLastWriteTime().toTime_t() }; + artistNode.setAttribute("coverArt", idToString(coverArtId)); + } + } + else if (const db::TrackEmbeddedImageId * embeddedImageId{ std::get_if(&imageResult) }) + artistNode.setAttribute("coverArt", idToString(*embeddedImageId)); } const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) }; diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 830a11a5..876721b4 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -33,9 +33,9 @@ #include "database/Release.hpp" #include "database/Track.hpp" #include "database/TrackArtistLink.hpp" -#include "database/TrackEmbeddedImage.hpp" #include "database/Types.hpp" #include "database/User.hpp" +#include "services/artwork/IArtworkService.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" @@ -111,28 +111,18 @@ namespace lms::api::subsonic trackResponse.setAttribute("transcodedContentType", core::getMimeType(std::filesystem::path{ "." + fileSuffix })); } - const Release::pointer release{ track->getRelease() }; - { - TrackEmbeddedImage::FindParameters params; - params.setTrack(track->getId()); - params.setIsPreferred(true); - params.setRange(Range{ .offset = 0, .size = 1 }); - - bool hasEmbeddedImage{}; - TrackEmbeddedImage::find(context.dbSession, params, [&](const TrackEmbeddedImage::pointer& image) { - const CoverArtId coverArtId{ image->getId() }; - trackResponse.setAttribute("coverArt", idToString(coverArtId)); - }); - - if (!hasEmbeddedImage && release) + const auto imageResult{ core::Service::get()->findPreferredTrackImage(track->getId()) }; + if (const db::ImageId * imageId{ std::get_if(&imageResult) }) { - if (const db::Image::pointer image{ release->getImage() }) + if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) }) { - const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() }; + const CoverArtId coverArtId{ *imageId, image->getLastWriteTime().toTime_t() }; trackResponse.setAttribute("coverArt", idToString(coverArtId)); } } + else if (const db::TrackEmbeddedImageId * embeddedImageId{ std::get_if(&imageResult) }) + trackResponse.setAttribute("coverArt", idToString(*embeddedImageId)); } const std::vector& artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; @@ -147,6 +137,7 @@ namespace lms::api::subsonic trackResponse.setAttribute("artistId", idToString(artists.front()->getId())); } + const Release::pointer release{ track->getRelease() }; if (release) { trackResponse.setAttribute("album", release->getName()); diff --git a/src/lms/ui/resource/ArtworkResource.cpp b/src/lms/ui/resource/ArtworkResource.cpp index b190b819..8cb14e9a 100644 --- a/src/lms/ui/resource/ArtworkResource.cpp +++ b/src/lms/ui/resource/ArtworkResource.cpp @@ -26,13 +26,6 @@ #include "core/ITraceLogger.hpp" #include "core/Service.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/Image.hpp" -#include "database/Release.hpp" -#include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/Types.hpp" #include "services/artwork/IArtworkService.hpp" #include "LmsApplication.hpp" @@ -58,19 +51,15 @@ namespace lms::ui { std::string url; - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - - const db::Artist::pointer artist{ db::Artist::find(LmsApp->getDbSession(), artistId) }; - if (artist) - { - if (const db::Image::pointer image{ artist->getImage() }) - url = getImageUrl(image->getId(), size, "artist"); - } - } - - if (url.empty()) - url = getDefaultArtistImageUrl(); + const auto imageResult{ core::Service::get()->findArtistImage(artistId) }; + std::visit([&](const auto& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + url = getDefaultArtistImageUrl(); + else + url = getImageUrl(arg, size, "artist"); + }, + imageResult); return url; } @@ -79,33 +68,15 @@ namespace lms::ui { std::string url; - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - - const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) }; - if (release) - { - if (const db::Image::pointer image{ release->getImage() }) - { - url = getImageUrl(image->getId(), size, "release"); - } - else - { - db::TrackEmbeddedImage::FindParameters params; - params.setRelease(releaseId); - params.setIsPreferred(true); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize); - params.setRange(db::Range{ 0, 1 }); - - db::TrackEmbeddedImage::find(LmsApp->getDbSession(), params, [&](const db::TrackEmbeddedImage::pointer& image) { - url = getImageUrl(image->getId(), size, "release"); - }); - } - } - } - - if (url.empty()) - url = getDefaultReleaseCoverUrl(); + const auto imageResult{ core::Service::get()->findReleaseImage(releaseId) }; + std::visit([&](const auto& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + url = getDefaultReleaseCoverUrl(); + else + url = getImageUrl(arg, size, "release"); + }, + imageResult); return url; } @@ -114,34 +85,15 @@ namespace lms::ui { std::string url; - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - - db::TrackEmbeddedImage::FindParameters params; - params.setTrack(trackId); - params.setIsPreferred(true); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - - db::TrackEmbeddedImage::find(LmsApp->getDbSession(), params, [&](const db::TrackEmbeddedImage::pointer& image) { - url = getImageUrl(image->getId(), size, "release"); - }); - - if (url.empty()) - { - db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), trackId) }; - if (track) - { - if (const db::Release::pointer release{ track->getRelease() }) - { - if (const db::Image::pointer image{ release->getImage() }) - url = getImageUrl(image->getId(), size, "release"); - } - } - } - } - - if (url.empty()) - url = getDefaultReleaseCoverUrl(); + const auto imageResult{ core::Service::get()->findPreferredTrackImage(trackId) }; + std::visit([&](const auto& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + url = getDefaultReleaseCoverUrl(); + else + url = getImageUrl(arg, size, "release"); + }, + imageResult); return url; } @@ -155,9 +107,9 @@ namespace lms::ui return res; } - std::string ArtworkResource::getImageUrl(db::TrackEmbeddedImageId trackId, std::optional size, std::string_view type) const + std::string ArtworkResource::getImageUrl(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional size, std::string_view type) const { - std::string res{ url() + "&trimageid=" + trackId.toString() + "&type=" + std::string{ type } }; + std::string res{ url() + "&trimageid=" + trackEmbeddedImageId.toString() + "&type=" + std::string{ type } }; if (size) res += "&size=" + std::to_string(static_cast(*size)); return res;