Changed the artists view to use artist images
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@
|
|||||||
${</if-has-non-release-tracks>}
|
${</if-has-non-release-tracks>}
|
||||||
${<if-has-similar-artists>}
|
${<if-has-similar-artists>}
|
||||||
<h3>${tr:Lms.Explore.Artist.similar-artists}</h3>
|
<h3>${tr:Lms.Explore.Artist.similar-artists}</h3>
|
||||||
${similar-artists class="d-grid gap-3"}
|
${similar-artists class="row row-cols-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-6 gx-2 gy-4"}
|
||||||
${</if-has-similar-artists>}
|
${</if-has-similar-artists>}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -14,11 +14,18 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artists.template.entry">
|
<message id="Lms.Explore.Artists.template.entry">
|
||||||
${name class="text-decoration-none link-secondary"}
|
<div class="col h-100 Lms-bg-dark-hover rounded">
|
||||||
|
<div class="p-2">
|
||||||
|
<div class="Lms-responsive-square Lms-cover-container mb-1">
|
||||||
|
${image class="shadow-sm"}
|
||||||
|
</div>
|
||||||
|
${name class="d-block text-center text-truncate text-decoration-none link-secondary"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artists.template.container">
|
<message id="Lms.Explore.Artists.template.container">
|
||||||
${elements class="d-grid gap-3"}
|
${elements class="row row-cols-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-6 gx-2 gy-4"}
|
||||||
${loading-indicator}
|
${loading-indicator}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<svg width="256px" height="256px" viewBox="0 0 16.00 16.00" xmlns="http://www.w3.org/2000/svg" fill="#c9c9c9" stroke="#c9c9c9" stroke-width="0.00016" transform="rotate(0)matrix(1, 0, 0, 1, 0, 0)">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0" transform="translate(0,0), scale(1)"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="0.6719999999999999"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<g color="#c9c9c9" font-weight="400" font-family="Ubuntu" letter-spacing="0" word-spacing="0" white-space="normal" fill="#c9c9c9">
|
||||||
|
<path d="M8 2a2.84 2.84 0 0 0-1.12.221c-.345.141-.651.348-.906.615v.003l-.001.002c-.248.269-.44.592-.574.96-.137.367-.203.769-.203 1.2 0 .435.065.841.203 1.209.135.361.327.68.574.95l.001.002c.254.267.558.477.901.624v.003c.346.141.723.21 1.12.21.395 0 .77-.069 1.117-.21v-.002c.343-.147.644-.357.892-.625.255-.268.45-.59.586-.952.138-.368.204-.774.204-1.21h.01c0-.43-.065-.831-.203-1.198a2.771 2.771 0 0 0-.585-.963 2.5 2.5 0 0 0-.897-.618A2.835 2.835 0 0 0 7.999 2zM8.024 10.002c-2.317 0-3.561.213-4.486.91-.462.35-.767.825-.939 1.378-.172.553-.226.975-.228 1.71L8 14.002h5.629c-.001-.736-.052-1.159-.225-1.712-.172-.553-.477-1.027-.94-1.376-.923-.697-2.124-.912-4.44-.912z"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,5 +1,5 @@
|
|||||||
|
add_subdirectory(artwork)
|
||||||
add_subdirectory(auth)
|
add_subdirectory(auth)
|
||||||
add_subdirectory(cover)
|
|
||||||
add_subdirectory(feedback)
|
add_subdirectory(feedback)
|
||||||
add_subdirectory(recommendation)
|
add_subdirectory(recommendation)
|
||||||
add_subdirectory(scanner)
|
add_subdirectory(scanner)
|
||||||
|
|||||||
@@ -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})
|
||||||
|
|
||||||
+26
-19
@@ -17,7 +17,7 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "CoverService.hpp"
|
#include "ArtworkService.hpp"
|
||||||
|
|
||||||
#include "av/IAudioFile.hpp"
|
#include "av/IAudioFile.hpp"
|
||||||
#include "core/IConfig.hpp"
|
#include "core/IConfig.hpp"
|
||||||
@@ -45,27 +45,29 @@ namespace lms::cover
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // 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;
|
using namespace image;
|
||||||
|
|
||||||
CoverService::CoverService(db::Db& db,
|
ArtworkService::ArtworkService(db::Db& db,
|
||||||
const std::filesystem::path& defaultSvgCoverPath)
|
const std::filesystem::path& defaultReleaseCoverSvgPath,
|
||||||
|
const std::filesystem::path& defaultArtistImageSvgPath)
|
||||||
: _db{ db }
|
: _db{ db }
|
||||||
, _cache{ core::Service<core::IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
|
, _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));
|
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());
|
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;
|
std::unique_ptr<IEncodedImage> image;
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ namespace lms::cover
|
|||||||
return image;
|
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;
|
std::unique_ptr<IEncodedImage> image;
|
||||||
|
|
||||||
@@ -106,12 +108,17 @@ namespace lms::cover
|
|||||||
return image;
|
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;
|
std::error_code ec;
|
||||||
|
|
||||||
@@ -127,7 +134,7 @@ namespace lms::cover
|
|||||||
return true;
|
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;
|
std::unique_ptr<IEncodedImage> image;
|
||||||
|
|
||||||
@@ -143,7 +150,7 @@ namespace lms::cover
|
|||||||
return image;
|
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 };
|
const ImageCache::EntryDesc cacheEntryDesc{ trackId, width };
|
||||||
|
|
||||||
@@ -157,7 +164,7 @@ namespace lms::cover
|
|||||||
|
|
||||||
const db::Track::pointer track{ db::Track::find(session, trackId) };
|
const db::Track::pointer track{ db::Track::find(session, trackId) };
|
||||||
if (track && track->hasCover())
|
if (track && track->hasCover())
|
||||||
cover = getFromTrack(track->getAbsoluteFilePath(), width);
|
cover = getTrackImage(track->getAbsoluteFilePath(), width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cover)
|
if (cover)
|
||||||
@@ -166,7 +173,7 @@ namespace lms::cover
|
|||||||
return 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;
|
using namespace db;
|
||||||
const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width };
|
const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width };
|
||||||
@@ -193,7 +200,7 @@ namespace lms::cover
|
|||||||
return image;
|
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;
|
using namespace db;
|
||||||
const ImageCache::EntryDesc cacheEntryDesc{ artistId, width };
|
const ImageCache::EntryDesc cacheEntryDesc{ artistId, width };
|
||||||
@@ -220,12 +227,12 @@ namespace lms::cover
|
|||||||
return artistImage;
|
return artistImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverService::flushCache()
|
void ArtworkService::flushCache()
|
||||||
{
|
{
|
||||||
_cache.flush();
|
_cache.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoverService::setJpegQuality(unsigned quality)
|
void ArtworkService::setJpegQuality(unsigned quality)
|
||||||
{
|
{
|
||||||
_jpegQuality = core::utils::clamp<unsigned>(quality, 1, 100);
|
_jpegQuality = core::utils::clamp<unsigned>(quality, 1, 100);
|
||||||
|
|
||||||
+15
-12
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
#include "image/IEncodedImage.hpp"
|
#include "image/IEncodedImage.hpp"
|
||||||
#include "services/cover/ICoverService.hpp"
|
#include "services/artwork/IArtworkService.hpp"
|
||||||
|
|
||||||
#include "ImageCache.hpp"
|
#include "ImageCache.hpp"
|
||||||
|
|
||||||
@@ -40,34 +40,37 @@ namespace lms::av
|
|||||||
|
|
||||||
namespace lms::cover
|
namespace lms::cover
|
||||||
{
|
{
|
||||||
class CoverService : public ICoverService
|
class ArtworkService : public IArtworkService
|
||||||
{
|
{
|
||||||
public:
|
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:
|
private:
|
||||||
CoverService(const CoverService&) = delete;
|
ArtworkService(const ArtworkService&) = delete;
|
||||||
CoverService& operator=(const CoverService&) = 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 flushCache() override;
|
||||||
void setJpegQuality(unsigned quality) 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> 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> 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;
|
bool checkImageFile(const std::filesystem::path& filePath) const;
|
||||||
|
|
||||||
db::Db& _db;
|
db::Db& _db;
|
||||||
|
|
||||||
ImageCache _cache;
|
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
|
static inline const std::vector<std::filesystem::path> _fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; // TODO parametrize
|
||||||
unsigned _jpegQuality;
|
unsigned _jpegQuality;
|
||||||
+9
-7
@@ -34,26 +34,28 @@ namespace lms::db
|
|||||||
|
|
||||||
namespace lms::cover
|
namespace lms::cover
|
||||||
{
|
{
|
||||||
class ICoverService
|
class IArtworkService
|
||||||
{
|
{
|
||||||
public:
|
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
|
// 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
|
// 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 flushCache() = 0;
|
||||||
|
|
||||||
virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100
|
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
|
} // namespace lms::cover
|
||||||
@@ -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})
|
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ target_link_libraries(lmssubsonic PRIVATE
|
|||||||
lmsrecommendation
|
lmsrecommendation
|
||||||
lmsscanner
|
lmsscanner
|
||||||
lmsscrobbling
|
lmsscrobbling
|
||||||
lmsservice-cover
|
lmsartwork
|
||||||
lmscore
|
lmscore
|
||||||
std::filesystem
|
std::filesystem
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ namespace lms::api::subsonic
|
|||||||
SubsonicResource::SubsonicResource(db::Db& db)
|
SubsonicResource::SubsonicResource(db::Db& db)
|
||||||
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
|
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
|
||||||
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
|
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
|
||||||
, _defaultCoverClients{ readDefaultCoverClients() }
|
, _defaultReleaseCoverClients{ readDefaultCoverClients() }
|
||||||
, _db{ db }
|
, _db{ db }
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -410,7 +410,7 @@ namespace lms::api::subsonic
|
|||||||
const ClientInfo clientInfo{ getClientInfo(request) };
|
const ClientInfo clientInfo{ getClientInfo(request) };
|
||||||
const db::UserId userId{ authenticateUser(request, clientInfo) };
|
const db::UserId userId{ authenticateUser(request, clientInfo) };
|
||||||
bool enableOpenSubsonic{ !_openSubsonicDisabledClients.contains(clientInfo.name) };
|
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 };
|
const ResponseFormat format{ getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml };
|
||||||
|
|
||||||
db::User::pointer user;
|
db::User::pointer user;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
|
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
|
||||||
const std::unordered_set<std::string> _openSubsonicDisabledClients;
|
const std::unordered_set<std::string> _openSubsonicDisabledClients;
|
||||||
const std::unordered_set<std::string> _defaultCoverClients;
|
const std::unordered_set<std::string> _defaultReleaseCoverClients;
|
||||||
|
|
||||||
db::Db& _db;
|
db::Db& _db;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "services/cover/ICoverService.hpp"
|
#include "services/artwork/IArtworkService.hpp"
|
||||||
|
|
||||||
#include "ParameterParsing.hpp"
|
#include "ParameterParsing.hpp"
|
||||||
#include "SubsonicId.hpp"
|
#include "SubsonicId.hpp"
|
||||||
@@ -267,14 +267,14 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
std::shared_ptr<image::IEncodedImage> cover;
|
std::shared_ptr<image::IEncodedImage> cover;
|
||||||
if (trackId)
|
if (trackId)
|
||||||
cover = core::Service<cover::ICoverService>::get()->getFromTrack(*trackId, size);
|
cover = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
|
||||||
else if (releaseId)
|
else if (releaseId)
|
||||||
cover = core::Service<cover::ICoverService>::get()->getFromRelease(*releaseId, size);
|
cover = core::Service<cover::IArtworkService>::get()->getReleaseCover(*releaseId, size);
|
||||||
else if (artistId)
|
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)
|
if (!cover && context.enableDefaultCover && !artistId)
|
||||||
cover = core::Service<cover::ICoverService>::get()->getDefaultSvgCover();
|
cover = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
|
||||||
|
|
||||||
if (!cover)
|
if (!cover)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ add_executable(lms
|
|||||||
ui/explore/TracksView.cpp
|
ui/explore/TracksView.cpp
|
||||||
ui/resource/AudioFileResource.cpp
|
ui/resource/AudioFileResource.cpp
|
||||||
ui/resource/AudioTranscodingResource.cpp
|
ui/resource/AudioTranscodingResource.cpp
|
||||||
ui/resource/CoverResource.cpp
|
ui/resource/ArtworkResource.cpp
|
||||||
ui/resource/DownloadResource.cpp
|
ui/resource/DownloadResource.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ target_link_libraries(lms PRIVATE
|
|||||||
lmsrecommendation
|
lmsrecommendation
|
||||||
lmsscanner
|
lmsscanner
|
||||||
lmsscrobbling
|
lmsscrobbling
|
||||||
lmsservice-cover
|
lmsartwork
|
||||||
lmssubsonic
|
lmssubsonic
|
||||||
lmscore
|
lmscore
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-3
@@ -35,10 +35,10 @@
|
|||||||
#include "database/Db.hpp"
|
#include "database/Db.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "image/Image.hpp"
|
#include "image/Image.hpp"
|
||||||
|
#include "services/artwork/IArtworkService.hpp"
|
||||||
#include "services/auth/IAuthTokenService.hpp"
|
#include "services/auth/IAuthTokenService.hpp"
|
||||||
#include "services/auth/IEnvService.hpp"
|
#include "services/auth/IEnvService.hpp"
|
||||||
#include "services/auth/IPasswordService.hpp"
|
#include "services/auth/IPasswordService.hpp"
|
||||||
#include "services/cover/ICoverService.hpp"
|
|
||||||
#include "services/feedback/IFeedbackService.hpp"
|
#include "services/feedback/IFeedbackService.hpp"
|
||||||
#include "services/recommendation/IPlaylistGeneratorService.hpp"
|
#include "services/recommendation/IPlaylistGeneratorService.hpp"
|
||||||
#include "services/recommendation/IRecommendationService.hpp"
|
#include "services/recommendation/IRecommendationService.hpp"
|
||||||
@@ -340,7 +340,7 @@ namespace lms
|
|||||||
throw core::LmsException{ "Bad value '" + authenticationBackend + "' for 'authentication-backend'" };
|
throw core::LmsException{ "Bad value '" + authenticationBackend + "' for 'authentication-backend'" };
|
||||||
|
|
||||||
image::init(argv[0]);
|
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::IRecommendationService> recommendationService{ recommendation::createRecommendationService(database) };
|
||||||
core::Service<recommendation::IPlaylistGeneratorService> playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) };
|
core::Service<recommendation::IPlaylistGeneratorService> playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) };
|
||||||
core::Service<scanner::IScannerService> scannerService{ scanner::createScannerService(database) };
|
core::Service<scanner::IScannerService> scannerService{ scanner::createScannerService(database) };
|
||||||
@@ -348,7 +348,7 @@ namespace lms
|
|||||||
scannerService->getEvents().scanComplete.connect([&] {
|
scannerService->getEvents().scanComplete.connect([&] {
|
||||||
// Flush cover cache even if no changes:
|
// 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)
|
// 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();
|
database.getTLSSession().refreshTracingLoggerStats();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,9 @@
|
|||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/TrackList.hpp"
|
#include "database/TrackList.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
|
#include "services/artwork/IArtworkService.hpp"
|
||||||
#include "services/auth/IEnvService.hpp"
|
#include "services/auth/IEnvService.hpp"
|
||||||
#include "services/auth/IPasswordService.hpp"
|
#include "services/auth/IPasswordService.hpp"
|
||||||
#include "services/cover/ICoverService.hpp"
|
|
||||||
#include "services/scrobbling/IScrobblingService.hpp"
|
#include "services/scrobbling/IScrobblingService.hpp"
|
||||||
|
|
||||||
#include "Auth.hpp"
|
#include "Auth.hpp"
|
||||||
@@ -61,9 +61,9 @@
|
|||||||
#include "common/Template.hpp"
|
#include "common/Template.hpp"
|
||||||
#include "explore/Explore.hpp"
|
#include "explore/Explore.hpp"
|
||||||
#include "explore/Filters.hpp"
|
#include "explore/Filters.hpp"
|
||||||
|
#include "resource/ArtworkResource.hpp"
|
||||||
#include "resource/AudioFileResource.hpp"
|
#include "resource/AudioFileResource.hpp"
|
||||||
#include "resource/AudioTranscodingResource.hpp"
|
#include "resource/AudioTranscodingResource.hpp"
|
||||||
#include "resource/CoverResource.hpp"
|
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
@@ -400,7 +400,7 @@ namespace lms::ui
|
|||||||
{
|
{
|
||||||
LMS_SCOPED_TRACE_OVERVIEW("UI", "ApplicationCreateHome");
|
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("onLoadCover", "function(id) { id.className += \" Lms-cover-loaded\"}");
|
||||||
declareJavaScriptFunction("updateActiveNav",
|
declareJavaScriptFunction("updateActiveNav",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ namespace lms::db
|
|||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
class CoverResource;
|
class ArtworkResource;
|
||||||
class LmsApplicationException;
|
class LmsApplicationException;
|
||||||
class MediaPlayer;
|
class MediaPlayer;
|
||||||
class PlayQueue;
|
class PlayQueue;
|
||||||
@@ -60,7 +60,7 @@ namespace lms::ui
|
|||||||
static LmsApplication* instance();
|
static LmsApplication* instance();
|
||||||
|
|
||||||
// Session application data
|
// Session application data
|
||||||
std::shared_ptr<CoverResource> getCoverResource() { return _coverResource; }
|
std::shared_ptr<ArtworkResource> getArtworkResource() { return _artworkResource; }
|
||||||
db::Db& getDb();
|
db::Db& getDb();
|
||||||
db::Session& getDbSession(); // always thread safe
|
db::Session& getDbSession(); // always thread safe
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ namespace lms::ui
|
|||||||
bool strongAuth{};
|
bool strongAuth{};
|
||||||
};
|
};
|
||||||
std::optional<UserAuthInfo> _user;
|
std::optional<UserAuthInfo> _user;
|
||||||
std::shared_ptr<CoverResource> _coverResource;
|
std::shared_ptr<ArtworkResource> _artworkResource;
|
||||||
MediaPlayer* _mediaPlayer{};
|
MediaPlayer* _mediaPlayer{};
|
||||||
PlayQueue* _playQueue{};
|
PlayQueue* _playQueue{};
|
||||||
NotificationContainer* _notificationContainer{};
|
NotificationContainer* _notificationContainer{};
|
||||||
|
|||||||
@@ -37,9 +37,9 @@
|
|||||||
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
#include "resource/ArtworkResource.hpp"
|
||||||
#include "resource/AudioFileResource.hpp"
|
#include "resource/AudioFileResource.hpp"
|
||||||
#include "resource/AudioTranscodingResource.hpp"
|
#include "resource/AudioTranscodingResource.hpp"
|
||||||
#include "resource/CoverResource.hpp"
|
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
@@ -255,13 +255,13 @@ namespace lms::ui
|
|||||||
<< " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\","
|
<< " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\","
|
||||||
<< " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
<< " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
||||||
<< " artwork: ["
|
<< " artwork: ["
|
||||||
<< " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
|
<< " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::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::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
|
||||||
<< " ]"
|
<< " ]"
|
||||||
<< "};";
|
<< "};";
|
||||||
// Update 'sizes' above to match this:
|
// 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<ArtworkResource::Size>>(ArtworkResource::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::Large) == 512);
|
||||||
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
||||||
|
|
||||||
_title->setTextFormat(Wt::TextFormat::Plain);
|
_title->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
|||||||
@@ -485,22 +485,20 @@ namespace lms::ui
|
|||||||
entry->bindWidget("artists-md", utils::createArtistAnchorList(artists));
|
entry->bindWidget("artists-md", utils::createArtistAnchorList(artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto release{ track->getRelease() };
|
auto image{ utils::createTrackImage(track->getId(), ArtworkResource::Size::Small) };
|
||||||
if (release)
|
image->addStyleClass("Lms-cover-track");
|
||||||
|
if (const auto release{ track->getRelease() })
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
entry->bindWidget("release", utils::createReleaseAnchor(release));
|
entry->bindWidget("release", utils::createReleaseAnchor(release));
|
||||||
{
|
|
||||||
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
||||||
auto cover{ utils::createCover(release->getId(), CoverResource::Size::Small) };
|
image->addStyleClass("Lms-cover-anchor"); // HACK
|
||||||
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
|
anchor->setImage(std::move(image));
|
||||||
anchor->setImage(std::move(cover));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto cover{ entry->bindWidget<Wt::WImage>("cover", utils::createCover(track->getId(), CoverResource::Size::Small)) };
|
entry->bindWidget("cover", std::move(image));
|
||||||
cover->addStyleClass("Lms-cover-track");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->bindString("duration", utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|||||||
+13
-51
@@ -63,66 +63,28 @@ namespace lms::ui::utils
|
|||||||
return oss.str();
|
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>() };
|
auto cover{ std::make_unique<Wt::WImage>() };
|
||||||
std::string internalUrl;
|
cover->setImageLink(LmsApp->getArtworkResource()->getReleaseCoverUrl(releaseId, size));
|
||||||
|
|
||||||
{
|
|
||||||
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->setStyleClass("Lms-cover img-fluid"); // HACK
|
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
||||||
return cover;
|
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>() };
|
auto cover{ std::make_unique<Wt::WImage>() };
|
||||||
std::string internalUrl;
|
cover->setImageLink(LmsApp->getArtworkResource()->getTrackImageUrl(trackId, size));
|
||||||
|
|
||||||
{
|
|
||||||
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->setStyleClass("Lms-cover img-fluid"); // HACK
|
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include "database/ReleaseId.hpp"
|
#include "database/ReleaseId.hpp"
|
||||||
#include "database/TrackId.hpp"
|
#include "database/TrackId.hpp"
|
||||||
|
|
||||||
#include "resource/CoverResource.hpp"
|
#include "resource/ArtworkResource.hpp"
|
||||||
|
|
||||||
namespace lms::db
|
namespace lms::db
|
||||||
{
|
{
|
||||||
@@ -54,8 +54,9 @@ namespace lms::ui::utils
|
|||||||
{
|
{
|
||||||
std::string durationToString(std::chrono::milliseconds msDuration);
|
std::string durationToString(std::chrono::milliseconds msDuration);
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> createCover(db::ReleaseId releaseId, CoverResource::Size size);
|
std::unique_ptr<Wt::WImage> createReleaseCover(db::ReleaseId releaseId, ArtworkResource::Size size);
|
||||||
std::unique_ptr<Wt::WImage> createCover(db::TrackId trackId, CoverResource::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> 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);
|
||||||
|
|||||||
@@ -28,9 +28,14 @@ namespace lms::ui::ArtistListHelpers
|
|||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WTemplate> createEntry(const db::ObjectPtr<db::Artist>& artist)
|
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")) };
|
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry")) };
|
||||||
res->bindWidget("name", utils::createArtistAnchor(artist));
|
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
|
} // namespace lms::ui::ArtistListHelpers
|
||||||
@@ -108,7 +108,7 @@ namespace lms::ui
|
|||||||
if (!artistId)
|
if (!artistId)
|
||||||
throw ArtistNotFoundException{};
|
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() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
|
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ 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 cover{ utils::createCover(release->getId(), CoverResource::Size::Large) };
|
auto image{ utils::createReleaseCover(release->getId(), ArtworkResource::Size::Large) };
|
||||||
cover->addStyleClass("Lms-cover-release Lms-cover-anchor");
|
image->addStyleClass("Lms-cover-release Lms-cover-anchor"); // hack
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(image));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto artistAnchors{ utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : ArtistId{}, "link-secondary") };
|
auto artistAnchors{ utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : ArtistId{}, "link-secondary") };
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ namespace lms::ui
|
|||||||
|
|
||||||
refreshReleaseArtists(release);
|
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") };
|
Wt::WContainerWidget* clusterContainers{ bindNew<Wt::WContainerWidget>("clusters") };
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "common/Template.hpp"
|
#include "common/Template.hpp"
|
||||||
#include "explore/PlayQueueController.hpp"
|
#include "explore/PlayQueueController.hpp"
|
||||||
#include "resource/CoverResource.hpp"
|
#include "resource/ArtworkResource.hpp"
|
||||||
#include "resource/DownloadResource.hpp"
|
#include "resource/DownloadResource.hpp"
|
||||||
|
|
||||||
namespace lms::ui::TrackListHelpers
|
namespace lms::ui::TrackListHelpers
|
||||||
@@ -173,20 +173,20 @@ 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) };
|
||||||
|
image->addStyleClass("Lms-cover-track");
|
||||||
if (track->getRelease())
|
if (track->getRelease())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
entry->bindWidget("release", utils::createReleaseAnchor(track->getRelease()));
|
entry->bindWidget("release", utils::createReleaseAnchor(track->getRelease()));
|
||||||
|
|
||||||
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
||||||
auto cover{ utils::createCover(release->getId(), CoverResource::Size::Small) };
|
image->addStyleClass("Lms-cover-anchor"); // HACK
|
||||||
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
|
anchor->setImage(std::move((image)));
|
||||||
anchor->setImage(std::move((cover)));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto cover{ utils::createCover(trackId, CoverResource::Size::Small) };
|
entry->bindWidget<Wt::WImage>("cover", std::move(image));
|
||||||
cover->addStyleClass("Lms-cover-track"); // HACK
|
|
||||||
entry->bindWidget<Wt::WImage>("cover", std::move(cover));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->bindString("duration", utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|||||||
@@ -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 <Wt/WResource.h>
|
||||||
|
|
||||||
|
#include "database/ArtistId.hpp"
|
||||||
#include "database/ReleaseId.hpp"
|
#include "database/ReleaseId.hpp"
|
||||||
#include "database/TrackId.hpp"
|
#include "database/TrackId.hpp"
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
class CoverResource : public Wt::WResource
|
class ArtworkResource : public Wt::WResource
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const std::size_t maxSize{ 512 };
|
static const std::size_t maxSize{ 512 };
|
||||||
|
|
||||||
CoverResource();
|
ArtworkResource();
|
||||||
~CoverResource();
|
~ArtworkResource();
|
||||||
|
|
||||||
enum class Size : std::size_t
|
enum class Size : std::size_t
|
||||||
{
|
{
|
||||||
@@ -40,11 +41,18 @@ namespace lms::ui
|
|||||||
Large = 512,
|
Large = 512,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string getReleaseUrl(db::ReleaseId releaseId, Size size) const;
|
std::string getArtistImageUrl(db::ArtistId artistId, Size size) const;
|
||||||
std::string getTrackUrl(db::TrackId trackId, Size size) const;
|
std::string getReleaseCoverUrl(db::ReleaseId releaseId, Size size) const;
|
||||||
std::string getDefaultUrl(Size size) const;
|
std::string getTrackImageUrl(db::TrackId trackId, Size size) const;
|
||||||
|
|
||||||
private:
|
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;
|
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
||||||
};
|
};
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
@@ -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
|
|
||||||
@@ -4,7 +4,7 @@ add_executable(lms-cover
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(lms-cover PRIVATE
|
target_link_libraries(lms-cover PRIVATE
|
||||||
lmsservice-cover
|
lmsartwork
|
||||||
Boost::program_options
|
Boost::program_options
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "image/Image.hpp"
|
#include "image/Image.hpp"
|
||||||
#include "services/cover/ICoverService.hpp"
|
#include "services/artwork/IArtworkService.hpp"
|
||||||
|
|
||||||
namespace lms
|
namespace lms
|
||||||
{
|
{
|
||||||
@@ -51,7 +51,7 @@ namespace lms
|
|||||||
for (const db::TrackId trackId : trackIds.results)
|
for (const db::TrackId trackId : trackIds.results)
|
||||||
{
|
{
|
||||||
std::cout << "Getting cover for track id " << trackId.toString() << std::endl;
|
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
|
} // 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) };
|
core::Service<core::logging::ILogger> logger{ std::make_unique<core::logging::StreamLogger>(std::cout) };
|
||||||
|
|
||||||
po::options_description desc{ "Allowed options" };
|
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::variables_map vm;
|
||||||
po::store(po::parse_command_line(argc, argv, desc), 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]);
|
image::init(argv[0]);
|
||||||
core::Service<core::IConfig> config{ core::createConfig(vm["conf"].as<std::string>()) };
|
core::Service<core::IConfig> config{ core::createConfig(vm["conf"].as<std::string>()) };
|
||||||
db::Db db{ config->getPath("working-dir") / "lms.db" };
|
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>()));
|
coverArtService->setJpegQuality(config->getULong("cover-jpeg-quality", vm["quality"].as<unsigned>()));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user