[Cover] Adding external cover file support (lookup in the same directory)
This commit is contained in:
@@ -12,7 +12,8 @@
|
|||||||
|
|
||||||
[Cover]
|
[Cover]
|
||||||
- Scaling: find something more "reliable" than GIL and its custom extensions (adobe work, io_new)?
|
- Scaling: find something more "reliable" than GIL and its custom extensions (adobe work, io_new)?
|
||||||
- Get the cover from image files in the same folder ("cover.jpg", etc.)
|
- Handle several file formats (not only jpg)
|
||||||
|
- Handle preferred cover file names
|
||||||
|
|
||||||
[Database]
|
[Database]
|
||||||
- Optim, use SQL query to get the "genre" orphans
|
- Optim, use SQL query to get the "genre" orphans
|
||||||
|
|||||||
@@ -11,7 +11,13 @@ main = {
|
|||||||
path = "/var/lms/lms.db";
|
path = "/var/lms/lms.db";
|
||||||
|
|
||||||
audio_extensions = "mp3 ogg oga aac m4a flac wav wma aif aiff ape mpc shn";
|
audio_extensions = "mp3 ogg oga aac m4a flac wav wma aif aiff ape mpc shn";
|
||||||
video_extensions = "flv avi mpg mpeg mp4 m4v mkv mov wmv ogv divx m2ts"
|
video_extensions = "flv avi mpg mpeg mp4 m4v mkv mov wmv ogv divx m2ts";
|
||||||
|
}
|
||||||
|
|
||||||
|
cover = {
|
||||||
|
file_extensions = "jpg jpeg";
|
||||||
|
file_max_size = 500000;
|
||||||
|
file_preferred_names = "cover front";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ InputFormatContext::getNbPictures(void) const
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
std::vector<Picture>
|
||||||
InputFormatContext::getPictures(std::vector<Picture>& pictures) const
|
InputFormatContext::getPictures(std::size_t nbMaxPictures) const
|
||||||
{
|
{
|
||||||
static const std::map<int, std::string> codecMimeMap =
|
static const std::map<int, std::string> codecMimeMap =
|
||||||
{
|
{
|
||||||
@@ -140,6 +140,8 @@ InputFormatContext::getPictures(std::vector<Picture>& pictures) const
|
|||||||
{ AV_CODEC_ID_PPM, "image/x-portable-pixmap" },
|
{ AV_CODEC_ID_PPM, "image/x-portable-pixmap" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::vector<Picture> pictures;
|
||||||
|
|
||||||
for (std::size_t i = 0; i < native()->nb_streams; ++i)
|
for (std::size_t i = 0; i < native()->nb_streams; ++i)
|
||||||
{
|
{
|
||||||
Stream stream(native()->streams[i]);
|
Stream stream(native()->streams[i]);
|
||||||
@@ -159,8 +161,13 @@ InputFormatContext::getPictures(std::vector<Picture>& pictures) const
|
|||||||
std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data));
|
std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data));
|
||||||
|
|
||||||
pictures.push_back( picture );
|
pictures.push_back( picture );
|
||||||
|
|
||||||
|
if (pictures.size() >= nbMaxPictures)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return pictures;
|
||||||
}
|
}
|
||||||
|
|
||||||
} //namespace Av
|
} //namespace Av
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class InputFormatContext : public FormatContext
|
|||||||
|
|
||||||
// Get attached pictures
|
// Get attached pictures
|
||||||
std::size_t getNbPictures(void) const;
|
std::size_t getNbPictures(void) const;
|
||||||
void getPictures(std::vector<Picture>& pictures) const;
|
std::vector<Picture> getPictures(std::size_t nbMaxPictures) const;
|
||||||
|
|
||||||
// Get the streams
|
// Get the streams
|
||||||
std::vector<Stream> getStreams(void);
|
std::vector<Stream> getStreams(void);
|
||||||
|
|||||||
@@ -47,6 +47,17 @@ ConfigReader::getLoggerConfig(Logger::Config& config)
|
|||||||
config.minSeverity = static_cast<Severity>((int)_config.lookup("main.logger.level"));
|
config.minSeverity = static_cast<Severity>((int)_config.lookup("main.logger.level"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ConfigReader::getCoverGrabberConfig(CoverArt::Grabber::Config& config)
|
||||||
|
{
|
||||||
|
std::string extensions = _config.lookup("main.cover.file_extensions");
|
||||||
|
config.maxFileSize = static_cast<unsigned int>(_config.lookup("main.cover.file_max_size"));
|
||||||
|
std::string filenames = _config.lookup("main.cover.file_preferred_names");
|
||||||
|
|
||||||
|
splitStrings(extensions, config.fileExtensions);
|
||||||
|
splitStrings(filenames, config.preferredFileNames);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ConfigReader::getUserInterfaceConfig(Service::UserInterfaceService::Config& config)
|
ConfigReader::getUserInterfaceConfig(Service::UserInterfaceService::Config& config)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <libconfig.h++>
|
#include <libconfig.h++>
|
||||||
|
|
||||||
|
#include "cover/CoverArtGrabber.hpp"
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
#include "service/UserInterfaceService.hpp"
|
#include "service/UserInterfaceService.hpp"
|
||||||
@@ -38,6 +39,9 @@ class ConfigReader
|
|||||||
// Logger configuration
|
// Logger configuration
|
||||||
void getLoggerConfig(Logger::Config& config);
|
void getLoggerConfig(Logger::Config& config);
|
||||||
|
|
||||||
|
// Covers
|
||||||
|
void getCoverGrabberConfig(CoverArt::Grabber::Config& config);
|
||||||
|
|
||||||
// Service configurations
|
// Service configurations
|
||||||
void getUserInterfaceConfig(Service::UserInterfaceService::Config& config);
|
void getUserInterfaceConfig(Service::UserInterfaceService::Config& config);
|
||||||
void getRemoteServerConfig(Service::RemoteServerService::Config& config);
|
void getRemoteServerConfig(Service::RemoteServerService::Config& config);
|
||||||
|
|||||||
+156
-38
@@ -26,19 +26,54 @@
|
|||||||
#include "CoverArtGrabber.hpp"
|
#include "CoverArtGrabber.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool
|
||||||
|
isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions)
|
||||||
|
{
|
||||||
|
boost::filesystem::path fileExtension = file.extension();
|
||||||
|
|
||||||
|
for (auto extension : extensions)
|
||||||
|
{
|
||||||
|
if (extension == fileExtension)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace CoverArt {
|
namespace CoverArt {
|
||||||
|
|
||||||
|
Grabber::Grabber()
|
||||||
|
: _maxFileSize(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Grabber&
|
||||||
|
Grabber::instance()
|
||||||
|
{
|
||||||
|
static Grabber instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Grabber::init(const Config& config)
|
||||||
|
{
|
||||||
|
for (auto extension : config.fileExtensions)
|
||||||
|
_fileExtensions.push_back("." + extension);
|
||||||
|
|
||||||
|
_maxFileSize = config.maxFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<CoverArt>
|
std::vector<CoverArt>
|
||||||
Grabber::getFromInputFormatContext(const Av::InputFormatContext& input)
|
Grabber::getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers) const
|
||||||
{
|
{
|
||||||
std::vector<CoverArt> res;
|
std::vector<CoverArt> res;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::vector<Av::Picture> pictures;
|
std::vector<Av::Picture> pictures = input.getPictures(nbMaxCovers);
|
||||||
input.getPictures(pictures);
|
|
||||||
|
|
||||||
BOOST_FOREACH(const Av::Picture& picture, pictures)
|
BOOST_FOREACH(const Av::Picture& picture, pictures)
|
||||||
res.push_back( CoverArt(picture.mimeType, picture.data) );
|
res.push_back( CoverArt(picture.mimeType, picture.data) );
|
||||||
@@ -53,7 +88,65 @@ Grabber::getFromInputFormatContext(const Av::InputFormatContext& input)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CoverArt>
|
std::vector<CoverArt>
|
||||||
Grabber::getFromTrack(const boost::filesystem::path& p)
|
Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const
|
||||||
|
{
|
||||||
|
std::vector<CoverArt> res;
|
||||||
|
|
||||||
|
std::vector<boost::filesystem::path> coverPathes = getCoverPaths(p, nbMaxCovers);
|
||||||
|
for (auto coverPath : coverPathes)
|
||||||
|
{
|
||||||
|
if (res.size() >= nbMaxCovers)
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::vector<unsigned char> data;
|
||||||
|
std::ifstream file(coverPath.string(), std::ios::binary);
|
||||||
|
char c;
|
||||||
|
while (file.get(c))
|
||||||
|
data.push_back(c);
|
||||||
|
|
||||||
|
// TODO handle other formats
|
||||||
|
res.push_back(CoverArt("image/jpeg", data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<boost::filesystem::path>
|
||||||
|
Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers) const
|
||||||
|
{
|
||||||
|
std::vector<boost::filesystem::path> res;
|
||||||
|
|
||||||
|
// TODO handle preferred file names
|
||||||
|
|
||||||
|
boost::filesystem::directory_iterator itPath(directoryPath);
|
||||||
|
boost::filesystem::directory_iterator itEnd;
|
||||||
|
while (itPath != itEnd)
|
||||||
|
{
|
||||||
|
boost::filesystem::path path = *itPath;
|
||||||
|
itPath++;
|
||||||
|
|
||||||
|
if (!boost::filesystem::is_regular(path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!isFileSupported(path, _fileExtensions))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (boost::filesystem::file_size(path) > _maxFileSize)
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_COVER, SEV_INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.push_back(path);
|
||||||
|
if (res.size() >= nbMaxCovers)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CoverArt>
|
||||||
|
Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const
|
||||||
{
|
{
|
||||||
std::vector<CoverArt> res;
|
std::vector<CoverArt> res;
|
||||||
|
|
||||||
@@ -61,55 +154,80 @@ Grabber::getFromTrack(const boost::filesystem::path& p)
|
|||||||
{
|
{
|
||||||
Av::InputFormatContext input(p);
|
Av::InputFormatContext input(p);
|
||||||
|
|
||||||
return getFromInputFormatContext(input);
|
res = getFromInputFormatContext(input, nbMaxCovers);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(std::exception& e)
|
catch(std::exception& e)
|
||||||
{
|
{
|
||||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what();
|
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get covers from file " << p << ": " << e.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CoverArt>
|
std::vector<CoverArt>
|
||||||
Grabber::getFromTrack(Database::Track::pointer track)
|
Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const
|
||||||
{
|
|
||||||
std::vector<CoverArt> res;
|
|
||||||
|
|
||||||
if (!track || !track->hasCover())
|
|
||||||
return std::vector<CoverArt>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Av::InputFormatContext input(track->getPath());
|
|
||||||
|
|
||||||
return getFromInputFormatContext(input);
|
|
||||||
}
|
|
||||||
catch(std::exception& e)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what();
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<CoverArt>
|
|
||||||
Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName)
|
|
||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
// For now, just return the embedded cover of the first track
|
Wt::Dbo::Transaction transaction(session);
|
||||||
SearchFilter filter;
|
|
||||||
filter.exactMatch[SearchFilter::Field::Release].push_back(releaseName);
|
|
||||||
|
|
||||||
std::vector<Track::pointer> tracks
|
Track::pointer track = Track::getById(session, trackId);
|
||||||
= Track::getAll(session, filter, -1, 1 /* limit result size */);
|
if (!track)
|
||||||
|
|
||||||
if (!tracks.empty())
|
|
||||||
return getFromTrack( tracks.front() );
|
|
||||||
else
|
|
||||||
return std::vector<CoverArt>();
|
return std::vector<CoverArt>();
|
||||||
|
|
||||||
|
Track::CoverType coverType = track->getCoverType();
|
||||||
|
boost::filesystem::path trackPath = track->getPath();
|
||||||
|
|
||||||
|
transaction.commit();
|
||||||
|
|
||||||
|
switch (coverType)
|
||||||
|
{
|
||||||
|
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<CoverArt>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<CoverArt>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<CoverArt>
|
||||||
|
Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std::size_t nbMaxCovers) const
|
||||||
|
{
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
|
boost::filesystem::path firstTrackPath;
|
||||||
|
bool embeddedCover = false;
|
||||||
|
|
||||||
|
// Get the first track of the release
|
||||||
|
{
|
||||||
|
SearchFilter filter;
|
||||||
|
filter.exactMatch[SearchFilter::Field::Release].push_back(releaseName);
|
||||||
|
|
||||||
|
Wt::Dbo::Transaction transaction(session);
|
||||||
|
|
||||||
|
std::vector<Track::pointer> tracks
|
||||||
|
= Track::getAll(session, filter, -1, 1 /* limit result size */);
|
||||||
|
|
||||||
|
if (tracks.empty())
|
||||||
|
return std::vector<CoverArt>();
|
||||||
|
|
||||||
|
firstTrackPath = tracks.front()->getPath();
|
||||||
|
embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, try to get covers from the directory of the release
|
||||||
|
std::vector<CoverArt> res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers);
|
||||||
|
|
||||||
|
// Fallback on the embedded cover of the first track
|
||||||
|
if (res.empty() && embeddedCover)
|
||||||
|
res = getFromTrack( firstTrackPath, nbMaxCovers);
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CoverArt
|
} // namespace CoverArt
|
||||||
|
|||||||
@@ -33,11 +33,35 @@ namespace CoverArt {
|
|||||||
class Grabber
|
class Grabber
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
Grabber(const Grabber&) = delete;
|
||||||
|
Grabber& operator=(const Grabber&) = delete;
|
||||||
|
|
||||||
|
static Grabber& instance();
|
||||||
|
|
||||||
|
struct Config
|
||||||
|
{
|
||||||
|
std::vector<std::string> fileExtensions;
|
||||||
|
std::size_t maxFileSize;
|
||||||
|
std::vector<std::string> preferredFileNames;
|
||||||
|
};
|
||||||
|
|
||||||
|
void init(const Config& config);
|
||||||
|
|
||||||
|
std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers = 1) const;
|
||||||
|
std::vector<CoverArt> getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
|
||||||
|
std::vector<CoverArt> getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers = 1) const;
|
||||||
|
std::vector<CoverArt> getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
|
||||||
|
std::vector<CoverArt> getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const;
|
||||||
|
std::vector<CoverArt> getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std::size_t nbMaxCovers = 1) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Grabber();
|
||||||
|
|
||||||
|
std::vector<boost::filesystem::path> _fileExtensions;
|
||||||
|
std::size_t _maxFileSize;
|
||||||
|
std::vector<boost::filesystem::path> _preferredFileNames;
|
||||||
|
|
||||||
|
|
||||||
static std::vector<CoverArt> getFromInputFormatContext(const Av::InputFormatContext& input);
|
|
||||||
static std::vector<CoverArt> getFromTrack(Database::Track::pointer track);
|
|
||||||
static std::vector<CoverArt> getFromTrack(const boost::filesystem::path& path);
|
|
||||||
static std::vector<CoverArt> getFromRelease(Wt::Dbo::Session& session, std::string releaseName);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CoverArt
|
} // namespace CoverArt
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include "Checksum.hpp"
|
#include "Checksum.hpp"
|
||||||
#include "DatabaseUpdater.hpp"
|
#include "DatabaseUpdater.hpp"
|
||||||
|
#include "cover/CoverArtGrabber.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -272,9 +273,23 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
|||||||
|
|
||||||
// Skip file if last write is the same
|
// Skip file if last write is the same
|
||||||
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file);
|
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file);
|
||||||
if (track && track->getLastWriteTime() == lastWriteTime)
|
|
||||||
|
// 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;
|
return;
|
||||||
|
|
||||||
|
// Check for external covers
|
||||||
|
std::vector<boost::filesystem::path> externalCovers = CoverArt::Grabber::instance().getCoverPaths(file.parent_path());
|
||||||
|
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;
|
MetaData::Items items;
|
||||||
_metadataParser.parse(file, items);
|
_metadataParser.parse(file, items);
|
||||||
|
|
||||||
@@ -400,11 +415,17 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
|||||||
|
|
||||||
if (items.find(MetaData::Type::HasCover) != items.end())
|
if (items.find(MetaData::Type::HasCover) != items.end())
|
||||||
{
|
{
|
||||||
track.modify()->setHasCover( boost::any_cast<bool>(items[MetaData::Type::HasCover]));
|
bool hasCover = boost::any_cast<bool>(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
|
|
||||||
}
|
}
|
||||||
catch( std::exception& e ) {
|
catch( std::exception& e ) {
|
||||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!";
|
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!";
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ Track::Track(const boost::filesystem::path& p)
|
|||||||
_trackNumber(0),
|
_trackNumber(0),
|
||||||
_discNumber(0),
|
_discNumber(0),
|
||||||
_filePath( p.string() ),
|
_filePath( p.string() ),
|
||||||
_hasCover(false)
|
_coverType(CoverType::None)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -104,6 +104,12 @@ class Track
|
|||||||
Track() {}
|
Track() {}
|
||||||
Track(const boost::filesystem::path& p);
|
Track(const boost::filesystem::path& p);
|
||||||
|
|
||||||
|
enum class CoverType
|
||||||
|
{
|
||||||
|
Embedded, // Contains embedded cover
|
||||||
|
ExternalFile, // Cover is in an external file
|
||||||
|
None, // No local cover available
|
||||||
|
};
|
||||||
|
|
||||||
// Find utility functions
|
// Find utility functions
|
||||||
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
||||||
@@ -141,14 +147,14 @@ class Track
|
|||||||
void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; }
|
void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; }
|
||||||
void setGenres(const std::string& genreList) { _genreList = genreList; }
|
void setGenres(const std::string& genreList) { _genreList = genreList; }
|
||||||
void setGenres(std::vector<Genre::pointer> genres);
|
void setGenres(std::vector<Genre::pointer> genres);
|
||||||
void setHasCover(bool hasCover) { _hasCover = hasCover; }
|
void setCoverType(CoverType coverType) { _coverType = coverType; }
|
||||||
|
|
||||||
int getTrackNumber(void) const { return _trackNumber; }
|
int getTrackNumber(void) const { return _trackNumber; }
|
||||||
int getDiscNumber(void) const { return _discNumber; }
|
int getDiscNumber(void) const { return _discNumber; }
|
||||||
std::string getName(void) const { return _name; }
|
std::string getName(void) const { return _name; }
|
||||||
std::string getArtistName(void) const { return _artistName; }
|
std::string getArtistName(void) const { return _artistName; }
|
||||||
std::string getReleaseName(void) const { return _releaseName; }
|
std::string getReleaseName(void) const { return _releaseName; }
|
||||||
const std::string& getPath(void) const { return _filePath; }
|
boost::filesystem::path getPath(void) const { return _filePath; }
|
||||||
boost::posix_time::time_duration getDuration(void) const { return _duration; }
|
boost::posix_time::time_duration getDuration(void) const { return _duration; }
|
||||||
boost::posix_time::ptime getDate(void) const { return _date; }
|
boost::posix_time::ptime getDate(void) const { return _date; }
|
||||||
boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; }
|
boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; }
|
||||||
@@ -157,7 +163,7 @@ class Track
|
|||||||
|
|
||||||
boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; }
|
boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; }
|
||||||
const std::vector<unsigned char>& getChecksum(void) const { return _fileChecksum; }
|
const std::vector<unsigned char>& getChecksum(void) const { return _fileChecksum; }
|
||||||
bool hasCover(void) const { return _hasCover; }
|
CoverType getCoverType(void) const { return _coverType; }
|
||||||
|
|
||||||
template<class Action>
|
template<class Action>
|
||||||
void persist(Action& a)
|
void persist(Action& a)
|
||||||
@@ -174,7 +180,7 @@ class Track
|
|||||||
Wt::Dbo::field(a, _filePath, "path");
|
Wt::Dbo::field(a, _filePath, "path");
|
||||||
Wt::Dbo::field(a, _fileLastWrite, "last_write");
|
Wt::Dbo::field(a, _fileLastWrite, "last_write");
|
||||||
Wt::Dbo::field(a, _fileChecksum, "checksum");
|
Wt::Dbo::field(a, _fileChecksum, "checksum");
|
||||||
Wt::Dbo::field(a, _hasCover, "has_cover");
|
Wt::Dbo::field(a, _coverType, "cover_type");
|
||||||
Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
||||||
Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track");
|
Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track");
|
||||||
}
|
}
|
||||||
@@ -199,7 +205,7 @@ class Track
|
|||||||
std::string _filePath;
|
std::string _filePath;
|
||||||
std::vector<unsigned char> _fileChecksum;
|
std::vector<unsigned char> _fileChecksum;
|
||||||
boost::posix_time::ptime _fileLastWrite;
|
boost::posix_time::ptime _fileLastWrite;
|
||||||
bool _hasCover;
|
CoverType _coverType;
|
||||||
|
|
||||||
Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track
|
Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track
|
||||||
Wt::Dbo::collection< Wt::Dbo::ptr<PlaylistEntry> > _playlistEntries;
|
Wt::Dbo::collection< Wt::Dbo::ptr<PlaylistEntry> > _playlistEntries;
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ class Logger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
Logger(const Logger&) = delete;
|
||||||
|
Logger& operator=(const Logger&) = delete;
|
||||||
|
|
||||||
static Logger& instance();
|
static Logger& instance();
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
@@ -76,7 +79,6 @@ class Logger
|
|||||||
get(Module module);
|
get(Module module);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
Logger();
|
Logger();
|
||||||
|
|
||||||
std::map<Module, boost::log::sources::severity_logger< Severity > > _loggers;
|
std::map<Module, boost::log::sources::severity_logger< Severity > > _loggers;
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ int main(int argc, char* argv[])
|
|||||||
Logger::instance().init(loggerConfig);
|
Logger::instance().init(loggerConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
CoverArt::Grabber::Config config;
|
||||||
|
configReader.getCoverGrabberConfig(config);
|
||||||
|
CoverArt::Grabber::instance().init(config);
|
||||||
|
}
|
||||||
|
|
||||||
LMS_LOG(MOD_MAIN, SEV_INFO) << "Reading service configurations...";
|
LMS_LOG(MOD_MAIN, SEV_INFO) << "Reading service configurations...";
|
||||||
|
|
||||||
Service::DatabaseUpdateService::Config dbUpdateConfig;
|
Service::DatabaseUpdateService::Config dbUpdateConfig;
|
||||||
@@ -103,6 +109,7 @@ int main(int argc, char* argv[])
|
|||||||
res = EXIT_SUCCESS;
|
res = EXIT_SUCCESS;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// TODO catch setting not found exception
|
||||||
catch( libconfig::ParseException& e)
|
catch( libconfig::ParseException& e)
|
||||||
{
|
{
|
||||||
std::cerr << "Caught libconfig::ParseException! error='" << e.getError() << "', file = '" << e.getFile() << "', line = " << e.getLine() << std::endl;
|
std::cerr << "Caught libconfig::ParseException! error='" << e.getError() << "', file = '" << e.getFile() << "', line = " << e.getLine() << std::endl;
|
||||||
|
|||||||
@@ -294,23 +294,14 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::
|
|||||||
|
|
||||||
response.set_type(AudioCollectionResponse::TypeCoverArt);
|
response.set_type(AudioCollectionResponse::TypeCoverArt);
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
std::vector<CoverArt::CoverArt> coverArts;
|
||||||
Database::Track::pointer track;
|
|
||||||
|
|
||||||
switch(request.type())
|
switch(request.type())
|
||||||
{
|
{
|
||||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease:
|
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease:
|
||||||
if (request.has_release())
|
if (request.has_release())
|
||||||
{
|
{
|
||||||
SearchFilter filter;
|
coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release());
|
||||||
filter.exactMatch[SearchFilter::Field::Release].push_back(request.release());
|
|
||||||
|
|
||||||
std::vector<Database::Track::pointer> tracks
|
|
||||||
= Database::Track::getAll(_db.getSession(), filter, -1, 1 /* limit reuslt size */);
|
|
||||||
|
|
||||||
if (!tracks.empty())
|
|
||||||
track = tracks.front();
|
|
||||||
|
|
||||||
res = true;
|
res = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -318,19 +309,12 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::
|
|||||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack:
|
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack:
|
||||||
if (request.has_track_id())
|
if (request.has_track_id())
|
||||||
{
|
{
|
||||||
// Get the request release
|
coverArts = CoverArt::Grabber::instance().getFromTrack(_db.getSession(), request.track_id());
|
||||||
track = Database::Track::getById( _db.getSession(), request.track_id());
|
|
||||||
|
|
||||||
res = true;
|
res = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::vector<CoverArt::CoverArt> coverArts = CoverArt::Grabber::getFromTrack(track);
|
|
||||||
|
|
||||||
BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts)
|
BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts)
|
||||||
{
|
{
|
||||||
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
|
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 <Wt/Http/Request>
|
||||||
|
#include <Wt/Http/Response>
|
||||||
|
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
#include "database/DatabaseHandler.hpp"
|
||||||
|
#include "transcode/Format.hpp"
|
||||||
|
#include "transcode/InputMediaFile.hpp"
|
||||||
|
#include "transcode/Parameters.hpp"
|
||||||
|
#include "transcode/AvConvTranscoder.hpp"
|
||||||
|
|
||||||
|
#include "Resource.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace RestAPI {
|
||||||
|
|
||||||
|
Resource::Resource(boost::filesystem::path dbPath)
|
||||||
|
: _dbPath(dbPath)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Resource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
|
||||||
|
{
|
||||||
|
static const std::size_t _bufferSize = 65536;
|
||||||
|
LMS_LOG(MOD_REST_API, SEV_DEBUG) << "Handle request...";
|
||||||
|
|
||||||
|
// see if this request is for a continuation:
|
||||||
|
Wt::Http::ResponseContinuation *continuation = request.continuation();
|
||||||
|
|
||||||
|
LMS_LOG(MOD_UI, SEV_DEBUG) << "Handling request. Continuation = " << std::boolalpha << continuation;
|
||||||
|
|
||||||
|
std::shared_ptr<Transcode::AvConvTranscoder> transcoder;
|
||||||
|
if (continuation)
|
||||||
|
{
|
||||||
|
transcoder = boost::any_cast<std::shared_ptr<Transcode::AvConvTranscoder> >(continuation->data());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const Wt::Http::ParameterMap& parameters = request.getParameterMap();
|
||||||
|
|
||||||
|
for (auto itParameter : parameters)
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_REST_API, SEV_DEBUG) << "Param name: '" << itParameter.first << "'";
|
||||||
|
|
||||||
|
for (auto value : itParameter.second)
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_REST_API, SEV_DEBUG) << "\tvalue: '" << value << "'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto itParamMediaId = parameters.find("mediaid");
|
||||||
|
if (itParamMediaId == parameters.end())
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_REST_API, SEV_DEBUG) << "Cannot find parameter mediaid";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mediaId = itParamMediaId->second.front();
|
||||||
|
LMS_LOG(MOD_REST_API, SEV_DEBUG) << "MediaId = " << mediaId;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Database::Handler db(_dbPath);
|
||||||
|
|
||||||
|
Wt::Dbo::Transaction transaction(db.getSession());
|
||||||
|
|
||||||
|
Database::Track::pointer track = Database::Track::getById(db.getSession(), std::stol(mediaId));
|
||||||
|
|
||||||
|
LMS_LOG(MOD_UI, SEV_DEBUG) << "Launching transcoder";
|
||||||
|
Transcode::InputMediaFile input(track->getPath());
|
||||||
|
Transcode::Parameters parameters(input, Transcode::Format::get(Transcode::Format::OGA));
|
||||||
|
|
||||||
|
transcoder = std::make_shared<Transcode::AvConvTranscoder>(parameters);
|
||||||
|
|
||||||
|
LMS_LOG(MOD_UI, SEV_DEBUG) << "Mime type set to '" << parameters.getOutputFormat().getMimeType() << "'";
|
||||||
|
response.setMimeType(parameters.getOutputFormat().getMimeType());
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(std::exception &e)
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_UI, SEV_DEBUG) << "Caught exception: " << e.what();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transcoder)
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_UI, SEV_ERROR) << "No transcoder ?!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transcoder->isComplete())
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> data;
|
||||||
|
data.reserve(_bufferSize);
|
||||||
|
|
||||||
|
transcoder->process(data, _bufferSize);
|
||||||
|
|
||||||
|
// Give the client all the output data
|
||||||
|
response.out().write(reinterpret_cast<char*>(&data[0]), data.size());
|
||||||
|
|
||||||
|
LMS_LOG(MOD_UI, SEV_DEBUG) << "Written " << data.size() << " bytes! complete = " << std::boolalpha << transcoder->isComplete() << ", produced bytes = " << transcoder->getOutputBytes();
|
||||||
|
|
||||||
|
if (!response.out())
|
||||||
|
LMS_LOG(MOD_UI, SEV_ERROR) << "Write failed!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transcoder->isComplete() && response.out()) {
|
||||||
|
continuation = response.createContinuation();
|
||||||
|
continuation->setData(transcoder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LMS_LOG(MOD_UI, SEV_DEBUG) << "No more data!";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace API
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef API_RESOURCE_HPP
|
||||||
|
#define API_RESOURCE_HPP
|
||||||
|
|
||||||
|
#include <Wt/WResource>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace RestAPI {
|
||||||
|
|
||||||
|
class Resource : public Wt::WResource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Resource(boost::filesystem::path dbPath);
|
||||||
|
|
||||||
|
void handleRequest(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
boost::filesystem::path _dbPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace API
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -96,8 +96,6 @@ InputMediaFile::InputMediaFile(const boost::filesystem::path& p)
|
|||||||
else
|
else
|
||||||
LMS_LOG(MOD_TRANSCODE, SEV_WARNING) << "Cannot find best stream for type " << type;
|
LMS_LOG(MOD_TRANSCODE, SEV_WARNING) << "Cannot find best stream for type " << type;
|
||||||
}
|
}
|
||||||
|
|
||||||
_covers = CoverArt::Grabber::getFromInputFormatContext(input);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Stream>
|
std::vector<Stream>
|
||||||
|
|||||||
@@ -34,8 +34,6 @@
|
|||||||
namespace Transcode
|
namespace Transcode
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InputMediaFile
|
class InputMediaFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -53,9 +51,6 @@ class InputMediaFile
|
|||||||
boost::filesystem::path getPath(void) const {return _path;}
|
boost::filesystem::path getPath(void) const {return _path;}
|
||||||
boost::posix_time::time_duration getDuration(void) const {return _duration;}
|
boost::posix_time::time_duration getDuration(void) const {return _duration;}
|
||||||
|
|
||||||
// Pictures
|
|
||||||
const std::vector< CoverArt::CoverArt >& getCovers(void) const { return _covers; }
|
|
||||||
|
|
||||||
// Stream handling
|
// Stream handling
|
||||||
const Stream& getStream(Stream::Id id) const;
|
const Stream& getStream(Stream::Id id) const;
|
||||||
std::vector<Stream> getStreams(Stream::Type type) const;
|
std::vector<Stream> getStreams(Stream::Type type) const;
|
||||||
@@ -68,8 +63,6 @@ class InputMediaFile
|
|||||||
|
|
||||||
std::vector<Stream> _streams;
|
std::vector<Stream> _streams;
|
||||||
std::map<Stream::Type, Stream::Id> _bestStreams;
|
std::map<Stream::Type, Stream::Id> _bestStreams;
|
||||||
|
|
||||||
std::vector< CoverArt::CoverArt > _covers;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ AudioMediaPlayer::AudioMediaPlayer(Wt::WContainerWidget *parent)
|
|||||||
mainLayout->addLayout(currentMediaLayout, 1);
|
mainLayout->addLayout(currentMediaLayout, 1);
|
||||||
|
|
||||||
currentMediaLayout->addWidget( _mediaCover = new Wt::WImage());
|
currentMediaLayout->addWidget( _mediaCover = new Wt::WImage());
|
||||||
_mediaCover->setImageLink( LmsApplication::instance()->getCoverResource()->getUnkownTrackUrl(72));
|
_mediaCover->setImageLink( LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(72));
|
||||||
_mediaCover->setStyleClass("mediaplayer-current-cover");
|
_mediaCover->setStyleClass("mediaplayer-current-cover");
|
||||||
|
|
||||||
Wt::WVBoxLayout* mediaInfoLayout = new Wt::WVBoxLayout();
|
Wt::WVBoxLayout* mediaInfoLayout = new Wt::WVBoxLayout();
|
||||||
|
|||||||
@@ -382,10 +382,11 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
|
|||||||
_model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole);
|
_model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole);
|
||||||
|
|
||||||
std::string coverUrl;
|
std::string coverUrl;
|
||||||
if (track->hasCover())
|
if (track->getCoverType() != Database::Track::CoverType::None)
|
||||||
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
|
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
|
||||||
else
|
else
|
||||||
coverUrl = LmsApplication::instance()->getCoverResource()->getUnkownTrackUrl(64);
|
coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64);
|
||||||
|
|
||||||
_model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole);
|
_model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole);
|
||||||
_model->setData(dataRow, COLUMN_ID_COVER, "playqueue-cover", Wt::StyleClassRole);
|
_model->setData(dataRow, COLUMN_ID_COVER, "playqueue-cover", Wt::StyleClassRole);
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ CoverResource::getTrackUrl(Database::Track::id_type trackId, std::size_t size) c
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
CoverResource::getUnkownTrackUrl(size_t size) const
|
CoverResource::getUnknownTrackUrl(size_t size) const
|
||||||
{
|
{
|
||||||
return url() + "&size=" + std::to_string(size);
|
return url() + "&size=" + std::to_string(size);
|
||||||
}
|
}
|
||||||
@@ -120,8 +120,8 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
|||||||
if (trackIdStr)
|
if (trackIdStr)
|
||||||
{
|
{
|
||||||
Database::Track::id_type trackId = std::stol(*trackIdStr);
|
Database::Track::id_type trackId = std::stol(*trackIdStr);
|
||||||
std::string path;
|
boost::filesystem::path path;
|
||||||
bool hasCover = false;
|
Database::Track::CoverType coverType = Database::Track::CoverType::None;
|
||||||
|
|
||||||
{
|
{
|
||||||
// transactions are not thread safe
|
// transactions are not thread safe
|
||||||
@@ -132,13 +132,24 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
|||||||
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
|
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
|
||||||
if (track)
|
if (track)
|
||||||
{
|
{
|
||||||
hasCover = track->hasCover();
|
coverType = track->getCoverType();
|
||||||
path = track->getPath();
|
path = track->getPath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCover)
|
switch (coverType)
|
||||||
covers = CoverArt::Grabber::getFromTrack(path);
|
{
|
||||||
|
case Database::Track::CoverType::Embedded:
|
||||||
|
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:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (releaseStr)
|
else if (releaseStr)
|
||||||
{
|
{
|
||||||
@@ -146,7 +157,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
|||||||
std::unique_lock<std::mutex> lock(_mutex);
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||||
|
|
||||||
covers = CoverArt::Grabber::getFromRelease(_db.getSession(), *releaseStr);
|
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), *releaseStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FOREACH(CoverArt::CoverArt& cover, covers)
|
BOOST_FOREACH(CoverArt::CoverArt& cover, covers)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class CoverResource : public Wt::WResource
|
|||||||
|
|
||||||
std::string getReleaseUrl(std::string releaseName, size_t size) const;
|
std::string getReleaseUrl(std::string releaseName, size_t size) const;
|
||||||
std::string getTrackUrl(Database::Track::id_type trackId, size_t size) const;
|
std::string getTrackUrl(Database::Track::id_type trackId, size_t size) const;
|
||||||
std::string getUnkownTrackUrl(size_t size) const;
|
std::string getUnknownTrackUrl(size_t size) const;
|
||||||
|
|
||||||
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response);
|
void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user