Subsonic API: getCoverArt: serve raw image if size is not provided

This commit is contained in:
emeric
2024-12-07 18:27:01 +01:00
parent 350e213529
commit def4d8c3e3
25 changed files with 305 additions and 372 deletions
@@ -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;
+11 -3
View File
@@ -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);
}
};