diff --git a/approot/images/unknown-cover.jpg b/approot/images/unknown-cover.jpg
deleted file mode 100755
index 3adc6dc1..00000000
Binary files a/approot/images/unknown-cover.jpg and /dev/null differ
diff --git a/approot/images/unknown-cover.svg b/approot/images/unknown-cover.svg
new file mode 100755
index 00000000..52ff34f6
--- /dev/null
+++ b/approot/images/unknown-cover.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/src/libs/image/CMakeLists.txt b/src/libs/image/CMakeLists.txt
index 32391b0f..f6228210 100644
--- a/src/libs/image/CMakeLists.txt
+++ b/src/libs/image/CMakeLists.txt
@@ -1,5 +1,6 @@
add_library(lmsimage SHARED
+ impl/SvgImage.cpp
)
target_include_directories(lmsimage INTERFACE
diff --git a/src/libs/image/impl/SvgImage.cpp b/src/libs/image/impl/SvgImage.cpp
new file mode 100644
index 00000000..433c58ad
--- /dev/null
+++ b/src/libs/image/impl/SvgImage.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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 .
+ */
+
+#include "SvgImage.hpp"
+
+#include
+#include "image/Exception.hpp"
+
+namespace lms::image
+{
+ std::unique_ptr readSvgFile(const std::filesystem::path& p)
+ {
+ if (p.extension() != ".svg")
+ throw Exception{ "Unexpected file extension: '" + p.extension().string() + "', expected .svg" };
+
+ std::ifstream ifs{ p.string(), std::ios::binary };
+ if (!ifs.is_open())
+ throw Exception{ "Cannot open file '" + p.string() + "' for reading purpose" };
+
+ std::vector data;
+ // read file content
+ ifs.seekg(0, std::ios::end);
+ std::streamsize size = ifs.tellg();
+ if (size < 0)
+ throw Exception{ "Cannot determine file size for '" + p.string() + "'" };
+
+ ifs.seekg(0, std::ios::beg);
+ data.resize(size);
+ if (!ifs.read(reinterpret_cast(data.data()), size))
+ throw Exception{ "Cannot read file content for '" + p.string() + "'" };
+
+ return std::make_unique(std::move(data));
+ }
+}
\ No newline at end of file
diff --git a/src/libs/image/impl/SvgImage.hpp b/src/libs/image/impl/SvgImage.hpp
new file mode 100644
index 00000000..ce54cb30
--- /dev/null
+++ b/src/libs/image/impl/SvgImage.hpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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 .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "image/IEncodedImage.hpp"
+
+namespace lms::image
+{
+ class SvgImage : public IEncodedImage
+ {
+ public:
+ SvgImage(std::vector&& data) : _data{ std::move(data) } {}
+
+ const std::byte* getData() const { return &_data.front(); }
+ std::size_t getDataSize() const { return _data.size(); }
+ std::string_view getMimeType() const { return "image/svg+xml"; }
+
+ private:
+ const std::vector _data;
+ };
+}
\ No newline at end of file
diff --git a/src/libs/image/include/image/Exception.hpp b/src/libs/image/include/image/Exception.hpp
index 1991be18..7e0d83ba 100644
--- a/src/libs/image/include/image/Exception.hpp
+++ b/src/libs/image/include/image/Exception.hpp
@@ -23,11 +23,9 @@
namespace lms::image
{
- class Exception : public core::LmsException
- {
- public:
- using LmsException::LmsException;
- };
-
+ class Exception : public core::LmsException
+ {
+ public:
+ using LmsException::LmsException;
+ };
} // namespace lms::cover
-
diff --git a/src/libs/image/include/image/IEncodedImage.hpp b/src/libs/image/include/image/IEncodedImage.hpp
index 0613f214..89685143 100644
--- a/src/libs/image/include/image/IEncodedImage.hpp
+++ b/src/libs/image/include/image/IEncodedImage.hpp
@@ -24,16 +24,15 @@
namespace lms::image
{
- using ImageSize = std::size_t;
+ using ImageSize = std::size_t;
- class IEncodedImage
- {
- public:
- virtual ~IEncodedImage() = default;
-
- virtual const std::byte* getData() const = 0;
- virtual std::size_t getDataSize() const = 0;
- virtual std::string_view getMimeType() const = 0;
- };
-}
+ class IEncodedImage
+ {
+ public:
+ virtual ~IEncodedImage() = default;
+ virtual const std::byte* getData() const = 0;
+ virtual std::size_t getDataSize() const = 0;
+ virtual std::string_view getMimeType() const = 0;
+ };
+}
\ No newline at end of file
diff --git a/src/libs/image/include/image/IRawImage.hpp b/src/libs/image/include/image/IRawImage.hpp
index e0949b2c..178da958 100644
--- a/src/libs/image/include/image/IRawImage.hpp
+++ b/src/libs/image/include/image/IRawImage.hpp
@@ -19,23 +19,16 @@
#pragma once
-#include
-#include
-
#include "image/IEncodedImage.hpp"
namespace lms::image
{
- class IRawImage
- {
- public:
- virtual ~IRawImage() = default;
- virtual void resize(ImageSize width) = 0;
- virtual std::unique_ptr encodeToJPEG(unsigned quality) const = 0;
- };
-
- void init(const std::filesystem::path& path);
- std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize);
- std::unique_ptr decodeImage(const std::filesystem::path& path);
+ class IRawImage
+ {
+ public:
+ virtual ~IRawImage() = default;
+ virtual void resize(ImageSize width) = 0;
+ virtual std::unique_ptr encodeToJPEG(unsigned quality) const = 0;
+ };
}
diff --git a/src/libs/image/include/image/Image.hpp b/src/libs/image/include/image/Image.hpp
new file mode 100644
index 00000000..a0ee4182
--- /dev/null
+++ b/src/libs/image/include/image/Image.hpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "image/IEncodedImage.hpp"
+#include "image/IRawImage.hpp"
+
+namespace lms::image
+{
+ void init(const std::filesystem::path& path);
+ std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize);
+ std::unique_ptr decodeImage(const std::filesystem::path& path);
+ std::unique_ptr readSvgFile(const std::filesystem::path& path);
+}
\ No newline at end of file
diff --git a/src/libs/services/cover/impl/CoverService.cpp b/src/libs/services/cover/impl/CoverService.cpp
index 49540e48..605e5a92 100644
--- a/src/libs/services/cover/impl/CoverService.cpp
+++ b/src/libs/services/cover/impl/CoverService.cpp
@@ -30,7 +30,7 @@
#include "database/Track.hpp"
#include "image/Exception.hpp"
-#include "image/IRawImage.hpp"
+#include "image/Image.hpp"
#include "core/IConfig.hpp"
#include "core/ILogger.hpp"
#include "core/Path.hpp"
@@ -107,18 +107,16 @@ namespace lms::cover
}
}
- std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath)
+ std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath)
{
- return std::make_unique(db, execPath, defaultCoverPath);
+ return std::make_unique(db, defaultSvgCoverPath);
}
using namespace image;
CoverService::CoverService(db::Db& db,
- const std::filesystem::path& execPath,
- const std::filesystem::path& defaultCoverPath)
+ const std::filesystem::path& defaultSvgCoverPath)
: _db{ db }
- , _defaultCoverPath{ defaultCoverPath }
, _maxCacheSize{ core::Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
, _maxFileSize{ core::Service::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 }
, _preferredFileNames{ constructPreferredFileNames() }
@@ -126,25 +124,12 @@ namespace lms::cover
{
setJpegQuality(core::Service::get()->getULong("cover-jpeg-quality", 75));
- LMS_LOG(COVER, INFO, "Default cover path = '" << _defaultCoverPath.string() << "'");
+ LMS_LOG(COVER, INFO, "Default cover path = '" << defaultSvgCoverPath.string() << "'");
LMS_LOG(COVER, INFO, "Max cache size = " << _maxCacheSize);
LMS_LOG(COVER, INFO, "Max file size = " << _maxFileSize);
LMS_LOG(COVER, INFO, "Preferred file names: " << core::stringUtils::joinStrings(_preferredFileNames, ","));
-#if LMS_SUPPORT_IMAGE_GM
- GraphicsMagick::init(execPath);
-#else
- (void)execPath;
-#endif
-
- try
- {
- getDefault(512);
- }
- catch (const image::Exception& e)
- {
- throw core::LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what());
- }
+ _defaultCover = image::readSvgFile(defaultSvgCoverPath); // may throw
}
std::unique_ptr CoverService::getFromAvMediaFile(const av::IAudioFile& input, ImageSize width) const
@@ -189,27 +174,9 @@ namespace lms::cover
return image;
}
- std::shared_ptr CoverService::getDefault(ImageSize width)
+ std::shared_ptr CoverService::getDefaultSvgCover()
{
- {
- std::shared_lock lock{ _cacheMutex };
-
- if (auto it{ _defaultCoverCache.find(width) }; it != std::cend(_defaultCoverCache))
- return it->second;
- }
-
- {
- std::unique_lock lock{ _cacheMutex };
-
- if (auto it{ _defaultCoverCache.find(width) }; it != std::cend(_defaultCoverCache))
- return it->second;
-
- std::shared_ptr image{ getFromCoverFile(_defaultCoverPath, width) };
- _defaultCoverCache[width] = image;
- LMS_LOG(COVER, DEBUG, "Default cache entries = " << _defaultCoverCache.size());
-
- return image;
- }
+ return _defaultCover;
}
std::unique_ptr CoverService::getFromDirectory(const std::filesystem::path& directory, ImageSize width, const std::vector& preferredFileNames, bool allowPickRandom) const
diff --git a/src/libs/services/cover/impl/CoverService.hpp b/src/libs/services/cover/impl/CoverService.hpp
index 30f24bad..40fffe4c 100644
--- a/src/libs/services/cover/impl/CoverService.hpp
+++ b/src/libs/services/cover/impl/CoverService.hpp
@@ -84,16 +84,16 @@ namespace lms::cover
class CoverService : public ICoverService
{
public:
- CoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
+ CoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath);
+ private:
CoverService(const CoverService&) = delete;
CoverService& operator=(const CoverService&) = delete;
- private:
std::shared_ptr getFromTrack(db::TrackId trackId, image::ImageSize width) override;
std::shared_ptr getFromRelease(db::ReleaseId releaseId, image::ImageSize width) override;
std::shared_ptr getFromArtist(db::ArtistId artistId, image::ImageSize width) override;
- std::shared_ptr getDefault(image::ImageSize width) override;
+ std::shared_ptr getDefaultSvgCover() override;
void flushCache() override;
void setJpegQuality(unsigned quality) override;
@@ -112,7 +112,7 @@ namespace lms::cover
std::shared_mutex _cacheMutex;
std::unordered_map> _cache;
- std::unordered_map> _defaultCoverCache;
+ std::shared_ptr _defaultCover;
std::atomic _cacheMisses{};
std::atomic _cacheHits{};
std::size_t _cacheSize{};
@@ -120,7 +120,6 @@ namespace lms::cover
void saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr image);
std::shared_ptr loadFromCache(const CacheEntryDesc& entryDesc);
- const std::filesystem::path _defaultCoverPath;
const std::size_t _maxCacheSize;
static inline const std::vector _fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; // TODO parametrize
const std::size_t _maxFileSize;
diff --git a/src/libs/services/cover/include/services/cover/ICoverService.hpp b/src/libs/services/cover/include/services/cover/ICoverService.hpp
index 4825cc2d..6c740d23 100644
--- a/src/libs/services/cover/include/services/cover/ICoverService.hpp
+++ b/src/libs/services/cover/include/services/cover/ICoverService.hpp
@@ -43,14 +43,14 @@ namespace lms::cover
virtual std::shared_ptr getFromRelease(db::ReleaseId releaseId, image::ImageSize width) = 0;
virtual std::shared_ptr getFromArtist(db::ArtistId artistId, image::ImageSize width) = 0;
- virtual std::shared_ptr getDefault(image::ImageSize width) = 0;
+ virtual std::shared_ptr getDefaultSvgCover() = 0;
virtual void flushCache() = 0;
virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100
};
- std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
+ std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath);
} // namespace lms::coverArt
diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp
index 2c729447..7d4ef6b2 100644
--- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp
+++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp
@@ -272,7 +272,7 @@ namespace lms::api::subsonic
cover = core::Service::get()->getFromArtist(*artistId, size);
if (!cover && context.enableDefaultCover && !artistId)
- cover = core::Service::get()->getDefault(size);
+ cover = core::Service::get()->getDefaultSvgCover();
if (!cover)
{
diff --git a/src/lms/main.cpp b/src/lms/main.cpp
index ed32dba8..4380005f 100644
--- a/src/lms/main.cpp
+++ b/src/lms/main.cpp
@@ -25,7 +25,7 @@
#include
#include
-#include "image/IRawImage.hpp"
+#include "image/Image.hpp"
#include "services/auth/IAuthTokenService.hpp"
#include "services/auth/IPasswordService.hpp"
#include "services/auth/IEnvService.hpp"
@@ -310,7 +310,7 @@ namespace lms
throw core::LmsException{ "Bad value '" + authenticationBackend + "' for 'authentication-backend'" };
image::init(argv[0]);
- core::Service coverService{ cover::createCoverService(database, argv[0], server.appRoot() + "/images/unknown-cover.jpg") };
+ core::Service coverService{ cover::createCoverService(database, server.appRoot() + "/images/unknown-cover.svg") };
core::Service recommendationService{ recommendation::createRecommendationService(database) };
core::Service playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) };
core::Service scannerService{ scanner::createScannerService(database) };
diff --git a/src/lms/ui/resource/CoverResource.cpp b/src/lms/ui/resource/CoverResource.cpp
index d0e7d0bb..6768a560 100644
--- a/src/lms/ui/resource/CoverResource.cpp
+++ b/src/lms/ui/resource/CoverResource.cpp
@@ -98,7 +98,7 @@ namespace lms::ui
cover = core::Service::get()->getFromTrack(*trackId, *size);
if (!cover)
- cover = core::Service::get()->getDefault(*size);
+ cover = core::Service::get()->getDefaultSvgCover();
}
else if (releaseIdStr)
{
@@ -110,7 +110,7 @@ namespace lms::ui
cover = core::Service::get()->getFromRelease(*releaseId, *size);
if (!cover)
- cover = core::Service::get()->getDefault(*size);
+ cover = core::Service::get()->getDefaultSvgCover();
}
else
{
diff --git a/src/tools/cover/LmsCover.cpp b/src/tools/cover/LmsCover.cpp
index 2867cacf..b7b22d1a 100644
--- a/src/tools/cover/LmsCover.cpp
+++ b/src/tools/cover/LmsCover.cpp
@@ -24,15 +24,16 @@
#include
-#include "database/Db.hpp"
-#include "database/Release.hpp"
-#include "database/Session.hpp"
-#include "database/Track.hpp"
-#include "services/cover/ICoverService.hpp"
#include "core/IConfig.hpp"
#include "core/ILogger.hpp"
#include "core/Service.hpp"
#include "core/StreamLogger.hpp"
+#include "database/Db.hpp"
+#include "database/Release.hpp"
+#include "database/Session.hpp"
+#include "database/Track.hpp"
+#include "image/Image.hpp"
+#include "services/cover/ICoverService.hpp"
namespace lms
{
@@ -83,9 +84,10 @@ int main(int argc, char* argv[])
return EXIT_SUCCESS;
}
+ image::init(argv[0]);
core::Service config{ core::createConfig(vm["conf"].as()) };
db::Db db{ config->getPath("working-dir") / "lms.db" };
- core::Service coverArtService{ cover::createCoverService(db, argv[0], vm["default-cover"].as()) };
+ core::Service coverArtService{ cover::createCoverService(db, vm["default-cover"].as()) };
coverArtService->setJpegQuality(config->getULong("cover-jpeg-quality", vm["quality"].as()));