From 784b73fc5aad535f0075bafbb0440f7f18497211 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 20 Dec 2014 15:31:13 +0100 Subject: [PATCH] [UI] Better handling covers: giving browsers a chance to properly cache them --- src/av/InputFormatContext.cpp | 33 +++++++++++++++---- src/av/InputFormatContext.hpp | 7 +++- src/cover/CoverArtGrabber.cpp | 6 ++-- src/ui/audio/PlayQueue.cpp | 52 +++++++---------------------- src/ui/audio/PlayQueue.hpp | 4 +++ src/ui/resource/CoverResource.cpp | 54 +++++++++++++++++++++++-------- src/ui/resource/CoverResource.hpp | 17 +++++----- 7 files changed, 100 insertions(+), 73 deletions(-) diff --git a/src/av/InputFormatContext.cpp b/src/av/InputFormatContext.cpp index 6fcdb432..2aab3f67 100644 --- a/src/av/InputFormatContext.cpp +++ b/src/av/InputFormatContext.cpp @@ -101,7 +101,7 @@ InputFormatContext::findStreamInfo(void) std::size_t InputFormatContext::getDurationSecs() const { - if (native()->duration != AV_NOPTS_VALUE ) + if (static_cast(native()->duration) != AV_NOPTS_VALUE ) return native()->duration / AV_TIME_BASE; else return 0; // TODO, do something better? @@ -128,18 +128,39 @@ InputFormatContext::getNbPictures(void) const } void -InputFormatContext::getPictures(std::vector< std::vector >& pictures) const +InputFormatContext::getPictures(std::vector& pictures) const { + static const std::map codecMimeMap = + { + { AV_CODEC_ID_BMP, "image/x-bmp" }, + { AV_CODEC_ID_GIF, "image/gif" }, + { AV_CODEC_ID_MJPEG, "image/jpeg" }, + { AV_CODEC_ID_PNG, "image/png" }, + { AV_CODEC_ID_PNG, "image/x-png" }, + { AV_CODEC_ID_PPM, "image/x-portable-pixmap" }, + }; + for (std::size_t i = 0; i < native()->nb_streams; ++i) { - if (native()->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) + Stream stream(native()->streams[i]); + + if (stream.hasAttachedPic()) { + Picture picture; + + auto itMime = codecMimeMap.find(stream.getCodecContext().getCodecId()); + if (itMime != codecMimeMap.end()) + picture.mimeType = itMime->second; + else + picture.mimeType = "application/octet-stream"; + + LMS_LOG(MOD_AV, SEV_DEBUG) << "MIME set to '" << picture.mimeType << "'" << std::endl; + AVPacket pkt = native()->streams[i]->attached_pic; - std::vector data; - std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(data)); + std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data)); - pictures.push_back( data ); + pictures.push_back( picture ); } } } diff --git a/src/av/InputFormatContext.hpp b/src/av/InputFormatContext.hpp index 91bb2725..7844ea97 100644 --- a/src/av/InputFormatContext.hpp +++ b/src/av/InputFormatContext.hpp @@ -31,6 +31,11 @@ namespace Av { +struct Picture { + std::string mimeType; + std::vector data; +}; + class InputFormatContext : public FormatContext { public: @@ -47,7 +52,7 @@ class InputFormatContext : public FormatContext // Get attached pictures std::size_t getNbPictures(void) const; - void getPictures(std::vector< std::vector >& pictures) const; + void getPictures(std::vector& pictures) const; // Get the streams std::vector getStreams(void); diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index 8ca350ff..0a3fce62 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -37,11 +37,11 @@ Grabber::getFromInputFormatContext(const Av::InputFormatContext& input) try { - std::vector< std::vector > pictures; + std::vector pictures; input.getPictures(pictures); - BOOST_FOREACH(const std::vector& picture, pictures) - res.push_back( CoverArt("application/octet-stream", picture) ); + BOOST_FOREACH(const Av::Picture& picture, pictures) + res.push_back( CoverArt(picture.mimeType, picture.data) ); } catch(std::exception& e) diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp index 9c4beb4b..ed392bad 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/PlayQueue.cpp @@ -34,15 +34,12 @@ #include "PlayQueue.hpp" -static const int CoverRole = Wt::UserRole + 1; -static const int NameRole = Wt::UserRole + 2; +static const int NameRole = Wt::UserRole; namespace { void swapRows(Wt::WStandardItemModel *model, int row1, int row2) { - LMS_LOG(MOD_UI, SEV_DEBUG) << "Swap called row1 = " << row1 << ", row2 = " << row2 << ", CoverRole = " << CoverRole; - // Swap data column by column for (int i = 0; i < model->columnCount(); ++i) { @@ -55,15 +52,7 @@ namespace { model->setItemData(index1, model->itemData(index2)); // 2 -> 1 model->setItemData(index2, tmp); // tmp-> 2 } - - // swap data associated with our custom roles - const std::vector(roles) = {CoverRole, NameRole}; - BOOST_FOREACH(int role, roles) - { - auto tmp = model->data(index1, role); - model->setData(index1, model->data(index2, role), role); - model->setData(index2, tmp, role); - } + // caution: swap data associated with our custom roles if any!! } } } @@ -245,33 +234,7 @@ class PlayQueueItemDelegate : public Wt::WItemDelegate { Wt::WWidget* res; - Wt::WString path = Wt::asString(index.data(CoverRole)); - if (!path.empty()) - { - // Create an image for this track - Wt::WImage* image = new Wt::WImage( ); - Wt::WResource* resource; - if (path != "none") - resource = new CoverResource(boost::filesystem::path(path.toUTF8()), 64, image); - else - resource = new Wt::WFileResource("image/jpeg", Wt::WApplication::instance()->docRoot() + "/images/unknown-cover.jpg", image); - - image->setImageLink(Wt::WLink(resource)); - - // Apply style if any - Wt::WString styleClass = Wt::asString(index.data(Wt::StyleClassRole)); - - // Apply selection style if any - if (flags & Wt::RenderSelected) - styleClass += " " + Wt::WApplication::instance()->theme()->activeClass(); - - image->setStyleClass(styleClass); - - res = image; - res->setObjectName("z"); - - } - else if (!index.data(NameRole).empty()) + if (!index.data(NameRole).empty()) { Name name = boost::any_cast(index.data(NameRole)); @@ -351,6 +314,7 @@ _trackSelector(new TrackSelector()) }, std::placeholders::_1, std::placeholders::_2)); + _coverResource = new CoverResource(db, 64); } void @@ -409,7 +373,13 @@ PlayQueue::addTracks(const std::vector& trackIds) _model->insertRows(dataRow, 1); _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); - _model->setData(dataRow, COLUMN_ID_COVER, track->hasCover() ? track->getPath() : "none", CoverRole); + + std::string coverUrl; + if (track->hasCover()) + coverUrl = _coverResource->url() + "&coverid=" + Wt::asString(track.id()).toUTF8(); + else + coverUrl = "images/unknown-cover.jpg"; + _model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole); Name name; name.track = Wt::WString::fromUTF8(track->getName()); diff --git a/src/ui/audio/PlayQueue.hpp b/src/ui/audio/PlayQueue.hpp index b82280b6..1f968353 100644 --- a/src/ui/audio/PlayQueue.hpp +++ b/src/ui/audio/PlayQueue.hpp @@ -26,6 +26,8 @@ #include "database/DatabaseHandler.hpp" #include "database/AudioTypes.hpp" +#include "resource/CoverResource.hpp" + namespace UserInterface { class PlayQueueItemDelegate; @@ -78,6 +80,8 @@ class PlayQueue : public Wt::WTableView PlayQueueItemDelegate* _itemDelegate; + CoverResource* _coverResource; + int _curPlayedTrackPos; std::unique_ptr _trackSelector; diff --git a/src/ui/resource/CoverResource.cpp b/src/ui/resource/CoverResource.cpp index d169278c..5af7ed2e 100644 --- a/src/ui/resource/CoverResource.cpp +++ b/src/ui/resource/CoverResource.cpp @@ -19,6 +19,7 @@ #include +#include #include #include "logger/Logger.hpp" @@ -29,9 +30,9 @@ namespace UserInterface { -CoverResource::CoverResource(const boost::filesystem::path& path, std::size_t size, Wt::WObject *parent) +CoverResource::CoverResource(Database::Handler& db, std::size_t size, Wt::WObject *parent) : Wt::WResource(parent), -_path(path), +_db(db), _size(size) { } @@ -45,25 +46,50 @@ CoverResource:: ~CoverResource() void CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { - if (_data.empty()) + // Get the id of the track + const std::string *trackIdStr = request.getParameter("coverid"); + + if (trackIdStr) { - std::vector covers = CoverArt::Grabber::getFromTrack( _path ); + Database::Track::id_type trackId; + { + std::istringstream iss(*trackIdStr); iss >> trackId; + } + + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + std::vector covers = CoverArt::Grabber::getFromTrack(track); + + transaction.commit(); BOOST_FOREACH(CoverArt::CoverArt& cover, covers) { - cover.scale(_size); - _data = cover.getData(); - _mimeType = cover.getMimeType(); - break; + if (cover.scale(_size)) + { + response.setMimeType( cover.getMimeType() ); + + BOOST_FOREACH(unsigned char c, cover.getData()) + response.out().put( c ); + + return; + } + else + LMS_LOG(MOD_UI, SEV_DEBUG) << "Resize error for track id = " << trackId; } + LMS_LOG(MOD_UI, SEV_DEBUG) << "no valid cover found for track id = " << trackId; + { + std::ifstream ist(Wt::WApplication::instance()->docRoot() + "/images/unknown-cover.jpg"); - // TODO if not found fallback on an empty image + char c; + while(ist.get(c)) + response.out().put( c ); + + response.setMimeType("image/jpeg"); + } } - - response.setMimeType(_mimeType); - - for (unsigned int i = 0; i < _data.size(); ++i) - response.out().put(_data[i]); + else + LMS_LOG(MOD_UI, SEV_DEBUG) << "no cover id parameter"; } diff --git a/src/ui/resource/CoverResource.hpp b/src/ui/resource/CoverResource.hpp index a8554da9..fdc45277 100644 --- a/src/ui/resource/CoverResource.hpp +++ b/src/ui/resource/CoverResource.hpp @@ -17,11 +17,15 @@ * along with LMS. If not, see . */ +#ifndef COVER_RESOURCE_HPP_ +#define COVER_RESOURCE_HPP_ + #include #include #include +#include "database/DatabaseHandler.hpp" #include "database/AudioTypes.hpp" namespace UserInterface { @@ -29,8 +33,8 @@ namespace UserInterface { class CoverResource : public Wt::WResource { public: - CoverResource(const boost::filesystem::path& p, // track to get cover from - std::size_t size, // size * size pixels + CoverResource(Database::Handler& db, + std::size_t size, // size * size pixels Wt::WObject *parent = 0); ~CoverResource(); @@ -38,13 +42,10 @@ class CoverResource : public Wt::WResource private: - boost::filesystem::path _path; + Database::Handler& _db; std::size_t _size; - std::string _mimeType; - - std::vector _data; }; - - } // namespace UserInterface + +#endif