Changed the artists view to use artist images

This commit is contained in:
emeric
2024-10-11 19:04:59 +02:00
parent e9f533bb6b
commit 8bdf3b42d0
33 changed files with 439 additions and 311 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
add_subdirectory(artwork)
add_subdirectory(auth)
add_subdirectory(cover)
add_subdirectory(feedback)
add_subdirectory(recommendation)
add_subdirectory(scanner)
+29
View File
@@ -0,0 +1,29 @@
add_library(lmsartwork SHARED
impl/ImageCache.cpp
impl/ArtworkService.cpp
)
target_include_directories(lmsartwork INTERFACE
include
)
target_include_directories(lmsartwork PRIVATE
include
impl
)
target_link_libraries(lmsartwork PRIVATE
lmsav
lmsimage
)
target_link_libraries(lmsartwork PUBLIC
lmsdatabase
lmsimage
lmscore
std::filesystem
)
install(TARGETS lmsartwork DESTINATION ${CMAKE_INSTALL_LIBDIR})
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CoverService.hpp"
#include "ArtworkService.hpp"
#include "av/IAudioFile.hpp"
#include "core/IConfig.hpp"
@@ -45,27 +45,29 @@ namespace lms::cover
}
} // namespace
std::unique_ptr<ICoverService> createCoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath)
std::unique_ptr<IArtworkService> createArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath)
{
return std::make_unique<CoverService>(db, defaultSvgCoverPath);
return std::make_unique<ArtworkService>(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath);
}
using namespace image;
CoverService::CoverService(db::Db& db,
const std::filesystem::path& defaultSvgCoverPath)
ArtworkService::ArtworkService(db::Db& db,
const std::filesystem::path& defaultReleaseCoverSvgPath,
const std::filesystem::path& defaultArtistImageSvgPath)
: _db{ db }
, _cache{ core::Service<core::IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
{
setJpegQuality(core::Service<core::IConfig>::get()->getULong("cover-jpeg-quality", 75));
LMS_LOG(COVER, INFO, "Default cover path = '" << defaultSvgCoverPath.string() << "'");
LMS_LOG(COVER, INFO, "Default release cover path = '" << defaultReleaseCoverSvgPath.string() << "'");
LMS_LOG(COVER, INFO, "Max cache size = " << _cache.getMaxCacheSize());
_defaultCover = image::readSvgFile(defaultSvgCoverPath); // may throw
_defaultReleaseCover = image::readSvgFile(defaultReleaseCoverSvgPath); // may throw
_defaultArtistImage = image::readSvgFile(defaultArtistImageSvgPath); // may throw
}
std::unique_ptr<IEncodedImage> CoverService::getFromAvMediaFile(const av::IAudioFile& input, ImageSize width) const
std::unique_ptr<IEncodedImage> ArtworkService::getFromAvMediaFile(const av::IAudioFile& input, ImageSize width) const
{
std::unique_ptr<IEncodedImage> image;
@@ -88,7 +90,7 @@ namespace lms::cover
return image;
}
std::unique_ptr<IEncodedImage> CoverService::getFromImageFile(const std::filesystem::path& p, ImageSize width) const
std::unique_ptr<IEncodedImage> ArtworkService::getFromImageFile(const std::filesystem::path& p, ImageSize width) const
{
std::unique_ptr<IEncodedImage> image;
@@ -106,12 +108,17 @@ namespace lms::cover
return image;
}
std::shared_ptr<IEncodedImage> CoverService::getDefaultSvgCover()
std::shared_ptr<IEncodedImage> ArtworkService::getDefaultReleaseCover()
{
return _defaultCover;
return _defaultReleaseCover;
}
bool CoverService::checkImageFile(const std::filesystem::path& filePath) const
std::shared_ptr<IEncodedImage> ArtworkService::getDefaultArtistImage()
{
return _defaultArtistImage;
}
bool ArtworkService::checkImageFile(const std::filesystem::path& filePath) const
{
std::error_code ec;
@@ -127,7 +134,7 @@ namespace lms::cover
return true;
}
std::unique_ptr<IEncodedImage> CoverService::getFromTrack(const std::filesystem::path& p, ImageSize width) const
std::unique_ptr<IEncodedImage> ArtworkService::getTrackImage(const std::filesystem::path& p, ImageSize width) const
{
std::unique_ptr<IEncodedImage> image;
@@ -143,7 +150,7 @@ namespace lms::cover
return image;
}
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(db::TrackId trackId, ImageSize width)
std::shared_ptr<IEncodedImage> ArtworkService::getTrackImage(db::TrackId trackId, ImageSize width)
{
const ImageCache::EntryDesc cacheEntryDesc{ trackId, width };
@@ -157,7 +164,7 @@ namespace lms::cover
const db::Track::pointer track{ db::Track::find(session, trackId) };
if (track && track->hasCover())
cover = getFromTrack(track->getAbsoluteFilePath(), width);
cover = getTrackImage(track->getAbsoluteFilePath(), width);
}
if (cover)
@@ -166,7 +173,7 @@ namespace lms::cover
return cover;
}
std::shared_ptr<IEncodedImage> CoverService::getFromRelease(db::ReleaseId releaseId, ImageSize width)
std::shared_ptr<IEncodedImage> ArtworkService::getReleaseCover(db::ReleaseId releaseId, ImageSize width)
{
using namespace db;
const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width };
@@ -193,7 +200,7 @@ namespace lms::cover
return image;
}
std::shared_ptr<IEncodedImage> CoverService::getFromArtist(db::ArtistId artistId, ImageSize width)
std::shared_ptr<IEncodedImage> ArtworkService::getArtistImage(db::ArtistId artistId, ImageSize width)
{
using namespace db;
const ImageCache::EntryDesc cacheEntryDesc{ artistId, width };
@@ -220,12 +227,12 @@ namespace lms::cover
return artistImage;
}
void CoverService::flushCache()
void ArtworkService::flushCache()
{
_cache.flush();
}
void CoverService::setJpegQuality(unsigned quality)
void ArtworkService::setJpegQuality(unsigned quality)
{
_jpegQuality = core::utils::clamp<unsigned>(quality, 1, 100);
@@ -24,7 +24,7 @@
#include "database/Types.hpp"
#include "image/IEncodedImage.hpp"
#include "services/cover/ICoverService.hpp"
#include "services/artwork/IArtworkService.hpp"
#include "ImageCache.hpp"
@@ -40,34 +40,37 @@ namespace lms::av
namespace lms::cover
{
class CoverService : public ICoverService
class ArtworkService : public IArtworkService
{
public:
CoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath);
ArtworkService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath, const std::filesystem::path& defaultArtistImageSvgPath);
private:
CoverService(const CoverService&) = delete;
CoverService& operator=(const CoverService&) = delete;
ArtworkService(const ArtworkService&) = delete;
ArtworkService& operator=(const ArtworkService&) = delete;
std::shared_ptr<image::IEncodedImage> getTrackImage(db::TrackId trackId, image::ImageSize width) override;
std::shared_ptr<image::IEncodedImage> getReleaseCover(db::ReleaseId releaseId, image::ImageSize width) override;
std::shared_ptr<image::IEncodedImage> getArtistImage(db::ArtistId artistId, image::ImageSize width) override;
std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() override;
std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() override;
std::shared_ptr<image::IEncodedImage> getFromTrack(db::TrackId trackId, image::ImageSize width) override;
std::shared_ptr<image::IEncodedImage> getFromRelease(db::ReleaseId releaseId, image::ImageSize width) override;
std::shared_ptr<image::IEncodedImage> getFromArtist(db::ArtistId artistId, image::ImageSize width) override;
std::shared_ptr<image::IEncodedImage> getDefaultSvgCover() override;
void flushCache() override;
void setJpegQuality(unsigned quality) override;
std::shared_ptr<image::IEncodedImage> getFromTrack(db::Session& dbSession, db::TrackId trackId, image::ImageSize width, bool allowReleaseFallback);
std::shared_ptr<image::IEncodedImage> getTrackImage(db::Session& dbSession, db::TrackId trackId, image::ImageSize width, bool allowReleaseFallback);
std::unique_ptr<image::IEncodedImage> getFromAvMediaFile(const av::IAudioFile& input, image::ImageSize width) const;
std::unique_ptr<image::IEncodedImage> getFromImageFile(const std::filesystem::path& p, image::ImageSize width) const;
std::unique_ptr<image::IEncodedImage> getFromTrack(const std::filesystem::path& path, image::ImageSize width) const;
std::unique_ptr<image::IEncodedImage> getTrackImage(const std::filesystem::path& path, image::ImageSize width) const;
bool checkImageFile(const std::filesystem::path& filePath) const;
db::Db& _db;
ImageCache _cache;
std::shared_ptr<image::IEncodedImage> _defaultCover;
std::shared_ptr<image::IEncodedImage> _defaultReleaseCover;
std::shared_ptr<image::IEncodedImage> _defaultArtistImage;
static inline const std::vector<std::filesystem::path> _fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; // TODO parametrize
unsigned _jpegQuality;
@@ -34,26 +34,28 @@ namespace lms::db
namespace lms::cover
{
class ICoverService
class IArtworkService
{
public:
virtual ~ICoverService() = default;
virtual ~IArtworkService() = default;
virtual std::shared_ptr<image::IEncodedImage> getFromArtist(db::ArtistId artistId, image::ImageSize width) = 0;
virtual std::shared_ptr<image::IEncodedImage> getArtistImage(db::ArtistId artistId, image::ImageSize width) = 0;
// no logic to fallback to release here
virtual std::shared_ptr<image::IEncodedImage> getFromTrack(db::TrackId trackId, image::ImageSize width) = 0;
virtual std::shared_ptr<image::IEncodedImage> getTrackImage(db::TrackId trackId, image::ImageSize width) = 0;
// no logic to fallback to track here
virtual std::shared_ptr<image::IEncodedImage> getFromRelease(db::ReleaseId releaseId, image::ImageSize width) = 0;
virtual std::shared_ptr<image::IEncodedImage> getReleaseCover(db::ReleaseId releaseId, image::ImageSize width) = 0;
virtual std::shared_ptr<image::IEncodedImage> getDefaultSvgCover() = 0;
// Svg images dont have image "size"
virtual std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() = 0;
virtual std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() = 0;
virtual void flushCache() = 0;
virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100
};
std::unique_ptr<ICoverService> createCoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath);
std::unique_ptr<IArtworkService> createArtworkService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath, const std::filesystem::path& defaultArtistImageSvgPath);
} // namespace lms::cover
-29
View File
@@ -1,29 +0,0 @@
add_library(lmsservice-cover SHARED
impl/ImageCache.cpp
impl/CoverService.cpp
)
target_include_directories(lmsservice-cover INTERFACE
include
)
target_include_directories(lmsservice-cover PRIVATE
include
impl
)
target_link_libraries(lmsservice-cover PRIVATE
lmsav
lmsimage
)
target_link_libraries(lmsservice-cover PUBLIC
lmsdatabase
lmsimage
lmscore
std::filesystem
)
install(TARGETS lmsservice-cover DESTINATION ${CMAKE_INSTALL_LIBDIR})
+1 -1
View File
@@ -48,7 +48,7 @@ target_link_libraries(lmssubsonic PRIVATE
lmsrecommendation
lmsscanner
lmsscrobbling
lmsservice-cover
lmsartwork
lmscore
std::filesystem
)
+2 -2
View File
@@ -282,7 +282,7 @@ namespace lms::api::subsonic
SubsonicResource::SubsonicResource(db::Db& db)
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
, _defaultCoverClients{ readDefaultCoverClients() }
, _defaultReleaseCoverClients{ readDefaultCoverClients() }
, _db{ db }
{
}
@@ -410,7 +410,7 @@ namespace lms::api::subsonic
const ClientInfo clientInfo{ getClientInfo(request) };
const db::UserId userId{ authenticateUser(request, clientInfo) };
bool enableOpenSubsonic{ !_openSubsonicDisabledClients.contains(clientInfo.name) };
bool enableDefaultCover{ _defaultCoverClients.contains(clientInfo.name) };
bool enableDefaultCover{ _defaultReleaseCoverClients.contains(clientInfo.name) };
const ResponseFormat format{ getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml };
db::User::pointer user;
+1 -1
View File
@@ -54,7 +54,7 @@ namespace lms::api::subsonic
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
const std::unordered_set<std::string> _openSubsonicDisabledClients;
const std::unordered_set<std::string> _defaultCoverClients;
const std::unordered_set<std::string> _defaultReleaseCoverClients;
db::Db& _db;
};
@@ -32,7 +32,7 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
#include "services/cover/ICoverService.hpp"
#include "services/artwork/IArtworkService.hpp"
#include "ParameterParsing.hpp"
#include "SubsonicId.hpp"
@@ -267,14 +267,14 @@ namespace lms::api::subsonic
std::shared_ptr<image::IEncodedImage> cover;
if (trackId)
cover = core::Service<cover::ICoverService>::get()->getFromTrack(*trackId, size);
cover = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
else if (releaseId)
cover = core::Service<cover::ICoverService>::get()->getFromRelease(*releaseId, size);
cover = core::Service<cover::IArtworkService>::get()->getReleaseCover(*releaseId, size);
else if (artistId)
cover = core::Service<cover::ICoverService>::get()->getFromArtist(*artistId, size);
cover = core::Service<cover::IArtworkService>::get()->getArtistImage(*artistId, size);
if (!cover && context.enableDefaultCover && !artistId)
cover = core::Service<cover::ICoverService>::get()->getDefaultSvgCover();
cover = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
if (!cover)
{
+2 -2
View File
@@ -51,7 +51,7 @@ add_executable(lms
ui/explore/TracksView.cpp
ui/resource/AudioFileResource.cpp
ui/resource/AudioTranscodingResource.cpp
ui/resource/CoverResource.cpp
ui/resource/ArtworkResource.cpp
ui/resource/DownloadResource.cpp
)
@@ -70,7 +70,7 @@ target_link_libraries(lms PRIVATE
lmsrecommendation
lmsscanner
lmsscrobbling
lmsservice-cover
lmsartwork
lmssubsonic
lmscore
)
+3 -3
View File
@@ -35,10 +35,10 @@
#include "database/Db.hpp"
#include "database/Session.hpp"
#include "image/Image.hpp"
#include "services/artwork/IArtworkService.hpp"
#include "services/auth/IAuthTokenService.hpp"
#include "services/auth/IEnvService.hpp"
#include "services/auth/IPasswordService.hpp"
#include "services/cover/ICoverService.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/recommendation/IPlaylistGeneratorService.hpp"
#include "services/recommendation/IRecommendationService.hpp"
@@ -340,7 +340,7 @@ namespace lms
throw core::LmsException{ "Bad value '" + authenticationBackend + "' for 'authentication-backend'" };
image::init(argv[0]);
core::Service<cover::ICoverService> coverService{ cover::createCoverService(database, server.appRoot() + "/images/unknown-cover.svg") };
core::Service<cover::IArtworkService> artworkService{ cover::createArtworkService(database, server.appRoot() + "/images/unknown-cover.svg", server.appRoot() + "/images/unknown-artist.svg") };
core::Service<recommendation::IRecommendationService> recommendationService{ recommendation::createRecommendationService(database) };
core::Service<recommendation::IPlaylistGeneratorService> playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) };
core::Service<scanner::IScannerService> scannerService{ scanner::createScannerService(database) };
@@ -348,7 +348,7 @@ namespace lms
scannerService->getEvents().scanComplete.connect([&] {
// Flush cover cache even if no changes:
// covers may be external files that changed and we don't keep track of them for now (but we should)
coverService->flushCache();
artworkService->flushCache();
database.getTLSSession().refreshTracingLoggerStats();
});
+3 -3
View File
@@ -37,9 +37,9 @@
#include "database/Session.hpp"
#include "database/TrackList.hpp"
#include "database/User.hpp"
#include "services/artwork/IArtworkService.hpp"
#include "services/auth/IEnvService.hpp"
#include "services/auth/IPasswordService.hpp"
#include "services/cover/ICoverService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "Auth.hpp"
@@ -61,9 +61,9 @@
#include "common/Template.hpp"
#include "explore/Explore.hpp"
#include "explore/Filters.hpp"
#include "resource/ArtworkResource.hpp"
#include "resource/AudioFileResource.hpp"
#include "resource/AudioTranscodingResource.hpp"
#include "resource/CoverResource.hpp"
namespace lms::ui
{
@@ -400,7 +400,7 @@ namespace lms::ui
{
LMS_SCOPED_TRACE_OVERVIEW("UI", "ApplicationCreateHome");
_coverResource = std::make_shared<CoverResource>();
_artworkResource = std::make_shared<ArtworkResource>();
declareJavaScriptFunction("onLoadCover", "function(id) { id.className += \" Lms-cover-loaded\"}");
declareJavaScriptFunction("updateActiveNav",
+3 -3
View File
@@ -42,7 +42,7 @@ namespace lms::db
namespace lms::ui
{
class CoverResource;
class ArtworkResource;
class LmsApplicationException;
class MediaPlayer;
class PlayQueue;
@@ -60,7 +60,7 @@ namespace lms::ui
static LmsApplication* instance();
// Session application data
std::shared_ptr<CoverResource> getCoverResource() { return _coverResource; }
std::shared_ptr<ArtworkResource> getArtworkResource() { return _artworkResource; }
db::Db& getDb();
db::Session& getDbSession(); // always thread safe
@@ -115,7 +115,7 @@ namespace lms::ui
bool strongAuth{};
};
std::optional<UserAuthInfo> _user;
std::shared_ptr<CoverResource> _coverResource;
std::shared_ptr<ArtworkResource> _artworkResource;
MediaPlayer* _mediaPlayer{};
PlayQueue* _playQueue{};
NotificationContainer* _notificationContainer{};
+5 -5
View File
@@ -37,9 +37,9 @@
#include "LmsApplication.hpp"
#include "Utils.hpp"
#include "resource/ArtworkResource.hpp"
#include "resource/AudioFileResource.hpp"
#include "resource/AudioTranscodingResource.hpp"
#include "resource/CoverResource.hpp"
namespace lms::ui
{
@@ -255,13 +255,13 @@ namespace lms::ui
<< " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\","
<< " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
<< " artwork: ["
<< " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
<< " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
<< " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
<< " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
<< " ]"
<< "};";
// Update 'sizes' above to match this:
static_assert(static_cast<std::underlying_type_t<CoverResource::Size>>(CoverResource::Size::Small) == 128);
static_assert(static_cast<std::underlying_type_t<CoverResource::Size>>(CoverResource::Size::Large) == 512);
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);
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
_title->setTextFormat(Wt::TextFormat::Plain);
+8 -10
View File
@@ -485,22 +485,20 @@ namespace lms::ui
entry->bindWidget("artists-md", utils::createArtistAnchorList(artists));
}
const auto release{ track->getRelease() };
if (release)
auto image{ utils::createTrackImage(track->getId(), ArtworkResource::Size::Small) };
image->addStyleClass("Lms-cover-track");
if (const auto release{ track->getRelease() })
{
entry->setCondition("if-has-release", true);
entry->bindWidget("release", utils::createReleaseAnchor(release));
{
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
auto cover{ utils::createCover(release->getId(), CoverResource::Size::Small) };
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
anchor->setImage(std::move(cover));
}
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
image->addStyleClass("Lms-cover-anchor"); // HACK
anchor->setImage(std::move(image));
}
else
{
auto cover{ entry->bindWidget<Wt::WImage>("cover", utils::createCover(track->getId(), CoverResource::Size::Small)) };
cover->addStyleClass("Lms-cover-track");
entry->bindWidget("cover", std::move(image));
}
entry->bindString("duration", utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
+13 -51
View File
@@ -63,66 +63,28 @@ namespace lms::ui::utils
return oss.str();
}
std::unique_ptr<Wt::WImage> createCover(db::ReleaseId releaseId, CoverResource::Size size)
std::unique_ptr<Wt::WImage> createArtistImage(db::ArtistId artistId, ArtworkResource::Size size)
{
auto image{ std::make_unique<Wt::WImage>() };
image->setImageLink(LmsApp->getArtworkResource()->getArtistImageUrl(artistId, size));
image->setStyleClass("Lms-cover img-fluid"); // HACK
image->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
return image;
}
std::unique_ptr<Wt::WImage> createReleaseCover(db::ReleaseId releaseId, ArtworkResource::Size size)
{
auto cover{ std::make_unique<Wt::WImage>() };
std::string internalUrl;
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) };
if (release)
{
if (release->getImage())
{
internalUrl = LmsApp->getCoverResource()->getReleaseUrl(release->getId(), size);
}
else
{
db::Track::FindParameters params;
params.setRelease(releaseId);
params.setHasEmbeddedImage(true);
params.setRange(db::Range{ 0, 1 });
db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track) {
internalUrl = LmsApp->getCoverResource()->getTrackUrl(track->getId(), size);
});
}
}
}
if (internalUrl.empty())
internalUrl = LmsApp->getCoverResource()->getDefaultUrl(size);
cover->setImageLink(internalUrl);
cover->setImageLink(LmsApp->getArtworkResource()->getReleaseCoverUrl(releaseId, size));
cover->setStyleClass("Lms-cover img-fluid"); // HACK
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
return cover;
}
std::unique_ptr<Wt::WImage> createCover(db::TrackId trackId, CoverResource::Size size)
std::unique_ptr<Wt::WImage> createTrackImage(db::TrackId trackId, ArtworkResource::Size size)
{
auto cover{ std::make_unique<Wt::WImage>() };
std::string internalUrl;
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), trackId) };
if (track)
{
if (track->hasCover())
internalUrl = LmsApp->getCoverResource()->getTrackUrl(trackId, size);
else if (const db::Release::pointer release{ track->getRelease() }; release && release->getImage())
internalUrl = LmsApp->getCoverResource()->getReleaseUrl(release->getId(), size);
}
}
if (internalUrl.empty())
internalUrl = LmsApp->getCoverResource()->getDefaultUrl(size);
cover->setImageLink(internalUrl);
cover->setImageLink(LmsApp->getArtworkResource()->getTrackImageUrl(trackId, size));
cover->setStyleClass("Lms-cover img-fluid"); // HACK
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
+4 -3
View File
@@ -34,7 +34,7 @@
#include "database/ReleaseId.hpp"
#include "database/TrackId.hpp"
#include "resource/CoverResource.hpp"
#include "resource/ArtworkResource.hpp"
namespace lms::db
{
@@ -54,8 +54,9 @@ namespace lms::ui::utils
{
std::string durationToString(std::chrono::milliseconds msDuration);
std::unique_ptr<Wt::WImage> createCover(db::ReleaseId releaseId, CoverResource::Size size);
std::unique_ptr<Wt::WImage> createCover(db::TrackId trackId, CoverResource::Size size);
std::unique_ptr<Wt::WImage> createReleaseCover(db::ReleaseId releaseId, ArtworkResource::Size size);
std::unique_ptr<Wt::WImage> createTrackImage(db::TrackId trackId, ArtworkResource::Size size);
std::unique_ptr<Wt::WImage> createArtistImage(db::ArtistId artistId, ArtworkResource::Size size);
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);
+8 -3
View File
@@ -28,9 +28,14 @@ namespace lms::ui::ArtistListHelpers
{
std::unique_ptr<Wt::WTemplate> createEntry(const db::ObjectPtr<db::Artist>& artist)
{
auto res{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry")) };
res->bindWidget("name", utils::createArtistAnchor(artist));
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry")) };
entry->bindWidget("name", utils::createArtistAnchor(artist));
return res;
Wt::WAnchor* anchor{ entry->bindWidget("image", utils::createArtistAnchor(artist, false)) };
auto image{ utils::createArtistImage(artist->getId(), ArtworkResource::Size::Large) };
image->addStyleClass("Lms-cover-release Lms-cover-anchor");
anchor->setImage(std::move(image));
return entry;
}
} // namespace lms::ui::ArtistListHelpers
+1 -1
View File
@@ -108,7 +108,7 @@ namespace lms::ui
if (!artistId)
throw ArtistNotFoundException{};
const auto similarArtistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(*artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, 5) };
const auto similarArtistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(*artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, 6) };
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
+3 -3
View File
@@ -43,9 +43,9 @@ namespace lms::ui::releaseListHelpers
{
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
auto cover{ utils::createCover(release->getId(), CoverResource::Size::Large) };
cover->addStyleClass("Lms-cover-release Lms-cover-anchor");
anchor->setImage(std::move(cover));
auto image{ utils::createReleaseCover(release->getId(), ArtworkResource::Size::Large) };
image->addStyleClass("Lms-cover-release Lms-cover-anchor"); // hack
anchor->setImage(std::move(image));
}
auto artistAnchors{ utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : ArtistId{}, "link-secondary") };
+1 -1
View File
@@ -245,7 +245,7 @@ namespace lms::ui
refreshReleaseArtists(release);
bindWidget<Wt::WImage>("cover", utils::createCover(release->getId(), CoverResource::Size::Large));
bindWidget<Wt::WImage>("cover", utils::createReleaseCover(release->getId(), ArtworkResource::Size::Large));
Wt::WContainerWidget* clusterContainers{ bindNew<Wt::WContainerWidget>("clusters") };
{
+7 -7
View File
@@ -43,7 +43,7 @@
#include "Utils.hpp"
#include "common/Template.hpp"
#include "explore/PlayQueueController.hpp"
#include "resource/CoverResource.hpp"
#include "resource/ArtworkResource.hpp"
#include "resource/DownloadResource.hpp"
namespace lms::ui::TrackListHelpers
@@ -173,20 +173,20 @@ namespace lms::ui::TrackListHelpers
entry->bindWidget("artists-md", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
}
auto image{ utils::createTrackImage(trackId, ArtworkResource::Size::Small) };
image->addStyleClass("Lms-cover-track");
if (track->getRelease())
{
entry->setCondition("if-has-release", true);
entry->bindWidget("release", utils::createReleaseAnchor(track->getRelease()));
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
auto cover{ utils::createCover(release->getId(), CoverResource::Size::Small) };
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
anchor->setImage(std::move((cover)));
image->addStyleClass("Lms-cover-anchor"); // HACK
anchor->setImage(std::move((image)));
}
else
{
auto cover{ utils::createCover(trackId, CoverResource::Size::Small) };
cover->addStyleClass("Lms-cover-track"); // HACK
entry->bindWidget<Wt::WImage>("cover", std::move(cover));
entry->bindWidget<Wt::WImage>("cover", std::move(image));
}
entry->bindString("duration", utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
+241
View File
@@ -0,0 +1,241 @@
/*
* Copyright (C) 2014 Emeric Poupon
*
* This file is part of 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 "ArtworkResource.hpp"
#include <Wt/Http/Response.h>
#include <Wt/WApplication.h>
#include "core/Exception.hpp"
#include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "database/Artist.hpp"
#include "database/Image.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "services/artwork/IArtworkService.hpp"
#include "LmsApplication.hpp"
#define LOG(severity, message) LMS_LOG(UI, severity, "Image resource: " << message)
namespace lms::ui
{
ArtworkResource::ArtworkResource()
{
LmsApp->getScannerEvents().scanComplete.connect(this, [this](const scanner::ScanStats& stats) {
if (stats.nbChanges())
setChanged();
});
}
ArtworkResource::~ArtworkResource()
{
beingDeleted();
}
std::string ArtworkResource::getArtistImageUrl(db::ArtistId artistId, Size size) const
{
std::string url;
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const db::Artist::pointer artist{ db::Artist::find(LmsApp->getDbSession(), artistId) };
if (artist && artist->getImage())
url = getArtistIdImageUrl(artistId, size);
}
if (url.empty())
url = getDefaultArtistImageUrl();
return url;
}
std::string ArtworkResource::getReleaseCoverUrl(db::ReleaseId releaseId, Size size) const
{
std::string url;
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) };
if (release)
{
if (release->getImage())
{
url = getReleaseIdCoverUrl(release->getId(), size);
}
else
{
db::Track::FindParameters params;
params.setRelease(releaseId);
params.setHasEmbeddedImage(true);
params.setRange(db::Range{ 0, 1 });
db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track) {
url = getTrackIdImageUrl(track->getId(), size);
});
}
}
}
if (url.empty())
url = getDefaultReleaseCoverUrl();
return url;
}
std::string ArtworkResource::getTrackImageUrl(db::TrackId trackId, Size size) const
{
std::string url;
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), trackId) };
if (track)
{
if (track->hasCover())
url = getTrackIdImageUrl(trackId, size);
else if (const db::Release::pointer release{ track->getRelease() }; release && release->getImage())
url = getReleaseIdCoverUrl(release->getId(), size);
}
}
if (url.empty())
url = getDefaultReleaseCoverUrl();
return url;
}
std::string ArtworkResource::getArtistIdImageUrl(db::ArtistId artistId, Size size) const
{
return url() + "&artistid=" + artistId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size));
}
std::string ArtworkResource::getReleaseIdCoverUrl(db::ReleaseId releaseId, Size size) const
{
return url() + "&releaseid=" + releaseId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size));
}
std::string ArtworkResource::getTrackIdImageUrl(db::TrackId trackId, Size size) const
{
return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size));
}
std::string ArtworkResource::getDefaultArtistImageUrl() const
{
return url() + "&type=defaultartistimage";
}
std::string ArtworkResource::getDefaultReleaseCoverUrl() const
{
return url() + "&type=defaultreleasecover";
}
void ArtworkResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{
LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest");
// Retrieve parameters
const std::string* artistIdStr = request.getParameter("artistid");
const std::string* trackIdStr = request.getParameter("trackid");
const std::string* releaseIdStr = request.getParameter("releaseid");
const std::string* sizeStr = request.getParameter("size");
const std::string* typeStr = request.getParameter("type");
std::shared_ptr<image::IEncodedImage> image;
// Mandatory parameter size
if ((artistIdStr || trackIdStr || releaseIdStr))
{
if (!sizeStr)
{
LOG(DEBUG, "no size provided!");
return;
}
const auto size{ core::stringUtils::readAs<std::size_t>(*sizeStr) };
if (!size || *size > maxSize)
{
LOG(DEBUG, "invalid size provided!");
return;
}
if (artistIdStr)
{
LOG(DEBUG, "Requested cover for track " << *artistIdStr << ", size = " << *size);
const std::optional<db::ArtistId> artistId{ core::stringUtils::readAs<db::ArtistId::ValueType>(*artistIdStr) };
if (!artistId)
return;
image = core::Service<cover::IArtworkService>::get()->getArtistImage(*artistId, *size);
if (!image)
image = core::Service<cover::IArtworkService>::get()->getDefaultArtistImage();
}
else if (releaseIdStr)
{
LOG(DEBUG, "Requested cover for release " << *releaseIdStr << ", size = " << *size);
const std::optional<db::ReleaseId> releaseId{ core::stringUtils::readAs<db::ReleaseId::ValueType>(*releaseIdStr) };
if (!releaseId)
return;
image = core::Service<cover::IArtworkService>::get()->getReleaseCover(*releaseId, *size);
if (!image)
image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
}
else if (trackIdStr)
{
LOG(DEBUG, "Requested cover for track " << *trackIdStr << ", size = " << *size);
const std::optional<db::TrackId> trackId{ core::stringUtils::readAs<db::TrackId::ValueType>(*trackIdStr) };
if (!trackId)
{
LOG(DEBUG, "track not found");
return;
}
image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, *size);
if (!image)
image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
}
}
else if (typeStr)
{
if (*typeStr == "defaultreleasecover")
image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
else if (*typeStr == "defaultartistimage")
image = core::Service<cover::IArtworkService>::get()->getDefaultArtistImage();
}
if (image)
{
response.setMimeType(std::string{ image->getMimeType() });
response.out().write(reinterpret_cast<const char*>(image->getData()), image->getDataSize());
}
else
response.setStatus(404);
}
} // namespace lms::ui
@@ -21,18 +21,19 @@
#include <Wt/WResource.h>
#include "database/ArtistId.hpp"
#include "database/ReleaseId.hpp"
#include "database/TrackId.hpp"
namespace lms::ui
{
class CoverResource : public Wt::WResource
class ArtworkResource : public Wt::WResource
{
public:
static const std::size_t maxSize{ 512 };
CoverResource();
~CoverResource();
ArtworkResource();
~ArtworkResource();
enum class Size : std::size_t
{
@@ -40,11 +41,18 @@ namespace lms::ui
Large = 512,
};
std::string getReleaseUrl(db::ReleaseId releaseId, Size size) const;
std::string getTrackUrl(db::TrackId trackId, Size size) const;
std::string getDefaultUrl(Size size) const;
std::string getArtistImageUrl(db::ArtistId artistId, Size size) const;
std::string getReleaseCoverUrl(db::ReleaseId releaseId, Size size) const;
std::string getTrackImageUrl(db::TrackId trackId, Size size) const;
private:
std::string getArtistIdImageUrl(db::ArtistId artistId, Size size) const;
std::string getReleaseIdCoverUrl(db::ReleaseId releaseId, Size size) const;
std::string getTrackIdImageUrl(db::TrackId trackId, Size size) const;
std::string getDefaultArtistImageUrl() const;
std::string getDefaultReleaseCoverUrl() const;
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
};
} // namespace lms::ui
-125
View File
@@ -1,125 +0,0 @@
/*
* Copyright (C) 2014 Emeric Poupon
*
* This file is part of 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 "CoverResource.hpp"
#include <Wt/Http/Response.h>
#include <Wt/WApplication.h>
#include "core/Exception.hpp"
#include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp"
#include "core/Service.hpp"
#include "core/String.hpp"
#include "database/Track.hpp"
#include "services/cover/ICoverService.hpp"
#include "LmsApplication.hpp"
#define LOG(severity, message) LMS_LOG(UI, severity, "Image resource: " << message)
namespace lms::ui
{
CoverResource::CoverResource()
{
LmsApp->getScannerEvents().scanComplete.connect(this, [this](const scanner::ScanStats& stats) {
if (stats.nbChanges())
setChanged();
});
}
CoverResource::~CoverResource()
{
beingDeleted();
}
std::string CoverResource::getReleaseUrl(db::ReleaseId releaseId, Size size) const
{
return url() + "&releaseid=" + releaseId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size));
}
std::string CoverResource::getTrackUrl(db::TrackId trackId, Size size) const
{
return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size));
}
std::string CoverResource::getDefaultUrl(Size size) const
{
return url() + "&size=" + std::to_string(static_cast<std::size_t>(size));
}
void CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{
LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest");
// Retrieve parameters
const std::string* trackIdStr = request.getParameter("trackid");
const std::string* releaseIdStr = request.getParameter("releaseid");
const std::string* sizeStr = request.getParameter("size");
// Mandatory parameter size
if (!sizeStr)
{
LOG(DEBUG, "no size provided!");
return;
}
const auto size{ core::stringUtils::readAs<std::size_t>(*sizeStr) };
if (!size || *size > maxSize)
{
LOG(DEBUG, "invalid size provided!");
return;
}
std::shared_ptr<image::IEncodedImage> cover;
if (trackIdStr)
{
LOG(DEBUG, "Requested cover for track " << *trackIdStr << ", size = " << *size);
const std::optional<db::TrackId> trackId{ core::stringUtils::readAs<db::TrackId::ValueType>(*trackIdStr) };
if (!trackId)
{
LOG(DEBUG, "track not found");
return;
}
cover = core::Service<cover::ICoverService>::get()->getFromTrack(*trackId, *size);
}
else if (releaseIdStr)
{
LOG(DEBUG, "Requested cover for release " << *releaseIdStr << ", size = " << *size);
const std::optional<db::ReleaseId> releaseId{ core::stringUtils::readAs<db::ReleaseId::ValueType>(*releaseIdStr) };
if (!releaseId)
return;
cover = core::Service<cover::ICoverService>::get()->getFromRelease(*releaseId, *size);
}
else
LOG(DEBUG, "Requested default cover");
if (!cover)
cover = core::Service<cover::ICoverService>::get()->getDefaultSvgCover();
response.setMimeType(std::string{ cover->getMimeType() });
response.out().write(reinterpret_cast<const char*>(cover->getData()), cover->getDataSize());
}
} // namespace lms::ui
+1 -1
View File
@@ -4,7 +4,7 @@ add_executable(lms-cover
)
target_link_libraries(lms-cover PRIVATE
lmsservice-cover
lmsartwork
Boost::program_options
)
+14 -4
View File
@@ -34,7 +34,7 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "image/Image.hpp"
#include "services/cover/ICoverService.hpp"
#include "services/artwork/IArtworkService.hpp"
namespace lms
{
@@ -51,7 +51,7 @@ namespace lms
for (const db::TrackId trackId : trackIds.results)
{
std::cout << "Getting cover for track id " << trackId.toString() << std::endl;
core::Service<cover::ICoverService>::get()->getFromTrack(trackId, width);
core::Service<cover::IArtworkService>::get()->getTrackImage(trackId, width);
}
}
} // namespace lms
@@ -67,7 +67,17 @@ int main(int argc, char* argv[])
core::Service<core::logging::ILogger> logger{ std::make_unique<core::logging::StreamLogger>(std::cout) };
po::options_description desc{ "Allowed options" };
desc.add_options()("help,h", "print usage message")("conf,c", po::value<std::string>()->default_value(core::sysconfDirectory / "lms.conf"), "LMS config file")("default-cover,d", po::value<std::string>(), "Default cover path")("tracks,t", "dump covers for tracks")("size,s", po::value<unsigned>()->default_value(512), "Requested cover size")("quality,q", po::value<unsigned>()->default_value(75), "JPEG quality (1-100)");
// clang-format off
desc.add_options()
("help,h", "print usage message")
("conf,c", po::value<std::string>()->default_value(core::sysconfDirectory / "lms.conf"), "LMS config file")
("default-release-cover,d", po::value<std::string>(), "Default release cover path")
("default-artist-image,d", po::value<std::string>(), "Default artist image")
("tracks,t", "dump covers for tracks")
("size,s", po::value<unsigned>()->default_value(512), "Requested cover size")
("quality,q", po::value<unsigned>()->default_value(75), "JPEG quality (1-100)");
// clang-format on
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
@@ -81,7 +91,7 @@ int main(int argc, char* argv[])
image::init(argv[0]);
core::Service<core::IConfig> config{ core::createConfig(vm["conf"].as<std::string>()) };
db::Db db{ config->getPath("working-dir") / "lms.db" };
core::Service<cover::ICoverService> coverArtService{ cover::createCoverService(db, vm["default-cover"].as<std::string>()) };
core::Service<cover::IArtworkService> coverArtService{ cover::createArtworkService(db, vm["default-release-cover,"].as<std::string>(), vm["default-artist-image"].as<std::string>()) };
coverArtService->setJpegQuality(config->getULong("cover-jpeg-quality", vm["quality"].as<unsigned>()));