diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 28a47531..02176093 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -229,6 +229,7 @@ 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_type_track_embedded_image_id_track_id_idx ON track_embedded_image_link(type,track_embedded_image_id,track_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 e818a372..9189828b 100644 --- a/src/libs/database/impl/TrackEmbeddedImage.cpp +++ b/src/libs/database/impl/TrackEmbeddedImage.cpp @@ -38,7 +38,9 @@ namespace lms::db auto query{ session.getDboSession()->query>("SELECT t_e_i FROM track_embedded_image t_e_i") }; - if (params.track.isValid() + if (params.artist.isValid() + || params.discNumber.has_value() + || params.track.isValid() || params.release.isValid() || params.trackList.isValid() || !params.imageTypes.empty() @@ -47,13 +49,36 @@ namespace lms::db { 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.artist.isValid()) + { + query.join("track_artist_link t_a_l ON t_a_l.track_id = t_e_i_l.track_id"); + query.where("t_a_l.artist_id = ?").bind(params.artist); + + if (!params.trackArtistLinkTypes.empty()) + { + std::string clause{ "t_a_l.type IN (" }; + for (const auto& type : params.trackArtistLinkTypes) + { + if (clause.back() != '(') + clause += ","; + clause += "?"; + query.bind(type); + } + clause += ")"; + query.where(clause); + } + } + if (params.track.isValid()) query.where("t_e_i_l.track_id = ?").bind(params.track); - if (params.release.isValid()) + if (params.release.isValid() || params.discNumber.has_value()) { query.join("track t ON t_e_i_l.track_id = t.id"); - query.where("t.release_id = ?").bind(params.release); + if (params.release.isValid()) + query.where("t.release_id = ?").bind(params.release); + if (params.discNumber.has_value()) + query.where("t.disc_number = ?").bind(params.discNumber.value()); } if (params.trackList.isValid()) diff --git a/src/libs/database/include/database/TrackEmbeddedImage.hpp b/src/libs/database/include/database/TrackEmbeddedImage.hpp index 37a63b3e..47e8fae8 100644 --- a/src/libs/database/include/database/TrackEmbeddedImage.hpp +++ b/src/libs/database/include/database/TrackEmbeddedImage.hpp @@ -26,6 +26,7 @@ #include #include "core/EnumSet.hpp" +#include "database/ArtistId.hpp" #include "database/Object.hpp" #include "database/ReleaseId.hpp" #include "database/TrackEmbeddedImageId.hpp" @@ -47,8 +48,11 @@ namespace lms::db struct FindParameters { std::optional range; + ArtistId artist; + core::EnumSet trackArtistLinkTypes; TrackId track; ReleaseId release; + std::optional discNumber; TrackListId trackList; core::EnumSet imageTypes; TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None }; @@ -58,6 +62,13 @@ namespace lms::db range = _range; return *this; } + FindParameters& setArtist(ArtistId _artist, core::EnumSet _trackArtistLinkTypes = {}) + { + artist = _artist; + trackArtistLinkTypes = _trackArtistLinkTypes; + return *this; + } + FindParameters& setTrack(TrackId _track) { track = _track; @@ -68,6 +79,11 @@ namespace lms::db release = _release; return *this; } + FindParameters& setDiscNumber(std::optional _discNumber) + { + discNumber = _discNumber; + return *this; + } FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; diff --git a/src/libs/database/test/TrackEmbeddedImage.cpp b/src/libs/database/test/TrackEmbeddedImage.cpp index 6d438bf0..0bc0f9ef 100644 --- a/src/libs/database/test/TrackEmbeddedImage.cpp +++ b/src/libs/database/test/TrackEmbeddedImage.cpp @@ -271,6 +271,77 @@ namespace lms::db::tests } } + TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_artist) + { + ScopedTrackEmbeddedImage image{ session }; + ScopedTrack track{ session }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() }; + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setArtist(artist.getId()); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; }); + ASSERT_TRUE(visited); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist }); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; }); + ASSERT_FALSE(visited); + } + } + + TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_discNumber) + { + ScopedTrackEmbeddedImage image{ session }; + ScopedTrack track{ session }; + ScopedRelease release{ session, "MyRelease" }; + ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + track.get().modify()->setDiscNumber(1); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setDiscNumber(1); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; }); + ASSERT_TRUE(visited); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setDiscNumber(2); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; }); + ASSERT_FALSE(visited); + } + } + TEST_F(DatabaseFixture, Track_findByEmbeddedImage) { ScopedTrackEmbeddedImage image{ session }; diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index 7e1ddbd6..f2ad00ed 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -38,6 +38,14 @@ namespace lms::cover { + namespace + { + bool isImageFound(const ArtworkService::ImageFindResult& res) + { + return !std::holds_alternative(res); + } + } // 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); @@ -147,6 +155,7 @@ namespace lms::cover if (const db::ImageId imageId{ artist->getImageId() }; imageId.isValid()) res = imageId; + // TODO fallback on embedded Band/LeadArtist/Artist? // TODO fallback on first release? } @@ -155,30 +164,76 @@ namespace lms::cover ArtworkService::ImageFindResult ArtworkService::findTrackImage(db::TrackId trackId) { - db::Session& session{ _db.getTLSSession() }; - auto transaction{ session.createReadTransaction() }; ImageFindResult res; - db::TrackEmbeddedImage::FindParameters params; - params.setTrack(trackId); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSizeDescDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); + db::Session& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; - 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 (const 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; - } - } + db::TrackEmbeddedImage::FindParameters params; + params.setTrack(trackId); + params.setImageTypes({ 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(); }); + } + + if (isImageFound(res)) + return res; + + // fallback on another track of the same disc + const db::Track::pointer track{ db::Track::find(session, trackId) }; + if (!track) + return res; + + const db::ReleaseId releaseId{ track->getReleaseId() }; + if (!releaseId.isValid()) + return res; + + { + db::TrackEmbeddedImage::FindParameters params; + params.setRelease(releaseId); + params.setDiscNumber(track->getDiscNumber()); + params.setImageTypes({ 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(); }); + } + + if (isImageFound(res)) + return res; + + // Fallback on front cover + { + db::TrackEmbeddedImage::FindParameters params; + params.setTrack(trackId); + params.setImageTypes({ db::ImageType::FrontCover }); + 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(); }); + } + + if (isImageFound(res)) + return res; + + // Fallback on whatever front cover found on the release + { + db::TrackEmbeddedImage::FindParameters params; + params.setRelease(releaseId); + params.setImageTypes({ db::ImageType::FrontCover }); + 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(); }); + } + + if (isImageFound(res)) + return res; + + // Fallback on external cover of the release + if (const db::Release::pointer release{ db::Release::find(session, releaseId) }) + { + if (const db::ImageId imageId{ release->getImageId() }; imageId.isValid()) + res = imageId; } return res; @@ -190,15 +245,33 @@ namespace lms::cover auto transaction{ session.createReadTransaction() }; ImageFindResult res; - db::TrackEmbeddedImage::FindParameters params; - params.setTrack(trackId); - params.setImageTypes({ db::ImageType::Media }); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); + { + db::TrackEmbeddedImage::FindParameters params; + params.setTrack(trackId); + params.setImageTypes({ 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(); }); + } - db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { - res = image->getId(); - }); + if (isImageFound(res)) + return res; + + // fallback on another track of the same disc + if (const db::Track::pointer track{ db::Track::find(session, trackId) }) + { + const db::ReleaseId releaseId{ track->getReleaseId() }; + if (releaseId.isValid()) + { + db::TrackEmbeddedImage::FindParameters params; + params.setRelease(releaseId); + params.setDiscNumber(track->getDiscNumber()); + params.setImageTypes({ 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; } @@ -209,9 +282,7 @@ namespace lms::cover auto transaction{ session.createReadTransaction() }; ImageFindResult res; - - const db::Release::pointer release{ db::Release::find(session, releaseId) }; - if (release) + if (const db::Release::pointer release{ db::Release::find(session, releaseId) }) { if (const db::ImageId imageId{ release->getImageId() }; imageId.isValid()) { @@ -221,12 +292,11 @@ namespace lms::cover { db::TrackEmbeddedImage::FindParameters params; params.setRelease(releaseId); + params.setImageTypes({ db::ImageType::FrontCover, db::ImageType::Media, db::ImageType::Unknown /* give unknown a chance */ }); params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontTypeThenSizeDesc); params.setRange(db::Range{ .offset = 0, .size = 1 }); - db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { - res = image->getId(); - }); + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { res = image->getId(); }); } } @@ -312,5 +382,4 @@ namespace lms::cover LMS_LOG(COVER, INFO, "JPEG export quality = " << _jpegQuality); } - } // namespace lms::cover