Simplify the covert art resource

This commit is contained in:
emeric
2018-09-14 21:03:25 +02:00
parent 352bb6506a
commit 060379a56b
7 changed files with 95 additions and 211 deletions
+57 -52
View File
@@ -43,12 +43,15 @@ isFileSupported(const boost::filesystem::path& file, const std::vector<boost::fi
return false; return false;
} }
} // namespace } // namespace
namespace CoverArt { namespace CoverArt {
Grabber::Grabber() Grabber::Grabber()
{ {
if (!_defaultCover.load( Wt::WApplication::instance()->docRoot() + "/images/unknown-cover.jpg"))
throw LmsException("Cannot read default cover file");
} }
Grabber& Grabber&
@@ -58,48 +61,42 @@ Grabber::instance()
return instance; return instance;
} }
static std::vector<Image::Image> static boost::optional<Image::Image>
getFromAvMediaFile(const Av::MediaFile& input, std::size_t nbMaxCovers) getFromAvMediaFile(const Av::MediaFile& input)
{ {
std::vector<Image::Image> res; std::vector<Image::Image> res;
for (Av::Picture& picture : input.getAttachedPictures(nbMaxCovers)) for (auto& picture : input.getAttachedPictures(2))
{ {
Image::Image image; Image::Image image;
if (image.load(picture.data)) if (image.load(picture.data))
res.push_back( image ); return image;
else else
LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath().string() << "'"; LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath().string() << "'";
} }
return res; return boost::none;
} }
std::vector<Image::Image> boost::optional<Image::Image>
Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const Grabber::getFromDirectory(const boost::filesystem::path& p) const
{ {
std::vector<Image::Image> res; for (auto coverPath : getCoverPaths(p))
std::vector<boost::filesystem::path> coverPathes = getCoverPaths(p, nbMaxCovers);
for (auto coverPath : coverPathes)
{ {
if (res.size() >= nbMaxCovers)
break;
Image::Image image; Image::Image image;
if (image.load(coverPath)) if (image.load(coverPath))
res.push_back(image); return image;
else else
LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath.string() << "'"; LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath.string() << "'";
} }
return res; return boost::none;
} }
std::vector<boost::filesystem::path> std::vector<boost::filesystem::path>
Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers) const Grabber::getCoverPaths(const boost::filesystem::path& directoryPath) const
{ {
std::vector<boost::filesystem::path> res; std::vector<boost::filesystem::path> res;
boost::system::error_code ec; boost::system::error_code ec;
@@ -126,40 +123,40 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
} }
res.push_back(path); res.push_back(path);
if (res.size() >= nbMaxCovers)
break;
} }
return res; return res;
} }
std::vector<Image::Image> boost::optional<Image::Image>
Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const Grabber::getFromTrack(const boost::filesystem::path& p) const
{ {
try try
{ {
Av::MediaFile input(p); Av::MediaFile input(p);
return getFromAvMediaFile(input, nbMaxCovers); return getFromAvMediaFile(input);
} }
catch (Av::MediaFileException& e) catch (Av::MediaFileException& e)
{ {
LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p.string() << ": " << e.what(); LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p.string() << ": " << e.what();
return boost::none;
} }
return std::vector<Image::Image>();
} }
std::vector<Image::Image> Image::Image
Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::size_t nbMaxCovers) const Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::size_t size) const
{ {
using namespace Database; using namespace Database;
boost::optional<Image::Image> cover;
{
Wt::Dbo::Transaction transaction(session); Wt::Dbo::Transaction transaction(session);
Track::pointer track = Track::getById(session, trackId); Track::pointer track = Track::getById(session, trackId);
if (!track) if (track)
return std::vector<Image::Image>(); {
Track::CoverType coverType = track->getCoverType(); Track::CoverType coverType = track->getCoverType();
boost::filesystem::path trackPath = track->getPath(); boost::filesystem::path trackPath = track->getPath();
@@ -168,44 +165,52 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::
switch (coverType) switch (coverType)
{ {
case Track::CoverType::Embedded: case Track::CoverType::Embedded:
return Grabber::getFromTrack(trackPath, nbMaxCovers); cover = getFromTrack(trackPath);
case Track::CoverType::None: case Track::CoverType::None:
return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers); cover = getFromDirectory(trackPath.parent_path());
}
}
} }
return std::vector<Image::Image>(); if (!cover)
cover = _defaultCover;
cover->scale(size);
return *cover;
} }
std::vector<Image::Image> Image::Image
Grabber::getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t nbMaxCovers) const Grabber::getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t size) const
{ {
using namespace Database; using namespace Database;
boost::optional<Image::Image> cover;
{
Wt::Dbo::Transaction transaction(session); Wt::Dbo::Transaction transaction(session);
// If the release does not exist, do nothing auto release = Release::getById(session, releaseId);
Release::pointer release = Release::getById(session, releaseId); if (release)
if (!release) {
return std::vector<Image::Image>(); auto tracks = release->getTracks();
if (!tracks.empty())
std::vector<Track::pointer> tracks = release->getTracks(); {
if (tracks.empty()) auto trackId = tracks.front().id();
return std::vector<Image::Image>();
boost::filesystem::path firstTrackPath = tracks.front()->getPath();
bool embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded);
transaction.commit(); transaction.commit();
// First, try to get covers from the directory of the release return getFromTrack(session, trackId, size);
std::vector<Image::Image> res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); }
}
}
// Fallback on the embedded cover of the first track if (!cover)
if (res.empty() && embeddedCover) cover = _defaultCover;
res = getFromTrack( firstTrackPath, nbMaxCovers);
return res; cover->scale(size);
return *cover;
} }
} // namespace CoverArt } // namespace CoverArt
+12 -5
View File
@@ -35,18 +35,25 @@ class Grabber
static Grabber& instance(); static Grabber& instance();
std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers = 1) const; Image::Image getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::size_t size) const;
std::vector<Image::Image> getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; Image::Image getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t size) const;
std::vector<Image::Image> getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
std::vector<Image::Image> getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::size_t nbMaxCovers = 1) const;
std::vector<Image::Image> getFromRelease(Wt::Dbo::Session& session, Database::IdType releaseId, std::size_t nbMaxCovers = 1) const;
private: private:
Grabber(); Grabber();
boost::optional<Image::Image> getFromTrack(const boost::filesystem::path& path) const;
std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath) const;
boost::optional<Image::Image> getFromDirectory(const boost::filesystem::path& path) const;
Image::Image _defaultCover;
std::vector<boost::filesystem::path> _fileExtensions std::vector<boost::filesystem::path> _fileExtensions
= {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize = {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize
std::size_t _maxFileSize = 5000000; std::size_t _maxFileSize = 5000000;
std::vector<boost::filesystem::path> _preferredFileNames std::vector<boost::filesystem::path> _preferredFileNames
= {"cover", "front"}; // TODO parametrize = {"cover", "front"}; // TODO parametrize
}; };
+2 -2
View File
@@ -328,8 +328,8 @@ LmsApplication::createHome()
_isAdmin = LmsApp->getUser()->isAdmin(); _isAdmin = LmsApp->getUser()->isAdmin();
} }
_imageResource = std::make_shared<ImageResource>(_db); _imageResource = std::make_shared<ImageResource>();
_transcodeResource = std::make_shared<TranscodeResource>(_db); _transcodeResource = std::make_shared<TranscodeResource>();
setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm")); setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm"));
+9 -113
View File
@@ -37,61 +37,11 @@ namespace UserInterface {
static const std::string unknownCoverPath = "/images/unknown-cover.jpg"; static const std::string unknownCoverPath = "/images/unknown-cover.jpg";
static const std::string unknownArtistImagePath = "/images/unknown-artist.jpg"; static const std::string unknownArtistImagePath = "/images/unknown-artist.jpg";
ImageResource::ImageResource(Database::Handler& db)
: _db(db)
{
}
ImageResource::~ImageResource() ImageResource::~ImageResource()
{ {
beingDeleted(); beingDeleted();
} }
Image::Image
ImageResource::getDefaultCover(std::size_t size)
{
std::unique_lock<std::mutex> lock(_mutex);
auto itCover = _defaultCovers.find(size);
if (itCover == _defaultCovers.end())
{
// Load default cover art for this size
Image::Image image;
if (!image.load( Wt::WApplication::instance()->docRoot() + unknownCoverPath ))
throw LmsException("Cannot read default cover file");
image.scale(size);
auto res = _defaultCovers.insert(std::make_pair(size, image));
itCover = res.first;
}
return itCover->second;
}
Image::Image
ImageResource::getDefaultArtistImage(std::size_t size)
{
std::unique_lock<std::mutex> lock(_mutex);
auto itCover = _defaultArtistImages.find(size);
if (itCover == _defaultArtistImages.end())
{
Image::Image image;
if (!image.load( Wt::WApplication::instance()->docRoot() + unknownArtistImagePath))
throw LmsException("Cannot read default artist image file");
image.scale(size);
auto res = _defaultArtistImages.insert(std::make_pair(size, image));
itCover = res.first;
}
return itCover->second;
}
std::string std::string
ImageResource::getReleaseUrl(Database::IdType releaseId, std::size_t size) const ImageResource::getReleaseUrl(Database::IdType releaseId, std::size_t size) const
{ {
@@ -127,29 +77,12 @@ ImageResource::putImage(Wt::Http::Response& response, Image::Image cover)
response.out().write(reinterpret_cast<const char *>(&data[0]), data.size()); response.out().write(reinterpret_cast<const char *>(&data[0]), data.size());
} }
void
ImageResource::putCover(Wt::Http::Response& response, std::vector<Image::Image> covers, size_t size)
{
for (Image::Image cover : covers)
{
if (cover.scale(size))
{
putImage(response, cover);
return;
}
}
// If no cover found, just send default one
putImage(response, getDefaultCover(size));
}
void void
ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{ {
// Retrieve parameters // Retrieve parameters
const std::string *trackIdStr = request.getParameter("trackid"); const std::string *trackIdStr = request.getParameter("trackid");
const std::string *releaseIdStr = request.getParameter("releaseid"); const std::string *releaseIdStr = request.getParameter("releaseid");
const std::string *artistIdStr = request.getParameter("artistid");
const std::string *sizeStr = request.getParameter("size"); const std::string *sizeStr = request.getParameter("size");
// Mandatory parameter size // Mandatory parameter size
@@ -160,73 +93,36 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
if (!size || *size > maxSize) if (!size || *size > maxSize)
return; return;
Image::Image cover;
if (trackIdStr) if (trackIdStr)
{ {
auto trackId = readAs<Database::IdType>(*trackIdStr); auto trackId = readAs<Database::IdType>(*trackIdStr);
if (!trackId) if (!trackId)
return; return;
boost::filesystem::path path;
Database::Track::CoverType coverType = Database::Track::CoverType::None;
{
// transactions are not thread safe // transactions are not thread safe
Wt::WApplication::UpdateLock lock(LmsApplication::instance());
Wt::Dbo::Transaction transaction(_db.getSession());
Database::Track::pointer track = Database::Track::getById(_db.getSession(), *trackId);
if (track)
{ {
coverType = track->getCoverType(); Wt::WApplication::UpdateLock lock(LmsApp);
path = track->getPath(); cover = CoverArt::Grabber::instance().getFromTrack(LmsApp->getDboSession(), *trackId, *size);
} }
} }
if (!path.empty())
{
std::vector<Image::Image> covers;
switch (coverType)
{
case Database::Track::CoverType::Embedded:
covers = CoverArt::Grabber::instance().getFromTrack(path);
break;
case Database::Track::CoverType::None:
covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path());
break;
}
putCover(response, covers, *size);
return;
}
putImage(response, getDefaultCover(*size));
}
else if (releaseIdStr) else if (releaseIdStr)
{ {
auto releaseId = readAs<Database::IdType>(*releaseIdStr); auto releaseId = readAs<Database::IdType>(*releaseIdStr);
if (!releaseId) if (!releaseId)
return; return;
std::vector<Image::Image> covers;
// transactions are not thread safe // transactions are not thread safe
{ {
Wt::WApplication::UpdateLock lock(LmsApplication::instance()); Wt::WApplication::UpdateLock lock(LmsApp);
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), *releaseId); cover = CoverArt::Grabber::instance().getFromRelease(LmsApp->getDboSession(), *releaseId, *size);
} }
putCover(response, covers, *size);
}
else if (artistIdStr)
{
putImage(response, getDefaultArtistImage(*size));
} }
else else
{ return;
putImage(response, getDefaultCover(*size));
} putImage(response, cover);
} }
} // namespace UserInterface } // namespace UserInterface
-14
View File
@@ -37,7 +37,6 @@ class ImageResource : public Wt::WResource
public: public:
static const std::size_t maxSize = 512; static const std::size_t maxSize = 512;
ImageResource(Database::Handler& db);
~ImageResource(); ~ImageResource();
std::string getReleaseUrl(Database::IdType releaseId, size_t size) const; std::string getReleaseUrl(Database::IdType releaseId, size_t size) const;
@@ -49,22 +48,9 @@ class ImageResource : public Wt::WResource
private: private:
Image::Image getDefaultCover(std::size_t size);
Image::Image getDefaultArtistImage(std::size_t size);
void putImage(Wt::Http::Response& response, Image::Image image); void putImage(Wt::Http::Response& response, Image::Image image);
void putCover(Wt::Http::Response& response, std::vector<Image::Image> covers, std::size_t size); void putCover(Wt::Http::Response& response, std::vector<Image::Image> covers, std::size_t size);
// Used to protect transactions since they are not thread safe
std::mutex _mutex;
Database::Handler& _db;
// Default cover for different sizes
std::map<std::size_t, Image::Image> _defaultCovers;
// Default artist images for different sizes
std::map<std::size_t, Image::Image> _defaultArtistImages;
// TODO construct a cache for covers?
}; };
} // namespace UserInterface } // namespace UserInterface
+4 -11
View File
@@ -29,15 +29,9 @@
namespace UserInterface { namespace UserInterface {
TranscodeResource::TranscodeResource(Database::Handler& db)
: _db(db)
{
LMS_LOG(UI, DEBUG) << "CONSTRUCTING RESOURCE";
}
TranscodeResource:: ~TranscodeResource() TranscodeResource:: ~TranscodeResource()
{ {
LMS_LOG(UI, DEBUG) << "DESTRUCTING RESOURCE";
beingDeleted(); beingDeleted();
} }
@@ -100,12 +94,11 @@ TranscodeResource::handleRequest(const Wt::Http::Request& request,
// transactions are not thread safe // transactions are not thread safe
{ {
Wt::WApplication::UpdateLock lock(LmsApplication::instance()); Wt::WApplication::UpdateLock lock(LmsApp);
Wt::Dbo::Transaction transaction(_db.getSession()); Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
Database::User::pointer user = _db.getCurrentUser(); Database::Track::pointer track = Database::Track::getById(LmsApp->getDboSession(), trackId);
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
if (!track) if (!track)
{ {
@@ -113,7 +106,7 @@ TranscodeResource::handleRequest(const Wt::Http::Request& request,
return; return;
} }
parameters.bitrate = user->getAudioBitrate(); parameters.bitrate = LmsApp->getUser()->getAudioBitrate();
transcoder = std::make_shared<Av::Transcoder>(track->getPath(), parameters); transcoder = std::make_shared<Av::Transcoder>(track->getPath(), parameters);
} }
-3
View File
@@ -34,7 +34,6 @@ namespace UserInterface {
class TranscodeResource : public Wt::WResource class TranscodeResource : public Wt::WResource
{ {
public: public:
TranscodeResource(Database::Handler& db);
~TranscodeResource(); ~TranscodeResource();
std::string getUrl(Database::IdType trackId, Av::Encoding encoding) const; std::string getUrl(Database::IdType trackId, Av::Encoding encoding) const;
@@ -44,8 +43,6 @@ class TranscodeResource : public Wt::WResource
private: private:
Database::Handler& _db;
static const std::size_t _chunkSize = 65536*4; static const std::size_t _chunkSize = 65536*4;
}; };