diff --git a/src/Makefile.am b/src/Makefile.am index 0aa398e0..33a25015 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,6 @@ lms_SOURCES = \ $(srcdir)/main/main.cpp \ $(srcdir)/av/AvInfo.cpp \ $(srcdir)/av/AvTranscoder.cpp \ - $(srcdir)/cover/CoverArt.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/Artist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ @@ -18,6 +17,7 @@ lms_SOURCES = \ $(srcdir)/database/Video.cpp \ $(srcdir)/database-updater/DatabaseUpdater.cpp \ $(srcdir)/database-updater/Checksum.cpp \ + $(srcdir)/image/Image.cpp \ $(srcdir)/logger/Logger.cpp \ $(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/Utils.cpp \ diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index d5530e6f..4b791049 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -23,7 +23,6 @@ #include "CoverArtGrabber.hpp" - namespace { bool @@ -44,7 +43,6 @@ isFileSupported(const boost::filesystem::path& file, const std::vector +static std::vector getFromAvMediaFile(const Av::MediaFile& input, std::size_t nbMaxCovers) { - std::vector res; + std::vector res; for (Av::Picture& picture : input.getAttachedPictures(nbMaxCovers)) - res.push_back( CoverArt(picture.data) ); + { + Image::Image image; + + if (image.load(picture.data)) + res.push_back( image ); + else + LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath() << "'"; + } return res; } -std::vector +std::vector Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const { - std::vector res; + std::vector res; std::vector coverPathes = getCoverPaths(p, nbMaxCovers); for (auto coverPath : coverPathes) @@ -77,13 +82,12 @@ Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCov if (res.size() >= nbMaxCovers) break; - std::vector data; - std::ifstream file(coverPath.string(), std::ios::binary); - char c; - while (file.get(c)) - data.push_back(c); + Image::Image image; - res.push_back(CoverArt(data)); + if (image.load(coverPath)) + res.push_back(image); + else + LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath << "'"; } return res; @@ -123,7 +127,7 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t return res; } -std::vector +std::vector Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const { Av::MediaFile input(p); @@ -131,10 +135,10 @@ Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) if (input.open()) return getFromAvMediaFile(input, nbMaxCovers); else - return std::vector(); + return std::vector(); } -std::vector +std::vector Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const { using namespace Database; @@ -143,7 +147,7 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI Track::pointer track = Track::getById(session, trackId); if (!track) - return std::vector(); + return std::vector(); Track::CoverType coverType = track->getCoverType(); boost::filesystem::path trackPath = track->getPath(); @@ -154,17 +158,15 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI { case Track::CoverType::Embedded: return Grabber::getFromTrack(trackPath, nbMaxCovers); - case Track::CoverType::ExternalFile: - return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers); case Track::CoverType::None: - return std::vector(); + return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers); } - return std::vector(); + return std::vector(); } -std::vector +std::vector Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers) const { using namespace Database; @@ -181,14 +183,14 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re -1, 1 /* limit result size */); if (tracks.empty()) - return std::vector(); + return std::vector(); firstTrackPath = tracks.front()->getPath(); embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded); } // First, try to get covers from the directory of the release - std::vector res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); + std::vector res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); // Fallback on the embedded cover of the first track if (res.empty() && embeddedCover) diff --git a/src/cover/CoverArtGrabber.hpp b/src/cover/CoverArtGrabber.hpp index 65685bc8..18d53a5d 100644 --- a/src/cover/CoverArtGrabber.hpp +++ b/src/cover/CoverArtGrabber.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2015 Emeric Poupon * * This file is part of LMS. * @@ -17,15 +17,13 @@ * along with LMS. If not, see . */ -#ifndef COVER_ART_GRABBER_HPP -#define COVER_ART_GRABBER_HPP +#pragma once #include #include "database/Types.hpp" -#include "CoverArt.hpp" - +#include "image/Image.hpp" namespace CoverArt { @@ -38,24 +36,20 @@ class Grabber static Grabber& instance(); std::vector getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers = 1) const; - std::vector getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; - std::vector getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; - std::vector getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const; - std::vector getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const; + std::vector getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; + std::vector getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; + std::vector getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const; + std::vector getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const; private: Grabber(); std::vector _fileExtensions - = {"jpg", "jpeg"}; + = {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize std::size_t _maxFileSize = 5000000; std::vector _preferredFileNames - = {"cover", "front"}; - - + = {"cover", "front"}; // TODO parametrize }; } // namespace CoverArt - -#endif diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index 524e09d7..a99808f1 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -366,27 +366,13 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) Wt::Dbo::Transaction transaction(_db.getSession()); - // Skip file if last write is the same Wt::Dbo::ptr track = Track::getByPath(_db.getSession(), file); - // if the file is the same and embeds covers, no need to update - if (track && track->getLastWriteTime() == lastWriteTime - && (track->getCoverType() == Database::Track::CoverType::Embedded)) - return; - - // Check for external covers - std::vector externalCovers = CoverArt::Grabber::instance().getCoverPaths(file.parent_path()); + // Skip file if last write is the same if (track && track->getLastWriteTime() == lastWriteTime) - { - // no change since last time we updated - // Skip only if no external covers has to be set - if (((track->getCoverType() == Database::Track::CoverType::None) && externalCovers.empty()) - || ((track->getCoverType() == Database::Track::CoverType::ExternalFile) && !externalCovers.empty())) return; - } MetaData::Items items; - if (!_metadataParser.parse(file, items)) return; @@ -535,12 +521,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) { bool hasCover = boost::any_cast(items[MetaData::Type::HasCover]); - if (hasCover) - track.modify()->setCoverType( Track::CoverType::Embedded ); - else if (!externalCovers.empty()) - track.modify()->setCoverType( Track::CoverType::ExternalFile ); - else - track.modify()->setCoverType( Track::CoverType::None); + track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None ); } transaction.commit(); diff --git a/src/database-updater/DatabaseUpdater.hpp b/src/database-updater/DatabaseUpdater.hpp index e934a43f..f614426b 100644 --- a/src/database-updater/DatabaseUpdater.hpp +++ b/src/database-updater/DatabaseUpdater.hpp @@ -97,10 +97,10 @@ class Updater Database::Handler _db; std::vector _audioExtensions - = {".mp3", ".ogg", ".oga", ".aac", ".m4a", ".flac", ".wav", ".wma", ".aif", ".aiff", ".ape", ".mpc", ".shn"}; + = {".mp3", ".ogg", ".oga", ".aac", ".m4a", ".flac", ".wav", ".wma", ".aif", ".aiff", ".ape", ".mpc", ".shn"}; // TODO parametrize std::vector _videoExtensions - = {".flv", ".avi", ".mpg", ".mpeg", ".mp4", ".m4v", ".mkv", ".mov", ".wmv", ".ogv", ".divx", ".m2ts"}; + = {".flv", ".avi", ".mpg", ".mpeg", ".mp4", ".m4v", ".mkv", ".mov", ".wmv", ".ogv", ".divx", ".m2ts"}; // TODO parametrize MetaData::Parser& _metadataParser; diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 14099ce7..36e22269 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -97,7 +97,6 @@ class Track enum class CoverType { Embedded, // Contains embedded cover - ExternalFile, // Cover is in an external file None, // No local cover available }; diff --git a/src/cover/CoverArt.cpp b/src/image/Image.cpp similarity index 64% rename from src/cover/CoverArt.cpp rename to src/image/Image.cpp index 227bca5e..d121d860 100644 --- a/src/cover/CoverArt.cpp +++ b/src/image/Image.cpp @@ -18,10 +18,10 @@ */ #include "logger/Logger.hpp" -#include "CoverArt.hpp" +#include "Image.hpp" -namespace CoverArt { +namespace Image { static std::string format_to_magick(Format format) @@ -45,20 +45,46 @@ std::string format_to_mimeType(Format format) } void -CoverArt::init(const char *path) +init(const char *path) { Magick::InitializeMagick(path); } -CoverArt::CoverArt(const std::vector& rawData) +bool +Image::load(const std::vector& rawData) { - Magick::Blob blob(&rawData[0], rawData.size()); - _image.read(blob); + try + { + Magick::Blob blob(&rawData[0], rawData.size()); + _image.read(blob); + + return true; + } + catch (Magick::Exception& e) + { + LMS_LOG(COVER, ERROR) << "Caught Magick exception while loading raw image: " << e.what(); + return false; + } } +bool +Image::load(boost::filesystem::path p) +{ + try + { + _image.read(p.string()); + + return true; + } + catch (Magick::Exception& e) + { + LMS_LOG(COVER, ERROR) << "Caught Magick exception while loading image from file '" << p << "': " << e.what(); + return false; + } +} bool -CoverArt::scale(std::size_t size) +Image::scale(std::size_t size) { if (!size) return false; @@ -71,15 +97,14 @@ CoverArt::scale(std::size_t size) } catch (Magick::Exception& e) { - LMS_LOG(COVER, ERROR) << "Caught exception: " << e.what(); + LMS_LOG(COVER, ERROR) << "Caught Magick exception: " << e.what(); return false; } } void -CoverArt::getData(std::vector& data, Format format) const +Image::save(std::vector& data, Format format) const { - Magick::Image outputImage(_image); outputImage.magick( format_to_magick(format)); @@ -91,4 +116,4 @@ CoverArt::getData(std::vector& data, Format format) const data.assign( charBuf, charBuf + blob.length() ); } -} // namespace CoverArt +} // namespace Image diff --git a/src/cover/CoverArt.hpp b/src/image/Image.hpp similarity index 70% rename from src/cover/CoverArt.hpp rename to src/image/Image.hpp index c3e66cc0..5520e289 100644 --- a/src/cover/CoverArt.hpp +++ b/src/image/Image.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2015 Emeric Poupon * * This file is part of LMS. * @@ -17,15 +17,15 @@ * along with LMS. If not, see . */ -#ifndef COVER_ART_HPP -#define COVER_ART_HPP +#pragma once #include -#include + +#include #include -namespace CoverArt +namespace Image { enum class Format @@ -35,26 +35,26 @@ enum class Format std::string format_to_mimeType(Format format); -class CoverArt +void init(const char *path); + +class Image { public: - static void init(const char *path); - CoverArt() {} - CoverArt(const std::vector& rawData); + // input + bool load(const std::vector& rawData); + bool load(boost::filesystem::path p); - // Operations on cover arts + // Operations bool scale(std::size_t size); // output - void getData(std::vector& data, Format format) const; + void save(std::vector& rawData, Format format) const; private: Magick::Image _image; }; -} // namespace CoverArt - -#endif +} // namespace Image diff --git a/src/main/main.cpp b/src/main/main.cpp index c1d871eb..32338881 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -23,7 +23,7 @@ #include "av/AvInfo.hpp" #include "av/AvTranscoder.hpp" #include "logger/Logger.hpp" -#include "cover/CoverArtGrabber.hpp" +#include "image/Image.hpp" #include "ui/LmsApplication.hpp" @@ -45,7 +45,6 @@ int main(int argc, char* argv[]) try { - CoverArt::CoverArt::init(argv[0]); Wt::WServer server(argv[0]); server.setServerConfiguration (argc, argv); @@ -54,6 +53,7 @@ int main(int argc, char* argv[]) Service::ServiceManager& serviceManager = Service::ServiceManager::instance(); // lib init + Image::init(argv[0]); Av::AvInit(); Av::Transcoder::init(); Database::Handler::configureAuth(); diff --git a/src/ui/audio/desktop/PlayQueue.cpp b/src/ui/audio/desktop/PlayQueue.cpp index 40a041f2..fb11cedd 100644 --- a/src/ui/audio/desktop/PlayQueue.cpp +++ b/src/ui/audio/desktop/PlayQueue.cpp @@ -389,10 +389,7 @@ PlayQueue::addTracks(const std::vector& trackIds) _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); std::string coverUrl; - if (track->getCoverType() != Track::CoverType::None) - coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64); - else - coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64); + coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64); _model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole); _model->setData(dataRow, COLUMN_ID_COVER, std::string("playqueue-cover"), Wt::StyleClassRole); diff --git a/src/ui/resource/CoverResource.cpp b/src/ui/resource/CoverResource.cpp index bfdf26b5..6090b91f 100644 --- a/src/ui/resource/CoverResource.cpp +++ b/src/ui/resource/CoverResource.cpp @@ -41,28 +41,21 @@ CoverResource:: ~CoverResource() beingDeleted(); } -const CoverArt::CoverArt& +const Image::Image& CoverResource::getDefaultCover(std::size_t size) { 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 std::runtime_error("Cannot read default cover file"); - std::vector data; - { - std::ifstream ist(Wt::WApplication::instance()->docRoot() + unknownCoverPath); - char c; - while(ist.get(c)) - data.push_back(c); - } + image.scale(size); - CoverArt::CoverArt defaultCover(data); - - defaultCover.scale(size); - - auto res = _defaultCovers.insert(std::make_pair(size, defaultCover)); + auto res = _defaultCovers.insert(std::make_pair(size, image)); itCover = res.first; } @@ -88,13 +81,13 @@ CoverResource::getUnknownTrackUrl(size_t size) const } void -CoverResource::putCover(Wt::Http::Response& response, const CoverArt::CoverArt& cover) +CoverResource::putCover(Wt::Http::Response& response, Image::Image cover) { - response.setMimeType( format_to_mimeType(CoverArt::Format::JPEG) ); - std::vector data; - cover.getData(data, CoverArt::Format::JPEG); + cover.save(data, Image::Format::JPEG); + + response.setMimeType( Image::format_to_mimeType(Image::Format::JPEG) ); response.out().write(reinterpret_cast(&data[0]), data.size()); } @@ -108,7 +101,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons try { - std::vector covers; + std::vector covers; // Mandatory parameter size if (!sizeStr) @@ -144,17 +137,14 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons covers = CoverArt::Grabber::instance().getFromTrack(path); break; - case Database::Track::CoverType::ExternalFile: - covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path()); - break; - case Database::Track::CoverType::None: + covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path()); break; } } else if (releaseIdStr) { - Database::Release::id_type releaseId = std::stol(*releaseIdStr); // TODO try catch + Database::Release::id_type releaseId = std::stol(*releaseIdStr); // transactions are not thread safe std::unique_lock lock(_mutex); Wt::Dbo::Transaction transaction(_db.getSession()); @@ -162,7 +152,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId); } - for (CoverArt::CoverArt& cover : covers) + for (Image::Image& cover : covers) { if (cover.scale(size)) { diff --git a/src/ui/resource/CoverResource.hpp b/src/ui/resource/CoverResource.hpp index 60fe12a8..07ce6918 100644 --- a/src/ui/resource/CoverResource.hpp +++ b/src/ui/resource/CoverResource.hpp @@ -25,7 +25,7 @@ #include #include "database/DatabaseHandler.hpp" -#include "cover/CoverArt.hpp" +#include "image/Image.hpp" namespace UserInterface { @@ -47,14 +47,14 @@ class CoverResource : public Wt::WResource private: - const CoverArt::CoverArt& getDefaultCover(std::size_t size); - void putCover(Wt::Http::Response& response, const CoverArt::CoverArt& cover); + const Image::Image& getDefaultCover(std::size_t size); + void putCover(Wt::Http::Response& response, Image::Image image); std::mutex _mutex; Database::Handler& _db; // Default cover for different sizes - std::map _defaultCovers; + std::map _defaultCovers; // TODO construct a cache for covers? };