Subsonic API: getCoverArt: serve raw image if size is not provided
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "ArtworkService.hpp"
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "av/IAudioFile.hpp"
|
||||
#include "av/Types.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
@@ -31,6 +33,7 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "image/Exception.hpp"
|
||||
#include "image/IEncodedImage.hpp"
|
||||
#include "image/Image.hpp"
|
||||
|
||||
namespace lms::cover
|
||||
@@ -48,8 +51,6 @@ namespace lms::cover
|
||||
return std::make_unique<ArtworkService>(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath);
|
||||
}
|
||||
|
||||
using namespace image;
|
||||
|
||||
ArtworkService::ArtworkService(db::Db& db,
|
||||
const std::filesystem::path& defaultReleaseCoverSvgPath,
|
||||
const std::filesystem::path& defaultArtistImageSvgPath)
|
||||
@@ -61,13 +62,13 @@ namespace lms::cover
|
||||
LMS_LOG(COVER, INFO, "Default release cover path = '" << defaultReleaseCoverSvgPath.string() << "'");
|
||||
LMS_LOG(COVER, INFO, "Max cache size = " << _cache.getMaxCacheSize());
|
||||
|
||||
_defaultReleaseCover = image::readSvgFile(defaultReleaseCoverSvgPath); // may throw
|
||||
_defaultArtistImage = image::readSvgFile(defaultArtistImageSvgPath); // may throw
|
||||
_defaultReleaseCover = image::readImage(defaultReleaseCoverSvgPath); // may throw
|
||||
_defaultArtistImage = image::readImage(defaultArtistImageSvgPath); // may throw
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> ArtworkService::getFromAvMediaFile(const av::IAudioFile& input, ImageSize width) const
|
||||
std::unique_ptr<image::IEncodedImage> ArtworkService::getFromAvMediaFile(const av::IAudioFile& input, std::optional<image::ImageSize> width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
std::unique_ptr<image::IEncodedImage> image;
|
||||
|
||||
input.visitAttachedPictures([&](const av::Picture& picture, const av::IAudioFile::MetadataMap& /* metadata */) {
|
||||
if (image)
|
||||
@@ -75,9 +76,16 @@ namespace lms::cover
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<IRawImage> rawImage{ decodeImage(picture.data, picture.dataSize) };
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
if (!width)
|
||||
{
|
||||
image = image::readImage(std::span{ picture.data, picture.dataSize }, picture.mimeType);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto rawImage{ image::decodeImage(std::span{ picture.data, picture.dataSize }) };
|
||||
rawImage->resize(*width);
|
||||
image = image::encodeToJPEG(*rawImage, _jpegQuality);
|
||||
}
|
||||
}
|
||||
catch (const image::Exception& e)
|
||||
{
|
||||
@@ -88,15 +96,22 @@ namespace lms::cover
|
||||
return image;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> ArtworkService::getFromImageFile(const std::filesystem::path& p, ImageSize width) const
|
||||
std::unique_ptr<image::IEncodedImage> ArtworkService::getFromImageFile(const std::filesystem::path& p, std::optional<image::ImageSize> width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
std::unique_ptr<image::IEncodedImage> image;
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<IRawImage> rawImage{ decodeImage(p) };
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
if (!width)
|
||||
{
|
||||
image = image::readImage(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto rawImage{ image::decodeImage(p) };
|
||||
rawImage->resize(*width);
|
||||
image = image::encodeToJPEG(*rawImage, _jpegQuality);
|
||||
}
|
||||
}
|
||||
catch (const image::Exception& e)
|
||||
{
|
||||
@@ -106,17 +121,17 @@ namespace lms::cover
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> ArtworkService::getDefaultReleaseCover()
|
||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getDefaultReleaseCover()
|
||||
{
|
||||
return _defaultReleaseCover;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> ArtworkService::getDefaultArtistImage()
|
||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getDefaultArtistImage()
|
||||
{
|
||||
return _defaultArtistImage;
|
||||
}
|
||||
|
||||
bool ArtworkService::checkImageFile(const std::filesystem::path& filePath) const
|
||||
bool ArtworkService::checkImageFile(const std::filesystem::path& filePath)
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
@@ -132,9 +147,9 @@ namespace lms::cover
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> ArtworkService::getTrackImage(const std::filesystem::path& p, ImageSize width) const
|
||||
std::unique_ptr<image::IEncodedImage> ArtworkService::getTrackImage(const std::filesystem::path& p, std::optional<image::ImageSize> width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
std::unique_ptr<image::IEncodedImage> image;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -148,77 +163,81 @@ namespace lms::cover
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> ArtworkService::getTrackImage(db::TrackId trackId, ImageSize width)
|
||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width)
|
||||
{
|
||||
const ImageCache::EntryDesc cacheEntryDesc{ trackId, width };
|
||||
|
||||
std::shared_ptr<IEncodedImage> cover{ _cache.getImage(cacheEntryDesc) };
|
||||
std::shared_ptr<image::IEncodedImage> cover{ _cache.getImage(cacheEntryDesc) };
|
||||
if (cover)
|
||||
return cover;
|
||||
|
||||
std::filesystem::path trackFile;
|
||||
{
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const db::Track::pointer track{ db::Track::find(session, trackId) };
|
||||
if (track && track->hasCover())
|
||||
cover = getTrackImage(track->getAbsoluteFilePath(), width);
|
||||
trackFile = track->getAbsoluteFilePath();
|
||||
}
|
||||
|
||||
cover = getTrackImage(trackFile, width);
|
||||
if (cover)
|
||||
_cache.addImage(cacheEntryDesc, cover);
|
||||
|
||||
return cover;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> ArtworkService::getReleaseCover(db::ReleaseId releaseId, ImageSize width)
|
||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getReleaseCover(db::ReleaseId releaseId, std::optional<image::ImageSize> width)
|
||||
{
|
||||
using namespace db;
|
||||
const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width };
|
||||
|
||||
std::shared_ptr<IEncodedImage> image{ _cache.getImage(cacheEntryDesc) };
|
||||
std::shared_ptr<image::IEncodedImage> image{ _cache.getImage(cacheEntryDesc) };
|
||||
if (image)
|
||||
return image;
|
||||
|
||||
std::filesystem::path imagePath;
|
||||
{
|
||||
Session& session{ _db.getTLSSession() };
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const db::Release::pointer release{ db::Release::find(session, releaseId) };
|
||||
if (release)
|
||||
{
|
||||
if (const db::Image::pointer dbImage{ release->getImage() })
|
||||
image = getFromImageFile(dbImage->getAbsoluteFilePath(), width);
|
||||
imagePath = dbImage->getAbsoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
image = getFromImageFile(imagePath, width);
|
||||
if (image)
|
||||
_cache.addImage(cacheEntryDesc, image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> ArtworkService::getArtistImage(db::ArtistId artistId, ImageSize width)
|
||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getArtistImage(db::ArtistId artistId, std::optional<image::ImageSize> width)
|
||||
{
|
||||
using namespace db;
|
||||
const ImageCache::EntryDesc cacheEntryDesc{ artistId, width };
|
||||
|
||||
std::shared_ptr<IEncodedImage> artistImage{ _cache.getImage(cacheEntryDesc) };
|
||||
std::shared_ptr<image::IEncodedImage> artistImage{ _cache.getImage(cacheEntryDesc) };
|
||||
if (artistImage)
|
||||
return artistImage;
|
||||
|
||||
std::filesystem::path imagePath;
|
||||
{
|
||||
Session& session{ _db.getTLSSession() };
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
if (const Artist::pointer artist{ Artist::find(session, artistId) })
|
||||
if (const db::Artist::pointer artist{ db::Artist::find(session, artistId) })
|
||||
{
|
||||
if (const db::Image::pointer image{ artist->getImage() })
|
||||
artistImage = getFromImageFile(image->getAbsoluteFilePath(), width);
|
||||
imagePath = image->getAbsoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
artistImage = getFromImageFile(imagePath, width);
|
||||
if (artistImage)
|
||||
_cache.addImage(cacheEntryDesc, artistImage);
|
||||
|
||||
|
||||
@@ -48,22 +48,20 @@ namespace lms::cover
|
||||
ArtworkService& operator=(const ArtworkService&) = delete;
|
||||
|
||||
private:
|
||||
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> getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getReleaseCover(db::ReleaseId releaseId, std::optional<image::ImageSize> width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getArtistImage(db::ArtistId artistId, std::optional<image::ImageSize> width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() override;
|
||||
std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() override;
|
||||
|
||||
void flushCache() override;
|
||||
void setJpegQuality(unsigned quality) override;
|
||||
|
||||
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> getFromAvMediaFile(const av::IAudioFile& input, std::optional<image::ImageSize> width) const;
|
||||
std::unique_ptr<image::IEncodedImage> getFromImageFile(const std::filesystem::path& p, std::optional<image::ImageSize> width) const;
|
||||
std::unique_ptr<image::IEncodedImage> getTrackImage(const std::filesystem::path& path, std::optional<image::ImageSize> width) const;
|
||||
|
||||
std::unique_ptr<image::IEncodedImage> getTrackImage(const std::filesystem::path& path, image::ImageSize width) const;
|
||||
|
||||
bool checkImageFile(const std::filesystem::path& filePath) const;
|
||||
static bool checkImageFile(const std::filesystem::path& filePath);
|
||||
|
||||
db::Db& _db;
|
||||
|
||||
|
||||
@@ -33,21 +33,29 @@ namespace lms::cover
|
||||
|
||||
void ImageCache::addImage(const EntryDesc& entryDesc, std::shared_ptr<image::IEncodedImage> image)
|
||||
{
|
||||
// cache only resized files
|
||||
if (!entryDesc.size)
|
||||
return;
|
||||
|
||||
const std::unique_lock lock{ _mutex };
|
||||
|
||||
while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty())
|
||||
while (_cacheSize + image->getData().size() > _maxCacheSize && !_cache.empty())
|
||||
{
|
||||
auto itRandom{ core::random::pickRandom(_cache) };
|
||||
_cacheSize -= itRandom->second->getDataSize();
|
||||
_cacheSize -= itRandom->second->getData().size();
|
||||
_cache.erase(itRandom);
|
||||
}
|
||||
|
||||
_cacheSize += image->getDataSize();
|
||||
_cacheSize += image->getData().size();
|
||||
_cache[entryDesc] = image;
|
||||
}
|
||||
|
||||
std::shared_ptr<image::IEncodedImage> ImageCache::getImage(const EntryDesc& entryDesc) const
|
||||
{
|
||||
// cache only resized files
|
||||
if (!entryDesc.size)
|
||||
return {};
|
||||
|
||||
const std::shared_lock lock{ _mutex };
|
||||
|
||||
const auto it{ _cache.find(entryDesc) };
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
@@ -40,7 +41,7 @@ namespace lms::cover
|
||||
{
|
||||
using VariantType = std::variant<db::ArtistId, db::ReleaseId, db::TrackId>;
|
||||
VariantType id;
|
||||
std::size_t size;
|
||||
std::optional<std::size_t> size;
|
||||
|
||||
bool operator==(const EntryDesc& other) const = default;
|
||||
};
|
||||
@@ -60,7 +61,8 @@ namespace lms::cover
|
||||
{
|
||||
std::size_t operator()(const EntryDesc& entry) const
|
||||
{
|
||||
return std::hash<EntryDesc::VariantType>{}(entry.id) ^ std::hash<std::size_t>{}(entry.size);
|
||||
assert(entry.size); // should not cache unresized images
|
||||
return std::hash<EntryDesc::VariantType>{}(entry.id) ^ std::hash<std::size_t>{}(*entry.size);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user