From df8dd91c4d0731248e177ae0134bdd0fa51b8f7e Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 9 May 2025 12:51:23 +0200 Subject: [PATCH 01/24] Centralized artwork lookup logic --- src/libs/database/impl/Artist.cpp | 5 + src/libs/database/impl/Release.cpp | 5 + src/libs/database/include/database/Artist.hpp | 2 + .../database/include/database/Release.hpp | 2 + .../services/artwork/impl/ArtworkService.cpp | 82 ++++++++++++++ .../services/artwork/impl/ArtworkService.hpp | 4 + .../services/artwork/IArtworkService.hpp | 11 ++ src/libs/subsonic/impl/responses/Album.cpp | 30 +++-- src/libs/subsonic/impl/responses/Artist.cpp | 15 ++- src/libs/subsonic/impl/responses/Song.cpp | 25 ++--- src/lms/ui/resource/ArtworkResource.cpp | 106 +++++------------- 11 files changed, 173 insertions(+), 114 deletions(-) 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; From d1314ddf0da38f1aec613dac24f29eb315f49776 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 9 May 2025 13:25:17 +0200 Subject: [PATCH 02/24] Centralized artwork lookup logic for playlists --- .../services/artwork/impl/ArtworkService.cpp | 20 +++++++++++++ .../services/artwork/impl/ArtworkService.hpp | 1 + .../services/artwork/IArtworkService.hpp | 2 ++ .../subsonic/impl/endpoints/Playlists.cpp | 6 ++-- src/libs/subsonic/impl/responses/Playlist.cpp | 30 +++++++++++-------- src/libs/subsonic/impl/responses/Playlist.hpp | 7 +++-- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index 5d34e9d2..a65a19f6 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -260,6 +260,26 @@ namespace lms::cover return res; } + ArtworkService::ImageFindResult ArtworkService::findTrackListImage(db::TrackListId trackListId) + { + db::Session& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + ImageFindResult res; + + db::TrackEmbeddedImage::FindParameters params; + params.setTrackList(trackListId); + 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 91b80177..e1b77906 100644 --- a/src/libs/services/artwork/impl/ArtworkService.hpp +++ b/src/libs/services/artwork/impl/ArtworkService.hpp @@ -50,6 +50,7 @@ namespace lms::cover ImageFindResult findArtistImage(db::ArtistId artistId) override; ImageFindResult findPreferredTrackImage(db::TrackId trackId) override; ImageFindResult findReleaseImage(db::ReleaseId releaseId) override; + ImageFindResult findTrackListImage(db::TrackListId trackListId) 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 d9dbb161..259474bc 100644 --- a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp +++ b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp @@ -29,6 +29,7 @@ #include "database/ReleaseId.hpp" #include "database/TrackEmbeddedImageId.hpp" #include "database/TrackId.hpp" +#include "database/TrackListId.hpp" #include "image/IEncodedImage.hpp" namespace lms::db @@ -48,6 +49,7 @@ namespace lms::cover virtual ImageFindResult findArtistImage(db::ArtistId artistId) = 0; virtual ImageFindResult findPreferredTrackImage(db::TrackId trackId) = 0; virtual ImageFindResult findReleaseImage(db::ReleaseId releaseId) = 0; + virtual ImageFindResult findTrackListImage(db::TrackListId trackListId) = 0; // Image retrieval virtual std::shared_ptr getImage(db::ImageId imageId, std::optional width) = 0; diff --git a/src/libs/subsonic/impl/endpoints/Playlists.cpp b/src/libs/subsonic/impl/endpoints/Playlists.cpp index 5224b988..a3d2e4bb 100644 --- a/src/libs/subsonic/impl/endpoints/Playlists.cpp +++ b/src/libs/subsonic/impl/endpoints/Playlists.cpp @@ -59,7 +59,7 @@ namespace lms::api::subsonic Response::Node& playlistsNode{ response.createNode("playlists") }; auto addTrackList{ [&](const db::TrackList::pointer& trackList) { - playlistsNode.addArrayChild("playlist", createPlaylistNode(trackList, context.dbSession)); + playlistsNode.addArrayChild("playlist", createPlaylistNode(context, trackList)); } }; // First add user's playlists @@ -104,7 +104,7 @@ namespace lms::api::subsonic throw RequestedDataNotFoundError{}; Response response{ Response::createOkResponse(context.serverProtocolVersion) }; - Response::Node playlistNode{ createPlaylistNode(trackList, context.dbSession) }; + Response::Node playlistNode{ createPlaylistNode(context, trackList) }; auto entries{ trackList->getEntries() }; for (const TrackListEntry::pointer& entry : entries.results) @@ -157,7 +157,7 @@ namespace lms::api::subsonic } Response response{ Response::createOkResponse(context.serverProtocolVersion) }; - Response::Node playlistNode{ createPlaylistNode(trackList, context.dbSession) }; + Response::Node playlistNode{ createPlaylistNode(context, trackList) }; auto entries{ trackList->getEntries() }; for (const TrackListEntry::pointer& entry : entries.results) diff --git a/src/libs/subsonic/impl/responses/Playlist.cpp b/src/libs/subsonic/impl/responses/Playlist.cpp index e6a01ff4..4b5e54cb 100644 --- a/src/libs/subsonic/impl/responses/Playlist.cpp +++ b/src/libs/subsonic/impl/responses/Playlist.cpp @@ -19,17 +19,20 @@ #include "Playlist.hpp" +#include "core/Service.hpp" #include "core/String.hpp" -#include "database/TrackEmbeddedImage.hpp" +#include "database/Image.hpp" #include "database/TrackList.hpp" #include "database/User.hpp" +#include "services/artwork/IArtworkService.hpp" #include "CoverArtId.hpp" +#include "RequestContext.hpp" #include "SubsonicId.hpp" namespace lms::api::subsonic { - Response::Node createPlaylistNode(const db::TrackList::pointer& tracklist, db::Session& session) + Response::Node createPlaylistNode(RequestContext& context, const db::TrackList::pointer& tracklist) { Response::Node playlistNode; @@ -43,16 +46,19 @@ namespace lms::api::subsonic if (const db::User::pointer user{ tracklist->getUser() }) playlistNode.setAttribute("owner", user->getLoginName()); - db::TrackEmbeddedImage::FindParameters params; - params.setTrackList(tracklist->getId()); - 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) { - const CoverArtId coverArtId{ image->getId() }; - playlistNode.setAttribute("coverArt", idToString(coverArtId)); - }); + { + const auto imageResult{ core::Service::get()->findTrackListImage(tracklist->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() }; + playlistNode.setAttribute("coverArt", idToString(coverArtId)); + } + } + else if (const db::TrackEmbeddedImageId * embeddedImageId{ std::get_if(&imageResult) }) + playlistNode.setAttribute("coverArt", idToString(*embeddedImageId)); + } return playlistNode; } diff --git a/src/libs/subsonic/impl/responses/Playlist.hpp b/src/libs/subsonic/impl/responses/Playlist.hpp index 07246674..6c6c8e00 100644 --- a/src/libs/subsonic/impl/responses/Playlist.hpp +++ b/src/libs/subsonic/impl/responses/Playlist.hpp @@ -26,10 +26,11 @@ namespace lms::db { class TrackList; - class Session; } // namespace lms::db namespace lms::api::subsonic { - Response::Node createPlaylistNode(const db::ObjectPtr& tracklist, db::Session& session); -} \ No newline at end of file + struct RequestContext; + + Response::Node createPlaylistNode(RequestContext& context, const db::ObjectPtr& tracklist); +} // namespace lms::api::subsonic \ No newline at end of file From d5aead9daf66aaa8c96817f9b2fe842430e1ed1d Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 10 May 2025 13:31:27 +0200 Subject: [PATCH 03/24] Now prefer 'disc' artworks for tracks, ref #604 --- src/libs/database/impl/Migration.cpp | 8 +- src/libs/database/impl/Session.cpp | 1 - src/libs/database/impl/TrackEmbeddedImage.cpp | 16 +-- .../database/impl/TrackEmbeddedImageLink.cpp | 8 ++ .../include/database/TrackEmbeddedImage.hpp | 6 -- .../database/TrackEmbeddedImageLink.hpp | 6 +- src/libs/database/include/database/Types.hpp | 3 +- src/libs/database/test/TrackEmbeddedImage.cpp | 79 ++++++++++++-- .../services/artwork/impl/ArtworkService.cpp | 100 +++++------------- .../services/artwork/impl/ArtworkService.hpp | 6 +- .../services/artwork/IArtworkService.hpp | 6 +- .../impl/scanners/AudioFileScanOperation.cpp | 12 --- src/libs/subsonic/impl/responses/Song.cpp | 2 +- src/lms/ui/MediaPlayer.cpp | 4 +- src/lms/ui/Utils.cpp | 2 +- src/lms/ui/resource/ArtworkResource.cpp | 4 +- src/lms/ui/resource/ArtworkResource.hpp | 2 +- 17 files changed, 136 insertions(+), 129 deletions(-) diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 47fbbde9..529a802c 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -35,7 +35,7 @@ namespace lms::db { namespace { - static constexpr Version LMS_DATABASE_VERSION{ 91 }; + static constexpr Version LMS_DATABASE_VERSION{ 92 }; } VersionInfo::VersionInfo() @@ -1213,6 +1213,11 @@ FROM tracklist)"); utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET artist_info_scan_version = artist_info_scan_version + 1"); } + void migrateFromV91(Session& session) + { + utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_embedded_image_link DROP COLUMN is_preferred"); + } + bool doDbMigration(Session& session) { constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; @@ -1280,6 +1285,7 @@ FROM tracklist)"); { 88, migrateFromV88 }, { 89, migrateFromV89 }, { 90, migrateFromV90 }, + { 91, migrateFromV91 } }; bool migrationPerformed{}; diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 0511fb97..28a47531 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -229,7 +229,6 @@ namespace lms::db utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_id_idx ON track_embedded_image_link(id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_id_idx ON track_embedded_image_link(track_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_embedded_image_id_track_id_idx ON track_embedded_image_link(track_embedded_image_id, track_id)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_is_preferred_track_id_track_embedded_image_id_idx ON track_embedded_image_link(is_preferred, track_id, track_embedded_image_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)"); diff --git a/src/libs/database/impl/TrackEmbeddedImage.cpp b/src/libs/database/impl/TrackEmbeddedImage.cpp index c802d4b8..0bf1df18 100644 --- a/src/libs/database/impl/TrackEmbeddedImage.cpp +++ b/src/libs/database/impl/TrackEmbeddedImage.cpp @@ -38,17 +38,14 @@ namespace lms::db auto query{ session.getDboSession()->query>("SELECT t_e_i FROM track_embedded_image t_e_i") }; - if (params.isPreferred - || params.track.isValid() + if (params.track.isValid() || params.release.isValid() || params.trackList.isValid() - || params.sortMethod == TrackEmbeddedImageSortMethod::FrontCoverAndSize) + || params.sortMethod == TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize + || params.sortMethod == TrackEmbeddedImageSortMethod::FrontTypeThenSize) { query.join("track_embedded_image_link t_e_i_l ON t_e_i_l.track_embedded_image_id = t_e_i.id"); - if (params.isPreferred) - query.where("t_e_i_l.is_preferred = ?").bind(params.isPreferred.value()); - if (params.track.isValid()) query.where("t_e_i_l.track_id = ?").bind(params.track); @@ -69,8 +66,11 @@ namespace lms::db { case TrackEmbeddedImageSortMethod::None: break; - case TrackEmbeddedImageSortMethod::FrontCoverAndSize: - query.orderBy("CASE WHEN t_e_i_l.type = ? THEN 0 ELSE 1 END, t_e_i.size").bind(ImageType::FrontCover); + case TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize: + query.orderBy("CASE t_e_i_l.type WHEN ? THEN 1 WHEN ? THEN 2 ELSE 3 END, t_e_i.size DESC").bind(ImageType::Media).bind(ImageType::FrontCover); + break; + case TrackEmbeddedImageSortMethod::FrontTypeThenSize: + query.orderBy("CASE WHEN t_e_i_l.type = ? THEN 0 ELSE 1 END, t_e_i.size DESC").bind(ImageType::FrontCover); break; } diff --git a/src/libs/database/impl/TrackEmbeddedImageLink.cpp b/src/libs/database/impl/TrackEmbeddedImageLink.cpp index cecc6f2d..300d2ed3 100644 --- a/src/libs/database/impl/TrackEmbeddedImageLink.cpp +++ b/src/libs/database/impl/TrackEmbeddedImageLink.cpp @@ -54,6 +54,14 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); } + void TrackEmbeddedImageLink::find(Session& session, TrackEmbeddedImageId trackEmbeddedImageId, std::function visitor) + { + auto query{ session.getDboSession()->find() }; + query.where("track_embedded_image_id = ?").bind(trackEmbeddedImageId); + + return utils::forEachQueryResult(query, visitor); + } + ObjectPtr TrackEmbeddedImageLink::getTrack() const { return _track; diff --git a/src/libs/database/include/database/TrackEmbeddedImage.hpp b/src/libs/database/include/database/TrackEmbeddedImage.hpp index b90ed6fc..a5efb896 100644 --- a/src/libs/database/include/database/TrackEmbeddedImage.hpp +++ b/src/libs/database/include/database/TrackEmbeddedImage.hpp @@ -49,7 +49,6 @@ namespace lms::db TrackId track; ReleaseId release; TrackListId trackList; - std::optional isPreferred; TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None }; FindParameters& setRange(std::optional _range) @@ -72,11 +71,6 @@ namespace lms::db trackList = _trackList; return *this; } - FindParameters& setIsPreferred(std::optional _isPreferred) - { - isPreferred = _isPreferred; - return *this; - } FindParameters& setSortMethod(TrackEmbeddedImageSortMethod _sortMethod) { sortMethod = _sortMethod; diff --git a/src/libs/database/include/database/TrackEmbeddedImageLink.hpp b/src/libs/database/include/database/TrackEmbeddedImageLink.hpp index e8b66534..77dd0d2e 100644 --- a/src/libs/database/include/database/TrackEmbeddedImageLink.hpp +++ b/src/libs/database/include/database/TrackEmbeddedImageLink.hpp @@ -25,6 +25,7 @@ #include #include "database/Object.hpp" +#include "database/TrackEmbeddedImageId.hpp" #include "database/TrackEmbeddedImageLinkId.hpp" #include "database/Types.hpp" @@ -44,18 +45,17 @@ namespace lms::db // find static std::size_t getCount(Session& session); static pointer find(Session& session, TrackEmbeddedImageLinkId id); + static void find(Session& session, TrackEmbeddedImageId trackEmbeddedImageId, std::function visitor); // getters ObjectPtr getTrack() const; ObjectPtr getImage() const; std::size_t getIndex() const { return _index; } - bool isPreferred() const { return _isPreferred; } ImageType getType() const { return _type; } std::string_view getDescription() const { return _description; } // setters void setIndex(std::size_t index) { _index = static_cast(index); } - void setIsPreferred(bool isPreferred) { _isPreferred = isPreferred; } void setType(ImageType type) { _type = type; } void setDescription(std::string_view description) { _description = description; } @@ -63,7 +63,6 @@ namespace lms::db void persist(Action& a) { Wt::Dbo::field(a, _index, "index"); - Wt::Dbo::field(a, _isPreferred, "is_preferred"); Wt::Dbo::field(a, _type, "type"); Wt::Dbo::field(a, _description, "description"); @@ -78,7 +77,6 @@ namespace lms::db static pointer create(Session& session, ObjectPtr track, ObjectPtr image); int _index{}; // index within the track - bool _isPreferred{}; ImageType _type{ ImageType::Unknown }; std::string _description; diff --git a/src/libs/database/include/database/Types.hpp b/src/libs/database/include/database/Types.hpp index 043ade8e..a126c12e 100644 --- a/src/libs/database/include/database/Types.hpp +++ b/src/libs/database/include/database/Types.hpp @@ -171,7 +171,8 @@ namespace lms::db enum class TrackEmbeddedImageSortMethod { None, - FrontCoverAndSize, + MediaTypeThenFrontTypeThenSize, + FrontTypeThenSize, }; enum class TrackListSortMethod diff --git a/src/libs/database/test/TrackEmbeddedImage.cpp b/src/libs/database/test/TrackEmbeddedImage.cpp index 3ba25be0..0a06fd24 100644 --- a/src/libs/database/test/TrackEmbeddedImage.cpp +++ b/src/libs/database/test/TrackEmbeddedImage.cpp @@ -118,23 +118,22 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setIsPreferred(true); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); - EXPECT_FALSE(visited); + EXPECT_TRUE(visited); } { auto transaction{ session.createWriteTransaction() }; - link.get().modify()->setIsPreferred(true); + link.get().modify()->setType(ImageType::FrontCover); } { auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setIsPreferred(true); + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); @@ -145,8 +144,8 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setIsPreferred(true); params.setRelease(release.getId()); + params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); @@ -162,9 +161,8 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setIsPreferred(true); params.setRelease(release.getId()); - params.setSortMethod(TrackEmbeddedImageSortMethod::FrontCoverAndSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); @@ -175,9 +173,8 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setIsPreferred(true); params.setTrack(track.getId()); - params.setSortMethod(TrackEmbeddedImageSortMethod::FrontCoverAndSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); @@ -185,6 +182,55 @@ namespace lms::db::tests } } + TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_sorts) + { + ScopedTrackEmbeddedImage image1{ session }; + ScopedTrackEmbeddedImage image2{ session }; + ScopedTrackEmbeddedImage image3{ session }; + ScopedTrack track{ session }; + ScopedRelease release{ session, "MyRelease" }; + ScopedTrackEmbeddedImageLink link1{ session, track.lockAndGet(), image1.lockAndGet() }; + ScopedTrackEmbeddedImageLink link2{ session, track.lockAndGet(), image2.lockAndGet() }; + ScopedTrackEmbeddedImageLink link3{ session, track.lockAndGet(), image3.lockAndGet() }; + + { + auto transaction{ session.createWriteTransaction() }; + link1.get().modify()->setType(ImageType::FrontCover); + link2.get().modify()->setType(ImageType::Media); + image2.get().modify()->setSize(1000); + link3.get().modify()->setType(ImageType::Media); + image3.get().modify()->setSize(2000); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize); + + std::vector visitedIds; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); }); + ASSERT_EQ(visitedIds.size(), 3); + EXPECT_EQ(visitedIds[0], image1.getId()); + EXPECT_EQ(visitedIds[1], image3.getId()); + EXPECT_EQ(visitedIds[2], image2.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); + + std::vector visitedIds; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); }); + ASSERT_EQ(visitedIds.size(), 3); + EXPECT_EQ(visitedIds[0], image3.getId()); + EXPECT_EQ(visitedIds[1], image2.getId()); + EXPECT_EQ(visitedIds[2], image1.getId()); + } + } + TEST_F(DatabaseFixture, Track_findByEmbeddedImage) { ScopedTrackEmbeddedImage image{ session }; @@ -279,5 +325,20 @@ namespace lms::db::tests EXPECT_EQ(img->getType(), ImageType::FrontCover); EXPECT_EQ(img->getDescription(), "MyDesc"); } + + { + auto transaction{ session.createReadTransaction() }; + + bool visited{}; + TrackEmbeddedImageLink::find(session, image->getId(), [&](const TrackEmbeddedImageLink::pointer& link) { + EXPECT_EQ(link->getIndex(), 2); + EXPECT_EQ(link->getType(), ImageType::FrontCover); + EXPECT_EQ(link->getDescription(), "MyDesc"); + EXPECT_EQ(link->getTrack(), track.get()); + + visited = true; + }); + EXPECT_TRUE(visited); + } } } // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index a65a19f6..4c4da83d 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -19,9 +19,6 @@ #include "ArtworkService.hpp" -#include -#include - #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/Utils.hpp" @@ -32,6 +29,8 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/TrackEmbeddedImage.hpp" +#include "database/TrackEmbeddedImageLink.hpp" +#include "database/Types.hpp" #include "image/Exception.hpp" #include "image/IEncodedImage.hpp" #include "image/Image.hpp" @@ -39,14 +38,6 @@ namespace lms::cover { - namespace - { - bool isFileSupported(const std::filesystem::path& file, const std::vector& extensions) - { - return (std::find(std::cbegin(extensions), std::cend(extensions), file.extension()) != std::cend(extensions)); - } - } // namespace - std::unique_ptr createArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) { return std::make_unique(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath); @@ -105,49 +96,18 @@ namespace lms::cover return _defaultArtistImage; } - bool ArtworkService::checkImageFile(const std::filesystem::path& filePath) + std::unique_ptr ArtworkService::getTrackImage(const std::filesystem::path& p, std::size_t index, std::optional width) const { - std::error_code ec; - - if (!isFileSupported(filePath, _fileExtensions)) - return false; - - if (!std::filesystem::exists(filePath, ec)) - return false; - - if (!std::filesystem::is_regular_file(filePath, ec)) - return false; - - return true; - } - - std::unique_ptr ArtworkService::getTrackImage(const std::filesystem::path& p, std::optional width) const - { - struct CandidateImage - { - std::unique_ptr image; - bool isFront{}; - std::size_t index; - - // > means is better candidate - bool operator>(const CandidateImage& other) const - { - if (!isFront && other.isFront) - return false; - if (isFront && !other.isFront) - return true; - - return index < other.index; - } - }; - - std::vector candidateImages; - std::size_t pictureIndex{}; + std::unique_ptr image; try { + std::size_t currentIndex{}; + _audioFileParser->parseImages(p, [&](const metadata::Image& parsedImage) { - std::unique_ptr image; + if (currentIndex++ != index) + return; + try { if (!width) @@ -165,8 +125,6 @@ namespace lms::cover { LMS_LOG(COVER, ERROR, "Cannot decode image from track " << p << ": " << e.what()); } - - candidateImages.emplace_back(CandidateImage{ .image = std::move(image), .isFront = parsedImage.type == metadata::Image::Type::FrontCover, .index = pictureIndex++ }); }); } catch (const metadata::Exception& e) @@ -174,11 +132,7 @@ namespace lms::cover LMS_LOG(COVER, ERROR, "Cannot parse images from track " << p << ": " << e.what()); } - std::stable_sort(std::begin(candidateImages), std::end(candidateImages), std::greater<>()); - if (!candidateImages.empty()) - return std::move(candidateImages.front().image); - - return {}; + return image; } ArtworkService::ImageFindResult ArtworkService::findArtistImage(db::ArtistId artistId) @@ -199,7 +153,7 @@ namespace lms::cover return res; } - ArtworkService::ImageFindResult ArtworkService::findPreferredTrackImage(db::TrackId trackId) + ArtworkService::ImageFindResult ArtworkService::findTrackImage(db::TrackId trackId) { db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; @@ -207,16 +161,17 @@ namespace lms::cover db::TrackEmbeddedImage::FindParameters params; params.setTrack(trackId); - params.setIsPreferred(true); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); params.setRange(db::Range{ .offset = 0, .size = 1 }); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { res = image->getId(); }); + // No embedded image found, fallback on release image if (res.index() == 0) { - if (db::Track::pointer track{ db::Track::find(session, trackId) }) + if (const db::Track::pointer track{ db::Track::find(session, trackId) }) { if (const db::Release::pointer release{ track->getRelease() }) { @@ -247,8 +202,7 @@ namespace lms::cover { db::TrackEmbeddedImage::FindParameters params; params.setRelease(releaseId); - params.setIsPreferred(true); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontTypeThenSize); params.setRange(db::Range{ .offset = 0, .size = 1 }); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { @@ -269,8 +223,7 @@ namespace lms::cover db::TrackEmbeddedImage::FindParameters params; params.setTrackList(trackListId); - params.setIsPreferred(true); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); params.setRange(db::Range{ .offset = 0, .size = 1 }); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { @@ -309,27 +262,24 @@ namespace lms::cover { const ImageCache::EntryDesc cacheEntryDesc{ trackEmbeddedImageId, width }; - std::shared_ptr cover{ _cache.getImage(cacheEntryDesc) }; - if (cover) - return cover; + std::shared_ptr image{ _cache.getImage(cacheEntryDesc) }; + if (image) + return image; - std::filesystem::path trackFile; { db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - db::Track::FindParameters params; - params.setEmbeddedImage(trackEmbeddedImageId); - db::Track::find(session, params, [&](const db::Track::pointer& track) { - if (!cover) - cover = getTrackImage(track->getAbsoluteFilePath(), width); + db::TrackEmbeddedImageLink::find(session, trackEmbeddedImageId, [&](const db::TrackEmbeddedImageLink::pointer& link) { + if (!image) + image = getTrackImage(link->getTrack()->getAbsoluteFilePath(), link->getIndex(), width); }); } - if (cover) - _cache.addImage(cacheEntryDesc, cover); + if (image) + _cache.addImage(cacheEntryDesc, image); - return cover; + return image; } void ArtworkService::flushCache() diff --git a/src/libs/services/artwork/impl/ArtworkService.hpp b/src/libs/services/artwork/impl/ArtworkService.hpp index e1b77906..9d90d1a7 100644 --- a/src/libs/services/artwork/impl/ArtworkService.hpp +++ b/src/libs/services/artwork/impl/ArtworkService.hpp @@ -48,7 +48,7 @@ namespace lms::cover private: ImageFindResult findArtistImage(db::ArtistId artistId) override; - ImageFindResult findPreferredTrackImage(db::TrackId trackId) override; + ImageFindResult findTrackImage(db::TrackId trackId) override; ImageFindResult findReleaseImage(db::ReleaseId releaseId) override; ImageFindResult findTrackListImage(db::TrackListId trackListId) override; @@ -62,9 +62,7 @@ namespace lms::cover void setJpegQuality(unsigned quality) override; std::unique_ptr getFromImageFile(const std::filesystem::path& p, std::optional width) const; - std::unique_ptr getTrackImage(const std::filesystem::path& path, std::optional width) const; - - static bool checkImageFile(const std::filesystem::path& filePath); + std::unique_ptr getTrackImage(const std::filesystem::path& path, std::size_t index, std::optional width) const; db::Db& _db; diff --git a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp index 259474bc..90a8319c 100644 --- a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp +++ b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp @@ -47,7 +47,11 @@ namespace lms::cover // Helpers to find artworks using ImageFindResult = std::variant; virtual ImageFindResult findArtistImage(db::ArtistId artistId) = 0; - virtual ImageFindResult findPreferredTrackImage(db::TrackId trackId) = 0; + + // Will get Disc/Media artwork if available, otherwise, will fallback on release artwork + virtual ImageFindResult findTrackImage(db::TrackId trackId) = 0; + + // Will get Release if available, otherwise, will fallback on embedded artworks virtual ImageFindResult findReleaseImage(db::ReleaseId releaseId) = 0; virtual ImageFindResult findTrackListImage(db::TrackListId trackListId) = 0; diff --git a/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp b/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp index 8a33e817..e5fcf43b 100644 --- a/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp +++ b/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp @@ -350,24 +350,12 @@ namespace lms::scanner void updateEmbeddedImages(db::Session& session, db::Track::pointer& track, std::span images) { - db::TrackEmbeddedImageLink::pointer preferredImageLink; - track.modify()->clearEmbeddedImageLinks(); for (const ImageInfo& imageInfo : images) { db::TrackEmbeddedImageLink::pointer link{ createTrackEmbeddedImageLink(session, track, imageInfo) }; track.modify()->addEmbeddedImageLink(link); - - if (!preferredImageLink - || (preferredImageLink->getType() != db::ImageType::FrontCover && link->getType() == db::ImageType::FrontCover) - || (preferredImageLink->getImage()->getSize() < link->getImage()->getSize())) - { - preferredImageLink = link; - } } - - if (preferredImageLink) - preferredImageLink.modify()->setIsPreferred(true); } db::Advisory getAdvisory(std::optional advisory) diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 876721b4..cdf55651 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -112,7 +112,7 @@ namespace lms::api::subsonic } { - const auto imageResult{ core::Service::get()->findPreferredTrackImage(track->getId()) }; + const auto imageResult{ core::Service::get()->findTrackImage(track->getId()) }; if (const db::ImageId * imageId{ std::get_if(&imageResult) }) { if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) }) diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index 22f4b984..87a76c35 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -253,8 +253,8 @@ namespace lms::ui << " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\"," << " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\"," << " artwork: [" - << " { src: \"" << LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," - << " { src: \"" << LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" }," + << " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," + << " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" }," << " ]" << "};"; // Update 'sizes' above to match this: diff --git a/src/lms/ui/Utils.cpp b/src/lms/ui/Utils.cpp index 0cbfe325..858c533f 100644 --- a/src/lms/ui/Utils.cpp +++ b/src/lms/ui/Utils.cpp @@ -97,7 +97,7 @@ namespace lms::ui::utils std::unique_ptr createTrackImage(db::TrackId trackId, ArtworkResource::Size size) { auto cover{ std::make_unique() }; - cover->setImageLink(LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, size)); + cover->setImageLink(LmsApp->getArtworkResource()->getTrackImageUrl(trackId, size)); cover->setStyleClass("Lms-cover img-fluid"); // HACK cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK diff --git a/src/lms/ui/resource/ArtworkResource.cpp b/src/lms/ui/resource/ArtworkResource.cpp index 8cb14e9a..424824a4 100644 --- a/src/lms/ui/resource/ArtworkResource.cpp +++ b/src/lms/ui/resource/ArtworkResource.cpp @@ -81,11 +81,11 @@ namespace lms::ui return url; } - std::string ArtworkResource::getPreferredTrackImageUrl(db::TrackId trackId, std::optional size) const + std::string ArtworkResource::getTrackImageUrl(db::TrackId trackId, std::optional size) const { std::string url; - const auto imageResult{ core::Service::get()->findPreferredTrackImage(trackId) }; + const auto imageResult{ core::Service::get()->findTrackImage(trackId) }; std::visit([&](const auto& arg) { using T = std::decay_t; if constexpr (std::is_same_v) diff --git a/src/lms/ui/resource/ArtworkResource.hpp b/src/lms/ui/resource/ArtworkResource.hpp index d54ddfa3..dda8b5e1 100644 --- a/src/lms/ui/resource/ArtworkResource.hpp +++ b/src/lms/ui/resource/ArtworkResource.hpp @@ -47,7 +47,7 @@ namespace lms::ui std::string getArtistImageUrl(db::ArtistId artistId, std::optional size = std::nullopt) const; std::string getReleaseCoverUrl(db::ReleaseId releaseId, std::optional size = std::nullopt) const; - std::string getPreferredTrackImageUrl(db::TrackId trackId, std::optional size = std::nullopt) const; + std::string getTrackImageUrl(db::TrackId trackId, std::optional size = std::nullopt) const; private: std::string getImageUrl(db::ImageId imageId, std::optional size, std::string_view type) const; From 4b3e8032cbcbb7a8d9bd42c8a2d74bae7dd3882a Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 10 May 2025 13:44:49 +0200 Subject: [PATCH 04/24] Added missing drop index call --- src/libs/database/impl/Migration.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 529a802c..f6ba2b4c 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -1215,6 +1215,7 @@ FROM tracklist)"); void migrateFromV91(Session& session) { + dropIndexes(session); utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_embedded_image_link DROP COLUMN is_preferred"); } From f906212fc4855cf4a989879409a9f49fd66aeba6 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 10 May 2025 14:04:46 +0200 Subject: [PATCH 05/24] Added a helper to get the disc cover for a given track --- src/libs/database/impl/TrackEmbeddedImage.cpp | 15 ++++-- .../include/database/TrackEmbeddedImage.hpp | 6 +++ src/libs/database/include/database/Types.hpp | 5 +- src/libs/database/test/TrackEmbeddedImage.cpp | 51 ++++++++++++++++--- .../services/artwork/impl/ArtworkService.cpp | 25 +++++++-- .../services/artwork/impl/ArtworkService.hpp | 1 + .../services/artwork/IArtworkService.hpp | 5 +- 7 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/libs/database/impl/TrackEmbeddedImage.cpp b/src/libs/database/impl/TrackEmbeddedImage.cpp index 0bf1df18..c6116b5f 100644 --- a/src/libs/database/impl/TrackEmbeddedImage.cpp +++ b/src/libs/database/impl/TrackEmbeddedImage.cpp @@ -41,8 +41,9 @@ namespace lms::db if (params.track.isValid() || params.release.isValid() || params.trackList.isValid() - || params.sortMethod == TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize - || params.sortMethod == TrackEmbeddedImageSortMethod::FrontTypeThenSize) + || params.imageType.has_value() + || params.sortMethod == TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc + || params.sortMethod == TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc) { query.join("track_embedded_image_link t_e_i_l ON t_e_i_l.track_embedded_image_id = t_e_i.id"); @@ -60,16 +61,22 @@ namespace lms::db query.join("tracklist_entry t_l_e ON t_l_e.track_id = t_e_i_l.track_id"); query.where("t_l_e.tracklist_id = ?").bind(params.trackList); } + + if (params.imageType.has_value()) + query.where("t_e_i_l.type = ?").bind(params.imageType.value()); } switch (params.sortMethod) { case TrackEmbeddedImageSortMethod::None: break; - case TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize: + case TrackEmbeddedImageSortMethod::SizeDesc: + query.orderBy("t_e_i.size DESC"); + break; + case TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc: query.orderBy("CASE t_e_i_l.type WHEN ? THEN 1 WHEN ? THEN 2 ELSE 3 END, t_e_i.size DESC").bind(ImageType::Media).bind(ImageType::FrontCover); break; - case TrackEmbeddedImageSortMethod::FrontTypeThenSize: + case TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc: query.orderBy("CASE WHEN t_e_i_l.type = ? THEN 0 ELSE 1 END, t_e_i.size DESC").bind(ImageType::FrontCover); break; } diff --git a/src/libs/database/include/database/TrackEmbeddedImage.hpp b/src/libs/database/include/database/TrackEmbeddedImage.hpp index a5efb896..e323e093 100644 --- a/src/libs/database/include/database/TrackEmbeddedImage.hpp +++ b/src/libs/database/include/database/TrackEmbeddedImage.hpp @@ -49,6 +49,7 @@ namespace lms::db TrackId track; ReleaseId release; TrackListId trackList; + std::optional imageType; TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None }; FindParameters& setRange(std::optional _range) @@ -71,6 +72,11 @@ namespace lms::db trackList = _trackList; return *this; } + FindParameters& setImageType(std::optional _imageType) + { + imageType = _imageType; + return *this; + } FindParameters& setSortMethod(TrackEmbeddedImageSortMethod _sortMethod) { sortMethod = _sortMethod; diff --git a/src/libs/database/include/database/Types.hpp b/src/libs/database/include/database/Types.hpp index a126c12e..74bee666 100644 --- a/src/libs/database/include/database/Types.hpp +++ b/src/libs/database/include/database/Types.hpp @@ -171,8 +171,9 @@ namespace lms::db enum class TrackEmbeddedImageSortMethod { None, - MediaTypeThenFrontTypeThenSize, - FrontTypeThenSize, + SizeDesc, + MediaTypeThenFrontTypeThenSizeDescDesc, + FrontTypeThenSizeDesc, }; enum class TrackListSortMethod diff --git a/src/libs/database/test/TrackEmbeddedImage.cpp b/src/libs/database/test/TrackEmbeddedImage.cpp index 0a06fd24..c33d9ffb 100644 --- a/src/libs/database/test/TrackEmbeddedImage.cpp +++ b/src/libs/database/test/TrackEmbeddedImage.cpp @@ -133,19 +133,43 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); EXPECT_TRUE(visited); } + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setImageType(ImageType::FrontCover); + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); + EXPECT_TRUE(visited); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setImageType(ImageType::Media); + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); + EXPECT_FALSE(visited); + } + { auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); @@ -162,7 +186,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); @@ -174,7 +198,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setTrack(track.getId()); - params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc); bool visited{}; TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); @@ -196,6 +220,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; link1.get().modify()->setType(ImageType::FrontCover); + image1.get().modify()->setSize(750); link2.get().modify()->setType(ImageType::Media); image2.get().modify()->setSize(1000); link3.get().modify()->setType(ImageType::Media); @@ -206,7 +231,7 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc); std::vector visitedIds; TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); }); @@ -220,7 +245,7 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; TrackEmbeddedImage::FindParameters params; - params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); + params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc); std::vector visitedIds; TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); }); @@ -229,6 +254,20 @@ namespace lms::db::tests EXPECT_EQ(visitedIds[1], image2.getId()); EXPECT_EQ(visitedIds[2], image1.getId()); } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setSortMethod(TrackEmbeddedImageSortMethod::SizeDesc); + + std::vector visitedIds; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); }); + ASSERT_EQ(visitedIds.size(), 3); + EXPECT_EQ(visitedIds[0], image3.getId()); + EXPECT_EQ(visitedIds[2], image1.getId()); + EXPECT_EQ(visitedIds[1], image2.getId()); + } } TEST_F(DatabaseFixture, Track_findByEmbeddedImage) diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index 4c4da83d..006d21d5 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -161,7 +161,7 @@ namespace lms::cover db::TrackEmbeddedImage::FindParameters params; params.setTrack(trackId); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc); params.setRange(db::Range{ .offset = 0, .size = 1 }); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { @@ -184,6 +184,25 @@ namespace lms::cover return res; } + ArtworkService::ImageFindResult ArtworkService::findTrackMediaImage(db::TrackId trackId) + { + db::Session& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + ImageFindResult res; + + db::TrackEmbeddedImage::FindParameters params; + params.setTrack(trackId); + params.setImageType(db::ImageType::Media); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); + params.setRange(db::Range{ .offset = 0, .size = 1 }); + + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + res = image->getId(); + }); + + return res; + } + ArtworkService::ImageFindResult ArtworkService::findReleaseImage(db::ReleaseId releaseId) { db::Session& session{ _db.getTLSSession() }; @@ -202,7 +221,7 @@ namespace lms::cover { db::TrackEmbeddedImage::FindParameters params; params.setRelease(releaseId); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontTypeThenSize); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc); params.setRange(db::Range{ .offset = 0, .size = 1 }); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { @@ -223,7 +242,7 @@ namespace lms::cover db::TrackEmbeddedImage::FindParameters params; params.setTrackList(trackListId); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc); params.setRange(db::Range{ .offset = 0, .size = 1 }); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { diff --git a/src/libs/services/artwork/impl/ArtworkService.hpp b/src/libs/services/artwork/impl/ArtworkService.hpp index 9d90d1a7..a5dd7a4d 100644 --- a/src/libs/services/artwork/impl/ArtworkService.hpp +++ b/src/libs/services/artwork/impl/ArtworkService.hpp @@ -49,6 +49,7 @@ namespace lms::cover private: ImageFindResult findArtistImage(db::ArtistId artistId) override; ImageFindResult findTrackImage(db::TrackId trackId) override; + ImageFindResult findTrackMediaImage(db::TrackId trackId) override; ImageFindResult findReleaseImage(db::ReleaseId releaseId) override; ImageFindResult findTrackListImage(db::TrackListId trackListId) override; diff --git a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp index 90a8319c..220bde91 100644 --- a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp +++ b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp @@ -51,7 +51,10 @@ namespace lms::cover // Will get Disc/Media artwork if available, otherwise, will fallback on release artwork virtual ImageFindResult findTrackImage(db::TrackId trackId) = 0; - // Will get Release if available, otherwise, will fallback on embedded artworks + // Will get Disc/Media artwork if available, no fallback + virtual ImageFindResult findTrackMediaImage(db::TrackId trackId) = 0; + + // Will get Release if available, otherwise, will fallback on embedded artworks virtual ImageFindResult findReleaseImage(db::ReleaseId releaseId) = 0; virtual ImageFindResult findTrackListImage(db::TrackListId trackListId) = 0; From dd57275858ad446f695c4281dcb32f1cb7413739 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 10 May 2025 14:41:59 +0200 Subject: [PATCH 06/24] Added media related image in the release view, if found, ref #604 --- approot/release.xml | 9 ++++++--- src/lms/ui/Utils.cpp | 14 ++++++++++++++ src/lms/ui/Utils.hpp | 2 ++ src/lms/ui/explore/ReleaseView.cpp | 17 ++++++++++++++--- src/lms/ui/resource/ArtworkResource.cpp | 15 +++++++++++++++ src/lms/ui/resource/ArtworkResource.hpp | 1 + 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/approot/release.xml b/approot/release.xml index 0dbbc8ed..caf2b811 100644 --- a/approot/release.xml +++ b/approot/release.xml @@ -54,8 +54,11 @@ -
-

${disc-title}

+

+ ${} +
${image class="Lms-cursor-pointer"}
+ ${
} +
${disc-title}
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
-

+ ${tracks class="d-grid gap-1 Lms-row-container"}
diff --git a/src/lms/ui/Utils.cpp b/src/lms/ui/Utils.cpp index 858c533f..1e8191f4 100644 --- a/src/lms/ui/Utils.cpp +++ b/src/lms/ui/Utils.cpp @@ -104,6 +104,20 @@ namespace lms::ui::utils return cover; } + std::unique_ptr createTrackMediaImage(db::TrackId trackId, ArtworkResource::Size size) + { + std::string url{ LmsApp->getArtworkResource()->getTrackMediaImageUrl(trackId, size) }; + if (url.empty()) + return nullptr; + + auto cover{ std::make_unique() }; + cover->setImageLink(std::move(url)); + cover->setStyleClass("Lms-cover img-fluid"); // HACK + cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK + + return cover; + } + std::unique_ptr createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete) { auto res{ std::make_unique(Wt::WString{ canDelete ? " " : "" } + name, Wt::TextFormat::UnsafeXHTML) }; diff --git a/src/lms/ui/Utils.hpp b/src/lms/ui/Utils.hpp index 2039db95..c29077e2 100644 --- a/src/lms/ui/Utils.hpp +++ b/src/lms/ui/Utils.hpp @@ -34,6 +34,7 @@ #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" +#include "database/Types.hpp" #include "resource/ArtworkResource.hpp" namespace lms::db @@ -58,6 +59,7 @@ namespace lms::ui::utils std::unique_ptr createReleaseCover(db::ReleaseId releaseId, ArtworkResource::Size size); std::unique_ptr createTrackImage(db::TrackId trackId, ArtworkResource::Size size); + std::unique_ptr createTrackMediaImage(db::TrackId trackId, ArtworkResource::Size size); // return nullptr if no image std::unique_ptr createArtistImage(db::ArtistId artistId, ArtworkResource::Size size); std::unique_ptr createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete = false); diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 8fd13d70..9241d1d0 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -391,13 +391,24 @@ namespace lms::ui // Expect to be called in asc order std::map trackContainers; - auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle) -> Wt::WContainerWidget* { + auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle, db::TrackId trackId) -> Wt::WContainerWidget* { if (auto it{ trackContainers.find(discNumber) }; it != std::cend(trackContainers)) return it->second; Template* disc{ rootContainer->addNew