Now using new artwork table everywhere in app and subsonic API to refer to artworks
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<message id="Lms.Explore.Artist.template">
|
<message id="Lms.Explore.Artist.template">
|
||||||
<div class="row mb-3 gy-3">
|
<div class="row mb-3 gy-3">
|
||||||
<div class="col-lg-3 col-md-4">
|
<div class="col-lg-3 col-md-4">
|
||||||
${artwork class="Lms-cursor-pointer rounded d-block mx-auto"}
|
${artwork class="rounded d-block mx-auto"}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2>${name}</h2>
|
<h2>${name}</h2>
|
||||||
|
|||||||
+4
-4
@@ -9,7 +9,7 @@
|
|||||||
<message id="Lms.Explore.Release.template">
|
<message id="Lms.Explore.Release.template">
|
||||||
<div class="row mb-3 gy-3">
|
<div class="row mb-3 gy-3">
|
||||||
<div class="col-lg-3 col-md-4">
|
<div class="col-lg-3 col-md-4">
|
||||||
${cover class="Lms-cursor-pointer rounded d-block mx-auto"}
|
${artwork class="rounded d-block mx-auto"}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2>${name}${<if-has-release-comment>}<span class="ms-1 text-muted">[${comment}]</span>${</if-has-release-comment>}</h2>
|
<h2>${name}${<if-has-release-comment>}<span class="ms-1 text-muted">[${comment}]</span>${</if-has-release-comment>}</h2>
|
||||||
@@ -55,9 +55,9 @@
|
|||||||
|
|
||||||
<message id="Lms.Explore.Release.template.entry-disc">
|
<message id="Lms.Explore.Release.template.entry-disc">
|
||||||
<h4 class="d-flex align-items-center">
|
<h4 class="d-flex align-items-center">
|
||||||
${<if-has-image>}
|
${<if-has-artwork>}
|
||||||
<div class="p-1">${image class="Lms-cursor-pointer"}</div>
|
<div class="p-1">${artwork class="Lms-cursor-pointer"}</div>
|
||||||
${</if-has-image>}
|
${</if-has-artwork>}
|
||||||
<div class="p-2 flex-fill">${disc-title}</div>
|
<div class="p-2 flex-fill">${disc-title}</div>
|
||||||
<div class="p-2 d-flex">
|
<div class="p-2 d-flex">
|
||||||
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
|
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "database/Artwork.hpp"
|
#include "database/Artwork.hpp"
|
||||||
|
|
||||||
|
#include <Wt/Dbo/WtSqlTraits.h>
|
||||||
|
|
||||||
#include "database/Image.hpp"
|
#include "database/Image.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/TrackEmbeddedImage.hpp"
|
#include "database/TrackEmbeddedImage.hpp"
|
||||||
@@ -74,4 +76,15 @@ namespace lms::db
|
|||||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artwork>>("SELECT a FROM artwork a").where("a.image_id = ?").bind(id));
|
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artwork>>("SELECT a FROM artwork a").where("a.image_id = ?").bind(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Wt::WDateTime Artwork::getLastWrittenTime() const
|
||||||
|
{
|
||||||
|
auto query{ session()->query<Wt::WDateTime>("SELECT MAX(COALESCE(image.file_last_write, track.file_last_write)) AS last_written_datetime FROM artwork") };
|
||||||
|
query.leftJoin("image ON artwork.image_id = image.id");
|
||||||
|
query.leftJoin("track_embedded_image ON artwork.track_embedded_image_id = track_embedded_image.id");
|
||||||
|
query.leftJoin("track_embedded_image_link ON track_embedded_image.id = track_embedded_image_link.track_embedded_image_id");
|
||||||
|
query.leftJoin("track ON track.id = track_embedded_image_link.track_id");
|
||||||
|
query.where("artwork.id = ?").bind(getId());
|
||||||
|
|
||||||
|
return utils::fetchQuerySingleResult(query);
|
||||||
|
}
|
||||||
} // namespace lms::db
|
} // namespace lms::db
|
||||||
|
|||||||
@@ -374,11 +374,21 @@ namespace lms::db
|
|||||||
return _preferredArtwork;
|
return _preferredArtwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArtworkId Track::getPreferredArtworkId() const
|
||||||
|
{
|
||||||
|
return _preferredArtwork.id();
|
||||||
|
}
|
||||||
|
|
||||||
ObjectPtr<Artwork> Track::getPreferredMediaArtwork() const
|
ObjectPtr<Artwork> Track::getPreferredMediaArtwork() const
|
||||||
{
|
{
|
||||||
return _preferredMediaArtwork;
|
return _preferredMediaArtwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArtworkId Track::getPreferredMediaArtworkId() const
|
||||||
|
{
|
||||||
|
return _preferredMediaArtwork.id();
|
||||||
|
}
|
||||||
|
|
||||||
RangeResults<TrackId> Track::findIds(Session& session, const FindParameters& parameters)
|
RangeResults<TrackId> Track::findIds(Session& session, const FindParameters& parameters)
|
||||||
{
|
{
|
||||||
session.checkReadTransaction();
|
session.checkReadTransaction();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Wt/Dbo/Dbo.h>
|
#include <Wt/Dbo/Dbo.h>
|
||||||
|
#include <Wt/WDateTime.h>
|
||||||
|
|
||||||
#include "database/ArtworkId.hpp"
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/ImageId.hpp"
|
#include "database/ImageId.hpp"
|
||||||
@@ -46,6 +47,7 @@ namespace lms::db
|
|||||||
// getters
|
// getters
|
||||||
TrackEmbeddedImageId getTrackEmbeddedImageId() const { return _trackEmbeddedImage.id(); }
|
TrackEmbeddedImageId getTrackEmbeddedImageId() const { return _trackEmbeddedImage.id(); }
|
||||||
ImageId getImageId() const { return _image.id(); }
|
ImageId getImageId() const { return _image.id(); }
|
||||||
|
Wt::WDateTime getLastWrittenTime() const;
|
||||||
|
|
||||||
template<class Action>
|
template<class Action>
|
||||||
void persist(Action& a)
|
void persist(Action& a)
|
||||||
|
|||||||
@@ -306,7 +306,9 @@ namespace lms::db
|
|||||||
ObjectPtr<MediaLibrary> getMediaLibrary() const;
|
ObjectPtr<MediaLibrary> getMediaLibrary() const;
|
||||||
ObjectPtr<Directory> getDirectory() const;
|
ObjectPtr<Directory> getDirectory() const;
|
||||||
ObjectPtr<Artwork> getPreferredArtwork() const;
|
ObjectPtr<Artwork> getPreferredArtwork() const;
|
||||||
|
ArtworkId getPreferredArtworkId() const;
|
||||||
ObjectPtr<Artwork> getPreferredMediaArtwork() const;
|
ObjectPtr<Artwork> getPreferredMediaArtwork() const;
|
||||||
|
ArtworkId getPreferredMediaArtworkId() const;
|
||||||
|
|
||||||
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypes, std::size_t size) const;
|
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypes, std::size_t size) const;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
#include "database/Artwork.hpp"
|
||||||
|
#include "database/Image.hpp"
|
||||||
|
#include "database/TrackEmbeddedImage.hpp"
|
||||||
|
#include "database/TrackEmbeddedImageLink.hpp"
|
||||||
|
|
||||||
|
namespace lms::db::tests
|
||||||
|
{
|
||||||
|
using ScopedArtwork = ScopedEntity<db::Artwork>;
|
||||||
|
using ScopedImage = ScopedEntity<db::Image>;
|
||||||
|
using ScopedTrackEmbeddedImage = ScopedEntity<db::TrackEmbeddedImage>;
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Artwork_image)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artwork::getCount(session), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedImage image{ session, "/MyImage" };
|
||||||
|
ScopedArtwork artwork{ session, image.lockAndGet() };
|
||||||
|
|
||||||
|
const Wt::WDateTime dateTime{ Wt::WDate{ 2025, 1, 1 } };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artwork::getCount(session), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
image.get().modify()->setLastWriteTime(dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(artwork.get()->getLastWrittenTime(), dateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Artwork_trackEmbeddedImage)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artwork::getCount(session), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedTrackEmbeddedImage image{ session };
|
||||||
|
ScopedTrack track{ session };
|
||||||
|
ScopedArtwork artwork{ session, image.lockAndGet() };
|
||||||
|
|
||||||
|
const Wt::WDateTime dateTime{ Wt::WDate{ 2025, 1, 1 } };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
session.create<db::TrackEmbeddedImageLink>(track.get(), image.get());
|
||||||
|
track.get().modify()->setLastWriteTime(dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(artwork.get()->getLastWrittenTime(), dateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace lms::db::tests
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
add_executable(test-database
|
add_executable(test-database
|
||||||
Artist.cpp
|
Artist.cpp
|
||||||
ArtistInfo.cpp
|
ArtistInfo.cpp
|
||||||
|
Artwork.cpp
|
||||||
AuthToken.cpp
|
AuthToken.cpp
|
||||||
Cluster.cpp
|
Cluster.cpp
|
||||||
Common.cpp
|
Common.cpp
|
||||||
|
|||||||
@@ -26,12 +26,13 @@
|
|||||||
#include "database/Artwork.hpp"
|
#include "database/Artwork.hpp"
|
||||||
#include "database/Db.hpp"
|
#include "database/Db.hpp"
|
||||||
#include "database/Image.hpp"
|
#include "database/Image.hpp"
|
||||||
|
#include "database/ImageId.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#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/TrackEmbeddedImageLink.hpp"
|
||||||
#include "database/Types.hpp"
|
#include "database/TrackList.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"
|
||||||
@@ -87,12 +88,12 @@ namespace lms::artwork
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getDefaultReleaseCover()
|
std::shared_ptr<image::IEncodedImage> ArtworkService::getDefaultReleaseArtwork()
|
||||||
{
|
{
|
||||||
return _defaultReleaseCover;
|
return _defaultReleaseCover;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getDefaultArtistImage()
|
std::shared_ptr<image::IEncodedImage> ArtworkService::getDefaultArtistArtwork()
|
||||||
{
|
{
|
||||||
return _defaultArtistImage;
|
return _defaultArtistImage;
|
||||||
}
|
}
|
||||||
@@ -136,150 +137,104 @@ namespace lms::artwork
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArtworkService::ImageFindResult ArtworkService::findArtistImage(db::ArtistId artistId)
|
db::ArtworkId ArtworkService::findTrackListImage(db::TrackListId trackListId)
|
||||||
{
|
{
|
||||||
|
db::ArtworkId artworkId;
|
||||||
|
|
||||||
|
// Iterate over all tracks and stop when we find an artwork
|
||||||
db::Session& session{ _db.getTLSSession() };
|
db::Session& session{ _db.getTLSSession() };
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
ImageFindResult res;
|
db::TrackList::pointer trackList{ db::TrackList::find(session, trackListId) };
|
||||||
|
if (!trackList)
|
||||||
|
return artworkId;
|
||||||
|
|
||||||
const db::Artist::pointer artist{ db::Artist::find(session, artistId) };
|
const auto entries{ trackList->getEntries(db::Range{ 0, 10 }) };
|
||||||
if (!artist)
|
for (const auto& entry : entries.results)
|
||||||
return res;
|
{
|
||||||
|
const auto track{ entry->getTrack() };
|
||||||
|
if (track->getPreferredMediaArtworkId().isValid())
|
||||||
|
{
|
||||||
|
artworkId = track->getPreferredMediaArtworkId();
|
||||||
|
break; // stop iteration
|
||||||
|
}
|
||||||
|
|
||||||
const db::Artwork::pointer artwork{ artist->getPreferredArtwork() };
|
if (track->getPreferredArtworkId().isValid())
|
||||||
if (artwork && artwork->getImageId().isValid())
|
{
|
||||||
res = artwork->getImageId();
|
artworkId = track->getPreferredArtworkId();
|
||||||
else if (artwork && artwork->getTrackEmbeddedImageId().isValid())
|
break; // stop iteration
|
||||||
res = artwork->getTrackEmbeddedImageId();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return artworkId;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArtworkService::ImageFindResult ArtworkService::findTrackImage(db::TrackId trackId)
|
std::shared_ptr<image::IEncodedImage> ArtworkService::getImage(db::ArtworkId artworkId, std::optional<image::ImageSize> width)
|
||||||
{
|
{
|
||||||
ImageFindResult res;
|
const ImageCache::EntryDesc cacheEntryDesc{ artworkId, width };
|
||||||
|
|
||||||
db::Session& session{ _db.getTLSSession() };
|
std::shared_ptr<image::IEncodedImage> image{ _cache.getImage(cacheEntryDesc) };
|
||||||
auto transaction{ session.createReadTransaction() };
|
if (image)
|
||||||
|
return image;
|
||||||
|
|
||||||
const db::Track::pointer track{ db::Track::find(session, trackId) };
|
db::TrackEmbeddedImageId trackEmbeddedImageId;
|
||||||
if (!track)
|
db::ImageId imageId;
|
||||||
return res;
|
|
||||||
|
|
||||||
const db::Artwork::pointer artwork{ track->getPreferredArtwork() };
|
{
|
||||||
if (artwork && artwork->getImageId().isValid())
|
db::Session& session{ _db.getTLSSession() };
|
||||||
res = artwork->getImageId();
|
auto transaction{ session.createReadTransaction() };
|
||||||
else if (artwork && artwork->getTrackEmbeddedImageId().isValid())
|
|
||||||
res = artwork->getTrackEmbeddedImageId();
|
|
||||||
|
|
||||||
return res;
|
db::Artwork::pointer artwork{ db::Artwork::find(session, artworkId) };
|
||||||
}
|
if (artwork)
|
||||||
|
{
|
||||||
|
trackEmbeddedImageId = artwork->getTrackEmbeddedImageId();
|
||||||
|
imageId = artwork->getImageId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ArtworkService::ImageFindResult ArtworkService::findTrackMediaImage(db::TrackId trackId)
|
if (trackEmbeddedImageId.isValid())
|
||||||
{
|
image = getTrackEmbeddedImage(trackEmbeddedImageId, width);
|
||||||
db::Session& session{ _db.getTLSSession() };
|
else if (imageId.isValid())
|
||||||
auto transaction{ session.createReadTransaction() };
|
image = getImage(imageId, width);
|
||||||
ImageFindResult res;
|
|
||||||
|
|
||||||
const db::Track::pointer track{ db::Track::find(session, trackId) };
|
if (image)
|
||||||
if (!track)
|
_cache.addImage(cacheEntryDesc, image);
|
||||||
return res;
|
|
||||||
|
|
||||||
const db::Artwork::pointer artwork{ track->getPreferredMediaArtwork() };
|
return image;
|
||||||
if (artwork && artwork->getImageId().isValid())
|
|
||||||
res = artwork->getImageId();
|
|
||||||
else if (artwork && artwork->getTrackEmbeddedImageId().isValid())
|
|
||||||
res = artwork->getTrackEmbeddedImageId();
|
|
||||||
|
|
||||||
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)
|
|
||||||
return res;
|
|
||||||
|
|
||||||
const db::Artwork::pointer artwork{ release->getPreferredArtwork() };
|
|
||||||
if (artwork && artwork->getImageId().isValid())
|
|
||||||
res = artwork->getImageId();
|
|
||||||
else if (artwork && artwork->getTrackEmbeddedImageId().isValid())
|
|
||||||
res = artwork->getTrackEmbeddedImageId();
|
|
||||||
|
|
||||||
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.setSortMethod(db::TrackEmbeddedImageSortMethod::TrackListIndexAscThenSizeDesc);
|
|
||||||
params.setRange(db::Range{ .offset = 0, .size = 1 });
|
|
||||||
|
|
||||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
|
||||||
res = image->getId();
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO fallback on release image if not found
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getImage(db::ImageId imageId, std::optional<image::ImageSize> width)
|
std::shared_ptr<image::IEncodedImage> ArtworkService::getImage(db::ImageId imageId, std::optional<image::ImageSize> width)
|
||||||
{
|
{
|
||||||
const ImageCache::EntryDesc cacheEntryDesc{ imageId, width };
|
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> cover{ _cache.getImage(cacheEntryDesc) };
|
|
||||||
if (cover)
|
|
||||||
return cover;
|
|
||||||
|
|
||||||
std::filesystem::path imageFile;
|
std::filesystem::path imageFile;
|
||||||
{
|
{
|
||||||
db::Session& session{ _db.getTLSSession() };
|
db::Session& session{ _db.getTLSSession() };
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
const db::Image::pointer image{ db::Image::find(session, imageId) };
|
const db::Image::pointer image{ db::Image::find(session, imageId) };
|
||||||
if (image)
|
if (!image)
|
||||||
imageFile = image->getAbsoluteFilePath();
|
return nullptr;
|
||||||
|
|
||||||
|
imageFile = image->getAbsoluteFilePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
cover = getFromImageFile(imageFile, width);
|
return getFromImageFile(imageFile, width);
|
||||||
if (cover)
|
|
||||||
_cache.addImage(cacheEntryDesc, cover);
|
|
||||||
|
|
||||||
return cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width)
|
std::shared_ptr<image::IEncodedImage> ArtworkService::getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width)
|
||||||
{
|
{
|
||||||
const ImageCache::EntryDesc cacheEntryDesc{ trackEmbeddedImageId, width };
|
std::shared_ptr<image::IEncodedImage> image;
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> image{ _cache.getImage(cacheEntryDesc) };
|
|
||||||
if (image)
|
|
||||||
return image;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
db::Session& session{ _db.getTLSSession() };
|
db::Session& session{ _db.getTLSSession() };
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
// TODO: could be put outside transaction
|
||||||
db::TrackEmbeddedImageLink::find(session, trackEmbeddedImageId, [&](const db::TrackEmbeddedImageLink::pointer& link) {
|
db::TrackEmbeddedImageLink::find(session, trackEmbeddedImageId, [&](const db::TrackEmbeddedImageLink::pointer& link) {
|
||||||
if (!image)
|
if (!image)
|
||||||
image = getTrackImage(link->getTrack()->getAbsoluteFilePath(), link->getIndex(), width);
|
image = getTrackImage(link->getTrack()->getAbsoluteFilePath(), link->getIndex(), width);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image)
|
|
||||||
_cache.addImage(cacheEntryDesc, image);
|
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "database/ImageId.hpp"
|
||||||
|
#include "database/TrackEmbeddedImageId.hpp"
|
||||||
#include "services/artwork/IArtworkService.hpp"
|
#include "services/artwork/IArtworkService.hpp"
|
||||||
|
|
||||||
#include "ImageCache.hpp"
|
#include "ImageCache.hpp"
|
||||||
@@ -47,21 +49,19 @@ namespace lms::artwork
|
|||||||
ArtworkService& operator=(const ArtworkService&) = delete;
|
ArtworkService& operator=(const ArtworkService&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ImageFindResult findArtistImage(db::ArtistId artistId) override;
|
db::ArtworkId findTrackListImage(db::TrackListId trackListId) 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;
|
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> getImage(db::ImageId imageId, std::optional<image::ImageSize> width) override;
|
std::shared_ptr<image::IEncodedImage> getImage(db::ArtworkId artworkId, std::optional<image::ImageSize> width) override;
|
||||||
std::shared_ptr<image::IEncodedImage> getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width) override;
|
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() override;
|
std::shared_ptr<image::IEncodedImage> getDefaultReleaseArtwork() override;
|
||||||
std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() override;
|
std::shared_ptr<image::IEncodedImage> getDefaultArtistArtwork() override;
|
||||||
|
|
||||||
void flushCache() override;
|
void flushCache() override;
|
||||||
void setJpegQuality(unsigned quality) override;
|
void setJpegQuality(unsigned quality) override;
|
||||||
|
|
||||||
|
std::shared_ptr<image::IEncodedImage> getImage(db::ImageId imageId, std::optional<image::ImageSize> width);
|
||||||
|
std::shared_ptr<image::IEncodedImage> getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width);
|
||||||
|
|
||||||
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::size_t index, 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;
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,8 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "database/ImageId.hpp"
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/TrackEmbeddedImageId.hpp"
|
|
||||||
#include "image/IEncodedImage.hpp"
|
#include "image/IEncodedImage.hpp"
|
||||||
|
|
||||||
namespace lms::artwork
|
namespace lms::artwork
|
||||||
@@ -40,8 +38,7 @@ namespace lms::artwork
|
|||||||
|
|
||||||
struct EntryDesc
|
struct EntryDesc
|
||||||
{
|
{
|
||||||
using VariantType = std::variant<db::TrackEmbeddedImageId, db::ImageId>;
|
db::ArtworkId id;
|
||||||
VariantType id;
|
|
||||||
std::optional<std::size_t> size;
|
std::optional<std::size_t> size;
|
||||||
|
|
||||||
bool operator==(const EntryDesc& other) const = default;
|
bool operator==(const EntryDesc& other) const = default;
|
||||||
@@ -63,13 +60,13 @@ namespace lms::artwork
|
|||||||
std::size_t operator()(const EntryDesc& entry) const
|
std::size_t operator()(const EntryDesc& entry) const
|
||||||
{
|
{
|
||||||
assert(entry.size); // should not cache unresized images
|
assert(entry.size); // should not cache unresized images
|
||||||
return std::hash<EntryDesc::VariantType>{}(entry.id) ^ std::hash<std::size_t>{}(*entry.size);
|
return std::hash<db::ArtworkId>{}(entry.id) ^ std::hash<std::size_t>{}(*entry.size);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_map<EntryDesc, std::shared_ptr<image::IEncodedImage>, EntryHasher> _cache;
|
std::unordered_map<EntryDesc, std::shared_ptr<image::IEncodedImage>, EntryHasher> _cache;
|
||||||
std::size_t _cacheSize{};
|
std::size_t _cacheSize{};
|
||||||
mutable std::atomic<std::size_t> _cacheMisses{};
|
mutable std::atomic<std::size_t> _cacheMisses;
|
||||||
mutable std::atomic<std::size_t> _cacheHits{};
|
mutable std::atomic<std::size_t> _cacheHits;
|
||||||
};
|
};
|
||||||
} // namespace lms::artwork
|
} // namespace lms::artwork
|
||||||
@@ -22,13 +22,8 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "database/ArtistId.hpp"
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/ImageId.hpp"
|
|
||||||
#include "database/ReleaseId.hpp"
|
|
||||||
#include "database/TrackEmbeddedImageId.hpp"
|
|
||||||
#include "database/TrackId.hpp"
|
|
||||||
#include "database/TrackListId.hpp"
|
#include "database/TrackListId.hpp"
|
||||||
#include "image/IEncodedImage.hpp"
|
#include "image/IEncodedImage.hpp"
|
||||||
|
|
||||||
@@ -44,27 +39,15 @@ namespace lms::artwork
|
|||||||
public:
|
public:
|
||||||
virtual ~IArtworkService() = default;
|
virtual ~IArtworkService() = default;
|
||||||
|
|
||||||
// Helpers to find artworks
|
// Helpers to get preferred artworks
|
||||||
using ImageFindResult = std::variant<std::monostate, db::ImageId, db::TrackEmbeddedImageId>;
|
virtual db::ArtworkId findTrackListImage(db::TrackListId trackListId) = 0;
|
||||||
virtual ImageFindResult findArtistImage(db::ArtistId artistId) = 0;
|
|
||||||
|
|
||||||
// Will get Disc/Media artwork if available, otherwise, will fallback on release artwork
|
// Image retrieval, no width means original size
|
||||||
virtual ImageFindResult findTrackImage(db::TrackId trackId) = 0;
|
virtual std::shared_ptr<image::IEncodedImage> getImage(db::ArtworkId artworkId, std::optional<image::ImageSize> width) = 0;
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Image retrieval
|
|
||||||
virtual std::shared_ptr<image::IEncodedImage> getImage(db::ImageId imageId, std::optional<image::ImageSize> width) = 0;
|
|
||||||
virtual std::shared_ptr<image::IEncodedImage> getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width) = 0;
|
|
||||||
|
|
||||||
// Svg images don't have image "size"
|
// Svg images don't have image "size"
|
||||||
virtual std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() = 0;
|
virtual std::shared_ptr<image::IEncodedImage> getDefaultReleaseArtwork() = 0;
|
||||||
virtual std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() = 0;
|
virtual std::shared_ptr<image::IEncodedImage> getDefaultArtistArtwork() = 0;
|
||||||
|
|
||||||
virtual void flushCache() = 0;
|
virtual void flushCache() = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ namespace lms::scanner
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReleaseImageAssociation
|
struct ReleaseArtworkAssociation
|
||||||
{
|
{
|
||||||
db::ReleaseId releaseId;
|
db::Release::pointer release;
|
||||||
ReleaseArtwork preferredArtwork;
|
ReleaseArtwork preferredArtwork;
|
||||||
};
|
};
|
||||||
using ReleaseImageAssociationContainer = std::deque<ReleaseImageAssociation>;
|
using ReleaseArtworkAssociationContainer = std::deque<ReleaseArtworkAssociation>;
|
||||||
|
|
||||||
struct SearchReleaseImageContext
|
struct SearchReleaseArtworkContext
|
||||||
{
|
{
|
||||||
db::Session& session;
|
db::Session& session;
|
||||||
db::ReleaseId lastRetrievedReleaseId;
|
db::ReleaseId lastRetrievedReleaseId;
|
||||||
@@ -75,7 +75,7 @@ namespace lms::scanner
|
|||||||
const std::vector<std::string>& releaseImageFileNames;
|
const std::vector<std::string>& releaseImageFileNames;
|
||||||
};
|
};
|
||||||
|
|
||||||
db::Image::pointer findImageInDirectory(SearchReleaseImageContext& searchContext, const std::filesystem::path& directoryPath)
|
db::Image::pointer findImageInDirectory(SearchReleaseArtworkContext& searchContext, const std::filesystem::path& directoryPath)
|
||||||
{
|
{
|
||||||
db::Image::pointer image;
|
db::Image::pointer image;
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ namespace lms::scanner
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
db::Image::pointer computePreferredReleaseImage(SearchReleaseImageContext& searchContext, const db::Release::pointer& release)
|
db::Image::pointer computePreferredReleaseImage(SearchReleaseArtworkContext& searchContext, const db::Release::pointer& release)
|
||||||
{
|
{
|
||||||
db::Image::pointer image;
|
db::Image::pointer image;
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ namespace lms::scanner
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReleaseArtwork computePreferredReleaseArtwork(SearchReleaseImageContext& searchContext, const db::Release::pointer& release)
|
ReleaseArtwork computePreferredReleaseArtwork(SearchReleaseArtworkContext& searchContext, const db::Release::pointer& release)
|
||||||
{
|
{
|
||||||
const db::Image::pointer image{ computePreferredReleaseImage(searchContext, release) };
|
const db::Image::pointer image{ computePreferredReleaseImage(searchContext, release) };
|
||||||
if (image)
|
if (image)
|
||||||
@@ -187,7 +187,7 @@ namespace lms::scanner
|
|||||||
return ReleaseArtwork{};
|
return ReleaseArtwork{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fetchNextReleaseArtworksToUpdate(SearchReleaseImageContext& searchContext, ReleaseImageAssociationContainer& releaseImageAssociations)
|
bool fetchNextReleaseArtworksToUpdate(SearchReleaseArtworkContext& searchContext, ReleaseArtworkAssociationContainer& ReleaseArtworkAssociations)
|
||||||
{
|
{
|
||||||
const db::ReleaseId releaseId{ searchContext.lastRetrievedReleaseId };
|
const db::ReleaseId releaseId{ searchContext.lastRetrievedReleaseId };
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ namespace lms::scanner
|
|||||||
const db::Artwork::pointer currentPreferredArtwork{ release->getPreferredArtwork() };
|
const db::Artwork::pointer currentPreferredArtwork{ release->getPreferredArtwork() };
|
||||||
|
|
||||||
if (!isSameArtwork(preferredArtwork, currentPreferredArtwork))
|
if (!isSameArtwork(preferredArtwork, currentPreferredArtwork))
|
||||||
releaseImageAssociations.push_back(ReleaseImageAssociation{ release->getId(), preferredArtwork });
|
ReleaseArtworkAssociations.push_back(ReleaseArtworkAssociation{ release, preferredArtwork });
|
||||||
searchContext.processedReleaseCount++;
|
searchContext.processedReleaseCount++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -209,16 +209,14 @@ namespace lms::scanner
|
|||||||
return releaseId != searchContext.lastRetrievedReleaseId;
|
return releaseId != searchContext.lastRetrievedReleaseId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateReleaseArtwork(db::Session& session, const ReleaseImageAssociation& releaseImageAssociation)
|
void updateReleaseArtwork(db::Session& session, const ReleaseArtworkAssociation& ReleaseArtworkAssociation)
|
||||||
{
|
{
|
||||||
db::Release::pointer release{ db::Release::find(session, releaseImageAssociation.releaseId) };
|
db::Release::pointer release{ ReleaseArtworkAssociation.release };
|
||||||
assert(release);
|
|
||||||
|
|
||||||
db::Artwork::pointer artwork;
|
db::Artwork::pointer artwork;
|
||||||
|
if (const db::TrackEmbeddedImageId * trackEmbeddedImageId{ std::get_if<db::TrackEmbeddedImageId>(&ReleaseArtworkAssociation.preferredArtwork) })
|
||||||
if (const db::TrackEmbeddedImageId * trackEmbeddedImageId{ std::get_if<db::TrackEmbeddedImageId>(&releaseImageAssociation.preferredArtwork) })
|
|
||||||
artwork = utils::getOrCreateArtworkFromTrackEmbeddedImage(session, *trackEmbeddedImageId);
|
artwork = utils::getOrCreateArtworkFromTrackEmbeddedImage(session, *trackEmbeddedImageId);
|
||||||
else if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&releaseImageAssociation.preferredArtwork) })
|
else if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&ReleaseArtworkAssociation.preferredArtwork) })
|
||||||
artwork = utils::getOrCreateArtworkFromImage(session, *imageId);
|
artwork = utils::getOrCreateArtworkFromImage(session, *imageId);
|
||||||
|
|
||||||
release.modify()->setPreferredArtwork(artwork);
|
release.modify()->setPreferredArtwork(artwork);
|
||||||
@@ -229,7 +227,7 @@ namespace lms::scanner
|
|||||||
LMS_LOG(DBUPDATER, DEBUG, "Removed preferred artwork from release '" << release->getName() << "'");
|
LMS_LOG(DBUPDATER, DEBUG, "Removed preferred artwork from release '" << release->getName() << "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateReleaseImages(db::Session& session, ReleaseImageAssociationContainer& imageAssociations)
|
void updateReleaseArtworks(db::Session& session, ReleaseArtworkAssociationContainer& imageAssociations)
|
||||||
{
|
{
|
||||||
constexpr std::size_t writeBatchSize{ 50 };
|
constexpr std::size_t writeBatchSize{ 50 };
|
||||||
|
|
||||||
@@ -280,19 +278,19 @@ namespace lms::scanner
|
|||||||
context.currentStepStats.totalElems = db::Release::getCount(session);
|
context.currentStepStats.totalElems = db::Release::getCount(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchReleaseImageContext searchContext{
|
SearchReleaseArtworkContext searchContext{
|
||||||
.session = session,
|
.session = session,
|
||||||
.lastRetrievedReleaseId = {},
|
.lastRetrievedReleaseId = {},
|
||||||
.releaseImageFileNames = _releaseImageFileNames,
|
.releaseImageFileNames = _releaseImageFileNames,
|
||||||
};
|
};
|
||||||
|
|
||||||
ReleaseImageAssociationContainer releaseImageAssociations;
|
ReleaseArtworkAssociationContainer ReleaseArtworkAssociations;
|
||||||
while (fetchNextReleaseArtworksToUpdate(searchContext, releaseImageAssociations))
|
while (fetchNextReleaseArtworksToUpdate(searchContext, ReleaseArtworkAssociations))
|
||||||
{
|
{
|
||||||
if (_abortScan)
|
if (_abortScan)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
updateReleaseImages(session, releaseImageAssociations);
|
updateReleaseArtworks(session, ReleaseArtworkAssociations);
|
||||||
context.currentStepStats.processedElems = searchContext.processedReleaseCount;
|
context.currentStepStats.processedElems = searchContext.processedReleaseCount;
|
||||||
_progressCallback(context.currentStepStats);
|
_progressCallback(context.currentStepStats);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,22 +62,22 @@ namespace lms::scanner
|
|||||||
return !std::holds_alternative<std::monostate>(res);
|
return !std::holds_alternative<std::monostate>(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrackImageAssociation
|
struct TrackArtworksAssociation
|
||||||
{
|
{
|
||||||
db::Track::pointer track;
|
db::Track::pointer track;
|
||||||
TrackArtwork preferredArtwork;
|
TrackArtwork preferredArtwork;
|
||||||
TrackArtwork preferredMediaArtwork;
|
TrackArtwork preferredMediaArtwork;
|
||||||
};
|
};
|
||||||
using TrackImageAssociationContainer = std::deque<TrackImageAssociation>;
|
using TrackArtworksAssociationContainer = std::deque<TrackArtworksAssociation>;
|
||||||
|
|
||||||
struct SearchTrackImageContext
|
struct SearchTrackArtworkContext
|
||||||
{
|
{
|
||||||
db::Session& session;
|
db::Session& session;
|
||||||
db::TrackId lastRetrievedTrackId;
|
db::TrackId lastRetrievedTrackId;
|
||||||
std::size_t processedTrackCount{};
|
std::size_t processedTrackCount{};
|
||||||
};
|
};
|
||||||
|
|
||||||
TrackArtwork computePreferredTrackArtwork(SearchTrackImageContext& searchContext, const db::Track::pointer& track)
|
TrackArtwork computePreferredTrackArtwork(SearchTrackArtworkContext& searchContext, const db::Track::pointer& track)
|
||||||
{
|
{
|
||||||
// Try to get a media image
|
// Try to get a media image
|
||||||
TrackArtwork res;
|
TrackArtwork res;
|
||||||
@@ -135,7 +135,7 @@ namespace lms::scanner
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackArtwork computePreferredTrackMediaArtwork(SearchTrackImageContext& searchContext, const db::Track::pointer& track)
|
TrackArtwork computePreferredTrackMediaArtwork(SearchTrackArtworkContext& searchContext, const db::Track::pointer& track)
|
||||||
{
|
{
|
||||||
TrackArtwork res;
|
TrackArtwork res;
|
||||||
{
|
{
|
||||||
@@ -165,7 +165,7 @@ namespace lms::scanner
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fetchNextTrackArtworksToUpdate(SearchTrackImageContext& searchContext, TrackImageAssociationContainer& trackImageAssociations)
|
bool fetchNextTrackArtworksToUpdate(SearchTrackArtworkContext& searchContext, TrackArtworksAssociationContainer& TrackArtworksAssociations)
|
||||||
{
|
{
|
||||||
const db::TrackId trackId{ searchContext.lastRetrievedTrackId };
|
const db::TrackId trackId{ searchContext.lastRetrievedTrackId };
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ namespace lms::scanner
|
|||||||
if (!isSameArtwork(preferredArtwork, currentPreferredArtwork)
|
if (!isSameArtwork(preferredArtwork, currentPreferredArtwork)
|
||||||
|| !isSameArtwork(preferredMediaArtwork, currentPreferredMediaArtwork))
|
|| !isSameArtwork(preferredMediaArtwork, currentPreferredMediaArtwork))
|
||||||
{
|
{
|
||||||
trackImageAssociations.push_back(TrackImageAssociation{ track, preferredArtwork, preferredMediaArtwork });
|
TrackArtworksAssociations.push_back(TrackArtworksAssociation{ track, preferredArtwork, preferredMediaArtwork });
|
||||||
}
|
}
|
||||||
|
|
||||||
searchContext.processedTrackCount++;
|
searchContext.processedTrackCount++;
|
||||||
@@ -226,24 +226,24 @@ namespace lms::scanner
|
|||||||
LMS_LOG(DBUPDATER, DEBUG, "Removed preferred media artwork from track '" << track->getAbsoluteFilePath() << "'");
|
LMS_LOG(DBUPDATER, DEBUG, "Removed preferred media artwork from track '" << track->getAbsoluteFilePath() << "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateTrackPreferredArtworks(db::Session& session, const TrackImageAssociation& trackImageAssociation)
|
void updateTrackPreferredArtworks(db::Session& session, const TrackArtworksAssociation& TrackArtworksAssociation)
|
||||||
{
|
{
|
||||||
db::Track::pointer track{ trackImageAssociation.track };
|
db::Track::pointer track{ TrackArtworksAssociation.track };
|
||||||
|
|
||||||
{
|
{
|
||||||
const db::Artwork::pointer currentPreferredArtwork{ track->getPreferredArtwork() };
|
const db::Artwork::pointer currentPreferredArtwork{ track->getPreferredArtwork() };
|
||||||
if (!isSameArtwork(trackImageAssociation.preferredArtwork, currentPreferredArtwork))
|
if (!isSameArtwork(TrackArtworksAssociation.preferredArtwork, currentPreferredArtwork))
|
||||||
updateTrackPreferredArtwork(session, track, trackImageAssociation.preferredArtwork);
|
updateTrackPreferredArtwork(session, track, TrackArtworksAssociation.preferredArtwork);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const db::Artwork::pointer currentPreferredMediaArtwork{ track->getPreferredMediaArtwork() };
|
const db::Artwork::pointer currentPreferredMediaArtwork{ track->getPreferredMediaArtwork() };
|
||||||
if (!isSameArtwork(trackImageAssociation.preferredMediaArtwork, currentPreferredMediaArtwork))
|
if (!isSameArtwork(TrackArtworksAssociation.preferredMediaArtwork, currentPreferredMediaArtwork))
|
||||||
updateTrackPreferredMediaArtwork(session, track, trackImageAssociation.preferredMediaArtwork);
|
updateTrackPreferredMediaArtwork(session, track, TrackArtworksAssociation.preferredMediaArtwork);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateTrackPreferredArtworks(db::Session& session, TrackImageAssociationContainer& imageAssociations)
|
void updateTrackPreferredArtworks(db::Session& session, TrackArtworksAssociationContainer& imageAssociations)
|
||||||
{
|
{
|
||||||
constexpr std::size_t writeBatchSize{ 50 };
|
constexpr std::size_t writeBatchSize{ 50 };
|
||||||
|
|
||||||
@@ -279,18 +279,18 @@ namespace lms::scanner
|
|||||||
context.currentStepStats.totalElems = db::Track::getCount(session);
|
context.currentStepStats.totalElems = db::Track::getCount(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchTrackImageContext searchContext{
|
SearchTrackArtworkContext searchContext{
|
||||||
.session = session,
|
.session = session,
|
||||||
.lastRetrievedTrackId = {},
|
.lastRetrievedTrackId = {},
|
||||||
};
|
};
|
||||||
|
|
||||||
TrackImageAssociationContainer trackImageAssociations;
|
TrackArtworksAssociationContainer TrackArtworksAssociations;
|
||||||
while (fetchNextTrackArtworksToUpdate(searchContext, trackImageAssociations))
|
while (fetchNextTrackArtworksToUpdate(searchContext, TrackArtworksAssociations))
|
||||||
{
|
{
|
||||||
if (_abortScan)
|
if (_abortScan)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
updateTrackPreferredArtworks(session, trackImageAssociations);
|
updateTrackPreferredArtworks(session, TrackArtworksAssociations);
|
||||||
context.currentStepStats.processedElems = searchContext.processedTrackCount;
|
context.currentStepStats.processedElems = searchContext.processedTrackCount;
|
||||||
_progressCallback(context.currentStepStats);
|
_progressCallback(context.currentStepStats);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
#include "database/ImageId.hpp"
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/TrackEmbeddedImageId.hpp"
|
|
||||||
|
|
||||||
namespace lms::api::subsonic
|
namespace lms::api::subsonic
|
||||||
{
|
{
|
||||||
@@ -31,14 +30,9 @@ namespace lms::api::subsonic
|
|||||||
{
|
{
|
||||||
constexpr char timestampSeparatorChar{ '-' };
|
constexpr char timestampSeparatorChar{ '-' };
|
||||||
|
|
||||||
std::string idToString(db::ImageId id)
|
std::string idToString(db::ArtworkId id)
|
||||||
{
|
{
|
||||||
return "im-" + id.toString();
|
return "art-" + id.toString();
|
||||||
}
|
|
||||||
|
|
||||||
std::string idToString(db::TrackEmbeddedImageId id)
|
|
||||||
{
|
|
||||||
return "trim-" + id.toString();
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -46,19 +40,10 @@ namespace lms::api::subsonic
|
|||||||
{
|
{
|
||||||
std::string res;
|
std::string res;
|
||||||
|
|
||||||
// produce "type-id[-timestamp]"
|
// produce "art-id-timestamp"
|
||||||
if (db::TrackEmbeddedImageId * imageId{ std::get_if<db::TrackEmbeddedImageId>(&coverId.id) })
|
res = idToString(coverId.id);
|
||||||
{
|
res += timestampSeparatorChar;
|
||||||
res = idToString(*imageId);
|
res += std::to_string(coverId.timestamp);
|
||||||
assert(!coverId.timestamp.has_value());
|
|
||||||
}
|
|
||||||
else if (db::ImageId * imageId{ std::get_if<db::ImageId>(&coverId.id) })
|
|
||||||
{
|
|
||||||
res = idToString(*imageId);
|
|
||||||
res += timestampSeparatorChar;
|
|
||||||
assert(coverId.timestamp.has_value());
|
|
||||||
res += std::to_string(*coverId.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(!res.empty());
|
assert(!res.empty());
|
||||||
return res;
|
return res;
|
||||||
@@ -74,30 +59,20 @@ namespace lms::core::stringUtils
|
|||||||
std::optional<api::subsonic::CoverArtId> res;
|
std::optional<api::subsonic::CoverArtId> res;
|
||||||
|
|
||||||
std::vector<std::string_view> values{ core::stringUtils::splitString(str, '-') };
|
std::vector<std::string_view> values{ core::stringUtils::splitString(str, '-') };
|
||||||
if (values.size() <= 1)
|
|
||||||
|
// expect "art-id-timestamp"
|
||||||
|
if (values.size() != 3)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (values[0] != "art")
|
||||||
|
return res;
|
||||||
|
|
||||||
|
const auto value{ core::stringUtils::readAs<db::ArtworkId::ValueType>(values[1]) };
|
||||||
|
const auto timestamp{ core::stringUtils::readAs<std::time_t>(values[2]) };
|
||||||
|
if (!value || !timestamp)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
if (values[0] == "trim")
|
res.emplace(api::subsonic::CoverArtId{ *value, *timestamp });
|
||||||
{
|
|
||||||
// expect "trim-id"
|
|
||||||
if (values.size() == 2)
|
|
||||||
{
|
|
||||||
if (const auto value{ core::stringUtils::readAs<db::TrackEmbeddedImageId::ValueType>(values[1]) })
|
|
||||||
res.emplace(db::TrackEmbeddedImageId{ *value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (values[0] == "im")
|
|
||||||
{
|
|
||||||
// expect "im-id-timestamp"
|
|
||||||
if (values.size() == 3)
|
|
||||||
{
|
|
||||||
const auto imageId{ core::stringUtils::readAs<db::ImageId::ValueType>(values[1]) };
|
|
||||||
const auto timestamp{ core::stringUtils::readAs<std::time_t>(values[2]) };
|
|
||||||
|
|
||||||
if (imageId && timestamp)
|
|
||||||
res.emplace(db::ImageId{ *imageId }, *timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,24 +20,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
#include "database/ImageId.hpp"
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/TrackEmbeddedImageId.hpp"
|
|
||||||
|
|
||||||
namespace lms::api::subsonic
|
namespace lms::api::subsonic
|
||||||
{
|
{
|
||||||
struct CoverArtId
|
struct CoverArtId
|
||||||
{
|
{
|
||||||
std::variant<db::ImageId, db::TrackEmbeddedImageId> id;
|
db::ArtworkId id;
|
||||||
std::optional<std::time_t> timestamp;
|
std::time_t timestamp;
|
||||||
|
|
||||||
CoverArtId(db::ImageId _id, std::time_t _timestamp)
|
|
||||||
: id{ _id }
|
|
||||||
, timestamp{ _timestamp } {}
|
|
||||||
CoverArtId(db::TrackEmbeddedImageId _id)
|
|
||||||
: id{ _id } {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string idToString(CoverArtId coverId);
|
std::string idToString(CoverArtId coverId);
|
||||||
@@ -46,9 +38,6 @@ namespace lms::api::subsonic
|
|||||||
// Used to parse parameters
|
// Used to parse parameters
|
||||||
namespace lms::core::stringUtils
|
namespace lms::core::stringUtils
|
||||||
{
|
{
|
||||||
template<>
|
|
||||||
std::optional<db::ImageId> readAs(std::string_view str);
|
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::optional<api::subsonic::CoverArtId> readAs(std::string_view str);
|
std::optional<api::subsonic::CoverArtId> readAs(std::string_view str);
|
||||||
} // namespace lms::core::stringUtils
|
} // namespace lms::core::stringUtils
|
||||||
|
|||||||
@@ -336,12 +336,7 @@ namespace lms::api::subsonic
|
|||||||
if (size)
|
if (size)
|
||||||
*size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 });
|
*size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 });
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> image;
|
std::shared_ptr<image::IEncodedImage> image{ core::Service<artwork::IArtworkService>::get()->getImage(coverArtId.id, size) };
|
||||||
if (const db::TrackEmbeddedImageId * trackEmbeddedImageId{ std::get_if<db::TrackEmbeddedImageId>(&coverArtId.id) })
|
|
||||||
image = core::Service<artwork::IArtworkService>::get()->getTrackEmbeddedImage(*trackEmbeddedImageId, size);
|
|
||||||
else if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&coverArtId.id) })
|
|
||||||
image = core::Service<artwork::IArtworkService>::get()->getImage(*imageId, size);
|
|
||||||
|
|
||||||
if (!image)
|
if (!image)
|
||||||
{
|
{
|
||||||
response.setStatus(404);
|
response.setStatus(404);
|
||||||
|
|||||||
@@ -23,14 +23,13 @@
|
|||||||
#include "core/Service.hpp"
|
#include "core/Service.hpp"
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
|
#include "database/Artwork.hpp"
|
||||||
#include "database/Cluster.hpp"
|
#include "database/Cluster.hpp"
|
||||||
#include "database/Directory.hpp"
|
#include "database/Directory.hpp"
|
||||||
#include "database/Image.hpp"
|
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "services/artwork/IArtworkService.hpp"
|
|
||||||
#include "services/feedback/IFeedbackService.hpp"
|
#include "services/feedback/IFeedbackService.hpp"
|
||||||
#include "services/scrobbling/IScrobblingService.hpp"
|
#include "services/scrobbling/IScrobblingService.hpp"
|
||||||
|
|
||||||
@@ -88,19 +87,12 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getAddedTime()));
|
albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getAddedTime()));
|
||||||
|
|
||||||
|
if (const auto artwork{ release->getPreferredArtwork() })
|
||||||
{
|
{
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findReleaseImage(release->getId()) };
|
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
|
||||||
if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&imageResult) })
|
albumNode.setAttribute("coverArt", idToString(coverArtId));
|
||||||
{
|
|
||||||
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<db::TrackEmbeddedImageId>(&imageResult) })
|
|
||||||
albumNode.setAttribute("coverArt", idToString(*embeddedImageId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto originalYear{ release->getOriginalYear() })
|
if (const auto originalYear{ release->getOriginalYear() })
|
||||||
albumNode.setAttribute("year", *originalYear);
|
albumNode.setAttribute("year", *originalYear);
|
||||||
else if (const auto year{ release->getYear() })
|
else if (const auto year{ release->getYear() })
|
||||||
|
|||||||
@@ -23,11 +23,10 @@
|
|||||||
#include "core/Service.hpp"
|
#include "core/Service.hpp"
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
#include "database/Image.hpp"
|
#include "database/Artwork.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/TrackArtistLink.hpp"
|
#include "database/TrackArtistLink.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "services/artwork/IArtworkService.hpp"
|
|
||||||
#include "services/feedback/IFeedbackService.hpp"
|
#include "services/feedback/IFeedbackService.hpp"
|
||||||
|
|
||||||
#include "CoverArtId.hpp"
|
#include "CoverArtId.hpp"
|
||||||
@@ -96,18 +95,10 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
artistNode.setAttribute("id", idToString(artist->getId()));
|
artistNode.setAttribute("id", idToString(artist->getId()));
|
||||||
artistNode.setAttribute("name", artist->getName());
|
artistNode.setAttribute("name", artist->getName());
|
||||||
|
if (const auto artwork{ artist->getPreferredArtwork() })
|
||||||
{
|
{
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findArtistImage(artist->getId()) };
|
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
|
||||||
if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&imageResult) })
|
artistNode.setAttribute("coverArt", idToString(coverArtId));
|
||||||
{
|
|
||||||
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<db::TrackEmbeddedImageId>(&imageResult) })
|
|
||||||
artistNode.setAttribute("coverArt", idToString(*embeddedImageId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
|
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
#include "core/Service.hpp"
|
#include "core/Service.hpp"
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
#include "database/Image.hpp"
|
#include "database/Artwork.hpp"
|
||||||
#include "database/TrackList.hpp"
|
#include "database/TrackList.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "services/artwork/IArtworkService.hpp"
|
#include "services/artwork/IArtworkService.hpp"
|
||||||
@@ -46,18 +46,13 @@ namespace lms::api::subsonic
|
|||||||
if (const db::User::pointer user{ tracklist->getUser() })
|
if (const db::User::pointer user{ tracklist->getUser() })
|
||||||
playlistNode.setAttribute("owner", user->getLoginName());
|
playlistNode.setAttribute("owner", user->getLoginName());
|
||||||
|
|
||||||
|
if (const db::ArtworkId artworkId{ core::Service<artwork::IArtworkService>::get()->findTrackListImage(tracklist->getId()) }; artworkId.isValid())
|
||||||
{
|
{
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findTrackListImage(tracklist->getId()) };
|
if (const auto artwork{ db::Artwork::find(context.dbSession, artworkId) })
|
||||||
if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&imageResult) })
|
|
||||||
{
|
{
|
||||||
if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) })
|
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
|
||||||
{
|
playlistNode.setAttribute("coverArt", idToString(coverArtId));
|
||||||
const CoverArtId coverArtId{ *imageId, image->getLastWriteTime().toTime_t() };
|
|
||||||
playlistNode.setAttribute("coverArt", idToString(coverArtId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (const db::TrackEmbeddedImageId * embeddedImageId{ std::get_if<db::TrackEmbeddedImageId>(&imageResult) })
|
|
||||||
playlistNode.setAttribute("coverArt", idToString(*embeddedImageId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlistNode;
|
return playlistNode;
|
||||||
|
|||||||
@@ -21,21 +21,19 @@
|
|||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include "av/IAudioFile.hpp"
|
|
||||||
#include "core/ITraceLogger.hpp"
|
#include "core/ITraceLogger.hpp"
|
||||||
#include "core/MimeTypes.hpp"
|
#include "core/MimeTypes.hpp"
|
||||||
#include "core/Service.hpp"
|
#include "core/Service.hpp"
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
|
#include "database/Artwork.hpp"
|
||||||
#include "database/Cluster.hpp"
|
#include "database/Cluster.hpp"
|
||||||
#include "database/Directory.hpp"
|
#include "database/Directory.hpp"
|
||||||
#include "database/Image.hpp"
|
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "database/TrackArtistLink.hpp"
|
#include "database/TrackArtistLink.hpp"
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "services/artwork/IArtworkService.hpp"
|
|
||||||
#include "services/feedback/IFeedbackService.hpp"
|
#include "services/feedback/IFeedbackService.hpp"
|
||||||
#include "services/scrobbling/IScrobblingService.hpp"
|
#include "services/scrobbling/IScrobblingService.hpp"
|
||||||
|
|
||||||
@@ -111,18 +109,14 @@ namespace lms::api::subsonic
|
|||||||
trackResponse.setAttribute("transcodedContentType", core::getMimeType(std::filesystem::path{ "." + fileSuffix }));
|
trackResponse.setAttribute("transcodedContentType", core::getMimeType(std::filesystem::path{ "." + fileSuffix }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto artwork{ track->getPreferredMediaArtwork() };
|
||||||
|
if (!artwork)
|
||||||
|
artwork = track->getPreferredArtwork();
|
||||||
|
|
||||||
|
if (artwork)
|
||||||
{
|
{
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findTrackImage(track->getId()) };
|
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
|
||||||
if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&imageResult) })
|
trackResponse.setAttribute("coverArt", idToString(coverArtId));
|
||||||
{
|
|
||||||
if (const db::Image::pointer image{ db::Image::find(context.dbSession, *imageId) })
|
|
||||||
{
|
|
||||||
const CoverArtId coverArtId{ *imageId, image->getLastWriteTime().toTime_t() };
|
|
||||||
trackResponse.setAttribute("coverArt", idToString(coverArtId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (const db::TrackEmbeddedImageId * embeddedImageId{ std::get_if<db::TrackEmbeddedImageId>(&imageResult) })
|
|
||||||
trackResponse.setAttribute("coverArt", idToString(*embeddedImageId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) };
|
const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) };
|
||||||
|
|||||||
@@ -251,12 +251,26 @@ namespace lms::ui
|
|||||||
<< " replayGain: " << replayGain << ","
|
<< " replayGain: " << replayGain << ","
|
||||||
<< " title: \"" << core::stringUtils::jsEscape(track->getName()) << "\","
|
<< " title: \"" << core::stringUtils::jsEscape(track->getName()) << "\","
|
||||||
<< " 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: ["
|
|
||||||
<< " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
|
db::ArtworkId artworkId{ track->getPreferredMediaArtworkId() };
|
||||||
<< " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
|
if (!artworkId.isValid())
|
||||||
<< " ]"
|
artworkId = track->getPreferredArtworkId();
|
||||||
<< "};";
|
if (artworkId.isValid())
|
||||||
|
{
|
||||||
|
oss << " artwork: ["
|
||||||
|
<< " { src: \"" << LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
|
||||||
|
<< " { src: \"" << LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
|
||||||
|
<< " ]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
oss << " artwork: ["
|
||||||
|
<< " { src: \"" << LmsApp->getArtworkResource()->getDefaultTrackArtworkUrl() << "\", type: \"image/svg+xml\" },"
|
||||||
|
<< " ]";
|
||||||
|
}
|
||||||
|
oss << "};";
|
||||||
|
|
||||||
// Update 'sizes' above to match this:
|
// Update 'sizes' above to match this:
|
||||||
static_assert(static_cast<std::underlying_type_t<ArtworkResource::Size>>(ArtworkResource::Size::Small) == 128);
|
static_assert(static_cast<std::underlying_type_t<ArtworkResource::Size>>(ArtworkResource::Size::Small) == 128);
|
||||||
static_assert(static_cast<std::underlying_type_t<ArtworkResource::Size>>(ArtworkResource::Size::Large) == 512);
|
static_assert(static_cast<std::underlying_type_t<ArtworkResource::Size>>(ArtworkResource::Size::Large) == 512);
|
||||||
|
|||||||
@@ -490,8 +490,18 @@ namespace lms::ui
|
|||||||
entry->bindWidget("artists-md", utils::createArtistAnchorList(artists));
|
entry->bindWidget("artists-md", utils::createArtistAnchorList(artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto image{ utils::createTrackImage(track->getId(), ArtworkResource::Size::Small) };
|
db::ArtworkId artworkId{ track->getPreferredMediaArtworkId() };
|
||||||
image->addStyleClass("Lms-cover-track rounded");
|
if (!artworkId.isValid())
|
||||||
|
artworkId = track->getPreferredArtworkId();
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WImage> image;
|
||||||
|
if (artworkId.isValid())
|
||||||
|
image = utils::createArtworkImage(artworkId, ArtworkResource::Size::Small);
|
||||||
|
else
|
||||||
|
image = utils::createDefaultTrackArtworkImage();
|
||||||
|
|
||||||
|
image->addStyleClass("Lms-cover-track rounded"); // HACK
|
||||||
|
|
||||||
if (const auto release{ track->getRelease() })
|
if (const auto release{ track->getRelease() })
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
|
|||||||
+27
-29
@@ -40,6 +40,18 @@
|
|||||||
|
|
||||||
namespace lms::ui::utils
|
namespace lms::ui::utils
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::unique_ptr<Wt::WImage> createArtworkImage()
|
||||||
|
{
|
||||||
|
auto image{ std::make_unique<Wt::WImage>() };
|
||||||
|
image->setStyleClass("Lms-cover img-fluid"); // HACK
|
||||||
|
image->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
std::string durationToString(std::chrono::milliseconds msDuration)
|
std::string durationToString(std::chrono::milliseconds msDuration)
|
||||||
{
|
{
|
||||||
const std::chrono::seconds duration{ std::chrono::duration_cast<std::chrono::seconds>(msDuration) };
|
const std::chrono::seconds duration{ std::chrono::duration_cast<std::chrono::seconds>(msDuration) };
|
||||||
@@ -76,46 +88,32 @@ namespace lms::ui::utils
|
|||||||
LmsApp->getModalManager().show(std::move(rawImage));
|
LmsApp->getModalManager().show(std::move(rawImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> createArtistImage(db::ArtistId artistId, ArtworkResource::Size size)
|
std::unique_ptr<Wt::WImage> createArtworkImage(db::ArtworkId artworkId, ArtworkResource::Size size)
|
||||||
{
|
{
|
||||||
auto image{ std::make_unique<Wt::WImage>() };
|
auto image{ createArtworkImage() };
|
||||||
image->setImageLink(LmsApp->getArtworkResource()->getArtistImageUrl(artistId, size));
|
image->setImageLink(LmsApp->getArtworkResource()->getArtworkUrl(artworkId, size));
|
||||||
image->setStyleClass("Lms-cover img-fluid"); // HACK
|
|
||||||
image->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> createReleaseCover(db::ReleaseId releaseId, ArtworkResource::Size size)
|
std::unique_ptr<Wt::WImage> createDefaultArtistArtworkImage()
|
||||||
{
|
{
|
||||||
auto cover{ std::make_unique<Wt::WImage>() };
|
auto image{ createArtworkImage() };
|
||||||
cover->setImageLink(LmsApp->getArtworkResource()->getReleaseCoverUrl(releaseId, size));
|
image->setImageLink(LmsApp->getArtworkResource()->getDefaultArtistArtworkUrl());
|
||||||
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
return image;
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
|
||||||
return cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> createTrackImage(db::TrackId trackId, ArtworkResource::Size size)
|
std::unique_ptr<Wt::WImage> createDefaultReleaseArtworkImage()
|
||||||
{
|
{
|
||||||
auto cover{ std::make_unique<Wt::WImage>() };
|
auto image{ createArtworkImage() };
|
||||||
cover->setImageLink(LmsApp->getArtworkResource()->getTrackImageUrl(trackId, size));
|
image->setImageLink(LmsApp->getArtworkResource()->getDefaultReleaseArtworkUrl());
|
||||||
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
return image;
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
|
||||||
|
|
||||||
return cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> createTrackMediaImage(db::TrackId trackId, ArtworkResource::Size size)
|
std::unique_ptr<Wt::WImage> createDefaultTrackArtworkImage()
|
||||||
{
|
{
|
||||||
std::string url{ LmsApp->getArtworkResource()->getTrackMediaImageUrl(trackId, size) };
|
auto image{ createArtworkImage() };
|
||||||
if (url.empty())
|
image->setImageLink(LmsApp->getArtworkResource()->getDefaultTrackArtworkUrl());
|
||||||
return nullptr;
|
return image;
|
||||||
|
|
||||||
auto cover{ std::make_unique<Wt::WImage>() };
|
|
||||||
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<Wt::WInteractWidget> createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete)
|
std::unique_ptr<Wt::WInteractWidget> createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete)
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ namespace lms::ui::utils
|
|||||||
|
|
||||||
void showArtworkModal(Wt::WLink image);
|
void showArtworkModal(Wt::WLink image);
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> createReleaseCover(db::ReleaseId releaseId, ArtworkResource::Size size);
|
std::unique_ptr<Wt::WImage> createArtworkImage(db::ArtworkId artworkId, ArtworkResource::Size size);
|
||||||
std::unique_ptr<Wt::WImage> createTrackImage(db::TrackId trackId, ArtworkResource::Size size);
|
std::unique_ptr<Wt::WImage> createDefaultArtistArtworkImage();
|
||||||
std::unique_ptr<Wt::WImage> createTrackMediaImage(db::TrackId trackId, ArtworkResource::Size size); // return nullptr if no image
|
std::unique_ptr<Wt::WImage> createDefaultReleaseArtworkImage();
|
||||||
std::unique_ptr<Wt::WImage> createArtistImage(db::ArtistId artistId, ArtworkResource::Size size);
|
std::unique_ptr<Wt::WImage> createDefaultTrackArtworkImage();
|
||||||
|
|
||||||
std::unique_ptr<Wt::WInteractWidget> createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete = false);
|
std::unique_ptr<Wt::WInteractWidget> createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete = false);
|
||||||
std::unique_ptr<Wt::WInteractWidget> createFilterCluster(db::ClusterId clusterId, bool canDelete = false);
|
std::unique_ptr<Wt::WInteractWidget> createFilterCluster(db::ClusterId clusterId, bool canDelete = false);
|
||||||
|
|||||||
@@ -22,18 +22,23 @@
|
|||||||
|
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
|
|
||||||
#include "LmsApplication.hpp"
|
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace lms::ui::ArtistListHelpers
|
namespace lms::ui::ArtistListHelpers
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WTemplate> createEntry(const db::ObjectPtr<db::Artist>& artist)
|
std::unique_ptr<Wt::WTemplate> createEntry(const db::Artist::pointer& artist)
|
||||||
{
|
{
|
||||||
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry")) };
|
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry")) };
|
||||||
entry->bindWidget("name", utils::createArtistAnchor(artist));
|
entry->bindWidget("name", utils::createArtistAnchor(artist));
|
||||||
|
|
||||||
Wt::WAnchor* anchor{ entry->bindWidget("image", utils::createArtistAnchor(artist, false)) };
|
Wt::WAnchor* anchor{ entry->bindWidget("image", utils::createArtistAnchor(artist, false)) };
|
||||||
auto image{ utils::createArtistImage(artist->getId(), ArtworkResource::Size::Large) };
|
|
||||||
|
std::unique_ptr<Wt::WImage> image;
|
||||||
|
if (artist->getPreferredArtworkId().isValid())
|
||||||
|
image = utils::createArtworkImage(artist->getPreferredArtworkId(), ArtworkResource::Size::Large);
|
||||||
|
else
|
||||||
|
image = utils::createDefaultArtistArtworkImage();
|
||||||
|
|
||||||
image->addStyleClass("Lms-cover-release Lms-cover-anchor rounded"); // hack
|
image->addStyleClass("Lms-cover-release Lms-cover-anchor rounded"); // hack
|
||||||
anchor->setImage(std::move(image));
|
anchor->setImage(std::move(image));
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
#include "database/ArtistInfo.hpp"
|
#include "database/ArtistInfo.hpp"
|
||||||
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/Cluster.hpp"
|
#include "database/Cluster.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/ScanSettings.hpp"
|
#include "database/ScanSettings.hpp"
|
||||||
@@ -119,7 +120,7 @@ namespace lms::ui
|
|||||||
LmsApp->setTitle(artist->getName());
|
LmsApp->setTitle(artist->getName());
|
||||||
_artistId = *artistId;
|
_artistId = *artistId;
|
||||||
|
|
||||||
refreshArtwork();
|
refreshArtwork(artist->getPreferredArtworkId());
|
||||||
refreshArtistInfo();
|
refreshArtistInfo();
|
||||||
refreshReleases();
|
refreshReleases();
|
||||||
refreshAppearsOnReleases();
|
refreshAppearsOnReleases();
|
||||||
@@ -191,12 +192,24 @@ namespace lms::ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Artist::refreshArtwork()
|
void Artist::refreshArtwork(db::ArtworkId artworkId)
|
||||||
{
|
{
|
||||||
auto* image{ bindWidget<Wt::WImage>("artwork", utils::createArtistImage(_artistId, ArtworkResource::Size::Large)) };
|
std::unique_ptr<Wt::WImage> artworkImage;
|
||||||
image->clicked().connect([this] {
|
if (artworkId.isValid())
|
||||||
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtistImageUrl(_artistId) });
|
{
|
||||||
});
|
artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::Size::Large);
|
||||||
|
artworkImage->addStyleClass("Lms-cursor-pointer"); // HACK
|
||||||
|
}
|
||||||
|
else
|
||||||
|
artworkImage = utils::createDefaultArtistArtworkImage();
|
||||||
|
|
||||||
|
auto* image{ bindWidget<Wt::WImage>("artwork", std::move(artworkImage)) };
|
||||||
|
if (artworkId.isValid())
|
||||||
|
{
|
||||||
|
image->clicked().connect([artworkId] {
|
||||||
|
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId) });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Artist::refreshArtistInfo()
|
void Artist::refreshArtistInfo()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "database/ArtistId.hpp"
|
#include "database/ArtistId.hpp"
|
||||||
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/Object.hpp"
|
#include "database/Object.hpp"
|
||||||
#include "database/ReleaseId.hpp"
|
#include "database/ReleaseId.hpp"
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ namespace lms::ui
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void refreshView();
|
void refreshView();
|
||||||
void refreshArtwork();
|
void refreshArtwork(db::ArtworkId artworkId);
|
||||||
void refreshArtistInfo();
|
void refreshArtistInfo();
|
||||||
void refreshReleases();
|
void refreshReleases();
|
||||||
void refreshAppearsOnReleases();
|
void refreshAppearsOnReleases();
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ namespace lms::ui::releaseListHelpers
|
|||||||
|
|
||||||
{
|
{
|
||||||
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
||||||
auto image{ utils::createReleaseCover(release->getId(), ArtworkResource::Size::Large) };
|
|
||||||
|
std::unique_ptr<Wt::WImage> image;
|
||||||
|
if (release->getPreferredArtworkId().isValid())
|
||||||
|
image = utils::createArtworkImage(release->getPreferredArtworkId(), ArtworkResource::Size::Large);
|
||||||
|
else
|
||||||
|
image = utils::createDefaultReleaseArtworkImage();
|
||||||
|
|
||||||
image->addStyleClass("Lms-cover-release Lms-cover-anchor rounded"); // hack
|
image->addStyleClass("Lms-cover-release Lms-cover-anchor rounded"); // hack
|
||||||
anchor->setImage(std::move(image));
|
anchor->setImage(std::move(image));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,11 +306,7 @@ namespace lms::ui
|
|||||||
bindString("duration", utils::durationToString(release->getDuration()), Wt::TextFormat::Plain);
|
bindString("duration", utils::durationToString(release->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
refreshReleaseArtists(release);
|
refreshReleaseArtists(release);
|
||||||
|
refreshArtwork(release->getPreferredArtworkId());
|
||||||
auto* image{ bindWidget<Wt::WImage>("cover", utils::createReleaseCover(release->getId(), ArtworkResource::Size::Large)) };
|
|
||||||
image->clicked().connect([=] {
|
|
||||||
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getReleaseCoverUrl(*releaseId) });
|
|
||||||
});
|
|
||||||
|
|
||||||
Wt::WContainerWidget* clusterContainers{ bindNew<Wt::WContainerWidget>("clusters") };
|
Wt::WContainerWidget* clusterContainers{ bindNew<Wt::WContainerWidget>("clusters") };
|
||||||
{
|
{
|
||||||
@@ -391,22 +387,24 @@ namespace lms::ui
|
|||||||
|
|
||||||
// Expect to be called in asc order
|
// Expect to be called in asc order
|
||||||
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
||||||
auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle, db::TrackId trackId) -> Wt::WContainerWidget* {
|
auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle, db::ArtworkId mediaArtworkId) -> Wt::WContainerWidget* {
|
||||||
if (auto it{ trackContainers.find(discNumber) }; it != std::cend(trackContainers))
|
if (auto it{ trackContainers.find(discNumber) }; it != std::cend(trackContainers))
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
Template* disc{ rootContainer->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc")) };
|
Template* disc{ rootContainer->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc")) };
|
||||||
disc->addFunction("id", &Wt::WTemplate::Functions::id);
|
disc->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
if (auto image{ utils::createTrackMediaImage(trackId, ArtworkResource::Size::Large) })
|
if (mediaArtworkId.isValid())
|
||||||
{
|
{
|
||||||
disc->setCondition("if-has-image", true);
|
auto image{ utils::createArtworkImage(mediaArtworkId, ArtworkResource::Size::Small) };
|
||||||
|
|
||||||
|
disc->setCondition("if-has-artwork", true);
|
||||||
|
|
||||||
image->addStyleClass("Lms-cover-track rounded"); // HACK
|
image->addStyleClass("Lms-cover-track rounded"); // HACK
|
||||||
image->clicked().connect([=] {
|
image->clicked().connect([=] {
|
||||||
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getTrackMediaImageUrl(trackId) });
|
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(mediaArtworkId) });
|
||||||
});
|
});
|
||||||
disc->bindWidget<Wt::WImage>("image", std::move(image));
|
disc->bindWidget<Wt::WImage>("artwork", std::move(image));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discSubtitle.empty())
|
if (discSubtitle.empty())
|
||||||
@@ -469,9 +467,9 @@ namespace lms::ui
|
|||||||
|
|
||||||
Wt::WContainerWidget* container{};
|
Wt::WContainerWidget* container{};
|
||||||
if (useSubtitleContainers && discNumber)
|
if (useSubtitleContainers && discNumber)
|
||||||
container = getOrAddDiscContainer(*discNumber, track->getDiscSubtitle(), track->getId());
|
container = getOrAddDiscContainer(*discNumber, track->getDiscSubtitle(), track->getPreferredMediaArtworkId());
|
||||||
else if (hasDiscSubtitle && !discNumber)
|
else if (hasDiscSubtitle && !discNumber)
|
||||||
container = getOrAddDiscContainer(0, track->getDiscSubtitle(), track->getId());
|
container = getOrAddDiscContainer(0, track->getDiscSubtitle(), track->getPreferredMediaArtworkId());
|
||||||
else
|
else
|
||||||
container = getOrAddNoDiscContainer();
|
container = getOrAddNoDiscContainer();
|
||||||
|
|
||||||
@@ -567,6 +565,26 @@ namespace lms::ui
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Release::refreshArtwork(db::ArtworkId artworkId)
|
||||||
|
{
|
||||||
|
std::unique_ptr<Wt::WImage> artworkImage;
|
||||||
|
if (artworkId.isValid())
|
||||||
|
{
|
||||||
|
artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::Size::Large);
|
||||||
|
artworkImage->addStyleClass("Lms-cursor-pointer"); // HACK
|
||||||
|
}
|
||||||
|
else
|
||||||
|
artworkImage = utils::createDefaultReleaseArtworkImage();
|
||||||
|
|
||||||
|
auto* image{ bindWidget<Wt::WImage>("artwork", std::move(artworkImage)) };
|
||||||
|
if (artworkId.isValid())
|
||||||
|
{
|
||||||
|
image->clicked().connect([artworkId] {
|
||||||
|
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId) });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Release::refreshReleaseArtists(const db::Release::pointer& release)
|
void Release::refreshReleaseArtists(const db::Release::pointer& release)
|
||||||
{
|
{
|
||||||
auto container{ utils::createArtistsAnchorsForRelease(release) };
|
auto container{ utils::createArtistsAnchorsForRelease(release) };
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/Object.hpp"
|
#include "database/Object.hpp"
|
||||||
#include "database/ReleaseId.hpp"
|
#include "database/ReleaseId.hpp"
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ namespace lms::ui
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void refreshView();
|
void refreshView();
|
||||||
|
void refreshArtwork(db::ArtworkId artworkId);
|
||||||
void refreshReleaseArtists(const db::ObjectPtr<db::Release>& release);
|
void refreshReleaseArtists(const db::ObjectPtr<db::Release>& release);
|
||||||
void refreshCopyright(const db::ObjectPtr<db::Release>& release);
|
void refreshCopyright(const db::ObjectPtr<db::Release>& release);
|
||||||
void refreshLinks(const db::ObjectPtr<db::Release>& release);
|
void refreshLinks(const db::ObjectPtr<db::Release>& release);
|
||||||
|
|||||||
@@ -228,8 +228,15 @@ namespace lms::ui::TrackListHelpers
|
|||||||
entry->bindWidget("artists-md", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
entry->bindWidget("artists-md", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto image{ utils::createTrackImage(trackId, ArtworkResource::Size::Small) };
|
std::unique_ptr<Wt::WImage> image;
|
||||||
image->addStyleClass("Lms-cover-track rounded");
|
if (track->getPreferredMediaArtworkId().isValid())
|
||||||
|
image = utils::createArtworkImage(track->getPreferredMediaArtworkId(), ArtworkResource::Size::Small);
|
||||||
|
else if (track->getPreferredArtworkId().isValid())
|
||||||
|
image = utils::createArtworkImage(track->getPreferredArtworkId(), ArtworkResource::Size::Small);
|
||||||
|
else
|
||||||
|
image = utils::createDefaultTrackArtworkImage();
|
||||||
|
|
||||||
|
image->addStyleClass("Lms-cover-track rounded"); // hack
|
||||||
if (track->getRelease())
|
if (track->getRelease())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
|
|||||||
@@ -47,97 +47,28 @@ namespace lms::ui
|
|||||||
beingDeleted();
|
beingDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ArtworkResource::getArtistImageUrl(db::ArtistId artistId, std::optional<Size> size) const
|
std::string ArtworkResource::getArtworkUrl(db::ArtworkId artworkId, std::optional<Size> size) const
|
||||||
{
|
{
|
||||||
std::string url;
|
std::string res{ url() + "&artworkid=" + artworkId.toString() };
|
||||||
|
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findArtistImage(artistId) };
|
|
||||||
std::visit([&](const auto& arg) {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (std::is_same_v<T, std::monostate>)
|
|
||||||
url = getDefaultArtistImageUrl();
|
|
||||||
else
|
|
||||||
url = getImageUrl(arg, size, "artist");
|
|
||||||
},
|
|
||||||
imageResult);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ArtworkResource::getReleaseCoverUrl(db::ReleaseId releaseId, std::optional<Size> size) const
|
|
||||||
{
|
|
||||||
std::string url;
|
|
||||||
|
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findReleaseImage(releaseId) };
|
|
||||||
std::visit([&](const auto& arg) {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (std::is_same_v<T, std::monostate>)
|
|
||||||
url = getDefaultReleaseCoverUrl();
|
|
||||||
else
|
|
||||||
url = getImageUrl(arg, size, "release");
|
|
||||||
},
|
|
||||||
imageResult);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ArtworkResource::getTrackImageUrl(db::TrackId trackId, std::optional<Size> size) const
|
|
||||||
{
|
|
||||||
std::string url;
|
|
||||||
|
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findTrackImage(trackId) };
|
|
||||||
std::visit([&](const auto& arg) {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (std::is_same_v<T, std::monostate>)
|
|
||||||
url = getDefaultReleaseCoverUrl();
|
|
||||||
else
|
|
||||||
url = getImageUrl(arg, size, "release");
|
|
||||||
},
|
|
||||||
imageResult);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ArtworkResource::getTrackMediaImageUrl(db::TrackId trackId, std::optional<Size> size)
|
|
||||||
{
|
|
||||||
std::string url;
|
|
||||||
|
|
||||||
const auto imageResult{ core::Service<artwork::IArtworkService>::get()->findTrackMediaImage(trackId) };
|
|
||||||
std::visit([&](const auto& arg) {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (!std::is_same_v<T, std::monostate>)
|
|
||||||
url = getImageUrl(arg, size, "release");
|
|
||||||
},
|
|
||||||
imageResult);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ArtworkResource::getImageUrl(db::ImageId imageId, std::optional<Size> size, std::string_view type) const
|
|
||||||
{
|
|
||||||
std::string res{ url() + "&imageid=" + imageId.toString() + "&type=" + std::string{ type } };
|
|
||||||
if (size)
|
if (size)
|
||||||
res += "&size=" + std::to_string(static_cast<std::size_t>(*size));
|
res += "&size=" + std::to_string(static_cast<std::size_t>(*size));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ArtworkResource::getImageUrl(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<Size> size, std::string_view type) const
|
std::string ArtworkResource::getDefaultArtistArtworkUrl() const
|
||||||
{
|
{
|
||||||
std::string res{ url() + "&trimageid=" + trackEmbeddedImageId.toString() + "&type=" + std::string{ type } };
|
return url() + "&type=defartist";
|
||||||
if (size)
|
|
||||||
res += "&size=" + std::to_string(static_cast<std::size_t>(*size));
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ArtworkResource::getDefaultArtistImageUrl() const
|
std::string ArtworkResource::getDefaultReleaseArtworkUrl() const
|
||||||
{
|
{
|
||||||
return url() + "&type=artist";
|
return url() + "&type=defrelease";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ArtworkResource::getDefaultReleaseCoverUrl() const
|
std::string ArtworkResource::getDefaultTrackArtworkUrl() const
|
||||||
{
|
{
|
||||||
return url() + "&type=release";
|
return url() + "&type=defrelease";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArtworkResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
|
void ArtworkResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
|
||||||
@@ -145,40 +76,64 @@ namespace lms::ui
|
|||||||
LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest");
|
LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest");
|
||||||
|
|
||||||
// Retrieve parameters
|
// Retrieve parameters
|
||||||
const std::string* imageIdStr = request.getParameter("imageid");
|
const std::string* artworkIdStr = request.getParameter("artworkid");
|
||||||
const std::string* trackEmbeddedImageIdStr = request.getParameter("trimageid");
|
|
||||||
const std::string* sizeStr = request.getParameter("size");
|
const std::string* sizeStr = request.getParameter("size");
|
||||||
const std::string* typeStr = request.getParameter("type");
|
const std::string* typeStr = request.getParameter("type");
|
||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> image;
|
std::shared_ptr<image::IEncodedImage> image;
|
||||||
|
|
||||||
if ((imageIdStr || trackEmbeddedImageIdStr))
|
if (artworkIdStr && typeStr)
|
||||||
|
{
|
||||||
|
ARTWORK_RESOURCE_LOG(DEBUG, "both artwork ID and type provided, only one is allowed");
|
||||||
|
response.setStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!artworkIdStr && !typeStr)
|
||||||
|
{
|
||||||
|
ARTWORK_RESOURCE_LOG(DEBUG, "no artwork ID or type provided");
|
||||||
|
response.setStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artworkIdStr)
|
||||||
{
|
{
|
||||||
const auto size{ sizeStr ? core::stringUtils::readAs<std::size_t>(*sizeStr) : std::nullopt };
|
const auto size{ sizeStr ? core::stringUtils::readAs<std::size_t>(*sizeStr) : std::nullopt };
|
||||||
if (size && *size > maxSize)
|
if (size && *size > maxSize)
|
||||||
{
|
{
|
||||||
ARTWORK_RESOURCE_LOG(DEBUG, "invalid size provided!");
|
ARTWORK_RESOURCE_LOG(DEBUG, "invalid size provided: " << *sizeStr);
|
||||||
|
response.setStatus(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageIdStr)
|
const auto artworkId{ core::stringUtils::readAs<db::ArtworkId::ValueType>(*artworkIdStr) };
|
||||||
|
if (!artworkId)
|
||||||
{
|
{
|
||||||
if (const auto imageId{ core::stringUtils::readAs<db::ImageId::ValueType>(*imageIdStr) })
|
ARTWORK_RESOURCE_LOG(DEBUG, "invalid artwork ID provided: '" << *artworkIdStr << "'");
|
||||||
image = core::Service<artwork::IArtworkService>::get()->getImage(*imageId, size);
|
response.setStatus(400);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (trackEmbeddedImageIdStr)
|
|
||||||
{
|
|
||||||
if (const auto imageId{ core::stringUtils::readAs<db::TrackEmbeddedImageId::ValueType>(*trackEmbeddedImageIdStr) })
|
|
||||||
image = core::Service<artwork::IArtworkService>::get()->getTrackEmbeddedImage(*imageId, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!image && typeStr)
|
image = core::Service<artwork::IArtworkService>::get()->getImage(*artworkId, size);
|
||||||
|
if (!image)
|
||||||
|
ARTWORK_RESOURCE_LOG(DEBUG, "no image found for artwork ID: '" << *artworkIdStr << "'");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (*typeStr == "release")
|
assert(typeStr);
|
||||||
image = core::Service<artwork::IArtworkService>::get()->getDefaultReleaseCover();
|
if (*typeStr == "defartist")
|
||||||
else if (*typeStr == "artist")
|
image = core::Service<artwork::IArtworkService>::get()->getDefaultArtistArtwork();
|
||||||
image = core::Service<artwork::IArtworkService>::get()->getDefaultArtistImage();
|
else if (*typeStr == "defrelease")
|
||||||
|
image = core::Service<artwork::IArtworkService>::get()->getDefaultReleaseArtwork();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ARTWORK_RESOURCE_LOG(DEBUG, "invalid type provided: '" << *typeStr << "'");
|
||||||
|
response.setStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image)
|
||||||
|
ARTWORK_RESOURCE_LOG(DEBUG, "no default image found for type: '" << *typeStr << "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image)
|
if (image)
|
||||||
@@ -187,6 +142,8 @@ namespace lms::ui
|
|||||||
response.out().write(reinterpret_cast<const char*>(image->getData().data()), image->getData().size());
|
response.out().write(reinterpret_cast<const char*>(image->getData().data()), image->getData().size());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
response.setStatus(404);
|
response.setStatus(404);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
@@ -23,11 +23,7 @@
|
|||||||
|
|
||||||
#include <Wt/WResource.h>
|
#include <Wt/WResource.h>
|
||||||
|
|
||||||
#include "database/ArtistId.hpp"
|
#include "database/ArtworkId.hpp"
|
||||||
#include "database/ImageId.hpp"
|
|
||||||
#include "database/ReleaseId.hpp"
|
|
||||||
#include "database/TrackEmbeddedImageId.hpp"
|
|
||||||
#include "database/TrackId.hpp"
|
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
@@ -37,7 +33,7 @@ namespace lms::ui
|
|||||||
static const std::size_t maxSize{ 512 };
|
static const std::size_t maxSize{ 512 };
|
||||||
|
|
||||||
ArtworkResource();
|
ArtworkResource();
|
||||||
~ArtworkResource();
|
~ArtworkResource() override;
|
||||||
|
|
||||||
enum class Size : std::size_t
|
enum class Size : std::size_t
|
||||||
{
|
{
|
||||||
@@ -45,18 +41,13 @@ namespace lms::ui
|
|||||||
Large = 512,
|
Large = 512,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string getArtistImageUrl(db::ArtistId artistId, std::optional<Size> size = std::nullopt) const;
|
std::string getArtworkUrl(db::ArtworkId artworkId, std::optional<Size> size = std::nullopt) const;
|
||||||
std::string getReleaseCoverUrl(db::ReleaseId releaseId, std::optional<Size> size = std::nullopt) const;
|
|
||||||
std::string getTrackImageUrl(db::TrackId trackId, std::optional<Size> size = std::nullopt) const;
|
std::string getDefaultArtistArtworkUrl() const;
|
||||||
std::string getTrackMediaImageUrl(db::TrackId trackId, std::optional<Size> size = std::nullopt);
|
std::string getDefaultReleaseArtworkUrl() const;
|
||||||
|
std::string getDefaultTrackArtworkUrl() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string getImageUrl(db::ImageId imageId, std::optional<Size> size, std::string_view type) const;
|
|
||||||
std::string getImageUrl(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<Size> size, std::string_view type) const;
|
|
||||||
|
|
||||||
std::string getDefaultArtistImageUrl() const;
|
|
||||||
std::string getDefaultReleaseCoverUrl() const;
|
|
||||||
|
|
||||||
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
||||||
};
|
};
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
Reference in New Issue
Block a user