Now prefer 'disc' artworks for tracks, ref #604

This commit is contained in:
emeric
2025-06-15 14:39:13 +02:00
parent d1314ddf0d
commit d5aead9daf
17 changed files with 136 additions and 129 deletions
+7 -1
View File
@@ -35,7 +35,7 @@ namespace lms::db
{ {
namespace namespace
{ {
static constexpr Version LMS_DATABASE_VERSION{ 91 }; static constexpr Version LMS_DATABASE_VERSION{ 92 };
} }
VersionInfo::VersionInfo() 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"); 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) bool doDbMigration(Session& session)
{ {
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1280,6 +1285,7 @@ FROM tracklist)");
{ 88, migrateFromV88 }, { 88, migrateFromV88 },
{ 89, migrateFromV89 }, { 89, migrateFromV89 },
{ 90, migrateFromV90 }, { 90, migrateFromV90 },
{ 91, migrateFromV91 }
}; };
bool migrationPerformed{}; bool migrationPerformed{};
-1
View File
@@ -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_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_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_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_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)");
@@ -38,17 +38,14 @@ namespace lms::db
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackEmbeddedImage>>("SELECT t_e_i FROM track_embedded_image t_e_i") }; auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackEmbeddedImage>>("SELECT t_e_i FROM track_embedded_image t_e_i") };
if (params.isPreferred if (params.track.isValid()
|| params.track.isValid()
|| params.release.isValid() || params.release.isValid()
|| params.trackList.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"); 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()) if (params.track.isValid())
query.where("t_e_i_l.track_id = ?").bind(params.track); query.where("t_e_i_l.track_id = ?").bind(params.track);
@@ -69,8 +66,11 @@ namespace lms::db
{ {
case TrackEmbeddedImageSortMethod::None: case TrackEmbeddedImageSortMethod::None:
break; break;
case TrackEmbeddedImageSortMethod::FrontCoverAndSize: case TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize:
query.orderBy("CASE WHEN t_e_i_l.type = ? THEN 0 ELSE 1 END, t_e_i.size").bind(ImageType::FrontCover); 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; break;
} }
@@ -54,6 +54,14 @@ namespace lms::db
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackEmbeddedImageLink>().where("id = ?").bind(id)); return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackEmbeddedImageLink>().where("id = ?").bind(id));
} }
void TrackEmbeddedImageLink::find(Session& session, TrackEmbeddedImageId trackEmbeddedImageId, std::function<void(const pointer&)> visitor)
{
auto query{ session.getDboSession()->find<TrackEmbeddedImageLink>() };
query.where("track_embedded_image_id = ?").bind(trackEmbeddedImageId);
return utils::forEachQueryResult(query, visitor);
}
ObjectPtr<Track> TrackEmbeddedImageLink::getTrack() const ObjectPtr<Track> TrackEmbeddedImageLink::getTrack() const
{ {
return _track; return _track;
@@ -49,7 +49,6 @@ namespace lms::db
TrackId track; TrackId track;
ReleaseId release; ReleaseId release;
TrackListId trackList; TrackListId trackList;
std::optional<bool> isPreferred;
TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None }; TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None };
FindParameters& setRange(std::optional<Range> _range) FindParameters& setRange(std::optional<Range> _range)
@@ -72,11 +71,6 @@ namespace lms::db
trackList = _trackList; trackList = _trackList;
return *this; return *this;
} }
FindParameters& setIsPreferred(std::optional<bool> _isPreferred)
{
isPreferred = _isPreferred;
return *this;
}
FindParameters& setSortMethod(TrackEmbeddedImageSortMethod _sortMethod) FindParameters& setSortMethod(TrackEmbeddedImageSortMethod _sortMethod)
{ {
sortMethod = _sortMethod; sortMethod = _sortMethod;
@@ -25,6 +25,7 @@
#include <Wt/Dbo/Dbo.h> #include <Wt/Dbo/Dbo.h>
#include "database/Object.hpp" #include "database/Object.hpp"
#include "database/TrackEmbeddedImageId.hpp"
#include "database/TrackEmbeddedImageLinkId.hpp" #include "database/TrackEmbeddedImageLinkId.hpp"
#include "database/Types.hpp" #include "database/Types.hpp"
@@ -44,18 +45,17 @@ namespace lms::db
// find // find
static std::size_t getCount(Session& session); static std::size_t getCount(Session& session);
static pointer find(Session& session, TrackEmbeddedImageLinkId id); static pointer find(Session& session, TrackEmbeddedImageLinkId id);
static void find(Session& session, TrackEmbeddedImageId trackEmbeddedImageId, std::function<void(const pointer&)> visitor);
// getters // getters
ObjectPtr<Track> getTrack() const; ObjectPtr<Track> getTrack() const;
ObjectPtr<TrackEmbeddedImage> getImage() const; ObjectPtr<TrackEmbeddedImage> getImage() const;
std::size_t getIndex() const { return _index; } std::size_t getIndex() const { return _index; }
bool isPreferred() const { return _isPreferred; }
ImageType getType() const { return _type; } ImageType getType() const { return _type; }
std::string_view getDescription() const { return _description; } std::string_view getDescription() const { return _description; }
// setters // setters
void setIndex(std::size_t index) { _index = static_cast<int>(index); } void setIndex(std::size_t index) { _index = static_cast<int>(index); }
void setIsPreferred(bool isPreferred) { _isPreferred = isPreferred; }
void setType(ImageType type) { _type = type; } void setType(ImageType type) { _type = type; }
void setDescription(std::string_view description) { _description = description; } void setDescription(std::string_view description) { _description = description; }
@@ -63,7 +63,6 @@ namespace lms::db
void persist(Action& a) void persist(Action& a)
{ {
Wt::Dbo::field(a, _index, "index"); Wt::Dbo::field(a, _index, "index");
Wt::Dbo::field(a, _isPreferred, "is_preferred");
Wt::Dbo::field(a, _type, "type"); Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _description, "description"); Wt::Dbo::field(a, _description, "description");
@@ -78,7 +77,6 @@ namespace lms::db
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image); static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image);
int _index{}; // index within the track int _index{}; // index within the track
bool _isPreferred{};
ImageType _type{ ImageType::Unknown }; ImageType _type{ ImageType::Unknown };
std::string _description; std::string _description;
+2 -1
View File
@@ -171,7 +171,8 @@ namespace lms::db
enum class TrackEmbeddedImageSortMethod enum class TrackEmbeddedImageSortMethod
{ {
None, None,
FrontCoverAndSize, MediaTypeThenFrontTypeThenSize,
FrontTypeThenSize,
}; };
enum class TrackListSortMethod enum class TrackListSortMethod
+70 -9
View File
@@ -118,23 +118,22 @@ namespace lms::db::tests
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
TrackEmbeddedImage::FindParameters params; TrackEmbeddedImage::FindParameters params;
params.setIsPreferred(true);
bool visited{}; bool visited{};
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
EXPECT_FALSE(visited); EXPECT_TRUE(visited);
} }
{ {
auto transaction{ session.createWriteTransaction() }; auto transaction{ session.createWriteTransaction() };
link.get().modify()->setIsPreferred(true); link.get().modify()->setType(ImageType::FrontCover);
} }
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
TrackEmbeddedImage::FindParameters params; TrackEmbeddedImage::FindParameters params;
params.setIsPreferred(true); params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize);
bool visited{}; bool visited{};
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
@@ -145,8 +144,8 @@ namespace lms::db::tests
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
TrackEmbeddedImage::FindParameters params; TrackEmbeddedImage::FindParameters params;
params.setIsPreferred(true);
params.setRelease(release.getId()); params.setRelease(release.getId());
params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize);
bool visited{}; bool visited{};
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
@@ -162,9 +161,8 @@ namespace lms::db::tests
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
TrackEmbeddedImage::FindParameters params; TrackEmbeddedImage::FindParameters params;
params.setIsPreferred(true);
params.setRelease(release.getId()); params.setRelease(release.getId());
params.setSortMethod(TrackEmbeddedImageSortMethod::FrontCoverAndSize); params.setSortMethod(TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize);
bool visited{}; bool visited{};
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
@@ -175,9 +173,8 @@ namespace lms::db::tests
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
TrackEmbeddedImage::FindParameters params; TrackEmbeddedImage::FindParameters params;
params.setIsPreferred(true);
params.setTrack(track.getId()); params.setTrack(track.getId());
params.setSortMethod(TrackEmbeddedImageSortMethod::FrontCoverAndSize); params.setSortMethod(TrackEmbeddedImageSortMethod::FrontTypeThenSize);
bool visited{}; bool visited{};
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); 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<TrackEmbeddedImageId> 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<TrackEmbeddedImageId> 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) TEST_F(DatabaseFixture, Track_findByEmbeddedImage)
{ {
ScopedTrackEmbeddedImage image{ session }; ScopedTrackEmbeddedImage image{ session };
@@ -279,5 +325,20 @@ namespace lms::db::tests
EXPECT_EQ(img->getType(), ImageType::FrontCover); EXPECT_EQ(img->getType(), ImageType::FrontCover);
EXPECT_EQ(img->getDescription(), "MyDesc"); 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 } // namespace lms::db::tests
@@ -19,9 +19,6 @@
#include "ArtworkService.hpp" #include "ArtworkService.hpp"
#include <algorithm>
#include <functional>
#include "core/IConfig.hpp" #include "core/IConfig.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
#include "core/Utils.hpp" #include "core/Utils.hpp"
@@ -32,6 +29,8 @@
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/TrackEmbeddedImage.hpp" #include "database/TrackEmbeddedImage.hpp"
#include "database/TrackEmbeddedImageLink.hpp"
#include "database/Types.hpp"
#include "image/Exception.hpp" #include "image/Exception.hpp"
#include "image/IEncodedImage.hpp" #include "image/IEncodedImage.hpp"
#include "image/Image.hpp" #include "image/Image.hpp"
@@ -39,14 +38,6 @@
namespace lms::cover namespace lms::cover
{ {
namespace
{
bool isFileSupported(const std::filesystem::path& file, const std::vector<std::filesystem::path>& extensions)
{
return (std::find(std::cbegin(extensions), std::cend(extensions), file.extension()) != std::cend(extensions));
}
} // namespace
std::unique_ptr<IArtworkService> createArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) std::unique_ptr<IArtworkService> createArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath)
{ {
return std::make_unique<ArtworkService>(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath); return std::make_unique<ArtworkService>(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath);
@@ -105,49 +96,18 @@ namespace lms::cover
return _defaultArtistImage; return _defaultArtistImage;
} }
bool ArtworkService::checkImageFile(const std::filesystem::path& filePath) std::unique_ptr<image::IEncodedImage> ArtworkService::getTrackImage(const std::filesystem::path& p, std::size_t index, std::optional<image::ImageSize> width) const
{ {
std::error_code ec; std::unique_ptr<image::IEncodedImage> image;
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<image::IEncodedImage> ArtworkService::getTrackImage(const std::filesystem::path& p, std::optional<image::ImageSize> width) const
{
struct CandidateImage
{
std::unique_ptr<image::IEncodedImage> 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<CandidateImage> candidateImages;
std::size_t pictureIndex{};
try try
{ {
std::size_t currentIndex{};
_audioFileParser->parseImages(p, [&](const metadata::Image& parsedImage) { _audioFileParser->parseImages(p, [&](const metadata::Image& parsedImage) {
std::unique_ptr<image::IEncodedImage> image; if (currentIndex++ != index)
return;
try try
{ {
if (!width) if (!width)
@@ -165,8 +125,6 @@ namespace lms::cover
{ {
LMS_LOG(COVER, ERROR, "Cannot decode image from track " << p << ": " << e.what()); 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) catch (const metadata::Exception& e)
@@ -174,11 +132,7 @@ namespace lms::cover
LMS_LOG(COVER, ERROR, "Cannot parse images from track " << p << ": " << e.what()); LMS_LOG(COVER, ERROR, "Cannot parse images from track " << p << ": " << e.what());
} }
std::stable_sort(std::begin(candidateImages), std::end(candidateImages), std::greater<>()); return image;
if (!candidateImages.empty())
return std::move(candidateImages.front().image);
return {};
} }
ArtworkService::ImageFindResult ArtworkService::findArtistImage(db::ArtistId artistId) ArtworkService::ImageFindResult ArtworkService::findArtistImage(db::ArtistId artistId)
@@ -199,7 +153,7 @@ namespace lms::cover
return res; return res;
} }
ArtworkService::ImageFindResult ArtworkService::findPreferredTrackImage(db::TrackId trackId) ArtworkService::ImageFindResult ArtworkService::findTrackImage(db::TrackId trackId)
{ {
db::Session& session{ _db.getTLSSession() }; db::Session& session{ _db.getTLSSession() };
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
@@ -207,16 +161,17 @@ namespace lms::cover
db::TrackEmbeddedImage::FindParameters params; db::TrackEmbeddedImage::FindParameters params;
params.setTrack(trackId); params.setTrack(trackId);
params.setIsPreferred(true); params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize);
params.setRange(db::Range{ .offset = 0, .size = 1 }); params.setRange(db::Range{ .offset = 0, .size = 1 });
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
res = image->getId(); res = image->getId();
}); });
// No embedded image found, fallback on release image
if (res.index() == 0) 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() }) if (const db::Release::pointer release{ track->getRelease() })
{ {
@@ -247,8 +202,7 @@ namespace lms::cover
{ {
db::TrackEmbeddedImage::FindParameters params; db::TrackEmbeddedImage::FindParameters params;
params.setRelease(releaseId); params.setRelease(releaseId);
params.setIsPreferred(true); params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontTypeThenSize);
params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize);
params.setRange(db::Range{ .offset = 0, .size = 1 }); params.setRange(db::Range{ .offset = 0, .size = 1 });
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
@@ -269,8 +223,7 @@ namespace lms::cover
db::TrackEmbeddedImage::FindParameters params; db::TrackEmbeddedImage::FindParameters params;
params.setTrackList(trackListId); params.setTrackList(trackListId);
params.setIsPreferred(true); params.setSortMethod(db::TrackEmbeddedImageSortMethod::MediaTypeThenFrontTypeThenSize);
params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize);
params.setRange(db::Range{ .offset = 0, .size = 1 }); params.setRange(db::Range{ .offset = 0, .size = 1 });
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
@@ -309,27 +262,24 @@ namespace lms::cover
{ {
const ImageCache::EntryDesc cacheEntryDesc{ trackEmbeddedImageId, width }; const ImageCache::EntryDesc cacheEntryDesc{ trackEmbeddedImageId, width };
std::shared_ptr<image::IEncodedImage> cover{ _cache.getImage(cacheEntryDesc) }; std::shared_ptr<image::IEncodedImage> image{ _cache.getImage(cacheEntryDesc) };
if (cover) if (image)
return cover; return image;
std::filesystem::path trackFile;
{ {
db::Session& session{ _db.getTLSSession() }; db::Session& session{ _db.getTLSSession() };
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
db::Track::FindParameters params; db::TrackEmbeddedImageLink::find(session, trackEmbeddedImageId, [&](const db::TrackEmbeddedImageLink::pointer& link) {
params.setEmbeddedImage(trackEmbeddedImageId); if (!image)
db::Track::find(session, params, [&](const db::Track::pointer& track) { image = getTrackImage(link->getTrack()->getAbsoluteFilePath(), link->getIndex(), width);
if (!cover)
cover = getTrackImage(track->getAbsoluteFilePath(), width);
}); });
} }
if (cover) if (image)
_cache.addImage(cacheEntryDesc, cover); _cache.addImage(cacheEntryDesc, image);
return cover; return image;
} }
void ArtworkService::flushCache() void ArtworkService::flushCache()
@@ -48,7 +48,7 @@ namespace lms::cover
private: private:
ImageFindResult findArtistImage(db::ArtistId artistId) override; 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 findReleaseImage(db::ReleaseId releaseId) override;
ImageFindResult findTrackListImage(db::TrackListId trackListId) override; ImageFindResult findTrackListImage(db::TrackListId trackListId) override;
@@ -62,9 +62,7 @@ namespace lms::cover
void setJpegQuality(unsigned quality) override; void setJpegQuality(unsigned quality) override;
std::unique_ptr<image::IEncodedImage> getFromImageFile(const std::filesystem::path& p, std::optional<image::ImageSize> width) const; std::unique_ptr<image::IEncodedImage> getFromImageFile(const std::filesystem::path& p, std::optional<image::ImageSize> width) const;
std::unique_ptr<image::IEncodedImage> getTrackImage(const std::filesystem::path& path, std::optional<image::ImageSize> width) const; std::unique_ptr<image::IEncodedImage> getTrackImage(const std::filesystem::path& path, std::size_t index, std::optional<image::ImageSize> width) const;
static bool checkImageFile(const std::filesystem::path& filePath);
db::Db& _db; db::Db& _db;
@@ -47,7 +47,11 @@ namespace lms::cover
// Helpers to find artworks // Helpers to find artworks
using ImageFindResult = std::variant<std::monostate, db::ImageId, db::TrackEmbeddedImageId>; using ImageFindResult = std::variant<std::monostate, db::ImageId, db::TrackEmbeddedImageId>;
virtual ImageFindResult findArtistImage(db::ArtistId artistId) = 0; 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 findReleaseImage(db::ReleaseId releaseId) = 0;
virtual ImageFindResult findTrackListImage(db::TrackListId trackListId) = 0; virtual ImageFindResult findTrackListImage(db::TrackListId trackListId) = 0;
@@ -350,24 +350,12 @@ namespace lms::scanner
void updateEmbeddedImages(db::Session& session, db::Track::pointer& track, std::span<const ImageInfo> images) void updateEmbeddedImages(db::Session& session, db::Track::pointer& track, std::span<const ImageInfo> images)
{ {
db::TrackEmbeddedImageLink::pointer preferredImageLink;
track.modify()->clearEmbeddedImageLinks(); track.modify()->clearEmbeddedImageLinks();
for (const ImageInfo& imageInfo : images) for (const ImageInfo& imageInfo : images)
{ {
db::TrackEmbeddedImageLink::pointer link{ createTrackEmbeddedImageLink(session, track, imageInfo) }; db::TrackEmbeddedImageLink::pointer link{ createTrackEmbeddedImageLink(session, track, imageInfo) };
track.modify()->addEmbeddedImageLink(link); 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<metadata::Track::Advisory> advisory) db::Advisory getAdvisory(std::optional<metadata::Track::Advisory> advisory)
+1 -1
View File
@@ -112,7 +112,7 @@ namespace lms::api::subsonic
} }
{ {
const auto imageResult{ core::Service<cover::IArtworkService>::get()->findPreferredTrackImage(track->getId()) }; const auto imageResult{ core::Service<cover::IArtworkService>::get()->findTrackImage(track->getId()) };
if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&imageResult) }) if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&imageResult) })
{ {
if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) }) if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) })
+2 -2
View File
@@ -253,8 +253,8 @@ namespace lms::ui
<< " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\"," << " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\","
<< " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\"," << " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
<< " artwork: [" << " artwork: ["
<< " { src: \"" << LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," << " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(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::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
<< " ]" << " ]"
<< "};"; << "};";
// Update 'sizes' above to match this: // Update 'sizes' above to match this:
+1 -1
View File
@@ -97,7 +97,7 @@ namespace lms::ui::utils
std::unique_ptr<Wt::WImage> createTrackImage(db::TrackId trackId, ArtworkResource::Size size) std::unique_ptr<Wt::WImage> createTrackImage(db::TrackId trackId, ArtworkResource::Size size)
{ {
auto cover{ std::make_unique<Wt::WImage>() }; auto cover{ std::make_unique<Wt::WImage>() };
cover->setImageLink(LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, size)); cover->setImageLink(LmsApp->getArtworkResource()->getTrackImageUrl(trackId, size));
cover->setStyleClass("Lms-cover img-fluid"); // HACK cover->setStyleClass("Lms-cover img-fluid"); // HACK
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
+2 -2
View File
@@ -81,11 +81,11 @@ namespace lms::ui
return url; return url;
} }
std::string ArtworkResource::getPreferredTrackImageUrl(db::TrackId trackId, std::optional<Size> size) const std::string ArtworkResource::getTrackImageUrl(db::TrackId trackId, std::optional<Size> size) const
{ {
std::string url; std::string url;
const auto imageResult{ core::Service<cover::IArtworkService>::get()->findPreferredTrackImage(trackId) }; const auto imageResult{ core::Service<cover::IArtworkService>::get()->findTrackImage(trackId) };
std::visit([&](const auto& arg) { std::visit([&](const auto& arg) {
using T = std::decay_t<decltype(arg)>; using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>) if constexpr (std::is_same_v<T, std::monostate>)
+1 -1
View File
@@ -47,7 +47,7 @@ namespace lms::ui
std::string getArtistImageUrl(db::ArtistId artistId, std::optional<Size> size = std::nullopt) const; std::string getArtistImageUrl(db::ArtistId artistId, std::optional<Size> size = std::nullopt) const;
std::string getReleaseCoverUrl(db::ReleaseId releaseId, std::optional<Size> size = std::nullopt) const; std::string getReleaseCoverUrl(db::ReleaseId releaseId, std::optional<Size> size = std::nullopt) const;
std::string getPreferredTrackImageUrl(db::TrackId trackId, std::optional<Size> size = std::nullopt) const; std::string getTrackImageUrl(db::TrackId trackId, std::optional<Size> size = std::nullopt) const;
private: private:
std::string getImageUrl(db::ImageId imageId, std::optional<Size> size, std::string_view type) const; std::string getImageUrl(db::ImageId imageId, std::optional<Size> size, std::string_view type) const;