Subsonic API: added timestamps into coverart ids, Made use of image ids to save a lookup, ref #558

This commit is contained in:
emeric
2024-12-08 15:48:52 +01:00
parent 38efdfaf87
commit 4a8754a7fa
18 changed files with 253 additions and 179 deletions
-3
View File
@@ -79,9 +79,6 @@ api-subsonic-support-user-password-auth = true;
# Main usage is to make auto detections for the 'p' (password) parameter work # Main usage is to make auto detections for the 'p' (password) parameter work
api-subsonic-old-server-protocol-clients = ("DSub"); api-subsonic-old-server-protocol-clients = ("DSub");
# List of clients for whom a default cover is served (as they do not have their own)
api-subsonic-default-cover-clients = ("DSub", "substreamer");
# List of clients for whom open subsonic extensions and extra fields are disabled # List of clients for whom open subsonic extensions and extra fields are disabled
api-open-subsonic-disabled-clients = ("DSub"); api-open-subsonic-disabled-clients = ("DSub");
@@ -192,6 +192,31 @@ namespace lms::cover
return image; return image;
} }
std::shared_ptr<image::IEncodedImage> ArtworkService::getImage(db::ImageId imageId, std::optional<image::ImageSize> width)
{
const ImageCache::EntryDesc cacheEntryDesc{ imageId, width };
std::shared_ptr<image::IEncodedImage> cover{ _cache.getImage(cacheEntryDesc) };
if (cover)
return cover;
std::filesystem::path imageFile;
{
db::Session& session{ _db.getTLSSession() };
auto transaction{ session.createReadTransaction() };
const db::Image::pointer image{ db::Image::find(session, imageId) };
if (image)
imageFile = image->getAbsoluteFilePath();
}
cover = getFromImageFile(imageFile, width);
if (cover)
_cache.addImage(cacheEntryDesc, cover);
return cover;
}
std::shared_ptr<image::IEncodedImage> ArtworkService::getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width) std::shared_ptr<image::IEncodedImage> ArtworkService::getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width)
{ {
const ImageCache::EntryDesc cacheEntryDesc{ trackId, width }; const ImageCache::EntryDesc cacheEntryDesc{ trackId, width };
@@ -217,62 +242,6 @@ namespace lms::cover
return cover; return cover;
} }
std::shared_ptr<image::IEncodedImage> ArtworkService::getReleaseCover(db::ReleaseId releaseId, std::optional<image::ImageSize> width)
{
const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width };
std::shared_ptr<image::IEncodedImage> image{ _cache.getImage(cacheEntryDesc) };
if (image)
return image;
std::filesystem::path imagePath;
{
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() })
imagePath = dbImage->getAbsoluteFilePath();
}
}
image = getFromImageFile(imagePath, width);
if (image)
_cache.addImage(cacheEntryDesc, image);
return image;
}
std::shared_ptr<image::IEncodedImage> ArtworkService::getArtistImage(db::ArtistId artistId, std::optional<image::ImageSize> width)
{
const ImageCache::EntryDesc cacheEntryDesc{ artistId, width };
std::shared_ptr<image::IEncodedImage> artistImage{ _cache.getImage(cacheEntryDesc) };
if (artistImage)
return artistImage;
std::filesystem::path imagePath;
{
db::Session& session{ _db.getTLSSession() };
auto transaction{ session.createReadTransaction() };
if (const db::Artist::pointer artist{ db::Artist::find(session, artistId) })
{
if (const db::Image::pointer image{ artist->getImage() })
imagePath = image->getAbsoluteFilePath();
}
}
artistImage = getFromImageFile(imagePath, width);
if (artistImage)
_cache.addImage(cacheEntryDesc, artistImage);
return artistImage;
}
void ArtworkService::flushCache() void ArtworkService::flushCache()
{ {
_cache.flush(); _cache.flush();
@@ -48,9 +48,8 @@ namespace lms::cover
ArtworkService& operator=(const ArtworkService&) = delete; ArtworkService& operator=(const ArtworkService&) = delete;
private: private:
std::shared_ptr<image::IEncodedImage> getImage(db::ImageId imageId, std::optional<image::ImageSize> width) override;
std::shared_ptr<image::IEncodedImage> getTrackImage(db::TrackId trackId, std::optional<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> getDefaultReleaseCover() override;
std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() override; std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() override;
@@ -25,8 +25,7 @@
#include <unordered_map> #include <unordered_map>
#include <variant> #include <variant>
#include "database/ArtistId.hpp" #include "database/ImageId.hpp"
#include "database/ReleaseId.hpp"
#include "database/TrackId.hpp" #include "database/TrackId.hpp"
#include "image/IEncodedImage.hpp" #include "image/IEncodedImage.hpp"
@@ -39,7 +38,7 @@ namespace lms::cover
struct EntryDesc struct EntryDesc
{ {
using VariantType = std::variant<db::ArtistId, db::ReleaseId, db::TrackId>; using VariantType = std::variant<db::TrackId, db::ImageId>;
VariantType id; VariantType id;
std::optional<std::size_t> size; std::optional<std::size_t> size;
@@ -23,8 +23,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include "database/ArtistId.hpp" #include "database/ImageId.hpp"
#include "database/ReleaseId.hpp"
#include "database/TrackId.hpp" #include "database/TrackId.hpp"
#include "image/IEncodedImage.hpp" #include "image/IEncodedImage.hpp"
@@ -40,14 +39,11 @@ namespace lms::cover
public: public:
virtual ~IArtworkService() = default; virtual ~IArtworkService() = default;
virtual std::shared_ptr<image::IEncodedImage> getArtistImage(db::ArtistId artistId, std::optional<image::ImageSize> width) = 0; virtual std::shared_ptr<image::IEncodedImage> getImage(db::ImageId imageId, std::optional<image::ImageSize> width) = 0;
// no logic to fallback to release here // no logic to fallback to release here
virtual std::shared_ptr<image::IEncodedImage> getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width) = 0; virtual std::shared_ptr<image::IEncodedImage> getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width) = 0;
// no logic to fallback to track here
virtual std::shared_ptr<image::IEncodedImage> getReleaseCover(db::ReleaseId releaseId, std::optional<image::ImageSize> width) = 0;
// Svg images dont have image "size" // Svg images dont have image "size"
virtual std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() = 0; virtual std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() = 0;
virtual std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() = 0; virtual std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() = 0;
+1
View File
@@ -25,6 +25,7 @@ add_library(lmssubsonic SHARED
impl/responses/ReplayGain.cpp impl/responses/ReplayGain.cpp
impl/responses/Song.cpp impl/responses/Song.cpp
impl/responses/User.cpp impl/responses/User.cpp
impl/CoverArtId.cpp
impl/ResponseFormat.cpp impl/ResponseFormat.cpp
impl/ProtocolVersion.cpp impl/ProtocolVersion.cpp
impl/ParameterParsing.cpp impl/ParameterParsing.cpp
+97
View File
@@ -0,0 +1,97 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "CoverArtId.hpp"
#include "SubsonicId.hpp"
#include "core/String.hpp"
namespace lms::api::subsonic
{
namespace
{
constexpr char timestampSeparatorChar{ ':' };
}
std::string idToString(db::ImageId id)
{
return "im-" + id.toString();
}
std::string idToString(CoverArtId coverId)
{
// produce "id:timestamp"
std::string res{ std::visit([](auto&& id) {
return idToString(id);
},
coverId.id) };
res += timestampSeparatorChar;
res += std::to_string(coverId.timestamp);
return res;
}
} // namespace lms::api::subsonic
// Used to parse parameters
namespace lms::core::stringUtils
{
template<>
std::optional<db::ImageId> readAs(std::string_view str)
{
std::vector<std::string_view> values{ core::stringUtils::splitString(str, '-') };
if (values.size() != 2)
return std::nullopt;
if (values[0] != "im")
return std::nullopt;
if (const auto value{ core::stringUtils::readAs<db::ReleaseId::ValueType>(values[1]) })
return db::ImageId{ *value };
return std::nullopt;
}
template<>
std::optional<api::subsonic::CoverArtId> readAs(std::string_view str)
{
// expect "id:timestamp"
auto timeStampSeparator{ str.find_last_of(api::subsonic::timestampSeparatorChar) };
if (timeStampSeparator == std::string_view::npos)
return std::nullopt;
std::string_view strId{ str.substr(0, timeStampSeparator) };
std::string_view strTimestamp{ str.substr(timeStampSeparator + 1) };
api::subsonic::CoverArtId cover;
if (const auto imagetId{ readAs<db::ImageId>(strId) })
cover.id = *imagetId;
else if (const auto trackId{ readAs<db::TrackId>(strId) })
cover.id = *trackId;
else
return std::nullopt;
if (const auto timestamp{ readAs<std::time_t>(strTimestamp) })
cover.timestamp = *timestamp;
else
return std::nullopt;
return cover;
}
} // namespace lms::core::stringUtils
+49
View File
@@ -0,0 +1,49 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <ctime>
#include <variant>
#include "core/String.hpp"
#include "database/ImageId.hpp"
#include "database/TrackId.hpp"
namespace lms::api::subsonic
{
struct CoverArtId
{
std::variant<db::ImageId, db::TrackId> id;
std::time_t timestamp;
};
std::string idToString(CoverArtId coverId);
std::string idToString(db::ImageId imageId);
} // namespace lms::api::subsonic
// Used to parse parameters
namespace lms::core::stringUtils
{
template<>
std::optional<db::ImageId> readAs(std::string_view str);
template<>
std::optional<api::subsonic::CoverArtId> readAs(std::string_view str);
} // namespace lms::core::stringUtils
@@ -47,6 +47,5 @@ namespace lms::api::subsonic
ProtocolVersion serverProtocolVersion; ProtocolVersion serverProtocolVersion;
ResponseFormat responseFormat; ResponseFormat responseFormat;
bool enableOpenSubsonic{ true }; bool enableOpenSubsonic{ true };
bool enableDefaultCover{};
}; };
} // namespace lms::api::subsonic } // namespace lms::api::subsonic
@@ -85,19 +85,6 @@ namespace lms::api::subsonic
return res; return res;
} }
std::unordered_set<std::string> readDefaultCoverClients()
{
std::unordered_set<std::string> res;
core::Service<core::IConfig>::get()->visitStrings("api-subsonic-default-cover-clients",
[&](std::string_view client) {
res.emplace(std::string{ client });
},
{ "DSub", "substreamer" });
return res;
}
std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap) std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
{ {
auto censorValue = [](const std::string& type, const std::string& value) -> std::string { auto censorValue = [](const std::string& type, const std::string& value) -> std::string {
@@ -307,7 +294,6 @@ namespace lms::api::subsonic
SubsonicResource::SubsonicResource(db::Db& db) SubsonicResource::SubsonicResource(db::Db& db)
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() } : _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() } , _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
, _defaultReleaseCoverClients{ readDefaultCoverClients() }
, _supportUserPasswordAuthentication{ core::Service<core::IConfig>::get()->getBool("api-subsonic-support-user-password-auth", true) } , _supportUserPasswordAuthentication{ core::Service<core::IConfig>::get()->getBool("api-subsonic-support-user-password-auth", true) }
, _db{ db } , _db{ db }
{ {
@@ -425,7 +411,6 @@ namespace lms::api::subsonic
const Wt::Http::ParameterMap& parameters{ request.getParameterMap() }; const Wt::Http::ParameterMap& parameters{ request.getParameterMap() };
const ClientInfo clientInfo{ getClientInfo(request) }; const ClientInfo clientInfo{ getClientInfo(request) };
bool enableOpenSubsonic{ !_openSubsonicDisabledClients.contains(clientInfo.name) }; bool enableOpenSubsonic{ !_openSubsonicDisabledClients.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 };
return RequestContext{ return RequestContext{
@@ -437,7 +422,6 @@ namespace lms::api::subsonic
.serverProtocolVersion = getServerProtocolVersion(clientInfo.name), .serverProtocolVersion = getServerProtocolVersion(clientInfo.name),
.responseFormat = format, .responseFormat = format,
.enableOpenSubsonic = enableOpenSubsonic, .enableOpenSubsonic = enableOpenSubsonic,
.enableDefaultCover = enableDefaultCover
}; };
} }
@@ -51,7 +51,6 @@ 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> _defaultReleaseCoverClients;
const bool _supportUserPasswordAuthentication; const bool _supportUserPasswordAuthentication;
db::Db& _db; db::Db& _db;
@@ -24,7 +24,6 @@
#include "av/TranscodingParameters.hpp" #include "av/TranscodingParameters.hpp"
#include "av/TranscodingResourceHandlerCreator.hpp" #include "av/TranscodingResourceHandlerCreator.hpp"
#include "av/Types.hpp" #include "av/Types.hpp"
#include "core/FileResourceHandlerCreator.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
#include "core/IResourceHandler.hpp" #include "core/IResourceHandler.hpp"
#include "core/String.hpp" #include "core/String.hpp"
@@ -35,6 +34,7 @@
#include "database/User.hpp" #include "database/User.hpp"
#include "services/artwork/IArtworkService.hpp" #include "services/artwork/IArtworkService.hpp"
#include "CoverArtId.hpp"
#include "ParameterParsing.hpp" #include "ParameterParsing.hpp"
#include "RequestContext.hpp" #include "RequestContext.hpp"
#include "SubsonicId.hpp" #include "SubsonicId.hpp"
@@ -326,36 +326,26 @@ namespace lms::api::subsonic
void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response) void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
{ {
// Mandatory params // Mandatory params
const auto trackId{ getParameterAs<TrackId>(context.parameters, "id") }; const CoverArtId coverArtId{ getMandatoryParameterAs<CoverArtId>(context.parameters, "id") };
const auto releaseId{ getParameterAs<ReleaseId>(context.parameters, "id") };
const auto artistId{ getParameterAs<ArtistId>(context.parameters, "id") };
if (!trackId && !releaseId && !artistId)
throw BadParameterGenericError{ "id" };
std::optional<std::size_t> size{ getParameterAs<std::size_t>(context.parameters, "size") }; std::optional<std::size_t> size{ getParameterAs<std::size_t>(context.parameters, "size") };
if (size) if (size)
*size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 }); *size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 });
std::shared_ptr<image::IEncodedImage> cover; std::shared_ptr<image::IEncodedImage> image;
if (trackId) if (const db::TrackId * trackId{ std::get_if<db::TrackId>(&coverArtId.id) })
cover = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size); image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
else if (releaseId) else if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&coverArtId.id) })
cover = core::Service<cover::IArtworkService>::get()->getReleaseCover(*releaseId, size); image = core::Service<cover::IArtworkService>::get()->getImage(*imageId, size);
else if (artistId)
cover = core::Service<cover::IArtworkService>::get()->getArtistImage(*artistId, size);
if (!cover && context.enableDefaultCover && !artistId) if (!image)
cover = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
if (!cover)
{ {
response.setStatus(404); response.setStatus(404);
return; return;
} }
response.out().write(reinterpret_cast<const char*>(cover->getData().data()), cover->getData().size()); response.out().write(reinterpret_cast<const char*>(image->getData().data()), image->getData().size());
response.setMimeType(std::string{ cover->getMimeType() }); response.setMimeType(std::string{ image->getMimeType() });
} }
} // namespace lms::api::subsonic } // namespace lms::api::subsonic
+6 -3
View File
@@ -32,6 +32,7 @@
#include "services/feedback/IFeedbackService.hpp" #include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp" #include "services/scrobbling/IScrobblingService.hpp"
#include "CoverArtId.hpp"
#include "RequestContext.hpp" #include "RequestContext.hpp"
#include "SubsonicId.hpp" #include "SubsonicId.hpp"
#include "responses/Artist.hpp" #include "responses/Artist.hpp"
@@ -84,9 +85,10 @@ namespace lms::api::subsonic
} }
albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getLastWritten())); albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getLastWritten()));
if (release->getImage()) if (const auto image{ release->getImage() })
{ {
albumNode.setAttribute("coverArt", idToString(release->getId())); const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() };
albumNode.setAttribute("coverArt", idToString(coverArtId));
} }
else else
{ {
@@ -96,7 +98,8 @@ namespace lms::api::subsonic
params.setRange(db::Range{ 0, 1 }); params.setRange(db::Range{ 0, 1 });
db::Track::find(context.dbSession, params, [&](const db::Track::pointer& track) { db::Track::find(context.dbSession, params, [&](const db::Track::pointer& track) {
albumNode.setAttribute("coverArt", idToString(track->getId())); const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };
albumNode.setAttribute("coverArt", idToString(coverArtId));
}); });
} }
if (const auto year{ release->getYear() }) if (const auto year{ release->getYear() })
+6 -2
View File
@@ -29,6 +29,7 @@
#include "database/User.hpp" #include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp" #include "services/feedback/IFeedbackService.hpp"
#include "CoverArtId.hpp"
#include "RequestContext.hpp" #include "RequestContext.hpp"
#include "SubsonicId.hpp" #include "SubsonicId.hpp"
@@ -94,8 +95,11 @@ namespace lms::api::subsonic
artistNode.setAttribute("id", idToString(artist->getId())); artistNode.setAttribute("id", idToString(artist->getId()));
artistNode.setAttribute("name", artist->getName()); artistNode.setAttribute("name", artist->getName());
if (artist->getImage()) if (const auto image{ artist->getImage() })
artistNode.setAttribute("coverArt", idToString(artist->getId())); {
const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() };
artistNode.setAttribute("coverArt", idToString(coverArtId));
}
const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) }; const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) };
artistNode.setAttribute("albumCount", count); artistNode.setAttribute("albumCount", count);
@@ -23,6 +23,7 @@
#include "database/TrackList.hpp" #include "database/TrackList.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "CoverArtId.hpp"
#include "SubsonicId.hpp" #include "SubsonicId.hpp"
namespace lms::api::subsonic namespace lms::api::subsonic
@@ -50,7 +51,8 @@ namespace lms::api::subsonic
params.setSortMethod(TrackSortMethod::TrackList); params.setSortMethod(TrackSortMethod::TrackList);
db::Track::find(session, params, [&](const db::Track::pointer& track) { db::Track::find(session, params, [&](const db::Track::pointer& track) {
playlistNode.setAttribute("coverArt", idToString(track->getId())); const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };
playlistNode.setAttribute("coverArt", idToString(coverArtId));
}); });
return playlistNode; return playlistNode;
+13 -3
View File
@@ -36,6 +36,7 @@
#include "services/feedback/IFeedbackService.hpp" #include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp" #include "services/scrobbling/IScrobblingService.hpp"
#include "CoverArtId.hpp"
#include "RequestContext.hpp" #include "RequestContext.hpp"
#include "SubsonicId.hpp" #include "SubsonicId.hpp"
#include "responses/Artist.hpp" #include "responses/Artist.hpp"
@@ -109,9 +110,18 @@ namespace lms::api::subsonic
const Release::pointer release{ track->getRelease() }; const Release::pointer release{ track->getRelease() };
if (track->hasCover()) if (track->hasCover())
trackResponse.setAttribute("coverArt", idToString(track->getId())); {
else if (release && release->getImage()) const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };
trackResponse.setAttribute("coverArt", idToString(release->getId())); trackResponse.setAttribute("coverArt", idToString(coverArtId));
}
else if (release)
{
if (const db::Image::pointer image{ release->getImage() })
{
const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() };
trackResponse.setAttribute("coverArt", idToString(coverArtId));
}
}
const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) };
if (!artists.empty()) if (!artists.empty())
+36 -60
View File
@@ -60,8 +60,11 @@ namespace lms::ui
auto transaction{ LmsApp->getDbSession().createReadTransaction() }; auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const db::Artist::pointer artist{ db::Artist::find(LmsApp->getDbSession(), artistId) }; const db::Artist::pointer artist{ db::Artist::find(LmsApp->getDbSession(), artistId) };
if (artist && artist->getImage()) if (artist)
url = getArtistIdImageUrl(artistId, size); {
if (const db::Image::pointer image{ artist->getImage() })
url = getImageUrl(image->getId(), size, "artist");
}
} }
if (url.empty()) if (url.empty())
@@ -80,9 +83,9 @@ namespace lms::ui
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) }; const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) };
if (release) if (release)
{ {
if (release->getImage()) if (const db::Image::pointer image{ release->getImage() })
{ {
url = getReleaseIdCoverUrl(release->getId(), size); url = getImageUrl(image->getId(), size, "release");
} }
else else
{ {
@@ -92,7 +95,7 @@ namespace lms::ui
params.setRange(db::Range{ 0, 1 }); params.setRange(db::Range{ 0, 1 });
db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track) { db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track) {
url = getTrackIdImageUrl(track->getId(), size); url = getImageUrl(track->getId(), size, "release");
}); });
} }
} }
@@ -115,9 +118,14 @@ namespace lms::ui
if (track) if (track)
{ {
if (track->hasCover()) if (track->hasCover())
url = getTrackIdImageUrl(trackId, size); {
else if (const db::Release::pointer release{ track->getRelease() }; release && release->getImage()) url = getImageUrl(trackId, size, "release");
url = getReleaseIdCoverUrl(release->getId(), size); }
else if (const db::Release::pointer release{ track->getRelease() })
{
if (const db::Image::pointer image{ release->getImage() })
url = getImageUrl(image->getId(), size, "release");
}
} }
} }
@@ -127,29 +135,24 @@ namespace lms::ui
return url; return url;
} }
std::string ArtworkResource::getArtistIdImageUrl(db::ArtistId artistId, Size size) const std::string ArtworkResource::getImageUrl(db::ImageId imageId, Size size, std::string_view type) const
{ {
return url() + "&artistid=" + artistId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size)); return url() + "&imageid=" + imageId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size)) + "&type=" + std::string{ type };
} }
std::string ArtworkResource::getReleaseIdCoverUrl(db::ReleaseId releaseId, Size size) const std::string ArtworkResource::getImageUrl(db::TrackId trackId, Size size, std::string_view type) const
{ {
return url() + "&releaseid=" + releaseId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size)); return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size)) + "&type=" + std::string{ type };
}
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 std::string ArtworkResource::getDefaultArtistImageUrl() const
{ {
return url() + "&type=defaultartistimage"; return url() + "&type=artist";
} }
std::string ArtworkResource::getDefaultReleaseCoverUrl() const std::string ArtworkResource::getDefaultReleaseCoverUrl() const
{ {
return url() + "&type=defaultreleasecover"; return url() + "&type=release";
} }
void ArtworkResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) void ArtworkResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
@@ -157,58 +160,32 @@ namespace lms::ui
LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest"); LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest");
// Retrieve parameters // Retrieve parameters
const std::string* artistIdStr = request.getParameter("artistid"); const std::string* imageIdStr = request.getParameter("imageid");
const std::string* trackIdStr = request.getParameter("trackid"); const std::string* trackIdStr = request.getParameter("trackid");
const std::string* releaseIdStr = request.getParameter("releaseid");
const std::string* sizeStr = request.getParameter("size"); const std::string* sizeStr = request.getParameter("size");
const std::string* typeStr = request.getParameter("type"); const std::string* typeStr = request.getParameter("type");
std::shared_ptr<image::IEncodedImage> image; std::shared_ptr<image::IEncodedImage> image;
// Mandatory parameter size if ((imageIdStr || trackIdStr))
if ((artistIdStr || trackIdStr || releaseIdStr))
{ {
if (!sizeStr) const auto size{ sizeStr ? core::stringUtils::readAs<std::size_t>(*sizeStr) : std::nullopt };
{ if (size && *size > maxSize)
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!"); LOG(DEBUG, "invalid size provided!");
return; return;
} }
if (artistIdStr) if (imageIdStr)
{ {
LOG(DEBUG, "Requested cover for track " << *artistIdStr << ", size = " << *size); const std::optional<db::ImageId> imageId{ core::stringUtils::readAs<db::ImageId::ValueType>(*imageIdStr) };
if (!imageId)
const std::optional<db::ArtistId> artistId{ core::stringUtils::readAs<db::ArtistId::ValueType>(*artistIdStr) };
if (!artistId)
return; return;
image = core::Service<cover::IArtworkService>::get()->getArtistImage(*artistId, *size); image = core::Service<cover::IArtworkService>::get()->getImage(*imageId, 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) 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) }; const std::optional<db::TrackId> trackId{ core::stringUtils::readAs<db::TrackId::ValueType>(*trackIdStr) };
if (!trackId) if (!trackId)
{ {
@@ -216,16 +193,15 @@ namespace lms::ui
return; return;
} }
image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, *size); image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
}
}
if (!image) if (!image)
image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
}
}
else if (typeStr)
{ {
if (*typeStr == "defaultreleasecover") if (*typeStr == "release")
image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover(); image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
else if (*typeStr == "defaultartistimage") else if (*typeStr == "artist")
image = core::Service<cover::IArtworkService>::get()->getDefaultArtistImage(); image = core::Service<cover::IArtworkService>::get()->getDefaultArtistImage();
} }
+3 -3
View File
@@ -22,6 +22,7 @@
#include <Wt/WResource.h> #include <Wt/WResource.h>
#include "database/ArtistId.hpp" #include "database/ArtistId.hpp"
#include "database/ImageId.hpp"
#include "database/ReleaseId.hpp" #include "database/ReleaseId.hpp"
#include "database/TrackId.hpp" #include "database/TrackId.hpp"
@@ -46,9 +47,8 @@ namespace lms::ui
std::string getTrackImageUrl(db::TrackId trackId, 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 getImageUrl(db::ImageId imageId, Size size, std::string_view type) const;
std::string getReleaseIdCoverUrl(db::ReleaseId releaseId, Size size) const; std::string getImageUrl(db::TrackId trackId, Size size, std::string_view type) const;
std::string getTrackIdImageUrl(db::TrackId trackId, Size size) const;
std::string getDefaultArtistImageUrl() const; std::string getDefaultArtistImageUrl() const;
std::string getDefaultReleaseCoverUrl() const; std::string getDefaultReleaseCoverUrl() const;