Subsonic API: added timestamps into coverart ids, Made use of image ids to save a lookup, ref #558
This commit is contained in:
@@ -79,9 +79,6 @@ api-subsonic-support-user-password-auth = true;
|
||||
# Main usage is to make auto detections for the 'p' (password) parameter work
|
||||
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
|
||||
api-open-subsonic-disabled-clients = ("DSub");
|
||||
|
||||
|
||||
@@ -192,6 +192,31 @@ namespace lms::cover
|
||||
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)
|
||||
{
|
||||
const ImageCache::EntryDesc cacheEntryDesc{ trackId, width };
|
||||
@@ -217,62 +242,6 @@ namespace lms::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()
|
||||
{
|
||||
_cache.flush();
|
||||
|
||||
@@ -48,9 +48,8 @@ namespace lms::cover
|
||||
ArtworkService& operator=(const ArtworkService&) = delete;
|
||||
|
||||
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> 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;
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "image/IEncodedImage.hpp"
|
||||
|
||||
@@ -39,7 +38,7 @@ namespace lms::cover
|
||||
|
||||
struct EntryDesc
|
||||
{
|
||||
using VariantType = std::variant<db::ArtistId, db::ReleaseId, db::TrackId>;
|
||||
using VariantType = std::variant<db::TrackId, db::ImageId>;
|
||||
VariantType id;
|
||||
std::optional<std::size_t> size;
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "image/IEncodedImage.hpp"
|
||||
|
||||
@@ -40,14 +39,11 @@ namespace lms::cover
|
||||
public:
|
||||
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
|
||||
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"
|
||||
virtual std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() = 0;
|
||||
virtual std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() = 0;
|
||||
|
||||
@@ -25,6 +25,7 @@ add_library(lmssubsonic SHARED
|
||||
impl/responses/ReplayGain.cpp
|
||||
impl/responses/Song.cpp
|
||||
impl/responses/User.cpp
|
||||
impl/CoverArtId.cpp
|
||||
impl/ResponseFormat.cpp
|
||||
impl/ProtocolVersion.cpp
|
||||
impl/ParameterParsing.cpp
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
ResponseFormat responseFormat;
|
||||
bool enableOpenSubsonic{ true };
|
||||
bool enableDefaultCover{};
|
||||
};
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -85,19 +85,6 @@ namespace lms::api::subsonic
|
||||
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)
|
||||
{
|
||||
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)
|
||||
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
|
||||
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
|
||||
, _defaultReleaseCoverClients{ readDefaultCoverClients() }
|
||||
, _supportUserPasswordAuthentication{ core::Service<core::IConfig>::get()->getBool("api-subsonic-support-user-password-auth", true) }
|
||||
, _db{ db }
|
||||
{
|
||||
@@ -425,7 +411,6 @@ namespace lms::api::subsonic
|
||||
const Wt::Http::ParameterMap& parameters{ request.getParameterMap() };
|
||||
const ClientInfo clientInfo{ getClientInfo(request) };
|
||||
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 };
|
||||
|
||||
return RequestContext{
|
||||
@@ -437,7 +422,6 @@ namespace lms::api::subsonic
|
||||
.serverProtocolVersion = getServerProtocolVersion(clientInfo.name),
|
||||
.responseFormat = format,
|
||||
.enableOpenSubsonic = enableOpenSubsonic,
|
||||
.enableDefaultCover = enableDefaultCover
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ namespace lms::api::subsonic
|
||||
|
||||
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
|
||||
const std::unordered_set<std::string> _openSubsonicDisabledClients;
|
||||
const std::unordered_set<std::string> _defaultReleaseCoverClients;
|
||||
const bool _supportUserPasswordAuthentication;
|
||||
|
||||
db::Db& _db;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "av/TranscodingParameters.hpp"
|
||||
#include "av/TranscodingResourceHandlerCreator.hpp"
|
||||
#include "av/Types.hpp"
|
||||
#include "core/FileResourceHandlerCreator.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
#include "core/String.hpp"
|
||||
@@ -35,6 +34,7 @@
|
||||
#include "database/User.hpp"
|
||||
#include "services/artwork/IArtworkService.hpp"
|
||||
|
||||
#include "CoverArtId.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "RequestContext.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)
|
||||
{
|
||||
// Mandatory params
|
||||
const auto trackId{ getParameterAs<TrackId>(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" };
|
||||
const CoverArtId coverArtId{ getMandatoryParameterAs<CoverArtId>(context.parameters, "id") };
|
||||
|
||||
std::optional<std::size_t> size{ getParameterAs<std::size_t>(context.parameters, "size") };
|
||||
if (size)
|
||||
*size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 });
|
||||
|
||||
std::shared_ptr<image::IEncodedImage> cover;
|
||||
if (trackId)
|
||||
cover = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
|
||||
else if (releaseId)
|
||||
cover = core::Service<cover::IArtworkService>::get()->getReleaseCover(*releaseId, size);
|
||||
else if (artistId)
|
||||
cover = core::Service<cover::IArtworkService>::get()->getArtistImage(*artistId, size);
|
||||
std::shared_ptr<image::IEncodedImage> image;
|
||||
if (const db::TrackId * trackId{ std::get_if<db::TrackId>(&coverArtId.id) })
|
||||
image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
|
||||
else if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&coverArtId.id) })
|
||||
image = core::Service<cover::IArtworkService>::get()->getImage(*imageId, size);
|
||||
|
||||
if (!cover && context.enableDefaultCover && !artistId)
|
||||
cover = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
|
||||
|
||||
if (!cover)
|
||||
if (!image)
|
||||
{
|
||||
response.setStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
response.out().write(reinterpret_cast<const char*>(cover->getData().data()), cover->getData().size());
|
||||
response.setMimeType(std::string{ cover->getMimeType() });
|
||||
response.out().write(reinterpret_cast<const char*>(image->getData().data()), image->getData().size());
|
||||
response.setMimeType(std::string{ image->getMimeType() });
|
||||
}
|
||||
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
|
||||
#include "CoverArtId.hpp"
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
@@ -84,9 +85,10 @@ namespace lms::api::subsonic
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
@@ -96,7 +98,8 @@ namespace lms::api::subsonic
|
||||
params.setRange(db::Range{ 0, 1 });
|
||||
|
||||
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() })
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
|
||||
#include "CoverArtId.hpp"
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
@@ -94,8 +95,11 @@ namespace lms::api::subsonic
|
||||
|
||||
artistNode.setAttribute("id", idToString(artist->getId()));
|
||||
artistNode.setAttribute("name", artist->getName());
|
||||
if (artist->getImage())
|
||||
artistNode.setAttribute("coverArt", idToString(artist->getId()));
|
||||
if (const auto image{ artist->getImage() })
|
||||
{
|
||||
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())) };
|
||||
artistNode.setAttribute("albumCount", count);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "CoverArtId.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
@@ -50,7 +51,8 @@ namespace lms::api::subsonic
|
||||
params.setSortMethod(TrackSortMethod::TrackList);
|
||||
|
||||
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;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
|
||||
#include "CoverArtId.hpp"
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
@@ -109,9 +110,18 @@ namespace lms::api::subsonic
|
||||
const Release::pointer release{ track->getRelease() };
|
||||
|
||||
if (track->hasCover())
|
||||
trackResponse.setAttribute("coverArt", idToString(track->getId()));
|
||||
else if (release && release->getImage())
|
||||
trackResponse.setAttribute("coverArt", idToString(release->getId()));
|
||||
{
|
||||
const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };
|
||||
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 }) };
|
||||
if (!artists.empty())
|
||||
|
||||
@@ -60,8 +60,11 @@ namespace lms::ui
|
||||
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 (artist)
|
||||
{
|
||||
if (const db::Image::pointer image{ artist->getImage() })
|
||||
url = getImageUrl(image->getId(), size, "artist");
|
||||
}
|
||||
}
|
||||
|
||||
if (url.empty())
|
||||
@@ -80,9 +83,9 @@ namespace lms::ui
|
||||
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) };
|
||||
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
|
||||
{
|
||||
@@ -92,7 +95,7 @@ namespace lms::ui
|
||||
params.setRange(db::Range{ 0, 1 });
|
||||
|
||||
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->hasCover())
|
||||
url = getTrackIdImageUrl(trackId, size);
|
||||
else if (const db::Release::pointer release{ track->getRelease() }; release && release->getImage())
|
||||
url = getReleaseIdCoverUrl(release->getId(), size);
|
||||
{
|
||||
url = getImageUrl(trackId, size, "release");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size)) + "&type=" + std::string{ type };
|
||||
}
|
||||
|
||||
std::string ArtworkResource::getDefaultArtistImageUrl() const
|
||||
{
|
||||
return url() + "&type=defaultartistimage";
|
||||
return url() + "&type=artist";
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -157,58 +160,32 @@ namespace lms::ui
|
||||
LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest");
|
||||
|
||||
// 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* 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 ((imageIdStr || trackIdStr))
|
||||
{
|
||||
if (!sizeStr)
|
||||
{
|
||||
LOG(DEBUG, "no size provided!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size{ core::stringUtils::readAs<std::size_t>(*sizeStr) };
|
||||
if (!size || *size > maxSize)
|
||||
const auto size{ sizeStr ? core::stringUtils::readAs<std::size_t>(*sizeStr) : std::nullopt };
|
||||
if (size && *size > maxSize)
|
||||
{
|
||||
LOG(DEBUG, "invalid size provided!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (artistIdStr)
|
||||
if (imageIdStr)
|
||||
{
|
||||
LOG(DEBUG, "Requested cover for track " << *artistIdStr << ", size = " << *size);
|
||||
|
||||
const std::optional<db::ArtistId> artistId{ core::stringUtils::readAs<db::ArtistId::ValueType>(*artistIdStr) };
|
||||
if (!artistId)
|
||||
const std::optional<db::ImageId> imageId{ core::stringUtils::readAs<db::ImageId::ValueType>(*imageIdStr) };
|
||||
if (!imageId)
|
||||
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();
|
||||
image = core::Service<cover::IArtworkService>::get()->getImage(*imageId, size);
|
||||
}
|
||||
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)
|
||||
{
|
||||
@@ -216,16 +193,15 @@ namespace lms::ui
|
||||
return;
|
||||
}
|
||||
|
||||
image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, *size);
|
||||
if (!image)
|
||||
image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
|
||||
image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
|
||||
}
|
||||
}
|
||||
else if (typeStr)
|
||||
|
||||
if (!image)
|
||||
{
|
||||
if (*typeStr == "defaultreleasecover")
|
||||
if (*typeStr == "release")
|
||||
image = core::Service<cover::IArtworkService>::get()->getDefaultReleaseCover();
|
||||
else if (*typeStr == "defaultartistimage")
|
||||
else if (*typeStr == "artist")
|
||||
image = core::Service<cover::IArtworkService>::get()->getDefaultArtistImage();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <Wt/WResource.h>
|
||||
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
|
||||
@@ -46,9 +47,8 @@ namespace lms::ui
|
||||
std::string getTrackImageUrl(db::TrackId trackId, Size size) const;
|
||||
|
||||
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 getImageUrl(db::ImageId imageId, Size size, std::string_view type) const;
|
||||
std::string getImageUrl(db::TrackId trackId, Size size, std::string_view type) const;
|
||||
|
||||
std::string getDefaultArtistImageUrl() const;
|
||||
std::string getDefaultReleaseCoverUrl() const;
|
||||
|
||||
Reference in New Issue
Block a user