Merge branch 'simplification' into develop
Conflicts: README.md
This commit is contained in:
+10
-61
@@ -2,14 +2,8 @@ bin_PROGRAMS = lms
|
||||
|
||||
lms_SOURCES = \
|
||||
$(srcdir)/main/main.cpp \
|
||||
$(srcdir)/av/CodecContext.cpp \
|
||||
$(srcdir)/av/Common.cpp \
|
||||
$(srcdir)/av/Dictionary.cpp \
|
||||
$(srcdir)/av/FormatContext.cpp \
|
||||
$(srcdir)/av/InputFormatContext.cpp \
|
||||
$(srcdir)/av/Stream.cpp \
|
||||
$(srcdir)/config/ConfigReader.cpp \
|
||||
$(srcdir)/cover/CoverArt.cpp \
|
||||
$(srcdir)/av/AvInfo.cpp \
|
||||
$(srcdir)/av/AvTranscoder.cpp \
|
||||
$(srcdir)/cover/CoverArtGrabber.cpp \
|
||||
$(srcdir)/database/Artist.cpp \
|
||||
$(srcdir)/database/DatabaseHandler.cpp \
|
||||
@@ -23,16 +17,12 @@ 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 \
|
||||
$(srcdir)/service/ServiceManager.cpp \
|
||||
$(srcdir)/service/DatabaseUpdateService.cpp \
|
||||
$(srcdir)/service/UserInterfaceService.cpp \
|
||||
$(srcdir)/transcode/AvConvTranscoder.cpp \
|
||||
$(srcdir)/transcode/Format.cpp \
|
||||
$(srcdir)/transcode/Parameters.cpp \
|
||||
$(srcdir)/transcode/InputMediaFile.cpp \
|
||||
$(srcdir)/ui/LmsApplication.cpp \
|
||||
$(srcdir)/ui/auth/LmsAuth.cpp \
|
||||
$(srcdir)/ui/audio/desktop/DesktopAudio.cpp \
|
||||
@@ -51,10 +41,6 @@ lms_SOURCES = \
|
||||
$(srcdir)/ui/common/LineEdit.cpp \
|
||||
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
|
||||
$(srcdir)/ui/resource/CoverResource.cpp \
|
||||
$(srcdir)/ui/video/VideoWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoDatabaseWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoParametersDialog.cpp \
|
||||
$(srcdir)/ui/settings/Settings.cpp \
|
||||
$(srcdir)/ui/settings/SettingsAccountFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsAudioFormView.cpp \
|
||||
@@ -65,51 +51,14 @@ lms_SOURCES = \
|
||||
$(srcdir)/ui/settings/SettingsUserFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsUsers.cpp
|
||||
|
||||
if LMSAPI
|
||||
if VIDEO
|
||||
lms_SOURCES += \
|
||||
$(srcdir)/lms-api/server/Connection.cpp \
|
||||
$(srcdir)/lms-api/server/ConnectionManager.cpp \
|
||||
$(srcdir)/lms-api/server/AudioCollectionRequestHandler.cpp \
|
||||
$(srcdir)/lms-api/server/AuthRequestHandler.cpp \
|
||||
$(srcdir)/lms-api/server/MediaRequestHandler.cpp \
|
||||
$(srcdir)/lms-api/server/RequestHandler.cpp \
|
||||
$(srcdir)/lms-api/server/Server.cpp \
|
||||
$(srcdir)/service/LmsAPIServerService.cpp
|
||||
|
||||
nodist_lms_SOURCES = \
|
||||
$(builddir)/auth.pb.cc \
|
||||
$(builddir)/auth.pb.h \
|
||||
$(builddir)/collection.pb.cc \
|
||||
$(builddir)/collection.pb.h \
|
||||
$(builddir)/media.pb.cc \
|
||||
$(builddir)/media.pb.h \
|
||||
$(builddir)/messages.pb.cc \
|
||||
$(builddir)/messages.pb.h
|
||||
|
||||
BUILT_SOURCES = \
|
||||
$(builddir)/auth.pb.cc \
|
||||
$(builddir)/auth.pb.h \
|
||||
$(builddir)/collection.pb.cc \
|
||||
$(builddir)/collection.pb.h \
|
||||
$(builddir)/media.pb.cc \
|
||||
$(builddir)/media.pb.h \
|
||||
$(builddir)/messages.pb.cc \
|
||||
$(builddir)/messages.pb.h
|
||||
|
||||
MOSTLYCLEANFILES = \
|
||||
$(builddir)/auth.pb.cc \
|
||||
$(builddir)/auth.pb.h \
|
||||
$(builddir)/collection.pb.cc \
|
||||
$(builddir)/collection.pb.h \
|
||||
$(builddir)/media.pb.cc \
|
||||
$(builddir)/media.pb.h \
|
||||
$(builddir)/messages.pb.cc \
|
||||
$(builddir)/messages.pb.h
|
||||
|
||||
%.pb.cc %.pb.h: $(top_srcdir)/lms-api/proto/%.proto
|
||||
$(PROTOC) --proto_path=$(top_srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^
|
||||
|
||||
$(srcdir)/ui/video/VideoWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoDatabaseWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoParametersDialog.cpp
|
||||
endif
|
||||
|
||||
lms_CXXFLAGS=-DBOOST_LOG_DYN_LINK -std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui -I$(srcdir)/lms-api
|
||||
lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS)
|
||||
|
||||
lms_LDADD=$(MAGICKXX_LIBS)
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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 <array>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AvInfo.hpp"
|
||||
|
||||
namespace Av {
|
||||
|
||||
static std::string streamType_to_string(Stream::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Stream::Type::Audio: return "audio";
|
||||
case Stream::Type::Video: return "video";
|
||||
case Stream::Type::Subtitle: return "subtitle";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static std::string averror_to_string(int error)
|
||||
{
|
||||
std::array<char, 128> buf = {0};
|
||||
|
||||
if (av_strerror(error, buf.data(), buf.size()) == 0)
|
||||
return std::string(&buf[0]);
|
||||
else
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
|
||||
void AvInit()
|
||||
{
|
||||
/* register all the codecs */
|
||||
avcodec_register_all();
|
||||
av_register_all();
|
||||
LMS_LOG(AV, INFO) << "avcodec version = " << avcodec_version();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MediaFile::MediaFile(const boost::filesystem::path& p)
|
||||
: _p(p), _context(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
MediaFile::~MediaFile()
|
||||
{
|
||||
if (_context != nullptr)
|
||||
avformat_close_input(&_context);
|
||||
}
|
||||
|
||||
bool
|
||||
MediaFile::open(void)
|
||||
{
|
||||
if (_context != nullptr)
|
||||
throw std::logic_error("inputfile '" + _p.string() + "' already open");
|
||||
|
||||
int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr);
|
||||
if (error < 0)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Cannot open '" << _p.string() << "', avformat_open_input returned " << averror_to_string(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaFile::scan(void)
|
||||
{
|
||||
if (_context == nullptr)
|
||||
throw std::logic_error("inputfile '" + _p.string() + "' not open");
|
||||
|
||||
int error = avformat_find_stream_info(_context, nullptr);
|
||||
if (error < 0)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Cannot find stream information: '" << averror_to_string(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::posix_time::time_duration
|
||||
MediaFile::getDuration() const
|
||||
{
|
||||
if (_context == nullptr)
|
||||
throw std::logic_error("inputfile '" + _p.string() + "' not open");
|
||||
|
||||
if (static_cast<int>(_context->duration) != AV_NOPTS_VALUE )
|
||||
return boost::posix_time::seconds(_context->duration / AV_TIME_BASE);
|
||||
else
|
||||
return boost::posix_time::seconds(0); // TODO, do something better?
|
||||
}
|
||||
|
||||
void
|
||||
getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map<std::string, std::string>& res)
|
||||
{
|
||||
if (!dictionnary)
|
||||
return;
|
||||
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
while ((tag = av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
|
||||
{
|
||||
res.insert( std::make_pair(tag->key, tag->value));
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string>
|
||||
MediaFile::getMetaData(void)
|
||||
{
|
||||
if (_context == nullptr)
|
||||
throw std::logic_error("inputfile '" + _p.string() + "' not open");
|
||||
|
||||
std::map<std::string, std::string> res;
|
||||
|
||||
getMetaDataFromDictionnary(_context->metadata, res);
|
||||
|
||||
// HACK for OGG files
|
||||
// If we did not find tags, search metadata in streams
|
||||
if (res.empty())
|
||||
{
|
||||
for (std::size_t i = 0; i < _context->nb_streams; ++i)
|
||||
{
|
||||
getMetaDataFromDictionnary(_context->streams[i]->metadata, res);
|
||||
|
||||
if (!res.empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<Stream>
|
||||
MediaFile::getStreams(Stream::Type type) const
|
||||
{
|
||||
if (_context == nullptr)
|
||||
throw std::logic_error("inputfile '" + _p.string() + "' not open");
|
||||
|
||||
std::vector<Stream> res;
|
||||
|
||||
for (std::size_t i = 0; i < _context->nb_streams; ++i)
|
||||
{
|
||||
AVStream* avstream = _context->streams[i];
|
||||
|
||||
// Skip attached pics
|
||||
if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
|
||||
continue;
|
||||
|
||||
if (avstream->codec == nullptr)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codec is set";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == Stream::Type::Audio && avstream->codec->codec_type != AVMEDIA_TYPE_AUDIO)
|
||||
continue;
|
||||
else if (type == Stream::Type::Video && avstream->codec->codec_type != AVMEDIA_TYPE_VIDEO)
|
||||
continue;
|
||||
else if (type == Stream::Type::Subtitle && avstream->codec->codec_type != AVMEDIA_TYPE_SUBTITLE)
|
||||
continue;
|
||||
|
||||
Stream stream;
|
||||
|
||||
stream.id = i; // or use stream->id ?
|
||||
stream.type = type;
|
||||
stream.bitrate = avstream->codec->bit_rate;
|
||||
|
||||
{
|
||||
std::array<char, 256> buf = {0};
|
||||
avcodec_string(buf.data(), buf.size(), avstream->codec, 0);
|
||||
|
||||
stream.desc = buf.data();
|
||||
}
|
||||
|
||||
res.push_back(stream);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
MediaFile::getBestStreamId(Stream::Type type) const
|
||||
{
|
||||
if (_context == nullptr)
|
||||
throw std::logic_error("inputfile '" + _p.string() + "' not open");
|
||||
|
||||
enum AVMediaType avMediaType;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Stream::Type::Audio: avMediaType = AVMEDIA_TYPE_AUDIO; break;
|
||||
case Stream::Type::Video: avMediaType = AVMEDIA_TYPE_VIDEO; break;
|
||||
case Stream::Type::Subtitle: avMediaType = AVMEDIA_TYPE_SUBTITLE; break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int res = av_find_best_stream(_context,
|
||||
avMediaType,
|
||||
-1, // Auto
|
||||
-1, // Auto
|
||||
NULL,
|
||||
0);
|
||||
if (res < 0)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Cannot find best stream for type " << streamType_to_string(type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaFile::hasAttachedPictures(void) const
|
||||
{
|
||||
for (std::size_t i = 0; i < _context->nb_streams; ++i)
|
||||
{
|
||||
if (_context->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Picture>
|
||||
MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const
|
||||
{
|
||||
if (_context == nullptr)
|
||||
throw std::logic_error("inputfile '" + _p.string() + "' not open");
|
||||
|
||||
static const std::map<int, std::string> 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" },
|
||||
};
|
||||
|
||||
std::vector<Picture> pictures;
|
||||
|
||||
for (std::size_t i = 0; i < _context->nb_streams; ++i)
|
||||
{
|
||||
AVStream *avstream = _context->streams[i];
|
||||
|
||||
// Skip attached pics
|
||||
if (!(avstream->disposition & AV_DISPOSITION_ATTACHED_PIC))
|
||||
continue;
|
||||
|
||||
if (avstream->codec == nullptr)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codec is set";
|
||||
continue;
|
||||
}
|
||||
|
||||
Picture picture;
|
||||
|
||||
auto itMime = codecMimeMap.find(avstream->codec->codec_id);
|
||||
if (itMime != codecMimeMap.end())
|
||||
{
|
||||
picture.mimeType = itMime->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
picture.mimeType = "application/octet-stream";
|
||||
LMS_LOG(AV, ERROR) << "CODEC ID " << avstream->codec->codec_id << " not handled in mime type conversion";
|
||||
}
|
||||
|
||||
AVPacket pkt = avstream->attached_pic;
|
||||
|
||||
std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data));
|
||||
|
||||
pictures.push_back( picture );
|
||||
|
||||
if (pictures.size() >= nbMaxPictures)
|
||||
break;
|
||||
}
|
||||
|
||||
return pictures;
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* This file contains some classes in order to get info from file using the libavconv */
|
||||
|
||||
#ifndef AV_INFO_HPP
|
||||
#define AV_INFO_HPP
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/error.h>
|
||||
}
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
void AvInit();
|
||||
|
||||
struct Picture
|
||||
{
|
||||
std::string mimeType;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
struct Stream
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
Audio,
|
||||
Video,
|
||||
Subtitle,
|
||||
};
|
||||
|
||||
int id;
|
||||
Type type;
|
||||
std::size_t bitrate;
|
||||
std::string desc; // Description of the stream
|
||||
};
|
||||
|
||||
class MediaFile
|
||||
{
|
||||
public:
|
||||
|
||||
MediaFile(const boost::filesystem::path& p);
|
||||
~MediaFile();
|
||||
|
||||
// non copyable
|
||||
MediaFile(const MediaFile&) = delete;
|
||||
MediaFile& operator=(const MediaFile&) = delete;
|
||||
|
||||
boost::filesystem::path getPath() const {return _p;};
|
||||
|
||||
bool open(void);
|
||||
bool scan(void);
|
||||
|
||||
boost::posix_time::time_duration getDuration() const;
|
||||
std::map<std::string, std::string> getMetaData(void);
|
||||
|
||||
std::vector<Stream> getStreams(Stream::Type type) const;
|
||||
int getBestStreamId(Stream::Type type) const; // -1 if failure/unknown
|
||||
bool hasAttachedPictures(void) const;
|
||||
std::vector<Picture> getAttachedPictures(std::size_t nbMaxPictures) const;
|
||||
|
||||
private:
|
||||
MediaFile();
|
||||
|
||||
boost::filesystem::path _p;
|
||||
AVFormatContext* _context;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
* Copyright (C) 2015 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
@@ -17,18 +17,29 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AvConvTranscoder.hpp"
|
||||
#include "AvTranscoder.hpp"
|
||||
|
||||
namespace Transcode
|
||||
namespace Av {
|
||||
|
||||
std::string encoding_to_mimetype(Encoding encoding)
|
||||
{
|
||||
switch(encoding)
|
||||
{
|
||||
case Encoding::MP3: return "audio/mp3";
|
||||
case Encoding::OGA: return "audio/ogg";
|
||||
case Encoding::OGV: return "video/ogg";
|
||||
case Encoding::WEBMA: return "audio/webm";
|
||||
case Encoding::WEBMV: return "video/webm";
|
||||
case Encoding::FLA: return "audio/x-flv";
|
||||
case Encoding::FLV: return "video/x-flv";
|
||||
case Encoding::M4A: return "audio/mp4";
|
||||
case Encoding::M4V: return "video/mp4";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// TODO, parametrize?
|
||||
const std::vector<std::string> execNames =
|
||||
@@ -37,15 +48,14 @@ const std::vector<std::string> execNames =
|
||||
"ffmpeg",
|
||||
};
|
||||
|
||||
boost::mutex Transcoder::_mutex;
|
||||
|
||||
boost::mutex AvConvTranscoder::_mutex;
|
||||
|
||||
boost::filesystem::path AvConvTranscoder::_avConvPath = boost::filesystem::path();
|
||||
boost::filesystem::path Transcoder::_avConvPath = boost::filesystem::path();
|
||||
|
||||
void
|
||||
AvConvTranscoder::init()
|
||||
Transcoder::init()
|
||||
{
|
||||
BOOST_FOREACH(std::string execName, execNames)
|
||||
for (std::string execName : execNames)
|
||||
{
|
||||
const boost::filesystem::path p = boost::process::search_path(execName);
|
||||
if (!p.empty())
|
||||
@@ -56,32 +66,35 @@ AvConvTranscoder::init()
|
||||
}
|
||||
|
||||
if (!_avConvPath.empty())
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Using transcoder " << _avConvPath;
|
||||
LMS_LOG(TRANSCODE, INFO) << "Using transcoder " << _avConvPath;
|
||||
else
|
||||
throw std::runtime_error("Cannot find any transcoder binary!");
|
||||
}
|
||||
|
||||
|
||||
//boost::filesystem::path AvConvTranscoder::_avConvPath = "";
|
||||
//boost::filesystem::path Transcoder::_avConvPath = "";
|
||||
|
||||
AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
|
||||
: _parameters(parameters),
|
||||
Transcoder::Transcoder(boost::filesystem::path filePath, TranscodeParameters parameters)
|
||||
: _filePath(filePath),
|
||||
_parameters(parameters),
|
||||
_outputPipe(boost::process::create_pipe()),
|
||||
_source(_outputPipe.source, boost::iostreams::close_handle),
|
||||
_is(_source),
|
||||
_in(&_is),
|
||||
_isComplete(false),
|
||||
_outputBytes(0)
|
||||
_isComplete(false)
|
||||
{
|
||||
|
||||
if (!boost::filesystem::exists(_parameters.getInputMediaFile().getPath())) {
|
||||
throw std::runtime_error("File " + _parameters.getInputMediaFile().getPath().string() + " does not exists!");
|
||||
}
|
||||
else if (!boost::filesystem::is_regular( _parameters.getInputMediaFile().getPath())) {
|
||||
throw std::runtime_error("File " + _parameters.getInputMediaFile().getPath().string() + " is not regular!");
|
||||
}
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Transcoding file '" << _parameters.getInputMediaFile().getPath() << "'";
|
||||
bool
|
||||
Transcoder::start()
|
||||
{
|
||||
if (!boost::filesystem::exists(_filePath))
|
||||
return false;
|
||||
else if (!boost::filesystem::is_regular( _filePath) )
|
||||
return false;
|
||||
|
||||
LMS_LOG(TRANSCODE, INFO) << "Transcoding file '" << _filePath << "'";
|
||||
|
||||
// Launch a process to handle the conversion
|
||||
boost::iostreams::file_descriptor_sink sink(_outputPipe.sink, boost::iostreams::close_handle);
|
||||
@@ -92,66 +105,59 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
|
||||
|
||||
// input Offset
|
||||
if (_parameters.getOffset().total_seconds() > 0)
|
||||
oss << " -ss " << _parameters.getOffset().total_seconds(); // to be placed before '-i' to speed up seeking
|
||||
oss << " -ss " << _parameters.getOffset().total_seconds(); // to be placed before '-i' to speed up seeking?
|
||||
|
||||
// Input file
|
||||
oss << " -i \"" << _parameters.getInputMediaFile().getPath().string() << "\"";
|
||||
oss << " -i " << _filePath;
|
||||
|
||||
// Output bitrates
|
||||
oss << " -b:a " << _parameters.getOutputBitrate(Stream::Audio) ;
|
||||
if (_parameters.getOutputFormat().getType() == Format::Video)
|
||||
oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video);
|
||||
oss << " -b:a " << _parameters.getBitrate(Stream::Type::Audio) ;
|
||||
// if (_parameters.getOutputFormat().getType() == Format::Video)
|
||||
// oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video);
|
||||
|
||||
// Stream mapping
|
||||
for (int streamId : _parameters.getSelectedStreamIds())
|
||||
{
|
||||
typedef std::map<Stream::Type, Stream::Id> StreamMap;
|
||||
|
||||
const StreamMap& streamMap = _parameters.getInputStreams();
|
||||
|
||||
BOOST_FOREACH(const StreamMap::value_type& inputStream, streamMap)
|
||||
{
|
||||
// HACK (no subtitle support yet)
|
||||
if (inputStream.first != Stream::Subtitle)
|
||||
oss << " -map 0:" << inputStream.second; // 0 means input file index
|
||||
}
|
||||
// 0 means the first input file
|
||||
oss << " -map 0:" << streamId;
|
||||
}
|
||||
|
||||
// Codecs and formats
|
||||
switch( _parameters.getOutputFormat().getEncoding())
|
||||
switch( _parameters.getEncoding())
|
||||
{
|
||||
case Format::MP3:
|
||||
case Encoding::MP3:
|
||||
oss << " -f mp3";
|
||||
break;
|
||||
case Format::OGA:
|
||||
case Encoding::OGA:
|
||||
oss << " -acodec libvorbis -f ogg";
|
||||
break;
|
||||
case Format::OGV:
|
||||
case Encoding::OGV:
|
||||
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libtheora -threads 4 -f ogg";
|
||||
break;
|
||||
case Format::WEBMA:
|
||||
case Encoding::WEBMA:
|
||||
oss << " -codec:a libvorbis -f webm";
|
||||
break;
|
||||
case Format::WEBMV:
|
||||
case Encoding::WEBMV:
|
||||
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libvpx -threads 4 -f webm";
|
||||
break;
|
||||
case Format::M4A:
|
||||
case Encoding::M4A:
|
||||
oss << " -acodec aac -f mp4 -strict experimental";
|
||||
break;
|
||||
case Format::M4V:
|
||||
case Encoding::M4V:
|
||||
oss << " -acodec aac -strict experimental -ac 2 -ar 44100 -vcodec libx264 -f m4v";
|
||||
break;
|
||||
case Format::FLV:
|
||||
case Encoding::FLV:
|
||||
oss << " -acodec libmp3lame -ac 2 -ar 44100 -vcodec libx264 -f flv";
|
||||
break;
|
||||
case Format::FLA:
|
||||
case Encoding::FLA:
|
||||
oss << " -acodec libmp3lame -f flv";
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
return false;
|
||||
}
|
||||
oss << " -"; // output to stdout
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Executing '" << oss.str() << "'";
|
||||
LMS_LOG(TRANSCODE, DEBUG) << "Executing '" << oss.str() << "'";
|
||||
|
||||
// make sure only one thread is executing this part of code
|
||||
// See boost process FAQ
|
||||
@@ -167,10 +173,11 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
AvConvTranscoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
|
||||
Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
|
||||
{
|
||||
std::size_t readDataSize = 0;
|
||||
|
||||
@@ -181,11 +188,10 @@ AvConvTranscoder::process(std::vector<unsigned char>& output, std::size_t maxSiz
|
||||
while(readDataSize < maxSize && _in && _in.get(ch)) {
|
||||
output.push_back(ch);
|
||||
readDataSize++;
|
||||
_outputBytes++; // stats
|
||||
}
|
||||
|
||||
if (!_in || _in.fail() || _in.eof()) {
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Transcode complete!";
|
||||
LMS_LOG(TRANSCODE, DEBUG) << "Transcode complete!";
|
||||
|
||||
waitChild();
|
||||
_isComplete = true;
|
||||
@@ -193,9 +199,9 @@ AvConvTranscoder::process(std::vector<unsigned char>& output, std::size_t maxSiz
|
||||
}
|
||||
|
||||
|
||||
AvConvTranscoder::~AvConvTranscoder()
|
||||
Transcoder::~Transcoder()
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "~AvConvTranscoder called!";
|
||||
LMS_LOG(TRANSCODE, DEBUG) << "~Transcoder called!";
|
||||
|
||||
if (_in.eof())
|
||||
waitChild();
|
||||
@@ -204,40 +210,46 @@ AvConvTranscoder::~AvConvTranscoder()
|
||||
}
|
||||
|
||||
void
|
||||
AvConvTranscoder::waitChild()
|
||||
Transcoder::waitChild()
|
||||
{
|
||||
if (_child)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child...";
|
||||
LMS_LOG(TRANSCODE, DEBUG) << "Waiting for child...";
|
||||
boost::process::wait_for_exit(*_child, ec);
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child: OK";
|
||||
LMS_LOG(TRANSCODE, DEBUG) << "Waiting for child: OK";
|
||||
|
||||
if (ec)
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::waitChild: error: " << ec.message();
|
||||
LMS_LOG(TRANSCODE, ERROR) << "Transcoder::waitChild: error: " << ec.message();
|
||||
|
||||
_child.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AvConvTranscoder::killChild()
|
||||
Transcoder::killChild()
|
||||
{
|
||||
if (_child)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child! pid = " << _child->pid;
|
||||
LMS_LOG(TRANSCODE, DEBUG) << "Killing child! pid = " << _child->pid;
|
||||
boost::process::terminate(*_child, ec);
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child DONE";
|
||||
LMS_LOG(TRANSCODE, DEBUG) << "Killing child DONE";
|
||||
|
||||
// If an error occured, force kill the child
|
||||
if (ec)
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::killChild: error: " << ec.message();
|
||||
LMS_LOG(TRANSCODE, ERROR) << "Transcoder::killChild: error: " << ec.message();
|
||||
|
||||
_child.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Transcoder::isComplete(void)
|
||||
{
|
||||
return _isComplete;
|
||||
}
|
||||
|
||||
} // namespace Transcode
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include "AvInfo.hpp"
|
||||
|
||||
namespace Av {
|
||||
|
||||
|
||||
enum class Encoding
|
||||
{
|
||||
OGA,
|
||||
OGV,
|
||||
MP3,
|
||||
WEBMA,
|
||||
WEBMV,
|
||||
FLA,
|
||||
FLV,
|
||||
M4A,
|
||||
M4V,
|
||||
};
|
||||
|
||||
std::string encoding_to_mimetype(Encoding encoding);
|
||||
|
||||
class TranscodeParameters
|
||||
{
|
||||
public:
|
||||
|
||||
// Setters
|
||||
void setEncoding(Encoding encoding) { _encoding = encoding; }
|
||||
void setOffset(boost::posix_time::time_duration offset) {_offset = offset; }
|
||||
void setBitrate(Stream::Type type, std::size_t bitrate) { _outputBitrate[type] = bitrate; }
|
||||
|
||||
// Manually add the streams to be transcoded
|
||||
// If no stream is added, input streams are selected automatically
|
||||
void addStream(int inputStreamId) { _selectedStreams.insert(inputStreamId); }
|
||||
|
||||
// Getters
|
||||
Encoding getEncoding(void) const { return _encoding; }
|
||||
boost::posix_time::time_duration getOffset(void) const { return _offset; }
|
||||
std::set<int> getSelectedStreamIds(void) const { return _selectedStreams; }
|
||||
std::size_t getBitrate(Stream::Type type) { return _outputBitrate[type]; }
|
||||
|
||||
private:
|
||||
Encoding _encoding = Encoding::MP3;
|
||||
boost::posix_time::time_duration _offset = boost::posix_time::seconds(0);
|
||||
std::set<int> _selectedStreams;
|
||||
std::map<Stream::Type, std::size_t> _outputBitrate = { {Stream::Type::Audio, 0}, { Stream::Type::Video, 0}, { Stream::Type::Subtitle, 0} };
|
||||
};
|
||||
|
||||
|
||||
class Transcoder
|
||||
{
|
||||
public:
|
||||
static void init();
|
||||
|
||||
Transcoder(boost::filesystem::path file, TranscodeParameters parameters);
|
||||
~Transcoder();
|
||||
|
||||
// non copyable
|
||||
Transcoder(const Transcoder&) = delete;
|
||||
Transcoder& operator=(const Transcoder&) = delete;
|
||||
|
||||
bool start();
|
||||
void process(std::vector<unsigned char>& output, std::size_t maxSize);
|
||||
bool isComplete(void);
|
||||
|
||||
private:
|
||||
|
||||
Transcoder();
|
||||
|
||||
boost::filesystem::path _filePath;
|
||||
TranscodeParameters _parameters;
|
||||
|
||||
static boost::mutex _mutex;
|
||||
|
||||
boost::process::pipe _outputPipe;
|
||||
boost::iostreams::file_descriptor_source _source;
|
||||
boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> _is;
|
||||
std::istream _in;
|
||||
|
||||
void waitChild();
|
||||
void killChild();
|
||||
|
||||
std::shared_ptr<boost::process::child> _child;
|
||||
|
||||
static boost::filesystem::path _avConvPath;
|
||||
bool _isComplete;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <cassert>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Codec.hpp"
|
||||
|
||||
Codec::Codec(enum CodecID codec, Type type )
|
||||
: _codec(nullptr)
|
||||
{
|
||||
if (type == Encoder)
|
||||
_codec = avcodec_find_encoder(codec);
|
||||
else if (type == Decoder)
|
||||
_codec = avcodec_find_decoder(codec);
|
||||
|
||||
if (_codec == nullptr) {
|
||||
LMS_LOG(MOD_AV, SEV_ERROR) << "Codec constructor failed! codec = " << codec << ", type = " << type;
|
||||
throw std::runtime_error("can't find codec using this id!");
|
||||
}
|
||||
}
|
||||
|
||||
Codec::Codec(const AVCodec* codec)
|
||||
: _codec(codec)
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
}
|
||||
|
||||
Codec::~Codec()
|
||||
{
|
||||
if (_codec == nullptr) {
|
||||
// TODO release iif ownership has not been taken?
|
||||
// av_codec_close(_codec);
|
||||
}
|
||||
}
|
||||
|
||||
const AVCodec*
|
||||
Codec::get() const
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
return _codec;
|
||||
}
|
||||
|
||||
Codec::Id
|
||||
Codec::getId() const
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
return _codec->id;
|
||||
}
|
||||
|
||||
std::string
|
||||
Codec::getName() const
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
return _codec->name;
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 CODEC_HPP__
|
||||
#define CODEC_HPP__
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <boost/utility.hpp>
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
class Codec : boost::noncopyable
|
||||
{
|
||||
friend class CodecContext;
|
||||
friend class FormatContext;
|
||||
friend class OutputFormatContext;
|
||||
|
||||
public:
|
||||
|
||||
typedef enum AVCodecID Id;
|
||||
enum Type {
|
||||
Encoder,
|
||||
Decoder,
|
||||
};
|
||||
|
||||
Codec(Id codecId, Type type);
|
||||
Codec(const AVCodec* codec); // Attach existing codec
|
||||
~Codec();
|
||||
|
||||
Id getId() const;
|
||||
std::string getName() const;
|
||||
|
||||
private:
|
||||
|
||||
const AVCodec* get() const;
|
||||
const AVCodec* _codec;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <stdexcept>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <boost/array.hpp>
|
||||
|
||||
#include "CodecContext.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
CodecContext::CodecContext(AVCodecContext* CodecContext)
|
||||
: _codecContext(CodecContext)
|
||||
{
|
||||
assert(_codecContext != nullptr);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CodecContext::dumpInfo(std::ostream& ost) const
|
||||
{
|
||||
ost << "BitRate = " << getBitRate() << ", SampleFormat = " << getSampleFormat() << ", SampleRate = " << getSampleRate() << ", ChannelLayout = " << getChannelLayout() << ", NbChannels = " << getNbChannels() << ", Timebase = " << getTimeBase().num << "/" << getTimeBase().den;
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
CodecContext::getCodecDesc(void) const
|
||||
{
|
||||
std::array<char, 256> buf;
|
||||
avcodec_string(buf.data(), buf.size(), _codecContext, 0);
|
||||
|
||||
return std::string(buf.data());
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 CODEC_CONTEXT_HPP
|
||||
#define CODEC_CONTEXT_HPP
|
||||
|
||||
#include <boost/utility.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "Codec.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class CodecContext
|
||||
{
|
||||
public:
|
||||
|
||||
CodecContext(AVCodecContext* CodecContext); // Attach existing codec context (no free will be done)
|
||||
|
||||
// Codec getCodec();
|
||||
|
||||
enum AVMediaType getType(void) const { return _codecContext->codec_type; }
|
||||
Codec::Id getCodecId(void) const { return _codecContext->codec_id; }
|
||||
|
||||
std::string getCodecDesc(void) const;
|
||||
|
||||
// Accessors
|
||||
std::size_t getBitRate() const {return _codecContext->bit_rate;}
|
||||
AVSampleFormat getSampleFormat() const {return _codecContext->sample_fmt;}
|
||||
std::size_t getSampleRate() const {return _codecContext->sample_rate;}
|
||||
std::uint64_t getChannelLayout() const {return _codecContext->channel_layout;}
|
||||
std::size_t getNbChannels() const {return _codecContext->channels; }
|
||||
AVRational getTimeBase() const {return _codecContext->time_base; }
|
||||
|
||||
void dumpInfo(std::ostream& ost) const;
|
||||
|
||||
private:
|
||||
AVCodecContext* native() { return _codecContext; }
|
||||
|
||||
AVCodecContext* _codecContext;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <boost/array.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
std::string
|
||||
AvError::to_str(void) const
|
||||
{
|
||||
boost::array<char, 128> buf = {0};
|
||||
if (av_strerror(_errnum, buf.data(), buf.size()) == 0)
|
||||
return std::string(&buf[0]);
|
||||
else
|
||||
return "Unknown error";
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& ost, const AvError& err)
|
||||
{
|
||||
ost << err.to_str();
|
||||
return ost;
|
||||
}
|
||||
|
||||
void AvInit()
|
||||
{
|
||||
/* register all the codecs */
|
||||
avcodec_register_all();
|
||||
av_register_all();
|
||||
LMS_LOG(MOD_AV, SEV_INFO) << "avcodec version = " << avcodec_version();
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 TRANSCODE_COMMON_HPP__
|
||||
#define TRANSCODE_COMMON_HPP__
|
||||
|
||||
// Hack to properly iinclude libavcodec/avcodec.h...
|
||||
//
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#ifdef _STDINT_H
|
||||
#undef _STDINT_H
|
||||
#endif
|
||||
# include <stdint.h>
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
}
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
|
||||
void AvInit();
|
||||
|
||||
class AvError
|
||||
{
|
||||
public:
|
||||
AvError() : _errnum(0) {}
|
||||
AvError(int ernum) : _errnum(ernum) {}
|
||||
|
||||
void operator=(int errnum) { _errnum = errnum; }
|
||||
|
||||
operator bool() { return _errnum < 0; }
|
||||
|
||||
std::string to_str() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& ost, const AvError&);
|
||||
|
||||
bool eof() { return _errnum == AVERROR_EOF; } // TODO
|
||||
|
||||
private:
|
||||
int _errnum;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 "Dictionary.hpp"
|
||||
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
Dictionary::Dictionary(AVDictionary* dictionary)
|
||||
: _dictionary(dictionary)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Dictionary::get(std::map<std::string, std::string>& entries)
|
||||
{
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
while ((tag = av_dict_get(_dictionary, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
||||
entries.insert( std::make_pair(tag->key, tag->value));
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
Dictionary::get(std::string key)
|
||||
{
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
tag = av_dict_get(_dictionary, key.c_str(), tag, 0);
|
||||
|
||||
return tag != NULL ? std::string(tag->value) : std::string();
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 AV_DICTIONARY_HPP
|
||||
#define AV_DICTIONARY_HPP
|
||||
|
||||
#include <map>
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class Dictionary
|
||||
{
|
||||
public:
|
||||
Dictionary(AVDictionary* dictionary);
|
||||
|
||||
// Get all
|
||||
void get(std::map<std::string, std::string>& entries);
|
||||
|
||||
// get a single entry
|
||||
std::string get(std::string key);
|
||||
|
||||
private:
|
||||
AVDictionary* _dictionary;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 "FormatContext.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
FormatContext::FormatContext()
|
||||
: _context(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
FormatContext::~FormatContext()
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "InputFormatContext.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
InputFormatContext::InputFormatContext(const boost::filesystem::path& p)
|
||||
: _path(p)
|
||||
{
|
||||
AVFormatContext* context = nullptr;
|
||||
|
||||
// The last three parameters specify the file format, buffer size and
|
||||
// format parameters. By simply specifying NULL or 0 we ask libavformat
|
||||
// to auto-detect the format and use a default buffer size.
|
||||
AvError error = avformat_open_input(&context, p.string().c_str(), nullptr, nullptr);
|
||||
if (error)
|
||||
{
|
||||
LMS_LOG(MOD_AV, SEV_ERROR) << "Cannot open '" << p.string() << "', avformat_open_input returned " << error;
|
||||
throw std::runtime_error("avformat_open_input failed: " + error.to_str());
|
||||
}
|
||||
|
||||
native(context);
|
||||
|
||||
}
|
||||
|
||||
InputFormatContext::~InputFormatContext()
|
||||
{
|
||||
AVFormatContext* context = native();
|
||||
avformat_close_input(&context);
|
||||
}
|
||||
|
||||
std::vector<Stream>
|
||||
InputFormatContext::getStreams(void)
|
||||
{
|
||||
std::vector<Stream> res;
|
||||
|
||||
for (std::size_t i = 0; i < native()->nb_streams; ++i) {
|
||||
res.push_back( Stream(native()->streams[i]));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
InputFormatContext::getBestStreamIdx(AVMediaType type, Stream::Idx& index)
|
||||
{
|
||||
int res = av_find_best_stream(native(),
|
||||
type,
|
||||
-1, // Auto
|
||||
-1, // Auto
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
AvError error(res);
|
||||
if (error) {
|
||||
LMS_LOG(MOD_AV, SEV_DEBUG) << "Cannot get best stream for type " << type << ": " << error;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
index = res;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
InputFormatContext::findStreamInfo(void)
|
||||
{
|
||||
AvError err = avformat_find_stream_info(native(), NULL);
|
||||
if (err)
|
||||
{
|
||||
LMS_LOG(MOD_AV, SEV_ERROR) << "Couldn't find stream information: " << err;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::size_t
|
||||
InputFormatContext::getDurationSecs() const
|
||||
{
|
||||
if (static_cast<int>(native()->duration) != AV_NOPTS_VALUE )
|
||||
return native()->duration / AV_TIME_BASE;
|
||||
else
|
||||
return 0; // TODO, do something better?
|
||||
}
|
||||
|
||||
Dictionary
|
||||
InputFormatContext::getMetadata(void)
|
||||
{
|
||||
return Dictionary(native()->metadata);
|
||||
|
||||
}
|
||||
|
||||
std::size_t
|
||||
InputFormatContext::getNbPictures(void) const
|
||||
{
|
||||
std::size_t res = 0;
|
||||
|
||||
for (std::size_t i = 0; i < native()->nb_streams; ++i)
|
||||
{
|
||||
if (native()->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)
|
||||
++res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<Picture>
|
||||
InputFormatContext::getPictures(std::size_t nbMaxPictures) const
|
||||
{
|
||||
static const std::map<int, std::string> 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" },
|
||||
};
|
||||
|
||||
std::vector<Picture> pictures;
|
||||
|
||||
for (std::size_t i = 0; i < native()->nb_streams; ++i)
|
||||
{
|
||||
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";
|
||||
|
||||
AVPacket pkt = native()->streams[i]->attached_pic;
|
||||
|
||||
std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data));
|
||||
|
||||
pictures.push_back( picture );
|
||||
|
||||
if (pictures.size() >= nbMaxPictures)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pictures;
|
||||
}
|
||||
|
||||
} //namespace Av
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 INPUT_FORMAT_CONTEXT_HPP
|
||||
#define INPUT_FORMAT_CONTEXT_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "FormatContext.hpp"
|
||||
#include "Stream.hpp"
|
||||
#include "Dictionary.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
struct Picture {
|
||||
std::string mimeType;
|
||||
std::vector<unsigned char> data;
|
||||
};
|
||||
|
||||
class InputFormatContext : public FormatContext
|
||||
{
|
||||
public:
|
||||
|
||||
InputFormatContext(const boost::filesystem::path& p);
|
||||
~InputFormatContext();
|
||||
|
||||
|
||||
Dictionary getMetadata(void); // metadata access
|
||||
|
||||
// Scan file
|
||||
bool findStreamInfo();
|
||||
|
||||
|
||||
// Get attached pictures
|
||||
std::size_t getNbPictures(void) const;
|
||||
std::vector<Picture> getPictures(std::size_t nbMaxPictures) const;
|
||||
|
||||
// Get the streams
|
||||
std::vector<Stream> getStreams(void);
|
||||
|
||||
bool getBestStreamIdx(enum AVMediaType type, Stream::Idx& idx);
|
||||
std::size_t getDurationSecs() const;
|
||||
|
||||
private:
|
||||
boost::filesystem::path _path;
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <cassert>
|
||||
#include "Stream.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
Stream::Stream(AVStream* stream)
|
||||
: _stream(stream)
|
||||
{
|
||||
assert(_stream != nullptr);
|
||||
assert(_stream->codec != nullptr);
|
||||
}
|
||||
|
||||
CodecContext
|
||||
Stream::getCodecContext()
|
||||
{
|
||||
return _stream->codec;
|
||||
}
|
||||
|
||||
Dictionary
|
||||
Stream::getMetadata(void)
|
||||
{
|
||||
return Dictionary(_stream->metadata);
|
||||
}
|
||||
|
||||
bool
|
||||
Stream::hasAttachedPic(void) const
|
||||
{
|
||||
return (_stream->disposition & AV_DISPOSITION_ATTACHED_PIC);
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 STREAM_HPP__
|
||||
#define STREAM_HPP__
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
#include "CodecContext.hpp"
|
||||
#include "Dictionary.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class Stream
|
||||
{
|
||||
friend class InputFormatContext;
|
||||
|
||||
public:
|
||||
// Attach existing stream
|
||||
Stream(AVStream* stream);
|
||||
|
||||
typedef size_t Idx;
|
||||
|
||||
// Idx getIdx() const { return _stream->index; }
|
||||
|
||||
bool hasAttachedPic(void) const;
|
||||
Dictionary getMetadata(void);
|
||||
CodecContext getCodecContext(void);
|
||||
|
||||
private:
|
||||
|
||||
AVStream* _stream;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <sstream>
|
||||
|
||||
#include "ConfigReader.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
}
|
||||
|
||||
ConfigReader::ConfigReader()
|
||||
: _config (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ConfigReader&
|
||||
ConfigReader::instance()
|
||||
{
|
||||
static ConfigReader instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigReader::setFile(boost::filesystem::path p)
|
||||
{
|
||||
if (_config != nullptr)
|
||||
delete _config;
|
||||
|
||||
_config = new libconfig::Config();
|
||||
|
||||
_config->readFile(p.string().c_str());
|
||||
}
|
||||
|
||||
std::string
|
||||
ConfigReader::getString(std::string setting, std::string def)
|
||||
{
|
||||
try {
|
||||
return _config->lookup(setting);
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long
|
||||
ConfigReader::getULong(std::string setting, unsigned long def)
|
||||
{
|
||||
try {
|
||||
return static_cast<unsigned int>(_config->lookup(setting));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
long
|
||||
ConfigReader::getLong(std::string setting, long def)
|
||||
{
|
||||
try {
|
||||
return _config->lookup(setting);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigReader::getBool(std::string setting, bool def)
|
||||
{
|
||||
try {
|
||||
return _config->lookup(setting);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 CONFIG_READER_HPP
|
||||
#define CONFIG_READER_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <libconfig.h++>
|
||||
|
||||
class ConfigReader
|
||||
{
|
||||
public:
|
||||
|
||||
ConfigReader(const ConfigReader&) = delete;
|
||||
ConfigReader& operator=(const ConfigReader&) = delete;
|
||||
|
||||
static ConfigReader& instance();
|
||||
|
||||
void setFile(boost::filesystem::path p);
|
||||
|
||||
/* Default values are returned in case of setting not found */
|
||||
std::string getString(std::string setting, std::string def = "");
|
||||
unsigned long getULong(std::string setting, unsigned long def = 0);
|
||||
long getLong(std::string setting, long def = 0);
|
||||
bool getBool(std::string setting, bool def = false);
|
||||
|
||||
private:
|
||||
|
||||
ConfigReader();
|
||||
|
||||
libconfig::Config *_config;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <boost/gil/image.hpp>
|
||||
#include <boost/gil/typedefs.hpp>
|
||||
#include <boost/gil/extension/io/jpeg_io.hpp>
|
||||
#include <boost/gil/extension/numeric/sampler.hpp>
|
||||
#include <boost/gil/extension/numeric/resample.hpp>
|
||||
#include <boost/gil/extension/io_new/jpeg_all.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "CoverArt.hpp"
|
||||
|
||||
namespace CoverArt {
|
||||
|
||||
bool
|
||||
CoverArt::scale(std::size_t size)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
if (!size)
|
||||
return false;
|
||||
|
||||
try {
|
||||
|
||||
boost::gil::rgb8_image_t source;
|
||||
boost::gil::rgb8_image_t dest(size, size);
|
||||
|
||||
// Read source
|
||||
{
|
||||
std::istringstream iss( std::string(_data.begin(), _data.end()));
|
||||
boost::gil::read_image(iss, source, boost::gil::jpeg_tag());
|
||||
}
|
||||
|
||||
if (source.width() == static_cast<int>(size)
|
||||
&& source.height() == static_cast<int>(size))
|
||||
return true;
|
||||
|
||||
// Resize
|
||||
boost::gil::resize_view(boost::gil::const_view(source),
|
||||
boost::gil::view(dest),
|
||||
boost::gil::bilinear_sampler());
|
||||
|
||||
// Output to dest
|
||||
{
|
||||
std::ostringstream oss;
|
||||
boost::gil::write_view(oss, boost::gil::const_view(dest), boost::gil::jpeg_tag());
|
||||
|
||||
std::string output = oss.str();
|
||||
_data.assign(output.begin(), output.end());
|
||||
}
|
||||
|
||||
res = true;
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Caught exception: " << e.what();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace CoverArt
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 COVER_ART_HPP
|
||||
#define COVER_ART_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace CoverArt
|
||||
{
|
||||
|
||||
class CoverArt
|
||||
{
|
||||
public:
|
||||
typedef std::vector<unsigned char> data_type;
|
||||
|
||||
CoverArt() {}
|
||||
CoverArt(const std::string& mime, const data_type& data) : _mimeType(mime), _data(data) {}
|
||||
|
||||
const std::string& getMimeType() const { return _mimeType; }
|
||||
const data_type& getData() const { return _data; }
|
||||
|
||||
void setMimeType(const std::string& mimeType) { _mimeType = mimeType;}
|
||||
void setData(const data_type& data) { _data = data; }
|
||||
|
||||
bool scale(std::size_t size);
|
||||
|
||||
private:
|
||||
|
||||
std::string _mimeType;
|
||||
data_type _data;
|
||||
};
|
||||
|
||||
|
||||
} // namespace CoverArt
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,26 +18,12 @@
|
||||
*/
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
#include "config/ConfigReader.hpp"
|
||||
#include "av/InputFormatContext.hpp"
|
||||
#include "av/AvInfo.hpp"
|
||||
|
||||
#include "CoverArtGrabber.hpp"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::string> splitStrings(const std::string& source)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
std::istringstream oss(source);
|
||||
|
||||
std::string str;
|
||||
while(oss >> str)
|
||||
res.push_back(str);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions)
|
||||
{
|
||||
@@ -56,7 +42,6 @@ isFileSupported(const boost::filesystem::path& file, const std::vector<boost::fi
|
||||
namespace CoverArt {
|
||||
|
||||
Grabber::Grabber()
|
||||
: _maxFileSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -67,40 +52,28 @@ Grabber::instance()
|
||||
return instance;
|
||||
}
|
||||
|
||||
void
|
||||
Grabber::init()
|
||||
static std::vector<Image::Image>
|
||||
getFromAvMediaFile(const Av::MediaFile& input, std::size_t nbMaxCovers)
|
||||
{
|
||||
for (const std::string& extension : splitStrings( ConfigReader::instance().getString("main.cover.file_extensions")))
|
||||
_fileExtensions.push_back("." + extension);
|
||||
std::vector<Image::Image> res;
|
||||
|
||||
_maxFileSize = ConfigReader::instance().getULong("main.cover.file_max_size");
|
||||
}
|
||||
|
||||
std::vector<CoverArt>
|
||||
Grabber::getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers) const
|
||||
{
|
||||
std::vector<CoverArt> res;
|
||||
|
||||
try
|
||||
for (Av::Picture& picture : input.getAttachedPictures(nbMaxCovers))
|
||||
{
|
||||
std::vector<Av::Picture> pictures = input.getPictures(nbMaxCovers);
|
||||
Image::Image image;
|
||||
|
||||
for (Av::Picture& picture : pictures)
|
||||
res.push_back( CoverArt(picture.mimeType, picture.data) );
|
||||
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what();
|
||||
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<CoverArt>
|
||||
std::vector<Image::Image>
|
||||
Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const
|
||||
{
|
||||
std::vector<CoverArt> res;
|
||||
std::vector<Image::Image> res;
|
||||
|
||||
std::vector<boost::filesystem::path> coverPathes = getCoverPaths(p, nbMaxCovers);
|
||||
for (auto coverPath : coverPathes)
|
||||
@@ -108,14 +81,12 @@ Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCov
|
||||
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);
|
||||
Image::Image image;
|
||||
|
||||
// TODO handle other formats
|
||||
res.push_back(CoverArt("image/jpeg", data));
|
||||
if (image.load(coverPath))
|
||||
res.push_back(image);
|
||||
else
|
||||
LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath << "'";
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -143,7 +114,7 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
|
||||
|
||||
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;
|
||||
LMS_LOG(COVER, INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -155,27 +126,18 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<CoverArt>
|
||||
std::vector<Image::Image>
|
||||
Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const
|
||||
{
|
||||
std::vector<CoverArt> res;
|
||||
Av::MediaFile input(p);
|
||||
|
||||
try
|
||||
{
|
||||
Av::InputFormatContext input(p);
|
||||
|
||||
res = getFromInputFormatContext(input, nbMaxCovers);
|
||||
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get covers from file " << p << ": " << e.what();
|
||||
}
|
||||
|
||||
return res;
|
||||
if (input.open())
|
||||
return getFromAvMediaFile(input, nbMaxCovers);
|
||||
else
|
||||
return std::vector<Image::Image>();
|
||||
}
|
||||
|
||||
std::vector<CoverArt>
|
||||
std::vector<Image::Image>
|
||||
Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const
|
||||
{
|
||||
using namespace Database;
|
||||
@@ -184,7 +146,7 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI
|
||||
|
||||
Track::pointer track = Track::getById(session, trackId);
|
||||
if (!track)
|
||||
return std::vector<CoverArt>();
|
||||
return std::vector<Image::Image>();
|
||||
|
||||
Track::CoverType coverType = track->getCoverType();
|
||||
boost::filesystem::path trackPath = track->getPath();
|
||||
@@ -195,17 +157,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<CoverArt>();
|
||||
return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers);
|
||||
}
|
||||
|
||||
return std::vector<CoverArt>();
|
||||
return std::vector<Image::Image>();
|
||||
}
|
||||
|
||||
|
||||
std::vector<CoverArt>
|
||||
std::vector<Image::Image>
|
||||
Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers) const
|
||||
{
|
||||
using namespace Database;
|
||||
@@ -222,14 +182,14 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re
|
||||
-1, 1 /* limit result size */);
|
||||
|
||||
if (tracks.empty())
|
||||
return std::vector<CoverArt>();
|
||||
return std::vector<Image::Image>();
|
||||
|
||||
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);
|
||||
std::vector<Image::Image> res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers);
|
||||
|
||||
// Fallback on the embedded cover of the first track
|
||||
if (res.empty() && embeddedCover)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
* Copyright (C) 2015 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
@@ -17,16 +17,13 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COVER_ART_GRABBER_HPP
|
||||
#define COVER_ART_GRABBER_HPP
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "CoverArt.hpp"
|
||||
|
||||
#include "image/Image.hpp"
|
||||
|
||||
namespace CoverArt {
|
||||
|
||||
@@ -38,26 +35,21 @@ class Grabber
|
||||
|
||||
static Grabber& instance();
|
||||
|
||||
void init();
|
||||
|
||||
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, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const;
|
||||
std::vector<Image::Image> getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) 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::Track::id_type trackId, std::size_t nbMaxCovers = 1) const;
|
||||
std::vector<Image::Image> getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const;
|
||||
|
||||
private:
|
||||
Grabber();
|
||||
|
||||
std::vector<boost::filesystem::path> _fileExtensions;
|
||||
std::size_t _maxFileSize;
|
||||
std::vector<boost::filesystem::path> _preferredFileNames;
|
||||
|
||||
|
||||
std::vector<boost::filesystem::path> _fileExtensions
|
||||
= {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize
|
||||
std::size_t _maxFileSize = 5000000;
|
||||
std::vector<boost::filesystem::path> _preferredFileNames
|
||||
= {"cover", "front"}; // TODO parametrize
|
||||
};
|
||||
|
||||
} // namespace CoverArt
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -47,7 +47,7 @@ void computeCrc(const boost::filesystem::path& p, std::vector<unsigned char>& cr
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Failed to open file '" << p << "'";
|
||||
LMS_LOG(DBUPDATER, ERROR) << "Failed to open file '" << p << "'";
|
||||
throw std::runtime_error("Failed to open file '" + p.string() + "'" );
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ Updater::processNextJob(void)
|
||||
MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession());
|
||||
|
||||
if (settings->getManualScanRequested()) {
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Manual scan requested!";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Manual scan requested!";
|
||||
scheduleScan( boost::posix_time::seconds(0) );
|
||||
}
|
||||
else
|
||||
@@ -213,7 +213,7 @@ Updater::processNextJob(void)
|
||||
void
|
||||
Updater::scheduleScan( boost::posix_time::time_duration duration)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan in " << duration;
|
||||
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration;
|
||||
_scheduleTimer.expires_from_now(duration);
|
||||
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) );
|
||||
}
|
||||
@@ -221,7 +221,7 @@ Updater::scheduleScan( boost::posix_time::time_duration duration)
|
||||
void
|
||||
Updater::scheduleScan( boost::posix_time::ptime time)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan at " << time;
|
||||
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << time;
|
||||
_scheduleTimer.expires_at(time);
|
||||
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) );
|
||||
}
|
||||
@@ -246,12 +246,12 @@ Updater::process(boost::system::error_code err)
|
||||
|
||||
for (RootDirectory rootDirectory : rootDirectories)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Processing root directory '" << rootDirectory.path << "'...";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "'...";
|
||||
processRootDirectory(rootDirectory, stats);
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Processing root directory '" << rootDirectory.path << "' DONE";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "' DONE";
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Scan complete. Changes = " << stats.nbChanges() << ", Errors = " << stats.nbScanErrors;
|
||||
LMS_LOG(DBUPDATER, INFO) << "Scan complete. Changes = " << stats.nbChanges() << ", Errors = " << stats.nbScanErrors;
|
||||
|
||||
// Update database stats
|
||||
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
|
||||
@@ -382,27 +382,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 = 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<boost::filesystem::path> 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;
|
||||
|
||||
@@ -412,7 +398,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
if (items.find(MetaData::Type::AudioStreams) == items.end()
|
||||
|| boost::any_cast<std::vector<MetaData::AudioStream> >(items[MetaData::Type::AudioStreams]).empty())
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no audio stream found)";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no audio stream found)";
|
||||
|
||||
// If Track exists here, delete it!
|
||||
if (track) {
|
||||
@@ -424,7 +410,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
if (items.find(MetaData::Type::Duration) == items.end()
|
||||
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() <= 0)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)";
|
||||
|
||||
// If Track exists here, delete it!
|
||||
if (track) {
|
||||
@@ -497,12 +483,12 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
{
|
||||
// Create a new song
|
||||
track = Track::create(_db.getSession(), file);
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Adding '" << file << "'";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'";
|
||||
stats.nbAdded++;
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Updating '" << file << "'";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'";
|
||||
stats.nbModified++;
|
||||
}
|
||||
|
||||
@@ -551,19 +537,14 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
{
|
||||
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);
|
||||
track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None );
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!";
|
||||
LMS_LOG(DBUPDATER, ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!";
|
||||
stats.nbRemoved++;
|
||||
}
|
||||
}
|
||||
@@ -618,7 +599,7 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::fi
|
||||
if (!boost::filesystem::exists( p )
|
||||
|| !boost::filesystem::is_regular( p ) )
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Missing file '" << p << "'";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Missing file '" << p << "'";
|
||||
status = false;
|
||||
}
|
||||
else
|
||||
@@ -636,12 +617,12 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::fi
|
||||
|
||||
if (!foundRoot)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Out of root file '" << p << "'";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Out of root file '" << p << "'";
|
||||
status = false;
|
||||
}
|
||||
else if (!isFileSupported(p, extensions))
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "File format no longer supported for '" << p << "'";
|
||||
LMS_LOG(DBUPDATER, INFO) << "File format no longer supported for '" << p << "'";
|
||||
status = false;
|
||||
}
|
||||
}
|
||||
@@ -651,7 +632,7 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::fi
|
||||
}
|
||||
catch (boost::filesystem::filesystem_error& e)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Caught exception while checking file '" << p << "': " << e.what();
|
||||
LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p << "': " << e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -661,12 +642,12 @@ void
|
||||
Updater::checkAudioFiles( Stats& stats )
|
||||
{
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Checking audio files...";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Checking audio files...";
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio);
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking tracks...";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks...";
|
||||
auto tracks = Track::getAll(_db.getSession());
|
||||
for (auto track : tracks)
|
||||
{
|
||||
@@ -678,45 +659,45 @@ Updater::checkAudioFiles( Stats& stats )
|
||||
}
|
||||
|
||||
// Now process orphan Genre (no track)
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres...";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking Genres...";
|
||||
auto genres = Genre::getAll(_db.getSession());
|
||||
for (auto genre : genres)
|
||||
{
|
||||
if (genre->getTracks().size() == 0)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan genre '" << genre->getName() << "'";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan genre '" << genre->getName() << "'";
|
||||
genre.remove();
|
||||
}
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking artists...";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking artists...";
|
||||
auto artists = Artist::getAllOrphans(_db.getSession());
|
||||
for (auto artist : artists)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
|
||||
artist.remove();
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking releases...";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking releases...";
|
||||
auto releases = Release::getAllOrphans(_db.getSession());
|
||||
for (auto release : releases)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan release '" << release->getName() << "'";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'";
|
||||
release.remove();
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Check audio files done!";
|
||||
LMS_LOG(DBUPDATER, INFO) << "Check audio files done!";
|
||||
}
|
||||
|
||||
void
|
||||
Updater::checkVideoFiles( Stats& stats )
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking video files...";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking video files...";
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Video);
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking videos...";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking videos...";
|
||||
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Video> > Videos;
|
||||
Videos videos = Video::getAll(_db.getSession());
|
||||
|
||||
@@ -731,7 +712,7 @@ Updater::checkVideoFiles( Stats& stats )
|
||||
}
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check video files done!";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Check video files done!";
|
||||
}
|
||||
void
|
||||
Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
|
||||
@@ -757,7 +738,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
|
||||
if (items.find(MetaData::Type::VideoStreams) == items.end()
|
||||
|| boost::any_cast<std::vector<MetaData::VideoStream> >(items[MetaData::Type::VideoStreams]).empty())
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no video stream found)";
|
||||
LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no video stream found)";
|
||||
|
||||
// If the video exists here, delete it!
|
||||
if (video) {
|
||||
@@ -769,7 +750,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
|
||||
if (items.find(MetaData::Type::Duration) == items.end()
|
||||
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() == 0)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no duration or duration 0)";
|
||||
LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no duration or duration 0)";
|
||||
|
||||
// If Track exists here, delete it!
|
||||
if (video) {
|
||||
@@ -785,12 +766,12 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
|
||||
if (!video)
|
||||
{
|
||||
video = Video::create(_db.getSession(), file);
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Adding '" << file << "'";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Adding '" << file << "'";
|
||||
stats.nbAdded++;
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Updating '" << file << "'";
|
||||
LMS_LOG(DBUPDATER, DEBUG) << "Updating '" << file << "'";
|
||||
stats.nbModified++;
|
||||
}
|
||||
|
||||
@@ -804,7 +785,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!";
|
||||
LMS_LOG(DBUPDATER, ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!";
|
||||
stats.nbScanErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,11 @@ class Updater
|
||||
|
||||
Database::Handler _db;
|
||||
|
||||
std::vector<boost::filesystem::path> _audioExtensions;
|
||||
std::vector<boost::filesystem::path> _videoExtensions;
|
||||
std::vector<boost::filesystem::path> _audioExtensions
|
||||
= {".mp3", ".ogg", ".oga", ".aac", ".m4a", ".flac", ".wav", ".wma", ".aif", ".aiff", ".ape", ".mpc", ".shn"}; // TODO parametrize
|
||||
|
||||
std::vector<boost::filesystem::path> _videoExtensions
|
||||
= {".flv", ".avi", ".mpg", ".mpeg", ".mp4", ".m4v", ".mkv", ".mov", ".wmv", ".ogv", ".divx", ".m2ts"}; // TODO parametrize
|
||||
|
||||
MetaData::Parser& _metadataParser;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
{
|
||||
_session.setConnectionPool(connectionPool);
|
||||
|
||||
|
||||
_session.mapClass<Database::Artist>("artist");
|
||||
_session.mapClass<Database::Genre>("genre");
|
||||
_session.mapClass<Database::Track>("track");
|
||||
@@ -95,6 +96,8 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
_session.mapClass<Database::AuthInfo::AuthTokenType>("auth_token");
|
||||
|
||||
try {
|
||||
Wt::Dbo::Transaction transaction(_session);
|
||||
|
||||
_session.createTables();
|
||||
_session.execute("CREATE INDEX artist_name_idx ON artist(name)");
|
||||
_session.execute("CREATE INDEX genre_name_idx ON genre(name)");
|
||||
@@ -102,7 +105,7 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
_session.execute("CREATE INDEX track_name_idx ON track(name)");
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what();
|
||||
LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what();
|
||||
}
|
||||
|
||||
_users = new UserDatabase(_session);
|
||||
@@ -133,7 +136,7 @@ Handler::getUser(const Wt::Auth::User& authUser)
|
||||
{
|
||||
|
||||
if (!authUser.isValid()) {
|
||||
LMS_LOG(MOD_DB, SEV_ERROR) << "Handler::getUser: invalid authUser";
|
||||
LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser";
|
||||
return User::pointer();
|
||||
}
|
||||
|
||||
@@ -152,6 +155,8 @@ Handler::getUser(const Wt::Auth::User& authUser)
|
||||
Wt::Dbo::SqlConnectionPool*
|
||||
Handler::createConnectionPool(boost::filesystem::path p)
|
||||
{
|
||||
LMS_LOG(DB, INFO) << "Creating connection pool on file " << p;
|
||||
|
||||
Wt::Dbo::backend::Sqlite3 *connection = new Wt::Dbo::backend::Sqlite3(p.string());
|
||||
|
||||
connection->executeSql("pragma journal_mode=WAL");
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 "logger/Logger.hpp"
|
||||
|
||||
#include "Image.hpp"
|
||||
|
||||
|
||||
namespace Image {
|
||||
|
||||
static
|
||||
std::string format_to_magick(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format::JPEG: return "JPEG";
|
||||
}
|
||||
|
||||
return "JPEG";
|
||||
}
|
||||
|
||||
std::string format_to_mimeType(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format::JPEG: return "JPEG";
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
void
|
||||
init(const char *path)
|
||||
{
|
||||
Magick::InitializeMagick(path);
|
||||
}
|
||||
|
||||
bool
|
||||
Image::load(const std::vector<unsigned char>& rawData)
|
||||
{
|
||||
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
|
||||
Image::scale(std::size_t size)
|
||||
{
|
||||
if (!size)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
_image.resize( Magick::Geometry(size, size ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Magick::Exception& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR) << "Caught Magick exception: " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Image::save(std::vector<unsigned char>& data, Format format) const
|
||||
{
|
||||
Magick::Image outputImage(_image);
|
||||
|
||||
outputImage.magick( format_to_magick(format));
|
||||
|
||||
Magick::Blob blob;
|
||||
outputImage.write(&blob);
|
||||
|
||||
unsigned char *charBuf = (unsigned char*)blob.data();
|
||||
data.assign( charBuf, charBuf + blob.length() );
|
||||
}
|
||||
|
||||
} // namespace Image
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
* Copyright (C) 2015 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
@@ -17,36 +17,44 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef FORMAT_CONTEXT_HPP
|
||||
#define FORMAT_CONTEXT_HPP
|
||||
#pragma once
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "Common.hpp"
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
namespace Av
|
||||
#include <Magick++.h>
|
||||
|
||||
namespace Image
|
||||
{
|
||||
|
||||
class FormatContext
|
||||
enum class Format
|
||||
{
|
||||
public:
|
||||
FormatContext();
|
||||
~FormatContext();
|
||||
|
||||
|
||||
protected:
|
||||
void native(AVFormatContext* c) { _context = c;}
|
||||
AVFormatContext* native() { return _context; }
|
||||
const AVFormatContext* native() const { return _context; }
|
||||
|
||||
private:
|
||||
|
||||
AVFormatContext* _context;
|
||||
|
||||
|
||||
JPEG,
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
std::string format_to_mimeType(Format format);
|
||||
|
||||
#endif
|
||||
void init(const char *path);
|
||||
|
||||
class Image
|
||||
{
|
||||
public:
|
||||
|
||||
// input
|
||||
bool load(const std::vector<unsigned char>& rawData);
|
||||
bool load(boost::filesystem::path p);
|
||||
|
||||
// Operations
|
||||
bool scale(std::size_t size);
|
||||
|
||||
// output
|
||||
void save(std::vector<unsigned char>& rawData, Format format) const;
|
||||
|
||||
private:
|
||||
Magick::Image _image;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Image
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 REMOTE_HEADER_HPP
|
||||
#define REMOTE_HEADER_HPP
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
namespace LmsAPI
|
||||
{
|
||||
|
||||
|
||||
class Header
|
||||
{
|
||||
public:
|
||||
static const std::size_t size = 8; // HeaderSize
|
||||
static const std::size_t max_data_size = 65536*64 - size;
|
||||
|
||||
Header() : _dataSize(0) {}
|
||||
|
||||
void setDataSize(std::size_t size) { _dataSize = size; }
|
||||
std::size_t getDataSize(void) const {return _dataSize;}
|
||||
|
||||
bool from_istream(std::istream &is)
|
||||
{
|
||||
std::array<unsigned char, size> buffer;
|
||||
|
||||
bool res = is.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
|
||||
|
||||
if (res && is.gcount() == buffer.size())
|
||||
return from_buffer(buffer);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool from_buffer(const std::array<unsigned char, size>& buffer)
|
||||
{
|
||||
if (decode32(&buffer[0]) != _magic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dataSize = decode32(&buffer[4]);
|
||||
return _dataSize <= max_data_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* void to_ostream(std::ostream& os)
|
||||
{
|
||||
|
||||
|
||||
}*/
|
||||
|
||||
void to_buffer(std::array<unsigned char, size>& buffer) const
|
||||
{
|
||||
encode32(_magic, &buffer[0]);
|
||||
encode32(_dataSize, &buffer[4]);
|
||||
}
|
||||
|
||||
private:
|
||||
static uint32_t decode32(const unsigned char* data)
|
||||
{
|
||||
return (static_cast<uint32_t>(data[0]) << 24)
|
||||
+ (static_cast<uint32_t>(data[1]) << 16)
|
||||
+ (static_cast<uint32_t>(data[2]) << 8)
|
||||
+ (static_cast<uint32_t>(data[3]));
|
||||
}
|
||||
|
||||
static void encode32(uint32_t value, unsigned char* data)
|
||||
{
|
||||
data[0] = (value >> 24) & 0xFF;
|
||||
data[1] = (value >> 16) & 0xFF;
|
||||
data[2] = (value >> 8) & 0xFF;
|
||||
data[3] = (value) & 0xFF;
|
||||
}
|
||||
|
||||
static const uint32_t _magic = 0xdeadbeef;
|
||||
|
||||
uint32_t _dataSize;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace LmsAPI
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <algorithm> // std::min
|
||||
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/uuid/sha1.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AudioCollectionRequestHandler.hpp"
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "cover/CoverArtGrabber.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
using namespace Database;
|
||||
|
||||
static SearchFilter SearchFilterFromRequest(const AudioCollectionRequest_SearchFilter& request)
|
||||
{
|
||||
SearchFilter filter;
|
||||
|
||||
for (int id = 0; id < request.artist_id_size(); ++id)
|
||||
filter.idMatch[SearchFilter::Field::Artist].push_back( request.artist_id(id) );
|
||||
|
||||
for (int id = 0; id < request.genre_id_size(); ++id)
|
||||
filter.idMatch[SearchFilter::Field::Genre].push_back( request.genre_id(id) );
|
||||
|
||||
for (int id = 0; id < request.release_id_size(); ++id)
|
||||
filter.idMatch[SearchFilter::Field::Release].push_back( request.release_id(id) );
|
||||
|
||||
for (int id = 0; id < request.track_id_size(); ++id)
|
||||
filter.idMatch[SearchFilter::Field::Track].push_back( request.track_id(id) );
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
AudioCollectionRequestHandler::AudioCollectionRequestHandler(Handler& db)
|
||||
: _db(db)
|
||||
{}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::process(const AudioCollectionRequest& request, AudioCollectionResponse& response)
|
||||
{
|
||||
|
||||
bool res = false;
|
||||
|
||||
switch (request.type())
|
||||
{
|
||||
case AudioCollectionRequest::TypeGetRevision:
|
||||
// No payload
|
||||
res = processGetRevision(*response.mutable_revision());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeRevision);
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetGenreList:
|
||||
if (request.has_get_genres())
|
||||
{
|
||||
res = processGetGenres(request.get_genres(), *response.mutable_genre_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeArtistList);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetGenreList";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetArtistList:
|
||||
if (request.has_get_artists())
|
||||
{
|
||||
res = processGetArtists(request.get_artists(), *response.mutable_artist_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeArtistList);
|
||||
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetArtistList message!";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetReleaseList:
|
||||
if (request.has_get_releases())
|
||||
{
|
||||
res = processGetReleases(request.get_releases(), *response.mutable_release_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeReleaseList);
|
||||
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetReleaseList message!";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetTrackList:
|
||||
if (request.has_get_tracks())
|
||||
{
|
||||
res = processGetTracks(request.get_tracks(), *response.mutable_track_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeTrackList);
|
||||
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetTrackList message!";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetCoverArt:
|
||||
if (request.has_get_cover_art())
|
||||
res = processGetCoverArt(request.get_cover_art(), response);
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetCoverArt message!";
|
||||
break;
|
||||
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled AudioCollectionRequest_Type = " << request.type();
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListArtists;
|
||||
size = std::min(size, _maxListArtists);
|
||||
|
||||
// Get filters
|
||||
SearchFilter filter;
|
||||
if (request.has_search_filter())
|
||||
filter = SearchFilterFromRequest(request.search_filter());
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
std::vector<Genre::pointer> genres = Genre::getByFilter( _db.getSession(), filter, request.batch_parameter().offset(), static_cast<int>(size));
|
||||
|
||||
for (Genre::pointer genre : genres)
|
||||
{
|
||||
AudioCollectionResponse_Genre* addGenre = response.add_genres();
|
||||
|
||||
addGenre->set_id(genre.id());
|
||||
addGenre->set_name( std::string( boost::locale::conv::to_utf<char>(genre->getName(), "UTF-8") ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListArtists;
|
||||
size = std::min(size, _maxListArtists);
|
||||
|
||||
// Get filters
|
||||
SearchFilter filter;
|
||||
if (request.has_search_filter())
|
||||
filter = SearchFilterFromRequest(request.search_filter());
|
||||
|
||||
// Now fetch requested data...
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
std::vector<Artist::pointer> artists
|
||||
= Artist::getByFilter(_db.getSession(), filter,
|
||||
request.batch_parameter().offset(), static_cast<int>(size) );
|
||||
|
||||
for (Artist::pointer artist : artists)
|
||||
{
|
||||
AudioCollectionResponse_Artist *addArtist = response.add_artists();
|
||||
|
||||
addArtist->set_id(artist.id());
|
||||
addArtist->set_name( std::string( boost::locale::conv::to_utf<char>(artist->getName(), "UTF-8") ) );
|
||||
if (!artist->getMBID().empty())
|
||||
addArtist->set_mbid(artist->getMBID());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListReleases;
|
||||
size = std::min(size, _maxListReleases);
|
||||
|
||||
// Get filters
|
||||
SearchFilter filter;
|
||||
if (request.has_search_filter())
|
||||
filter = SearchFilterFromRequest(request.search_filter());
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
std::vector<Release::pointer> releases
|
||||
= Release::getByFilter( _db.getSession(), filter,
|
||||
request.batch_parameter().offset(), static_cast<int>(size));
|
||||
|
||||
for (Release::pointer release : releases)
|
||||
{
|
||||
AudioCollectionResponse_Release *addRelease = response.add_releases();
|
||||
|
||||
addRelease->set_id(release.id());
|
||||
addRelease->set_name( std::string( boost::locale::conv::to_utf<char>(release->getName(), "UTF-8")));
|
||||
if (!release->getMBID().empty())
|
||||
addRelease->set_mbid(release->getMBID());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListTracks;
|
||||
size = std::min(size, _maxListTracks);
|
||||
|
||||
// Get filters
|
||||
SearchFilter filter;
|
||||
if (request.has_search_filter())
|
||||
filter = SearchFilterFromRequest(request.search_filter());
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
std::vector<Track::pointer> tracks
|
||||
= Track::getByFilter( _db.getSession(), filter,
|
||||
request.batch_parameter().offset(), static_cast<int>(size));
|
||||
|
||||
for (Track::pointer track : tracks)
|
||||
{
|
||||
AudioCollectionResponse_Track* newTrack = response.add_tracks();
|
||||
|
||||
newTrack->set_id(track.id());
|
||||
newTrack->set_disc_number( track->getDiscNumber() );
|
||||
newTrack->set_track_number( track->getTrackNumber() );
|
||||
newTrack->set_artist_id( track->getArtist().id() );
|
||||
newTrack->set_release_id( track->getRelease().id() );
|
||||
|
||||
newTrack->set_name( std::string( boost::locale::conv::to_utf<char>(track->getName(), "UTF-8") ) );
|
||||
newTrack->set_duration_secs( track->getDuration().total_seconds() );
|
||||
|
||||
// Only send the year part of the release times
|
||||
if (!track->getDate().is_special())
|
||||
newTrack->set_release_date( std::to_string(track->getDate().date().year()) );
|
||||
if (!track->getOriginalDate().is_special())
|
||||
newTrack->set_original_release_date( std::to_string(track->getOriginalDate().date().year()) );
|
||||
if (!track->getMBID().empty())
|
||||
newTrack->set_mbid(track->getMBID());
|
||||
|
||||
for (Genre::pointer genre : track->getGenres())
|
||||
newTrack->add_genre_id( genre.id() );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
response.set_type(AudioCollectionResponse::TypeCoverArt);
|
||||
|
||||
std::vector<CoverArt::CoverArt> coverArts;
|
||||
|
||||
switch(request.type())
|
||||
{
|
||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease:
|
||||
if (request.has_release_id())
|
||||
{
|
||||
coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release_id());
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack:
|
||||
if (request.has_track_id())
|
||||
{
|
||||
coverArts = CoverArt::Grabber::instance().getFromTrack(_db.getSession(), request.track_id());
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (CoverArt::CoverArt& coverArt : coverArts)
|
||||
{
|
||||
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
|
||||
|
||||
if (request.has_size())
|
||||
{
|
||||
std::size_t size = request.size();
|
||||
if (size > _maxCoverArtSize || size == 0)
|
||||
size = _maxCoverArtSize;
|
||||
if (size < _minCoverArtSize)
|
||||
size = _minCoverArtSize;
|
||||
|
||||
coverArt.scale(size);
|
||||
}
|
||||
|
||||
cover_art->set_mime_type(coverArt.getMimeType());
|
||||
cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) );
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetRevision(AudioCollectionResponse::Revision& response)
|
||||
{
|
||||
|
||||
bool res = false;
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
MediaDirectorySettings::pointer settings = MediaDirectorySettings::get( _db.getSession() );
|
||||
|
||||
std::string hashStr = boost::posix_time::to_iso_string(settings->getLastUpdated());
|
||||
|
||||
boost::uuids::detail::sha1 s;
|
||||
for (const char c : hashStr)
|
||||
s.process_byte(c);
|
||||
|
||||
unsigned int digest[5];
|
||||
s.get_digest(digest);
|
||||
|
||||
std::ostringstream oss;
|
||||
for (std::size_t i = 0; i < 5; ++i)
|
||||
oss << std::hex << std::setfill('0') << std::setw(4) << digest[i];
|
||||
|
||||
response.set_rev(oss.str());
|
||||
|
||||
res = true;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace LmsAPI
|
||||
} // namespace Server
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
|
||||
#define REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
class AudioCollectionRequestHandler
|
||||
{
|
||||
public:
|
||||
AudioCollectionRequestHandler(Database::Handler& db);
|
||||
|
||||
bool process(const AudioCollectionRequest& request, AudioCollectionResponse& response);
|
||||
|
||||
private:
|
||||
|
||||
bool processGetRevision(AudioCollectionResponse::Revision& response);
|
||||
bool processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response);
|
||||
bool processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
||||
bool processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response);
|
||||
bool processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response);
|
||||
bool processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response);
|
||||
|
||||
Database::Handler& _db;
|
||||
|
||||
static const std::size_t _maxListArtists = 256;
|
||||
static const std::size_t _maxListGenres = 256;
|
||||
static const std::size_t _maxListReleases = 128;
|
||||
static const std::size_t _maxListTracks = 128;
|
||||
|
||||
static const std::size_t _minCoverArtSize = 64; // in pixels, square
|
||||
static const std::size_t _maxCoverArtSize = 512; // in pixels, square
|
||||
};
|
||||
|
||||
} // namespace LmsAPI
|
||||
} // namespace Server
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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/Auth/Identity>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AuthRequestHandler.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
AuthRequestHandler::AuthRequestHandler(Database::Handler& db)
|
||||
: _db(db)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
AuthRequestHandler::process(const AuthRequest& request, AuthResponse& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
switch (request.type())
|
||||
{
|
||||
case AuthRequest::TypePassword:
|
||||
if (request.has_password())
|
||||
{
|
||||
res = processPassword(request.password(), *response.mutable_password_result());
|
||||
if (res)
|
||||
response.set_type(AuthResponse::TypePasswordResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AuthRequest::TypePassword";
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AuthRequestHandler::processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
// Get the user
|
||||
const Wt::Auth::User& user = _db.getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, request.user_login());
|
||||
if (user.isValid())
|
||||
{
|
||||
// Now attempt to log the user in
|
||||
|
||||
Wt::Auth::PasswordResult result = _db.getPasswordService().verifyPassword(user, request.user_password());
|
||||
switch( result )
|
||||
{
|
||||
case Wt::Auth::PasswordInvalid:
|
||||
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
|
||||
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "': invalid password";
|
||||
res = true;
|
||||
break;
|
||||
case Wt::Auth::LoginThrottling:
|
||||
response.set_type(AuthResponse::PasswordResult::TypeLoginThrottling);
|
||||
response.set_delay(_db.getPasswordService().delayForNextAttempt(user));
|
||||
res = true;
|
||||
break;
|
||||
case Wt::Auth::PasswordValid:
|
||||
response.set_type(AuthResponse::PasswordResult::TypePasswordValid);
|
||||
// Log the user in
|
||||
_db.getLogin().login( user );
|
||||
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "' successfully logged in";
|
||||
res = true;
|
||||
break;
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot handle password result!";
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Invalid user '" << request.user_login();
|
||||
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 LMSAPI_AUTH_REQUEST_HANDLER
|
||||
#define LMSAPI_AUTH_REQUEST_HANDLER
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
class AuthRequestHandler
|
||||
{
|
||||
public:
|
||||
AuthRequestHandler(Database::Handler& db);
|
||||
|
||||
bool process(const AuthRequest& request, AuthResponse& response);
|
||||
|
||||
private:
|
||||
|
||||
bool processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response);
|
||||
|
||||
Database::Handler& _db;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
#endif
|
||||
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
#include "ConnectionManager.hpp"
|
||||
#include "Connection.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
Connection::Connection(boost::asio::io_service& ioService,
|
||||
boost::asio::ssl::context& context,
|
||||
ConnectionManager& manager,
|
||||
Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
: _closing(false),
|
||||
_socket(ioService, context),
|
||||
_connectionManager(manager),
|
||||
_requestHandler(connectionPool)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::Connection, Creating connection";
|
||||
}
|
||||
|
||||
void
|
||||
Connection::start()
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Starting connection...";
|
||||
_socket.async_handshake(boost::asio::ssl::stream_base::server,
|
||||
boost::bind(&Connection::handleHandshake, this,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void
|
||||
Connection::handleHandshake(const boost::system::error_code& error)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Handshake successfully performed... Now reading messages";
|
||||
readMsg();
|
||||
}
|
||||
else if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleHandshake: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Handshake error: " << error.message();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Connection::readMsg()
|
||||
{
|
||||
// Read a header first
|
||||
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(LmsAPI::Header::size);
|
||||
|
||||
boost::asio::async_read(_socket,
|
||||
bufs,
|
||||
boost::asio::transfer_exactly(LmsAPI::Header::size),
|
||||
boost::bind(&Connection::handleReadHeader, shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void
|
||||
Connection::stop()
|
||||
{
|
||||
if (!_closing)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
_closing = true;
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, Stopping connection " << this;
|
||||
_socket.shutdown(ec);
|
||||
|
||||
if (ec)
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Error while shutting down connection " << this << ": " << ec.message();
|
||||
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, connection stopped " << this;
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Stop: close already in progress...";
|
||||
}
|
||||
|
||||
void
|
||||
Connection::handleReadHeader(const boost::system::error_code& error, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
if (bytes_transferred != LmsAPI::Header::size)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "bytes_transferred (" << bytes_transferred << ") != LmsAPI::Header::size!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
_inputStreamBuf.commit(bytes_transferred);
|
||||
|
||||
std::istream is(&_inputStreamBuf);
|
||||
|
||||
LmsAPI::Header header;
|
||||
if (!header.from_istream(is))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot read header from buffer!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
// Now read the real message payload
|
||||
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(header.getDataSize());
|
||||
|
||||
boost::asio::async_read(_socket,
|
||||
bufs,
|
||||
boost::asio::transfer_exactly(header.getDataSize()),
|
||||
boost::bind(&Connection::handleReadMsg, shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
|
||||
}
|
||||
else if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleReadHeader: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Connection::handleReadMsg(const boost::system::error_code& error, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
_inputStreamBuf.commit(bytes_transferred);
|
||||
|
||||
std::istream is(&_inputStreamBuf);
|
||||
std::ostream os(&_outputStreamBuf);
|
||||
|
||||
LmsAPI::ServerMessage response;
|
||||
LmsAPI::ClientMessage request;
|
||||
|
||||
if (!request.ParseFromIstream(&is))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Parse request failed!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_requestHandler.process(request, response))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Process request failed!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
if (!response.SerializeToOstream(&os))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot serialize to ostream!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_outputStreamBuf.size() >= LmsAPI::Header::max_data_size)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "output message is too big! " << _outputStreamBuf.size() << " > " << LmsAPI::Header::max_data_size;
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<unsigned char, LmsAPI::Header::size> headerBuffer;
|
||||
{
|
||||
LmsAPI::Header header;
|
||||
header.setDataSize(_outputStreamBuf.size());
|
||||
header.to_buffer(headerBuffer);
|
||||
}
|
||||
|
||||
std::size_t n = boost::asio::write(_socket,
|
||||
boost::asio::buffer(headerBuffer),
|
||||
boost::asio::transfer_exactly(LmsAPI::Header::size),
|
||||
ec);
|
||||
if (ec)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write header: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(n == LmsAPI::Header::size);
|
||||
}
|
||||
|
||||
// Now send serialized payload
|
||||
n = boost::asio::write(_socket,
|
||||
_outputStreamBuf.data(),
|
||||
boost::asio::transfer_exactly(_outputStreamBuf.size()),
|
||||
ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write msg: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(n == _outputStreamBuf.size());
|
||||
_outputStreamBuf.consume(n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// All good here, read another message
|
||||
readMsg();
|
||||
}
|
||||
else if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleRead: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 LMSAPI_CONNECTION_HPP
|
||||
#define LMSAPI_CONNECTION_HPP
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
#include "messages/Header.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
class ConnectionManager;
|
||||
|
||||
/// Represents a single connection from a client.
|
||||
class Connection : public std::enable_shared_from_this<Connection>
|
||||
{
|
||||
public:
|
||||
|
||||
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
|
||||
|
||||
Connection(const Connection&) = delete;
|
||||
Connection& operator=(const Connection&) = delete;
|
||||
|
||||
typedef std::shared_ptr<Connection> pointer;
|
||||
|
||||
/// Construct a connection with the given io_service.
|
||||
explicit Connection(boost::asio::io_service& ioService, boost::asio::ssl::context& context,
|
||||
ConnectionManager& manager,
|
||||
Wt::Dbo::SqlConnectionPool& connectionPool);
|
||||
|
||||
ssl_socket::lowest_layer_type& getSocket() {return _socket.lowest_layer();}
|
||||
|
||||
/// Start the first asynchronous operation for the connection.
|
||||
void start();
|
||||
|
||||
/// Stop all asynchronous operations associated with the connection.
|
||||
void stop();
|
||||
|
||||
private:
|
||||
bool _closing;
|
||||
|
||||
/// Read a new message on the the connection
|
||||
void readMsg();
|
||||
|
||||
/// Handle completion of ssl handshake
|
||||
void handleHandshake(const boost::system::error_code& error);
|
||||
|
||||
/// Handle completion of a read operation.
|
||||
void handleReadHeader(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
void handleReadMsg(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
/// Socket for the connection.
|
||||
ssl_socket _socket;
|
||||
|
||||
/// The manager for this connection.
|
||||
ConnectionManager& _connectionManager;
|
||||
|
||||
/// The handler used to process the incoming requests.
|
||||
RequestHandler _requestHandler;
|
||||
|
||||
boost::asio::streambuf _inputStreamBuf;
|
||||
boost::asio::streambuf _outputStreamBuf;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <algorithm>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "ConnectionManager.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
ConnectionManager::ConnectionManager()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ConnectionManager::start(Connection::pointer c)
|
||||
{
|
||||
_connections.insert(c);
|
||||
c->start();
|
||||
}
|
||||
|
||||
void
|
||||
ConnectionManager::stop(Connection::pointer c)
|
||||
{
|
||||
_connections.erase(c);
|
||||
c->stop();
|
||||
}
|
||||
|
||||
void
|
||||
ConnectionManager::stopAll()
|
||||
{
|
||||
BOOST_FOREACH(Connection::pointer c, _connections)
|
||||
{
|
||||
c->stop();
|
||||
}
|
||||
_connections.clear();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 REMOTE_CONNECTION_MANAGER_HPP
|
||||
#define REMOTE_CONNECTION_MANAGER_HPP
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "Connection.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
/// Manages open connections so that they may be cleanly stopped when the server
|
||||
/// needs to shut down.
|
||||
|
||||
class ConnectionManager
|
||||
{
|
||||
public:
|
||||
ConnectionManager(const ConnectionManager&) = delete;
|
||||
ConnectionManager& operator=(const ConnectionManager&) = delete;
|
||||
|
||||
ConnectionManager();
|
||||
|
||||
/// Add the specified connection to the manager and start it.
|
||||
void start(Connection::pointer c);
|
||||
|
||||
/// Stop the specified connection.
|
||||
void stop(Connection::pointer c);
|
||||
|
||||
/// Stop all connections.
|
||||
void stopAll();
|
||||
|
||||
private:
|
||||
/// The managed connections.
|
||||
std::set<Connection::pointer> _connections;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
#endif
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 "logger/Logger.hpp"
|
||||
|
||||
#include "MediaRequestHandler.hpp"
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
MediaRequestHandler::MediaRequestHandler(Database::Handler& db)
|
||||
: _db(db)
|
||||
{}
|
||||
|
||||
bool
|
||||
MediaRequestHandler::process(const MediaRequest& request, MediaResponse& response)
|
||||
{
|
||||
|
||||
bool res = false;
|
||||
|
||||
switch (request.type())
|
||||
{
|
||||
case MediaRequest::TypeMediaPrepare:
|
||||
if (request.has_prepare())
|
||||
{
|
||||
if (request.prepare().has_audio())
|
||||
res = processAudioPrepare(request.prepare().audio(), *response.mutable_prepare_result());
|
||||
else if (request.prepare().has_video())
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Video prepare not supported!";
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
||||
|
||||
if (res)
|
||||
response.set_type(MediaResponse::TypePrepareResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
||||
break;
|
||||
|
||||
case MediaRequest::TypeMediaGetPart:
|
||||
if (request.has_get_part())
|
||||
{
|
||||
res = processGetPart(request.get_part(), *response.mutable_part_result());
|
||||
if (res)
|
||||
response.set_type(MediaResponse::TypePartResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaGet!";
|
||||
|
||||
break;
|
||||
|
||||
case MediaRequest::TypeMediaTerminate:
|
||||
if (request.has_terminate())
|
||||
{
|
||||
res = processTerminate(request.terminate(), *response.mutable_terminate_result());
|
||||
if (res)
|
||||
response.set_type(MediaResponse::TypePartResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaTerminate!";
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled MediaRequest type = " << request.type();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRequestHandler::processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response)
|
||||
{
|
||||
std::size_t bitrate;
|
||||
Transcode::Format::Encoding format;
|
||||
|
||||
switch( request.codec_type())
|
||||
{
|
||||
case MediaRequest::Prepare::AudioCodecTypeOGA: format = Transcode::Format::OGA; break;
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled codec type = " << request.codec_type();
|
||||
return false;
|
||||
}
|
||||
|
||||
switch( request.bitrate() )
|
||||
{
|
||||
case MediaRequest::Prepare::AudioBitrate_32_kbps: bitrate = 32000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_64_kbps: bitrate = 64000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_96_kbps: bitrate = 96000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_128_kbps: bitrate = 128000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_192_kbps: bitrate = 192000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_256_kbps: bitrate = 256000; break;
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled bitrate type = " << request.bitrate();
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO use user's bitrate limits!
|
||||
// TODO limit transcoder number by user?
|
||||
|
||||
if (_transcoders.size() + 1 > _maxTranscoders)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Transcoder limit reached!" << std::endl;
|
||||
// Just answer an empty response, dont delete existing trasncode jobs
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Wt::Dbo::Transaction transaction( _db.getSession());
|
||||
|
||||
Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id() );
|
||||
if (!track)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Requested track does not exist" << std::endl;
|
||||
// Track does no longer exist, just answer an empty response
|
||||
return true;
|
||||
}
|
||||
|
||||
Transcode::InputMediaFile inputFile(track->getPath());
|
||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get( format ));
|
||||
parameters.setBitrate( Transcode::Stream::Audio, bitrate);
|
||||
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = std::make_shared<Transcode::AvConvTranscoder>( parameters );
|
||||
|
||||
// now get a unique id (relative to this connection!)
|
||||
uint32_t handle = _curHandle++;
|
||||
assert(_transcoders.find(handle) == _transcoders.end());
|
||||
|
||||
_transcoders[handle] = transcoder;
|
||||
|
||||
response.set_handle(handle);
|
||||
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Set up new transcode, handle = " << handle;
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Caught exception: " << e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRequestHandler::processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response)
|
||||
{
|
||||
std::size_t dataSize = request.requested_data_size();
|
||||
std::vector<unsigned char> data;
|
||||
if (dataSize > _maxPartSize)
|
||||
dataSize = _maxPartSize;
|
||||
|
||||
TranscoderMap::iterator itTranscoder = _transcoders.find(request.handle());
|
||||
if (itTranscoder == _transcoders.end())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = itTranscoder->second;
|
||||
|
||||
if (!transcoder->isComplete())
|
||||
{
|
||||
data.reserve(dataSize);
|
||||
transcoder->process(data, dataSize);
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler::processGetPart, handle = " << request.handle() << ", isComplete = " << std::boolalpha << transcoder->isComplete() << ", size = " << data.size();
|
||||
|
||||
std::copy(data.begin(), data.end(), std::back_inserter(*response.mutable_data()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
MediaRequestHandler::processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler: resetting transcoder for handle " << request.handle();
|
||||
|
||||
if (_transcoders.find(request.handle()) == _transcoders.end())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_transcoders.erase(request.handle());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace LmsAPI
|
||||
} // namespace Server
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 REMOTE_MEDIA_REQUEST_HANDLER
|
||||
#define REMOTE_MEDIA_REQUEST_HANDLER
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "transcode/AvConvTranscoder.hpp"
|
||||
|
||||
#include "media.pb.h"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
class MediaRequestHandler
|
||||
{
|
||||
public:
|
||||
MediaRequestHandler(Database::Handler& db);
|
||||
|
||||
bool process(const MediaRequest& request, MediaResponse& response);
|
||||
|
||||
private:
|
||||
|
||||
bool processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response);
|
||||
bool processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response);
|
||||
bool processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response);
|
||||
|
||||
// bool processVideoPrepare(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
||||
|
||||
typedef std::map<uint32_t, std::shared_ptr<Transcode::AvConvTranscoder> > TranscoderMap;
|
||||
|
||||
TranscoderMap _transcoders;
|
||||
|
||||
Database::Handler& _db;
|
||||
|
||||
uint32_t _curHandle = 0;
|
||||
|
||||
static const std::size_t _maxPartSize = 65536 - 128;
|
||||
static const std::size_t _maxTranscoders = 1;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 "logger/Logger.hpp"
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
RequestHandler::RequestHandler(Wt::Dbo::SqlConnectionPool &connectionPool)
|
||||
: _db( connectionPool ),
|
||||
_authRequestHandler(_db),
|
||||
_audioCollectionRequestHandler(_db),
|
||||
_mediaRequestHandler(_db)
|
||||
{
|
||||
}
|
||||
|
||||
RequestHandler::~RequestHandler()
|
||||
{
|
||||
// TODO manually log out user if needed?
|
||||
_db.getLogin().logout();
|
||||
}
|
||||
|
||||
bool
|
||||
RequestHandler::process(const ClientMessage& request, ServerMessage& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
switch(request.type())
|
||||
{
|
||||
|
||||
case ClientMessage::AuthRequest:
|
||||
if (request.has_auth_request())
|
||||
{
|
||||
res = _authRequestHandler.process(request.auth_request(), *response.mutable_auth_response());
|
||||
if (res)
|
||||
response.set_type(ServerMessage::AuthResponse);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AuthRequest !";
|
||||
break;
|
||||
case ClientMessage::AudioCollectionRequest:
|
||||
// Not allowed if the user is not logged in
|
||||
if (_db.getLogin().loggedIn())
|
||||
{
|
||||
if (request.has_audio_collection_request())
|
||||
{
|
||||
res = _audioCollectionRequestHandler.process(request.audio_collection_request(), *response.mutable_audio_collection_response());
|
||||
if (res)
|
||||
response.set_type( ServerMessage::AudioCollectionResponse);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AudioCollectionRequest message!";
|
||||
}
|
||||
break;
|
||||
|
||||
case ClientMessage::MediaRequest:
|
||||
// Not allowed if the user is not logged in
|
||||
if (_db.getLogin().loggedIn())
|
||||
{
|
||||
if (request.has_media_request())
|
||||
{
|
||||
res = _mediaRequestHandler.process(request.media_request(), *response.mutable_media_response());
|
||||
if (res)
|
||||
response.set_type( ServerMessage::MediaResponse);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Malformed ClientMessage::MediaRequest message!";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled message type = " << request.type();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 REMOTE_REQUEST_HANDLER
|
||||
#define REMOTE_REQUEST_HANDLER
|
||||
|
||||
#include <Wt/Dbo/SqlConnectionPool>
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
#include "AuthRequestHandler.hpp"
|
||||
#include "AudioCollectionRequestHandler.hpp"
|
||||
#include "MediaRequestHandler.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
class RequestHandler
|
||||
{
|
||||
public:
|
||||
|
||||
RequestHandler(Wt::Dbo::SqlConnectionPool& connectionPool);
|
||||
~RequestHandler();
|
||||
|
||||
bool process(const ClientMessage& request, ServerMessage& response);
|
||||
|
||||
private:
|
||||
|
||||
Database::Handler _db;
|
||||
|
||||
AuthRequestHandler _authRequestHandler;
|
||||
AudioCollectionRequestHandler _audioCollectionRequestHandler;
|
||||
MediaRequestHandler _mediaRequestHandler;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <utility>
|
||||
|
||||
#include <boost/asio/placeholders.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Server.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
Server::Server(const endpoint_type& bindEndpoint,
|
||||
boost::filesystem::path certPath,
|
||||
boost::filesystem::path privKeyPath,
|
||||
boost::filesystem::path dhPath,
|
||||
Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
:
|
||||
_acceptor(_ioService, bindEndpoint, true /*SO_REUSEADDR*/),
|
||||
_connectionManager(),
|
||||
_context(boost::asio::ssl::context::tlsv1_server),
|
||||
_connectionPool(connectionPool)
|
||||
{
|
||||
_ioService.setThreadCount(1); // TODO parametrize
|
||||
|
||||
_context.set_options( boost::asio::ssl::context::default_workarounds // TODO check this thing
|
||||
| boost::asio::ssl::context::single_dh_use
|
||||
| boost::asio::ssl::context::no_sslv2
|
||||
| boost::asio::ssl::context::no_sslv3
|
||||
);
|
||||
// context_.set_password_callback(boost::bind(&server::get_password, this));
|
||||
_context.use_certificate_chain_file(certPath.string());
|
||||
_context.use_private_key_file(privKeyPath.string(), boost::asio::ssl::context::pem);
|
||||
_context.use_tmp_dh_file(dhPath.string());
|
||||
}
|
||||
|
||||
void
|
||||
Server::start()
|
||||
{
|
||||
// While the server is running, there is always at least one
|
||||
// asynchronous operation outstanding: the asynchronous accept call waiting
|
||||
// for new incoming connections.
|
||||
asyncAccept();
|
||||
|
||||
_ioService.start();
|
||||
}
|
||||
|
||||
void
|
||||
Server::asyncAccept()
|
||||
{
|
||||
std::shared_ptr<Connection> newConnection = std::make_shared<Connection>(_ioService, _context, _connectionManager, _connectionPool);
|
||||
|
||||
_acceptor.async_accept(newConnection->getSocket(),
|
||||
boost::bind(&Server::handleAccept, this, newConnection, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void
|
||||
Server::handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec)
|
||||
{
|
||||
// Check whether the server was stopped before this
|
||||
// completion handler had a chance to run.
|
||||
if (!_acceptor.is_open())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ec)
|
||||
{
|
||||
_connectionManager.start(newConnection);
|
||||
|
||||
// Accept another connection
|
||||
// TODO: add some limit?
|
||||
asyncAccept();
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "handleAccept: " << ec.message();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Server::stop()
|
||||
{
|
||||
// The server is stopped by cancelling all outstanding asynchronous
|
||||
// operations.
|
||||
_acceptor.close();
|
||||
_connectionManager.stopAll();
|
||||
|
||||
_ioService.stop();
|
||||
}
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 REMOTE_SERVER_HPP
|
||||
#define REMOTE_SERVER_HPP
|
||||
|
||||
#include <Wt/WIOService>
|
||||
#include <Wt/Dbo/SqlConnectionPool>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "Connection.hpp"
|
||||
#include "ConnectionManager.hpp"
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
namespace LmsAPI {
|
||||
namespace Server {
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
|
||||
Server(const Server&) = delete;
|
||||
Server& operator=(const Server&) = delete;
|
||||
|
||||
typedef boost::asio::ip::tcp::endpoint endpoint_type;
|
||||
|
||||
// Serve up data from the given database
|
||||
Server(const endpoint_type& bindEndpoint,
|
||||
boost::filesystem::path certPath,
|
||||
boost::filesystem::path privKeyPath,
|
||||
boost::filesystem::path dhPath,
|
||||
Wt::Dbo::SqlConnectionPool& connectionPool);
|
||||
|
||||
// Run the server's io_service loop.
|
||||
void start();
|
||||
|
||||
void stop();
|
||||
|
||||
private:
|
||||
/// Perform an asynchronous accept operation.
|
||||
void asyncAccept();
|
||||
void handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec);
|
||||
|
||||
Wt::WIOService _ioService;
|
||||
|
||||
/// Acceptor used to listen for incoming connections.
|
||||
boost::asio::ip::tcp::acceptor _acceptor;
|
||||
|
||||
/// The connection manager which owns all live connections.
|
||||
ConnectionManager _connectionManager;
|
||||
|
||||
boost::asio::ssl::context _context;
|
||||
|
||||
/// The database to be used for requests
|
||||
Wt::Dbo::SqlConnectionPool& _connectionPool;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace LmsAPI
|
||||
|
||||
#endif
|
||||
+26
-95
@@ -17,105 +17,36 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/sinks/text_file_backend.hpp>
|
||||
#include <boost/log/keywords/filter.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||
#include <boost/log/utility/setup/console.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/sources/record_ostream.hpp>
|
||||
#include <boost/log/attributes/constant.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/sources/record_ostream.hpp>
|
||||
#include <boost/log/support/date_time.hpp>
|
||||
#include <boost/log/attributes/named_scope.hpp>
|
||||
|
||||
#include <boost/date_time/posix_time/ptime.hpp>
|
||||
|
||||
#include "config/ConfigReader.hpp"
|
||||
|
||||
|
||||
#include "Logger.hpp"
|
||||
|
||||
Logger&
|
||||
Logger::instance()
|
||||
std::string getModuleName(Module mod)
|
||||
{
|
||||
static Logger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Logger::Logger()
|
||||
{
|
||||
// Initialiaz loggers
|
||||
static const std::vector<Module> modules =
|
||||
switch (mod)
|
||||
{
|
||||
MOD_AV,
|
||||
MOD_COVER,
|
||||
MOD_DB,
|
||||
MOD_DBUPDATER,
|
||||
MOD_MAIN,
|
||||
MOD_METADATA,
|
||||
MOD_REMOTE,
|
||||
MOD_SERVICE,
|
||||
MOD_TRANSCODE,
|
||||
MOD_UI,
|
||||
};
|
||||
|
||||
for(Module module : modules)
|
||||
_loggers[module].add_attribute("Module", boost::log::attributes::constant< Module >(module));
|
||||
}
|
||||
|
||||
boost::log::sources::severity_logger< Severity >&
|
||||
Logger::get(Module module)
|
||||
{
|
||||
return _loggers[module];
|
||||
}
|
||||
|
||||
void
|
||||
Logger::init()
|
||||
{
|
||||
boost::log::add_common_attributes();
|
||||
|
||||
boost::log::register_simple_formatter_factory< Severity, char >("Severity");
|
||||
|
||||
if (ConfigReader::instance().getBool("main.logger.file.enable", false))
|
||||
{
|
||||
boost::log::add_file_log
|
||||
(
|
||||
boost::log::keywords::file_name = ConfigReader::instance().getString("main.logger.file.path") + std::string(".%N"),
|
||||
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
|
||||
boost::log::keywords::open_mode = std::ios_base::app,
|
||||
boost::log::keywords::auto_flush = true,
|
||||
boost::log::keywords::format = (
|
||||
boost::log::expressions::stream
|
||||
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]")
|
||||
<< " [" << boost::log::expressions::attr< Module >("Module") << "]"
|
||||
<< " [" << boost::log::expressions::attr< Severity >("Severity") << "]"
|
||||
<< " " << boost::log::expressions::smessage
|
||||
)
|
||||
);
|
||||
case Module::AV: return "AV";
|
||||
case Module::COVER: return "COVER";
|
||||
case Module::DB: return "DB";
|
||||
case Module::DBUPDATER: return "DB UPDATER";
|
||||
case Module::MAIN: return "MAIN";
|
||||
case Module::METADATA: return "METADATA";
|
||||
case Module::REMOTE: return "REMOTE";
|
||||
case Module::SERVICE: return "SERVICE";
|
||||
case Module::TRANSCODE: return "TRANSCODE";
|
||||
case Module::UI: return "UI";
|
||||
}
|
||||
|
||||
if (ConfigReader::instance().getBool("main.logger.console.enable", false))
|
||||
{
|
||||
boost::log::add_console_log(std::cout,
|
||||
boost::log::keywords::format = (
|
||||
boost::log::expressions::stream
|
||||
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]")
|
||||
<< " [" << boost::log::expressions::attr< Module >("Module") << "]"
|
||||
<< " [" << boost::log::expressions::attr< Severity >("Severity") << "]"
|
||||
<< " " << boost::log::expressions::smessage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
boost::log::core::get()->set_filter
|
||||
(
|
||||
boost::log::expressions::attr<Severity>("Severity") <= ConfigReader::instance().getULong("main.logger.level", SEV_DEBUG)
|
||||
);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string getSeverityName(Severity sev)
|
||||
{
|
||||
switch (sev)
|
||||
{
|
||||
case Severity::FATAL: return "fatal";
|
||||
case Severity::ERROR: return "error";
|
||||
case Severity::WARNING: return "warning";
|
||||
case Severity::INFO: return "info";
|
||||
case Severity::DEBUG: return "debug";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
+25
-98
@@ -20,112 +20,39 @@
|
||||
#ifndef LOGGER_HPP__
|
||||
#define LOGGER_HPP__
|
||||
|
||||
#include <map>
|
||||
#include <Wt/WServer>
|
||||
#include <Wt/WApplication>
|
||||
#include <Wt/WLogger>
|
||||
|
||||
#include <boost/log/expressions/keyword_fwd.hpp>
|
||||
#include <boost/log/expressions/keyword.hpp>
|
||||
#include <string>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/attributes/named_scope.hpp>
|
||||
|
||||
#define LMS_LOG(module, level) BOOST_LOG_SEV(Logger::instance().get(module), level)
|
||||
|
||||
enum Severity
|
||||
enum class Severity
|
||||
{
|
||||
SEV_CRIT = 2,
|
||||
SEV_ERROR = 3,
|
||||
SEV_WARNING = 4,
|
||||
SEV_NOTICE = 5,
|
||||
SEV_INFO = 6,
|
||||
SEV_DEBUG = 7,
|
||||
FATAL,
|
||||
ERROR,
|
||||
WARNING,
|
||||
INFO,
|
||||
DEBUG,
|
||||
};
|
||||
|
||||
enum Module
|
||||
enum class Module
|
||||
{
|
||||
MOD_AV = 0,
|
||||
MOD_COVER,
|
||||
MOD_DB,
|
||||
MOD_DBUPDATER,
|
||||
MOD_MAIN,
|
||||
MOD_METADATA,
|
||||
MOD_REMOTE,
|
||||
MOD_SERVICE,
|
||||
MOD_TRANSCODE,
|
||||
MOD_UI,
|
||||
AV,
|
||||
COVER,
|
||||
DB,
|
||||
DBUPDATER,
|
||||
MAIN,
|
||||
METADATA,
|
||||
REMOTE,
|
||||
SERVICE,
|
||||
TRANSCODE,
|
||||
UI,
|
||||
};
|
||||
|
||||
BOOST_LOG_ATTRIBUTE_KEYWORD(module, "Module", Module)
|
||||
std::string getModuleName(Module mod);
|
||||
std::string getSeverityName(Severity sev);
|
||||
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
|
||||
Logger(const Logger&) = delete;
|
||||
Logger& operator=(const Logger&) = delete;
|
||||
|
||||
static Logger& instance();
|
||||
|
||||
void init();
|
||||
|
||||
boost::log::sources::severity_logger< Severity >&
|
||||
get(Module module);
|
||||
|
||||
private:
|
||||
Logger();
|
||||
|
||||
std::map<Module, boost::log::sources::severity_logger< Severity > > _loggers;
|
||||
};
|
||||
|
||||
// The formatting logic for the severity level
|
||||
template< typename CharT, typename TraitsT >
|
||||
inline std::basic_ostream< CharT, TraitsT >& operator<< (
|
||||
std::basic_ostream< CharT, TraitsT >& strm, Severity lvl)
|
||||
{
|
||||
static const char* const str[] =
|
||||
{
|
||||
"",
|
||||
"",
|
||||
"CRIT",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"NOTICE",
|
||||
"INFO",
|
||||
"DEBUG"
|
||||
};
|
||||
if (static_cast< std::size_t >(lvl) < (sizeof(str) / sizeof(*str)))
|
||||
strm << str[lvl];
|
||||
else
|
||||
strm << static_cast< int >(lvl);
|
||||
return strm;
|
||||
}
|
||||
|
||||
template< typename CharT, typename TraitsT >
|
||||
inline std::basic_ostream< CharT, TraitsT >& operator<< (
|
||||
std::basic_ostream< CharT, TraitsT >& strm, Module val)
|
||||
{
|
||||
const char* res = NULL;
|
||||
|
||||
switch(val)
|
||||
{
|
||||
case MOD_AV: res = "AV"; break;
|
||||
case MOD_COVER: res = "COVER"; break;
|
||||
case MOD_DB: res = "DB"; break;
|
||||
case MOD_DBUPDATER: res = "DBUPDATER"; break;
|
||||
case MOD_MAIN: res = "MAIN"; break;
|
||||
case MOD_METADATA: res = "METADATA"; break;
|
||||
case MOD_REMOTE: res = "REMOTE"; break;
|
||||
case MOD_SERVICE: res = "SERVICE"; break;
|
||||
case MOD_TRANSCODE: res = "TRANSCODE"; break;
|
||||
case MOD_UI: res = "UI"; break;
|
||||
}
|
||||
|
||||
if (res)
|
||||
strm << res;
|
||||
else
|
||||
strm << static_cast< int >(val);
|
||||
return strm;
|
||||
}
|
||||
|
||||
|
||||
#endif // LOGGER_HPP__
|
||||
#define LMS_LOG(module, level) Wt::log(getSeverityName(Severity::level)) << Wt::WLogger::sep << "[" << getModuleName(Module::module) << "]" << Wt::WLogger::sep
|
||||
|
||||
#endif
|
||||
|
||||
+38
-41
@@ -20,22 +20,23 @@
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "config/ConfigReader.hpp"
|
||||
#include "transcode/AvConvTranscoder.hpp"
|
||||
#include "av/Common.hpp"
|
||||
#include "av/AvInfo.hpp"
|
||||
#include "av/AvTranscoder.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "cover/CoverArtGrabber.hpp"
|
||||
#include "image/Image.hpp"
|
||||
|
||||
#include "ui/LmsApplication.hpp"
|
||||
|
||||
#include "service/ServiceManager.hpp"
|
||||
#include "service/DatabaseUpdateService.hpp"
|
||||
#include "service/UserInterfaceService.hpp"
|
||||
#if defined HAVE_LMSAPI
|
||||
#include "service/LmsAPIServerService.hpp"
|
||||
#endif
|
||||
|
||||
#include <Wt/WServer>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
|
||||
int res = EXIT_FAILURE;
|
||||
|
||||
assert(argc > 0);
|
||||
@@ -43,65 +44,61 @@ int main(int argc, char* argv[])
|
||||
|
||||
try
|
||||
{
|
||||
// TODO generate a nice command line help with args
|
||||
|
||||
// Open configuration file
|
||||
boost::filesystem::path configFile("/etc/lms.conf"); // TODO
|
||||
if (argc > 1)
|
||||
configFile = boost::filesystem::path(argv[1]);
|
||||
Wt::WServer server(argv[0]);
|
||||
server.setServerConfiguration (argc, argv);
|
||||
|
||||
if ( !boost::filesystem::exists(configFile))
|
||||
{
|
||||
std::cerr << "Config file '" << configFile << "' does not exist!" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
else if (!boost::filesystem::is_regular(configFile))
|
||||
{
|
||||
std::cerr << "Config file '" << configFile << "' is not regular!" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ConfigReader::instance().setFile(configFile);
|
||||
|
||||
Logger::instance().init();
|
||||
CoverArt::Grabber::instance().init();
|
||||
Wt::WServer::instance()->logger().configure("*"); // log everything
|
||||
|
||||
Service::ServiceManager& serviceManager = Service::ServiceManager::instance();
|
||||
|
||||
// lib init
|
||||
Image::init(argv[0]);
|
||||
Av::AvInit();
|
||||
Transcode::AvConvTranscoder::init();
|
||||
Av::Transcoder::init();
|
||||
Database::Handler::configureAuth();
|
||||
|
||||
// Initializing a connection pool to the database that will be shared along services
|
||||
std::unique_ptr<Wt::Dbo::SqlConnectionPool> connectionPool( Database::Handler::createConnectionPool( ConfigReader::instance().getString("main.database.path") ));
|
||||
std::unique_ptr<Wt::Dbo::SqlConnectionPool> connectionPool( Database::Handler::createConnectionPool("/var/lms/lms.db")); // TODO use $datadir from autotools
|
||||
|
||||
LMS_LOG(MOD_MAIN, SEV_INFO) << "Starting services...";
|
||||
serviceManager.add( std::make_shared<Service::DatabaseUpdateService>(*connectionPool));
|
||||
|
||||
serviceManager.startService( std::make_shared<Service::DatabaseUpdateService>(*connectionPool));
|
||||
serviceManager.startService( std::make_shared<Service::UserInterfaceService>(boost::filesystem::path(argv[0]), *connectionPool));
|
||||
#if defined HAVE_LMSAPI
|
||||
serviceManager.startService( std::make_shared<Service::LmsAPIService>(*connectionPool));
|
||||
serviceManager.add( std::make_shared<Service::LmsAPIService>(*connectionPool));
|
||||
#endif
|
||||
|
||||
LMS_LOG(MOD_MAIN, SEV_NOTICE) << "Now running...";
|
||||
// bind entry point
|
||||
server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, boost::ref(*connectionPool)));
|
||||
|
||||
serviceManager.run();
|
||||
// Starting the main server
|
||||
LMS_LOG(MAIN, INFO) << "Starting server...";
|
||||
server.start();
|
||||
|
||||
// Start underlying services
|
||||
LMS_LOG(MAIN, INFO) << "Starting services...";
|
||||
serviceManager.start();
|
||||
|
||||
LMS_LOG(MAIN, INFO) << "Now running...";
|
||||
|
||||
// Waiting for shutdown command
|
||||
Wt::WServer::waitForShutdown(argv[0]);
|
||||
|
||||
LMS_LOG(MAIN, INFO) << "Stopping services...";
|
||||
serviceManager.stop();
|
||||
serviceManager.clear();
|
||||
|
||||
LMS_LOG(MAIN, INFO) << "Stopping server...";
|
||||
server.stop();
|
||||
|
||||
res = EXIT_SUCCESS;
|
||||
}
|
||||
// TODO catch setting not found exception
|
||||
catch( libconfig::ParseException& e)
|
||||
{
|
||||
std::cerr << "Caught libconfig::ParseException! error='" << e.getError() << "', file = '" << e.getFile() << "', line = " << e.getLine() << std::endl;
|
||||
}
|
||||
catch( Wt::WServer::Exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught a WServer::Exception: " << e.what();
|
||||
LMS_LOG(MAIN, FATAL) << "Caught a WServer::Exception: " << e.what();
|
||||
}
|
||||
catch( std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught std::exception: " << e.what();
|
||||
LMS_LOG(MAIN, FATAL) << "Caught std::exception: " << e.what();
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
+46
-57
@@ -23,7 +23,7 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
#include "av/AvInfo.hpp"
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
@@ -36,86 +36,75 @@ bool
|
||||
AvFormat::parse(const boost::filesystem::path& p, Items& items)
|
||||
{
|
||||
|
||||
Av::InputFormatContext input(p);
|
||||
Av::MediaFile mediaFile(p);
|
||||
|
||||
if (!input.findStreamInfo())
|
||||
if (!mediaFile.open())
|
||||
return false;
|
||||
|
||||
std::map<std::string, std::string> metadata;
|
||||
input.getMetadata().get(metadata);
|
||||
|
||||
// HACK or OGG files
|
||||
// If we did not find tags, searched metadata in streams
|
||||
if (metadata.empty())
|
||||
{
|
||||
// Get input streams
|
||||
std::vector<Av::Stream> streams = input.getStreams();
|
||||
|
||||
BOOST_FOREACH(Av::Stream& stream, streams)
|
||||
{
|
||||
stream.getMetadata().get(metadata);
|
||||
|
||||
if (!metadata.empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mediaFile.scan())
|
||||
return false;
|
||||
|
||||
std::map<std::string, std::string> metadata = mediaFile.getMetaData();
|
||||
|
||||
// Stream info
|
||||
{
|
||||
std::vector<Av::Stream> avStreams = input.getStreams();
|
||||
std::vector<AudioStream> audioStreams;
|
||||
|
||||
std::vector<AudioStream> audioStreams;
|
||||
std::vector<VideoStream> videoStreams;
|
||||
std::vector<SubtitleStream> subtitleStreams;
|
||||
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Audio);
|
||||
|
||||
BOOST_FOREACH(Av::Stream& avStream, avStreams)
|
||||
for (Av::Stream& stream : streams)
|
||||
{
|
||||
switch(avStream.getCodecContext().getType())
|
||||
{
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (!avStream.hasAttachedPic())
|
||||
{
|
||||
VideoStream stream;
|
||||
stream.bitRate = avStream.getCodecContext().getBitRate();
|
||||
videoStreams.push_back(stream);
|
||||
}
|
||||
break;
|
||||
AudioStream audioStream;
|
||||
audioStream.desc = stream.desc;
|
||||
audioStream.bitRate = stream.bitrate;
|
||||
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
{
|
||||
AudioStream stream;
|
||||
stream.nbChannels = avStream.getCodecContext().getNbChannels();
|
||||
stream.bitRate = avStream.getCodecContext().getBitRate();
|
||||
audioStreams.push_back(stream);
|
||||
}
|
||||
break;
|
||||
audioStreams.push_back(audioStream);
|
||||
}
|
||||
|
||||
case AVMEDIA_TYPE_SUBTITLE:
|
||||
{
|
||||
subtitleStreams.push_back( SubtitleStream() );
|
||||
}
|
||||
break;
|
||||
if (!audioStreams.empty())
|
||||
items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams));
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
{
|
||||
std::vector<VideoStream> videoStreams;
|
||||
|
||||
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Video);
|
||||
|
||||
for (Av::Stream& stream : streams)
|
||||
{
|
||||
VideoStream videoStream;
|
||||
videoStream.desc = stream.desc;
|
||||
videoStream.bitRate = stream.bitrate;
|
||||
|
||||
videoStreams.push_back(videoStream);
|
||||
}
|
||||
|
||||
if (!videoStreams.empty())
|
||||
items.insert( std::make_pair(MetaData::Type::VideoStreams, videoStreams));
|
||||
if (!audioStreams.empty())
|
||||
items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams));
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<SubtitleStream> subtitleStreams;
|
||||
|
||||
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Subtitle);
|
||||
|
||||
for (Av::Stream& stream : streams)
|
||||
{
|
||||
SubtitleStream subtitleStream;
|
||||
subtitleStream.desc = stream.desc;
|
||||
|
||||
subtitleStreams.push_back(subtitleStream);
|
||||
}
|
||||
|
||||
if (!subtitleStreams.empty())
|
||||
items.insert( std::make_pair(MetaData::Type::SubtitleStreams, subtitleStreams));
|
||||
|
||||
}
|
||||
|
||||
// Duration
|
||||
items.insert( std::make_pair(MetaData::Type::Duration, boost::posix_time::time_duration( boost::posix_time::seconds( input.getDurationSecs() )) ));
|
||||
items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() ));
|
||||
|
||||
// Cover
|
||||
items.insert( std::make_pair(MetaData::Type::HasCover, input.getNbPictures() > 0));
|
||||
items.insert( std::make_pair(MetaData::Type::HasCover, mediaFile.hasAttachedPictures()));
|
||||
|
||||
// Embedded MetaData
|
||||
// Make sure to convert strings into UTF-8
|
||||
|
||||
@@ -48,17 +48,21 @@ namespace MetaData
|
||||
};
|
||||
|
||||
// Used by Streams
|
||||
struct AudioStream {
|
||||
std::size_t nbChannels;
|
||||
struct AudioStream
|
||||
{
|
||||
std::string desc;
|
||||
std::size_t bitRate;
|
||||
};
|
||||
|
||||
struct VideoStream {
|
||||
struct VideoStream
|
||||
{
|
||||
std::string desc;
|
||||
std::size_t bitRate;
|
||||
};
|
||||
|
||||
struct SubtitleStream {
|
||||
;
|
||||
struct SubtitleStream
|
||||
{
|
||||
std::string desc;
|
||||
};
|
||||
|
||||
// Type and associated data
|
||||
|
||||
@@ -19,52 +19,31 @@
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "config/ConfigReader.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "DatabaseUpdateService.hpp"
|
||||
|
||||
static std::vector<std::string> splitStrings(const std::string& source)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
std::istringstream oss(source);
|
||||
|
||||
std::string str;
|
||||
while(oss >> str)
|
||||
res.push_back(str);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
namespace Service {
|
||||
|
||||
DatabaseUpdateService::DatabaseUpdateService(Wt::Dbo::SqlConnectionPool &connectionPool)
|
||||
: _metadataParser(),
|
||||
_databaseUpdater( connectionPool, _metadataParser)
|
||||
{
|
||||
_databaseUpdater.setAudioExtensions(splitStrings(ConfigReader::instance().getString("main.database.audio_extensions")));
|
||||
_databaseUpdater.setVideoExtensions(splitStrings(ConfigReader::instance().getString("main.database.video_extensions")));
|
||||
}
|
||||
|
||||
void
|
||||
DatabaseUpdateService::start(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, starting...";
|
||||
_databaseUpdater.start();
|
||||
}
|
||||
|
||||
void
|
||||
DatabaseUpdateService::stop(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopping...";
|
||||
_databaseUpdater.stop();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopped";
|
||||
}
|
||||
|
||||
void
|
||||
DatabaseUpdateService::restart(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, restart";
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include "ServiceManager.hpp"
|
||||
|
||||
namespace Service {
|
||||
@@ -34,112 +29,56 @@ ServiceManager::instance()
|
||||
}
|
||||
|
||||
ServiceManager::ServiceManager()
|
||||
: _signalSet(_ioService)
|
||||
{
|
||||
_signalSet.add(SIGINT);
|
||||
_signalSet.add(SIGTERM);
|
||||
#if defined(SIGQUIT)
|
||||
_signalSet.add(SIGQUIT);
|
||||
#endif // defined(SIGQUIT)
|
||||
|
||||
_signalSet.add(SIGHUP);
|
||||
|
||||
// Excplicitely ignore SIGCHLD to avoid zombies
|
||||
// when avconv child processes are being killed
|
||||
if (::signal(SIGCHLD, SIG_IGN) == SIG_ERR)
|
||||
throw std::runtime_error("ServiceManager::ServiceManager, signal failed!");
|
||||
}
|
||||
|
||||
ServiceManager::~ServiceManager()
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Stopping services...";
|
||||
stopServices();
|
||||
stop();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::run()
|
||||
{
|
||||
|
||||
asyncWaitSignals();
|
||||
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "ServiceManager: waiting for events...";
|
||||
try {
|
||||
// Wait for events
|
||||
_ioService.run();
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_ERROR) << "ServiceManager: exception in ioService::run: " << e.what();
|
||||
}
|
||||
|
||||
// Stopping services
|
||||
stopServices();
|
||||
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "ServiceManager: run complete !";
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::asyncWaitSignals(void)
|
||||
{
|
||||
_signalSet.async_wait(boost::bind(&ServiceManager::handleSignal,
|
||||
this,
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::signal_number));
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::startService(Service::pointer service)
|
||||
ServiceManager::add(Service::pointer service)
|
||||
{
|
||||
_services.insert(service);
|
||||
service->start();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::stopService(Service::pointer service)
|
||||
ServiceManager::del(Service::pointer service)
|
||||
{
|
||||
_services.erase(service);
|
||||
service->stop();
|
||||
_services.erase(service);
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::stopServices(void)
|
||||
ServiceManager::clear(void)
|
||||
{
|
||||
BOOST_FOREACH(Service::pointer service, _services)
|
||||
stop();
|
||||
_services.clear();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::start(void)
|
||||
{
|
||||
for (Service::pointer service : _services)
|
||||
service->start();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::stop(void)
|
||||
{
|
||||
for (Service::pointer service : _services)
|
||||
service->stop();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ServiceManager::restartServices(void)
|
||||
ServiceManager::restart(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Restarting services...";
|
||||
BOOST_FOREACH(Service::pointer service, _services)
|
||||
for (Service::pointer service : _services)
|
||||
service->restart();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::handleSignal(boost::system::error_code /*ec*/, int signo)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_INFO) << "Received signal " << signo;
|
||||
|
||||
switch (signo)
|
||||
{
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
case SIGQUIT:
|
||||
stopServices();
|
||||
|
||||
// Do not listen for signals, this will make the ioservice.run return
|
||||
break;
|
||||
case SIGHUP:
|
||||
restartServices();
|
||||
|
||||
asyncWaitSignals();
|
||||
break;
|
||||
default:
|
||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Unhandled signal " << signo;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#ifndef SERVICE_CONTROLER_HPP
|
||||
#define SERVICE_CONTROLER_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <set>
|
||||
|
||||
#include "Service.hpp"
|
||||
@@ -35,15 +34,15 @@ class ServiceManager
|
||||
static ServiceManager& instance();
|
||||
~ServiceManager();
|
||||
|
||||
void stopService(Service::pointer service);
|
||||
void startService(Service::pointer service);
|
||||
void add(Service::pointer service);
|
||||
void del(Service::pointer service);
|
||||
void clear();
|
||||
|
||||
void stopAllServices();
|
||||
void start();
|
||||
void stop();
|
||||
void restart();
|
||||
|
||||
// Return in case of failure/stop by user
|
||||
void run();
|
||||
|
||||
template <class T> typename T::pointer getService();
|
||||
template <class T> typename T::pointer get();
|
||||
|
||||
boost::mutex& mutex() { return _mutex;}
|
||||
|
||||
@@ -53,32 +52,20 @@ class ServiceManager
|
||||
ServiceManager(ServiceManager const&); // Don't Implement
|
||||
void operator=(ServiceManager const&); // Don't implement
|
||||
|
||||
void restartServices(void);
|
||||
void stopServices(void);
|
||||
|
||||
void asyncWaitSignals(void);
|
||||
|
||||
void handleSignal(boost::system::error_code error, int signo);
|
||||
|
||||
boost::mutex _mutex;
|
||||
|
||||
boost::asio::io_service _ioService;
|
||||
|
||||
// Listen for interesting signals
|
||||
boost::asio::signal_set _signalSet;
|
||||
|
||||
std::set<Service::pointer> _services;
|
||||
};
|
||||
|
||||
|
||||
template <class T> typename T::pointer
|
||||
ServiceManager::getService()
|
||||
ServiceManager::get()
|
||||
{
|
||||
std::set<Service::pointer>::iterator it;
|
||||
for (std::set<Service::pointer>::iterator it = _services.begin(); it != _services.end(); ++it)
|
||||
for (Service::pointer service : _services)
|
||||
{
|
||||
if (typeid(*(*it)) == typeid(T)) {
|
||||
return std::dynamic_pointer_cast<T>(*it);
|
||||
if (typeid(*(service)) == typeid(T)) {
|
||||
return std::dynamic_pointer_cast<T>(service);
|
||||
}
|
||||
}
|
||||
return std::shared_ptr<T>();
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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/Dbo/SqlConnectionPool>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "UserInterfaceService.hpp"
|
||||
#include "ui/LmsApplication.hpp"
|
||||
|
||||
#include "config/ConfigReader.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
UserInterfaceService::UserInterfaceService( boost::filesystem::path runAppPath, Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||
: _server(runAppPath.string())
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
|
||||
args.push_back(runAppPath.string());
|
||||
args.push_back("--docroot=" + ConfigReader::instance().getString("ui.resources.docroot"));
|
||||
args.push_back("--approot=" + ConfigReader::instance().getString("ui.resources.approot"));
|
||||
args.push_back("--https-port=" + std::to_string( ConfigReader::instance().getULong("ui.listen-endpoint.port")));
|
||||
args.push_back("--https-address=" + ConfigReader::instance().getString("ui.listen-endpoint.addr"));
|
||||
args.push_back("--ssl-certificate=" + ConfigReader::instance().getString("ui.ssl-crypto.cert"));
|
||||
args.push_back("--ssl-private-key=" + ConfigReader::instance().getString("ui.ssl-crypto.key"));
|
||||
args.push_back("--ssl-tmp-dh=" + ConfigReader::instance().getString("ui.ssl-crypto.dh"));
|
||||
|
||||
// Construct argc/argv
|
||||
int argc = args.size();
|
||||
const char* argv[args.size()];
|
||||
for (int i = 0; i < argc; ++i)
|
||||
argv[i] = args[i].c_str();
|
||||
|
||||
for(int i = 0; i < argc; ++i)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "i = " << i << ", arg = '" << argv[i] << "'";
|
||||
}
|
||||
|
||||
_server.setServerConfiguration (argc, const_cast<char**>(argv));
|
||||
|
||||
// bind entry point
|
||||
_server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, boost::ref(connectionPool)));
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
UserInterfaceService::start(void)
|
||||
{
|
||||
_server.start();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::start -> Service started...";
|
||||
}
|
||||
|
||||
void
|
||||
UserInterfaceService::stop(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::stop -> stopping...";
|
||||
_server.stop();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::stop -> stopped!";
|
||||
}
|
||||
|
||||
void
|
||||
UserInterfaceService::restart(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 WEB_SERVER_SERVICE_HPP
|
||||
#define WEB_SERVER_SERVICE_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <Wt/WServer>
|
||||
|
||||
#include "Service.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
class UserInterfaceService : public Service
|
||||
{
|
||||
public:
|
||||
|
||||
UserInterfaceService(boost::filesystem::path runAppPath, Wt::Dbo::SqlConnectionPool& connectionPool);
|
||||
|
||||
void start(void);
|
||||
void stop(void);
|
||||
void restart(void);
|
||||
|
||||
private:
|
||||
|
||||
Wt::WServer _server;
|
||||
|
||||
};
|
||||
|
||||
} //namespace Service
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 AVCONV_TRANSCODER_HPP
|
||||
#define AVCONV_TRANSCODER_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "Parameters.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
class AvConvTranscoder
|
||||
{
|
||||
public:
|
||||
static void init();
|
||||
|
||||
~AvConvTranscoder();
|
||||
|
||||
AvConvTranscoder(const Parameters& parameters);
|
||||
|
||||
const Parameters& getParameters(void) const { return _parameters; }
|
||||
|
||||
// Get a bunch of input data
|
||||
// Place it at the end of the parameter, no more that maxSize bytes
|
||||
void process(std::vector<unsigned char>& output, std::size_t maxSize);
|
||||
|
||||
bool isComplete(void) const { return _isComplete;};
|
||||
std::size_t getOutputBytes(void) const { return _outputBytes; }
|
||||
|
||||
private:
|
||||
|
||||
typedef std::shared_ptr<boost::process::child> ChildPtr;
|
||||
|
||||
void waitChild();
|
||||
void killChild();
|
||||
|
||||
const Parameters _parameters;
|
||||
|
||||
static boost::mutex _mutex;
|
||||
|
||||
boost::process::pipe _outputPipe;
|
||||
boost::iostreams::file_descriptor_source _source;
|
||||
boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> _is;
|
||||
std::istream _in;
|
||||
|
||||
std::shared_ptr<boost::process::child> _child;
|
||||
|
||||
static boost::filesystem::path _avConvPath;
|
||||
|
||||
bool _isComplete;
|
||||
std::size_t _outputBytes; // Bytes produced so far
|
||||
};
|
||||
|
||||
} // naspace Transcode
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <boost/foreach.hpp>
|
||||
#include <stdexcept>
|
||||
#include <cassert>
|
||||
|
||||
#include "Format.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
const std::vector<Format> Format::_supportedFormats
|
||||
{
|
||||
{Format::OGA, Format::Audio, "audio/ogg", "Ogg"},
|
||||
{Format::OGV, Format::Video, "video/ogg", "Ogg"},
|
||||
{Format::MP3, Format::Audio, "audio/mpeg", "MP3"},
|
||||
{Format::WEBMA, Format::Audio, "audio/webm", "WebM"},
|
||||
{Format::WEBMV, Format::Video, "video/webm", "WebM"},
|
||||
{Format::FLA, Format::Audio, "audio/x-flv", "Flash Audio"},
|
||||
{Format::FLV, Format::Video, "video/x-flv", "Flash Video"},
|
||||
{Format::M4A, Format::Audio, "audio/mp4", "MP4"},
|
||||
{Format::M4V, Format::Video, "video/mp4", "MP4"},
|
||||
};
|
||||
|
||||
Format::Format(Encoding encoding, Type type, std::string mimeType, std::string desc)
|
||||
:
|
||||
_encoding(encoding),
|
||||
_type(type),
|
||||
_mineType(mimeType),
|
||||
_desc(desc)
|
||||
{}
|
||||
|
||||
|
||||
const Format&
|
||||
Format::get(Encoding encoding)
|
||||
{
|
||||
BOOST_FOREACH(const Format& format, _supportedFormats)
|
||||
{
|
||||
if (format.getEncoding() == encoding)
|
||||
return format;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Cannot find format");
|
||||
}
|
||||
|
||||
std::vector<Format>
|
||||
Format::get(Type type)
|
||||
{
|
||||
std::vector<Format> res;
|
||||
BOOST_FOREACH(const Format& format, _supportedFormats)
|
||||
{
|
||||
if (format.getType() == type)
|
||||
res.push_back( format );
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
Format::operator==(const Format& other) const
|
||||
{
|
||||
return getEncoding() == other.getEncoding();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 TRANSCODE_FORMAT_HPP
|
||||
#define TRANSCODE_FORMAT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
|
||||
class Format
|
||||
{
|
||||
public:
|
||||
|
||||
// Output format
|
||||
enum Encoding {
|
||||
OGA,
|
||||
OGV,
|
||||
MP3,
|
||||
WEBMA,
|
||||
WEBMV,
|
||||
FLA,
|
||||
FLV,
|
||||
M4A,
|
||||
M4V,
|
||||
};
|
||||
|
||||
enum Type {
|
||||
Video,
|
||||
Audio,
|
||||
};
|
||||
|
||||
// Utility
|
||||
static const Format& get(Encoding encoding);
|
||||
static std::vector<Format> get(Type type);
|
||||
|
||||
Format(Encoding format, Type type, std::string mimeType, std::string desc);
|
||||
|
||||
Encoding getEncoding(void) const { return _encoding;}
|
||||
Type getType(void) const { return _type;}
|
||||
const std::string& getMimeType(void) const { return _mineType;}
|
||||
const std::string& getDesc(void) const { return _desc;}
|
||||
|
||||
bool operator==(const Format& other) const;
|
||||
|
||||
private:
|
||||
Encoding _encoding;
|
||||
Type _type;
|
||||
std::string _mineType;
|
||||
std::string _desc;
|
||||
|
||||
static const std::vector<Format> _supportedFormats;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <list>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
#include "cover/CoverArtGrabber.hpp"
|
||||
|
||||
#include "InputMediaFile.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
bool getStreamType(enum AVMediaType type, Transcode::Stream::Type& streamType)
|
||||
{
|
||||
switch (type) {
|
||||
case AVMEDIA_TYPE_VIDEO: streamType = Transcode::Stream::Video; return true;
|
||||
case AVMEDIA_TYPE_AUDIO: streamType = Transcode::Stream::Audio; return true;
|
||||
case AVMEDIA_TYPE_SUBTITLE: streamType = Transcode::Stream::Subtitle; return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InputMediaFile::InputMediaFile(const boost::filesystem::path& p)
|
||||
: _path(p)
|
||||
{
|
||||
Av::InputFormatContext input(_path);
|
||||
|
||||
if (!input.findStreamInfo())
|
||||
throw std::runtime_error("Cannot find stream info in file: " + p.string());
|
||||
|
||||
// Calculate estimated duration
|
||||
if (input.getDurationSecs())
|
||||
_duration = boost::posix_time::seconds(input.getDurationSecs() + 1);
|
||||
|
||||
// Get input streams
|
||||
std::vector<Av::Stream> avStreams = input.getStreams();
|
||||
|
||||
std::list<enum AVMediaType> avMediaTypes; // List of encountered streams
|
||||
for (std::size_t avStreamId = 0; avStreamId < avStreams.size(); ++avStreamId)
|
||||
{
|
||||
Av::Stream& avStream(avStreams[avStreamId]);
|
||||
Stream::Type type;
|
||||
|
||||
if (getStreamType(avStream.getCodecContext().getType(), type))
|
||||
{
|
||||
// Reject Video stream hat are in fact cover arts
|
||||
if (avStream.hasAttachedPic())
|
||||
continue;
|
||||
|
||||
avMediaTypes.push_back(avStream.getCodecContext().getType());
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Stream idx " << avStreamId << ", type = " << type << ", bitrate = " << avStream.getCodecContext().getBitRate() << ", codec desc = " << avStream.getCodecContext().getCodecDesc();
|
||||
|
||||
_streams.push_back( Stream(avStreamId,
|
||||
type,
|
||||
avStream.getCodecContext().getBitRate(),
|
||||
avStream.getMetadata().get("language"), // TODO define somewhere else?
|
||||
avStream.getCodecContext().getCodecDesc()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
avMediaTypes.unique();
|
||||
// Scan for best streams
|
||||
for (enum AVMediaType type : avMediaTypes)
|
||||
{
|
||||
Av::Stream::Idx index;
|
||||
|
||||
if (input.getBestStreamIdx(type, index))
|
||||
{
|
||||
Stream::Type streamType;
|
||||
if (getStreamType(type, streamType))
|
||||
_bestStreams.insert(std::make_pair( streamType, index) );
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_WARNING) << "Cannot find best stream for type " << type;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Stream>
|
||||
InputMediaFile::getStreams(Stream::Type type) const
|
||||
{
|
||||
std::vector<Stream> res;
|
||||
for (const Stream& stream : _streams)
|
||||
{
|
||||
if (stream.getType() == type)
|
||||
res.push_back(stream);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
const Stream&
|
||||
InputMediaFile::getStream(Stream::Id index) const
|
||||
{
|
||||
for (const Stream& stream : _streams)
|
||||
{
|
||||
if (stream.getId() == index)
|
||||
return stream;
|
||||
}
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_CRIT) << "Cannot find stream index " << index << " in stream map!";
|
||||
throw std::runtime_error("InputMediaFile::getStream, cannot find stream idx");
|
||||
}
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 TRANSCODE_INPUT_MEDIA_FILE
|
||||
#define TRANSCODE_INPUT_MEDIA_FILE
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "cover/CoverArt.hpp"
|
||||
|
||||
#include "Stream.hpp"
|
||||
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
class InputMediaFile
|
||||
{
|
||||
public:
|
||||
|
||||
enum StreamType
|
||||
{
|
||||
StreamAudio,
|
||||
StreamVideo,
|
||||
StreamSubtitle,
|
||||
};
|
||||
|
||||
InputMediaFile(const boost::filesystem::path& p);
|
||||
|
||||
// Accessors
|
||||
boost::filesystem::path getPath(void) const {return _path;}
|
||||
boost::posix_time::time_duration getDuration(void) const {return _duration;}
|
||||
|
||||
// Stream handling
|
||||
const Stream& getStream(Stream::Id id) const;
|
||||
std::vector<Stream> getStreams(Stream::Type type) const;
|
||||
const std::map<Stream::Type, Stream::Id>& getBestStreams(void) const {return _bestStreams;}
|
||||
|
||||
private:
|
||||
|
||||
boost::filesystem::path _path;
|
||||
boost::posix_time::time_duration _duration;
|
||||
|
||||
std::vector<Stream> _streams;
|
||||
std::map<Stream::Type, Stream::Id> _bestStreams;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif // TRANSCODE_INPUT_MEDIA_FILE
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 "logger/Logger.hpp"
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
|
||||
#include "Parameters.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
Parameters::Parameters(const InputMediaFile& inputMediaFile,
|
||||
const Format& outputFormat)
|
||||
:
|
||||
_mediaFile(inputMediaFile),
|
||||
_outputFormat(outputFormat)
|
||||
{
|
||||
// By default, select the best stream indexes
|
||||
_inputStreams = _mediaFile.getBestStreams();
|
||||
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Parameters::setBitrate(Stream::Type type, std::size_t bitrate)
|
||||
{
|
||||
// Limit the output bitrate to the input bitrate
|
||||
if (_inputStreams.find(type) != _inputStreams.end())
|
||||
{
|
||||
const Transcode::Stream& stream = _mediaFile.getStream( _inputStreams[type] );
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Stream bitrate = " << stream.getBitrate();
|
||||
|
||||
if (bitrate > stream.getBitrate())
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Setting bitrate for stream idx " << _inputStreams[type] << " to input bitrate (" << stream.getBitrate() << ")";
|
||||
_outputBitrate[type] = stream.getBitrate();
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Setting bitrate for stream idx " << _inputStreams[type] << " to " << bitrate;
|
||||
_outputBitrate[type] = bitrate;
|
||||
}
|
||||
return _outputBitrate[type];
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Cannot find stream type " << type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Parameters::getOutputBitrate(Stream::Type type) const
|
||||
{
|
||||
std::map<Stream::Type, std::size_t>::const_iterator it = _outputBitrate.find(type);
|
||||
|
||||
if (it != _outputBitrate.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "output bitrate not set for type " << type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 TRANSCODE_PARAMETERS_HPP
|
||||
#define TRANSCODE_PARAMETERS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "Format.hpp"
|
||||
|
||||
#include "InputMediaFile.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
|
||||
class Parameters {
|
||||
|
||||
public:
|
||||
|
||||
Parameters(const InputMediaFile& InputFile, const Format& outputFormat);
|
||||
|
||||
// Modifiers
|
||||
void setOffset(boost::posix_time::time_duration offset) { _offset = offset; } // Set input offset
|
||||
void setOutputFormat(const Format& format) { _outputFormat = format; }
|
||||
std::size_t setBitrate(Stream::Type type, std::size_t bitrate);
|
||||
|
||||
// Manually select an input stream to output
|
||||
// There can be only one stream per each type (video, audio, subtitle)
|
||||
typedef std::map<Stream::Type, Stream::Id> StreamMap;
|
||||
void selectInputStream(Stream::Type type, Stream::Id id) { _inputStreams[type] = id; }
|
||||
const StreamMap& getInputStreams(void) const {return _inputStreams;}
|
||||
|
||||
//Accessors
|
||||
boost::posix_time::time_duration getOffset(void) const {return _offset;}
|
||||
const Format& getOutputFormat(void) const { return _outputFormat; }
|
||||
std::size_t getOutputBitrate(Stream::Type type) const;
|
||||
const InputMediaFile& getInputMediaFile(void) const { return _mediaFile;}
|
||||
InputMediaFile& getInputMediaFile(void) { return _mediaFile;}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
InputMediaFile _mediaFile;
|
||||
|
||||
boost::posix_time::time_duration _offset; // start input offset
|
||||
|
||||
Format _outputFormat; // OGA, OGV, etc.
|
||||
std::map<Stream::Type, std::size_t> _outputBitrate;
|
||||
|
||||
StreamMap _inputStreams;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 TRANSCODE_STREAM_HPP
|
||||
#define TRANSCODE_STREAM_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
class Stream
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
enum Type
|
||||
{
|
||||
Audio,
|
||||
Video,
|
||||
Subtitle,
|
||||
};
|
||||
|
||||
typedef std::size_t Id;
|
||||
|
||||
Stream(Id id, Type type, std::size_t bitrate, const std::string& lang, const std::string& desc)
|
||||
: _id(id), _type(type), _bitrate(bitrate), _language(lang), _desc(desc) {}
|
||||
|
||||
// Accessors
|
||||
Id getId() const { return _id;}
|
||||
Type getType() const { return _type;}
|
||||
std::size_t getBitrate() const { return _bitrate;}
|
||||
const std::string& getLanguage() const { return _language;}
|
||||
const std::string& getDesc() const { return _desc;}
|
||||
|
||||
private:
|
||||
|
||||
Id _id;
|
||||
Type _type;
|
||||
std::size_t _bitrate;
|
||||
std::string _language;
|
||||
std::string _desc;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <Wt/WVBoxLayout>
|
||||
#include <Wt/Auth/Identity>
|
||||
|
||||
#include "config/config.h"
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "settings/Settings.hpp"
|
||||
@@ -36,7 +38,9 @@
|
||||
#include "auth/LmsAuth.hpp"
|
||||
#include "audio/desktop/DesktopAudio.hpp"
|
||||
#include "audio/mobile/MobileAudio.hpp"
|
||||
#if HAVE_VIDEO
|
||||
#include "video/VideoWidget.hpp"
|
||||
#endif
|
||||
#include "common/LineEdit.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
@@ -167,7 +171,7 @@ LmsApplication::handleAuthEvent(void)
|
||||
{
|
||||
if (DbHandler().getLogin().loggedIn())
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_NOTICE) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId();
|
||||
LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId();
|
||||
|
||||
this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden);
|
||||
setConfirmCloseMessage("Closing LMS. Are you sure?");
|
||||
@@ -196,10 +200,10 @@ LmsApplication::handleAuthEvent(void)
|
||||
else
|
||||
audio = new Desktop::Audio();
|
||||
|
||||
VideoWidget *videoWidget = new VideoWidget();
|
||||
|
||||
leftMenu->addItem("Audio", audio);
|
||||
leftMenu->addItem("Video", videoWidget);
|
||||
#if defined HAVE_VIDEO
|
||||
leftMenu->addItem("Video", new VideoWidget());
|
||||
#endif
|
||||
leftMenu->addItem("Settings", new Settings::Settings());
|
||||
|
||||
// Setup a Right-aligned menu.
|
||||
@@ -238,7 +242,7 @@ LmsApplication::handleAuthEvent(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_NOTICE) << "User logged out, session = " << Wt::WApplication::instance()->sessionId();
|
||||
LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId();
|
||||
|
||||
quit("");
|
||||
redirect("/");
|
||||
|
||||
@@ -1,423 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||
<!--FORMS message blocks-->
|
||||
|
||||
<message id="firstConnectionForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:name}">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${name}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${name-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:email}">
|
||||
e-Mail
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${email}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${email-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password}">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password}
|
||||
</div>
|
||||
${password-info}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password-confirm}">
|
||||
Confirm password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password-confirm}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-confirm-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
|
||||
<message id="userForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:name}">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${name}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${name-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:email}">
|
||||
e-Mail
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${email}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${email-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password}">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password-confirm}">
|
||||
Confirm password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password-confirm}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-confirm-info}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<legend>${access}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:admin}">
|
||||
Admin
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${admin}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${admin-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:audio-bitrate-limit}">
|
||||
Audio Bitrate Limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${audio-bitrate-limit}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${audio-bitrate-limit-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:video-bitrate-limit}">
|
||||
Video Bitrate Limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${video-bitrate-limit}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${video-bitrate-limit-info}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</message>
|
||||
|
||||
<message id="userAccountForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:name}">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${name}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${name-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:email}">
|
||||
e-Mail
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${email}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${email-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password}">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password-confirm}">
|
||||
Confirm password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password-confirm}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-confirm-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="audioForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:bitrate}">
|
||||
Audio bitrate
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${bitrate}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${bitrate-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:encoding}">
|
||||
Audio Encoding
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${encoding}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${encoding-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mediaDirectoryForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:path}">
|
||||
Path
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${path}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${path-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:type}">
|
||||
Type
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${type}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${type-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="databaseForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:update-period}">
|
||||
Update period
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${update-period}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${update-period-info}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:update-start-time}">
|
||||
Update start time
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${update-start-time}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${update-start-time-info}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${apply-button} ${discard-button} ${immediate-scan-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mediaplayer-controls">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
${prev}
|
||||
${play}
|
||||
${pause}
|
||||
${next}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mobile-search">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">${search}</div>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mobile-search-title">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="mobile-search-title">${text}</div>
|
||||
</div>
|
||||
</div>
|
||||
</message>
|
||||
<message id="mobile-search-more">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="mobile-search-more vertical-align">${text}</div>
|
||||
</div>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mobile-artist-res">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="mobile-search-entry">${name}</div>
|
||||
</div>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mobile-release-res">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="mobile-search-entry">
|
||||
<div class="row">
|
||||
<div class ="vertical-align">
|
||||
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
|
||||
<div class="col-xs-10">${name}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mobile-track-res">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="mobile-search-entry">
|
||||
<div class="row">
|
||||
<div class="vertical-align">
|
||||
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
|
||||
<div class="col-xs-7">${name}</div>
|
||||
<div class="col-xs-3">${btn}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mobile-audio-footer">
|
||||
<nav class="navbar navbar-default navbar-fixed-bottom" role="navigation">
|
||||
${player}
|
||||
</nav>
|
||||
</message>
|
||||
|
||||
<message id="mobile-audio-player">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="row">
|
||||
<div class="mobile-audio-footer">
|
||||
<div class="vertical-align mobile-audio-footer">
|
||||
<div class="col-xs-2">${cover}</div>
|
||||
<div class="col-xs-7">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">${track}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">${artist}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-3">${play}${pause}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
</messages>
|
||||
@@ -64,7 +64,7 @@ AudioMediaPlayer::AudioMediaPlayer(Wt::WContainerWidget *parent)
|
||||
}
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_INFO) << "Audio player using encoding " << _encoding;
|
||||
LMS_LOG(UI, INFO) << "Audio player using encoding " << _encoding;
|
||||
|
||||
// Current Media info
|
||||
Wt::WHBoxLayout *currentMediaLayout = new Wt::WHBoxLayout();
|
||||
@@ -172,85 +172,92 @@ AudioMediaPlayer::AudioMediaPlayer(Wt::WContainerWidget *parent)
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayer::loadPlayer(void)
|
||||
AudioMediaPlayer::loadPlayer(boost::filesystem::path filePath, Av::TranscodeParameters& parameters)
|
||||
{
|
||||
_currentFile = filePath;
|
||||
_currentParameters = parameters;
|
||||
|
||||
_mediaPlayer->clearSources();
|
||||
|
||||
if (_mediaResource)
|
||||
delete _mediaResource;
|
||||
|
||||
assert( _currentParameters );
|
||||
_mediaResource = new AvConvTranscodeStreamResource( *_currentParameters, this );
|
||||
_mediaResource = new AvConvTranscodeStreamResource( filePath, parameters, this );
|
||||
|
||||
_mediaPlayer->addSource( getEncoding(), Wt::WLink(_mediaResource));
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayer::load(Database::Track::id_type trackId)
|
||||
{
|
||||
std::size_t bitrate = 0;
|
||||
boost::filesystem::path trackPath;
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
|
||||
Database::Track::pointer track = Database::Track::getById(DboSession(), trackId);
|
||||
|
||||
bitrate = CurrentUser()->getAudioBitrate();
|
||||
trackPath = track->getPath();
|
||||
_mediaTitle->setText ( Wt::WString::fromUTF8(track->getName()) );
|
||||
_mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtist()->getName()) + " - " + Wt::WString::fromUTF8(track->getRelease()->getName()) );
|
||||
_mediaCover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 72)));
|
||||
}
|
||||
|
||||
Transcode::Format::Encoding encoding;
|
||||
switch (_encoding)
|
||||
{
|
||||
case Wt::WMediaPlayer::MP3: encoding = Transcode::Format::MP3; break;
|
||||
case Wt::WMediaPlayer::FLA: encoding = Transcode::Format::FLA; break;
|
||||
case Wt::WMediaPlayer::OGA: encoding = Transcode::Format::OGA; break;
|
||||
case Wt::WMediaPlayer::WEBMA: encoding = Transcode::Format::WEBMA; break;
|
||||
default:
|
||||
encoding = Transcode::Format::MP3;
|
||||
}
|
||||
|
||||
_timeSlider->setDisabled(false);
|
||||
|
||||
try
|
||||
{
|
||||
Transcode::Parameters parameters(trackPath, Transcode::Format::get(encoding));
|
||||
parameters.setBitrate(Transcode::Stream::Audio, bitrate);
|
||||
|
||||
_currentParameters = std::make_shared<Transcode::Parameters>( parameters );
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Cannot load input file '" << trackPath << "'";
|
||||
return;
|
||||
}
|
||||
|
||||
loadPlayer();
|
||||
|
||||
_timeSlider->setRange(0, _currentParameters->getInputMediaFile().getDuration().total_seconds() );
|
||||
_timeSlider->setValue(0);
|
||||
|
||||
_duration->setText( boost::posix_time::to_simple_string( _currentParameters->getInputMediaFile().getDuration() ));
|
||||
|
||||
// Auto play
|
||||
_mediaPlayer->play();
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayer::load(Database::Track::id_type trackId)
|
||||
{
|
||||
Av::TranscodeParameters parameters;
|
||||
|
||||
boost::filesystem::path path;
|
||||
|
||||
boost::posix_time::time_duration duration;
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
|
||||
Database::Track::pointer track = Database::Track::getById(DboSession(), trackId);
|
||||
|
||||
path = track->getPath();
|
||||
parameters.setBitrate(Av::Stream::Type::Audio, CurrentUser()->getAudioBitrate() );
|
||||
|
||||
duration = track->getDuration();
|
||||
|
||||
_mediaTitle->setText ( Wt::WString::fromUTF8(track->getName()) );
|
||||
_mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtist()->getName()) + " - " + Wt::WString::fromUTF8(track->getRelease()->getName()) );
|
||||
_mediaCover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 72)));
|
||||
}
|
||||
|
||||
Av::MediaFile mediaFile(path);
|
||||
|
||||
if (!mediaFile.open())
|
||||
{
|
||||
// No longer exist ? TODO next?
|
||||
LMS_LOG(UI, INFO) << "Cannot open file '" << path << "'";
|
||||
return;
|
||||
}
|
||||
|
||||
// It seems to be far better to manually map the streams
|
||||
// otherwise, some files may have to be fully transcoded to be played by browser...
|
||||
int audioBestStreamId = mediaFile.getBestStreamId(Av::Stream::Type::Audio);
|
||||
if (audioBestStreamId != -1)
|
||||
parameters.addStream(audioBestStreamId);
|
||||
|
||||
Av::Encoding encoding;
|
||||
switch (_encoding)
|
||||
{
|
||||
case Wt::WMediaPlayer::MP3: encoding = Av::Encoding::MP3; break;
|
||||
case Wt::WMediaPlayer::FLA: encoding = Av::Encoding::FLA; break;
|
||||
case Wt::WMediaPlayer::OGA: encoding = Av::Encoding::OGA; break;
|
||||
case Wt::WMediaPlayer::WEBMA: encoding = Av::Encoding::WEBMA; break;
|
||||
default:
|
||||
encoding = Av::Encoding::MP3;
|
||||
}
|
||||
parameters.setEncoding(encoding);
|
||||
|
||||
_timeSlider->setDisabled(false);
|
||||
_timeSlider->setRange(0, duration.total_seconds() );
|
||||
_timeSlider->setValue(0);
|
||||
|
||||
_duration->setText( boost::posix_time::to_simple_string( duration ));
|
||||
|
||||
loadPlayer(path, parameters);
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayer::handlePlayOffset(int offsetSecs)
|
||||
{
|
||||
if (!_currentParameters)
|
||||
return;
|
||||
Av::TranscodeParameters parameters = _currentParameters;
|
||||
|
||||
_currentParameters->setOffset( boost::posix_time::seconds(offsetSecs) );
|
||||
parameters.setOffset( boost::posix_time::seconds(offsetSecs) );
|
||||
|
||||
loadPlayer();
|
||||
|
||||
_mediaPlayer->play();
|
||||
loadPlayer(_currentFile, parameters);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -262,10 +269,7 @@ AudioMediaPlayer::handleTrackEnded(void)
|
||||
void
|
||||
AudioMediaPlayer::handleTimeUpdated(void)
|
||||
{
|
||||
if (!_currentParameters)
|
||||
return;
|
||||
|
||||
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters->getOffset().total_seconds()));
|
||||
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
|
||||
|
||||
_timeSlider->setValue( currentTime.total_seconds() );
|
||||
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "av/AvTranscoder.hpp"
|
||||
#include "resource/AvConvTranscodeStreamResource.hpp"
|
||||
#include "resource/CoverResource.hpp"
|
||||
|
||||
@@ -70,7 +70,7 @@ class AudioMediaPlayer : public Wt::WContainerWidget
|
||||
|
||||
void handleVolumeSliderMoved(int value);
|
||||
|
||||
void loadPlayer(void);
|
||||
void loadPlayer(boost::filesystem::path filePath, Av::TranscodeParameters& parameters);
|
||||
|
||||
// Signals
|
||||
Wt::Signal<void> _playbackEnded;
|
||||
@@ -90,7 +90,6 @@ class AudioMediaPlayer : public Wt::WContainerWidget
|
||||
Wt::WText* _mediaArtistRelease;
|
||||
|
||||
// Controls
|
||||
std::shared_ptr<Transcode::Parameters> _currentParameters;
|
||||
Wt::WPushButton* _playBtn;
|
||||
Wt::WPushButton* _pauseBtn;
|
||||
Wt::WSlider* _timeSlider;
|
||||
@@ -98,6 +97,10 @@ class AudioMediaPlayer : public Wt::WContainerWidget
|
||||
Wt::WText* _curTime;
|
||||
Wt::WText* _duration;
|
||||
|
||||
// Transcode
|
||||
boost::filesystem::path _currentFile;
|
||||
Av::TranscodeParameters _currentParameters;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Desktop
|
||||
|
||||
@@ -215,7 +215,7 @@ _playQueue(nullptr)
|
||||
_mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1));
|
||||
|
||||
_playQueue->tracksUpdated().connect(std::bind([=] () {
|
||||
LMS_LOG(MOD_UI, SEV_INFO) << "Playqueue updated!";
|
||||
LMS_LOG(UI, INFO) << "Playqueue updated!";
|
||||
|
||||
playlistSaveFromPlayqueue(CurrentQueuePlaylistName);
|
||||
}));
|
||||
@@ -308,14 +308,14 @@ Audio::playlistShowSaveDialog(std::string playlistName)
|
||||
void
|
||||
Audio::playlistSaveFromPlayqueue(std::string playlistName)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "'";
|
||||
LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "'";
|
||||
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
|
||||
Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser());
|
||||
if (playlist)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'";
|
||||
LMS_LOG(UI, INFO) << "Erasing playlist '" << playlistName << "'";
|
||||
playlist.remove();
|
||||
}
|
||||
|
||||
@@ -333,13 +333,13 @@ Audio::playlistSaveFromPlayqueue(std::string playlistName)
|
||||
PlaylistEntry::create(DboSession(), track, playlist, pos++);
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries";
|
||||
LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries";
|
||||
}
|
||||
|
||||
void
|
||||
Audio::playlistLoadToPlayqueue(std::string playlistName)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue";
|
||||
LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue";
|
||||
|
||||
std::vector<Track::id_type> entries;
|
||||
|
||||
@@ -356,7 +356,7 @@ Audio::playlistLoadToPlayqueue(std::string playlistName)
|
||||
_playQueue->clear();
|
||||
_playQueue->addTracks(entries);
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue done. " << entries.size() << " entries";
|
||||
LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue done. " << entries.size() << " entries";
|
||||
}
|
||||
|
||||
|
||||
@@ -395,7 +395,7 @@ Audio::playlistRefreshMenus()
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
|
||||
// Clear playlists in each menu
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Save item count: " << _popupMenuSave->count();
|
||||
LMS_LOG(UI, DEBUG) << "Save item count: " << _popupMenuSave->count();
|
||||
|
||||
WPopupMenuClear(_popupMenuDelete);
|
||||
WPopupMenuClear(_popupMenuLoad);
|
||||
@@ -457,7 +457,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
|
||||
{
|
||||
std::vector<Track::id_type> trackIds;
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected");
|
||||
LMS_LOG(UI, DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected");
|
||||
|
||||
_playQueue->clear();
|
||||
|
||||
@@ -472,7 +472,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
|
||||
break;
|
||||
case PlayQueueAddSelectedTracks:
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding selected tracks...";
|
||||
LMS_LOG(UI, DEBUG) << "Adding selected tracks...";
|
||||
|
||||
// If nothing selected, get all the track and play everything
|
||||
if (_trackView->getNbSelectedTracks() == 0)
|
||||
|
||||
@@ -371,7 +371,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding " << trackIds.size() << " tracks to play queue";
|
||||
LMS_LOG(UI, DEBUG) << "Adding " << trackIds.size() << " tracks to play queue";
|
||||
|
||||
// Add tracks to model
|
||||
for (Track::id_type trackId : trackIds)
|
||||
@@ -389,10 +389,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& 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);
|
||||
@@ -465,7 +462,7 @@ PlayQueue::playPrevious(void)
|
||||
void
|
||||
PlayQueue::readTrack(int rowPos)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount();
|
||||
LMS_LOG(UI, DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount();
|
||||
|
||||
if (rowPos < _model->rowCount())
|
||||
{
|
||||
|
||||
@@ -150,7 +150,7 @@ TrackView::refresh(SearchFilter& filter)
|
||||
void
|
||||
TrackView::getSelectedTracks(std::vector<Track::id_type>& track_ids)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting selected tracks...";
|
||||
LMS_LOG(UI, DEBUG) << "Getting selected tracks...";
|
||||
|
||||
Wt::WModelIndexSet indexSet = this->selectedIndexes();
|
||||
|
||||
@@ -164,7 +164,7 @@ TrackView::getSelectedTracks(std::vector<Track::id_type>& track_ids)
|
||||
track_ids.push_back(id);
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks: " << track_ids.size();
|
||||
LMS_LOG(UI, DEBUG) << "Getting all selected tracks: " << track_ids.size();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
@@ -192,7 +192,7 @@ TrackView::getFirstSelectedTrackPosition(void)
|
||||
void
|
||||
TrackView::getTracks(std::vector<Track::id_type>& trackIds)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks...";
|
||||
LMS_LOG(UI, DEBUG) << "Getting all tracks...";
|
||||
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
Wt::Dbo::collection<Track::UIQueryResult> results = _queryModel.query();
|
||||
@@ -203,7 +203,7 @@ TrackView::getTracks(std::vector<Track::id_type>& trackIds)
|
||||
trackIds.push_back(id);
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!";
|
||||
LMS_LOG(UI, DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!";
|
||||
}
|
||||
|
||||
} // namespace Desktop
|
||||
|
||||
@@ -156,7 +156,7 @@ Audio::Audio(Wt::WContainerWidget *parent)
|
||||
|
||||
trackSearch->trackPlay().connect(std::bind([=] (Track::id_type id)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing track id " << id;
|
||||
LMS_LOG(UI, DEBUG) << "Playing track id " << id;
|
||||
// TODO reduce transaction scope here
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
|
||||
@@ -164,21 +164,23 @@ Audio::Audio(Wt::WContainerWidget *parent)
|
||||
|
||||
if (track)
|
||||
{
|
||||
Av::TranscodeParameters parameters;
|
||||
|
||||
// Determine the output format using the encoding of the player
|
||||
Transcode::Format::Encoding encoding;
|
||||
Av::Encoding encoding;
|
||||
switch(mediaPlayer->getEncoding())
|
||||
{
|
||||
case Wt::WMediaPlayer::MP3: encoding = Transcode::Format::MP3; break;
|
||||
case Wt::WMediaPlayer::FLA: encoding = Transcode::Format::FLA; break;
|
||||
case Wt::WMediaPlayer::OGA: encoding = Transcode::Format::OGA; break;
|
||||
case Wt::WMediaPlayer::WEBMA: encoding = Transcode::Format::WEBMA; break;
|
||||
case Wt::WMediaPlayer::MP3: encoding = Av::Encoding::MP3; break;
|
||||
case Wt::WMediaPlayer::FLA: encoding = Av::Encoding::FLA; break;
|
||||
case Wt::WMediaPlayer::OGA: encoding = Av::Encoding::OGA; break;
|
||||
case Wt::WMediaPlayer::WEBMA: encoding = Av::Encoding::WEBMA; break;
|
||||
default:
|
||||
encoding = Transcode::Format::MP3;
|
||||
encoding = Av::Encoding::MP3;
|
||||
}
|
||||
// TODO compute parameters using user s profile
|
||||
Transcode::InputMediaFile inputFile(track->getPath());
|
||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding));
|
||||
parameters.setBitrate(Transcode::Stream::Audio, 96000);
|
||||
parameters.setEncoding(encoding);
|
||||
|
||||
// TODO compute parameters using user's profile
|
||||
parameters.setBitrate(Av::Stream::Type::Audio, 96000);
|
||||
|
||||
mediaPlayer->play(track.id(), parameters);
|
||||
}
|
||||
|
||||
@@ -80,17 +80,9 @@ _encoding(encoding)
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayer::play(Database::Track::id_type trackId, const Transcode::Parameters& parameters)
|
||||
AudioMediaPlayer::play(Database::Track::id_type trackId, Av::TranscodeParameters parameters)
|
||||
{
|
||||
// FIXME memleak here
|
||||
AvConvTranscodeStreamResource *resource = new AvConvTranscodeStreamResource( parameters, this );
|
||||
|
||||
_player->clearSources();
|
||||
_player->addSource( _encoding, Wt::WLink(resource));
|
||||
|
||||
_player->play();
|
||||
|
||||
_cover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 48)));
|
||||
boost::filesystem::path path;
|
||||
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
@@ -99,7 +91,19 @@ AudioMediaPlayer::play(Database::Track::id_type trackId, const Transcode::Parame
|
||||
|
||||
_track->setText( Wt::WString::fromUTF8(track->getName() ));
|
||||
_artistRelease->setText( Wt::WString::fromUTF8(track->getArtist()->getName()) );
|
||||
path = track->getPath();
|
||||
}
|
||||
|
||||
// FIXME memleak here
|
||||
AvConvTranscodeStreamResource *resource = new AvConvTranscodeStreamResource( path, parameters, this );
|
||||
|
||||
_player->clearSources();
|
||||
_player->addSource( _encoding, Wt::WLink(resource));
|
||||
|
||||
_player->play();
|
||||
|
||||
_cover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 48)));
|
||||
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <Wt/WImage>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "av/AvTranscoder.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
namespace Mobile {
|
||||
@@ -38,7 +38,7 @@ class AudioMediaPlayer : public Wt::WContainerWidget
|
||||
|
||||
AudioMediaPlayer(Wt::WMediaPlayer::Encoding encoding, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
void play(Database::Track::id_type trackId, const Transcode::Parameters& parameters);
|
||||
void play(Database::Track::id_type trackId, Av::TranscodeParameters parameters);
|
||||
|
||||
Wt::WMediaPlayer::Encoding getEncoding() const { return _encoding; }
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
.main-nav {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.Wt-hrh2 {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.Wt-vrh2 {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
div.contents {
|
||||
padding: 0px 12px 6px;
|
||||
}
|
||||
|
||||
.playqueue {
|
||||
background-color: #EEE;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.playqueue-playing {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.playqueue-track {
|
||||
font-size: 120%;
|
||||
margin-top: 12px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.playqueue-artist {
|
||||
line-height: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.playqueue-cover {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.mediaplayer {
|
||||
background-color: #CCC;
|
||||
border-radius: 10px;
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
.mediaplayer-btn-controls {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mediaplayer-current-cover {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.mediaplayer-current-track {
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
.mediaplayer-current-artist {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.mobile-search-title {
|
||||
font-weight: bold;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
background-color: grey;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mobile-search-entry {
|
||||
min-height: 64px;
|
||||
border-bottom: 1px solid lightgray;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mobile-search-entry:active {
|
||||
background-color: lightgrey;
|
||||
}
|
||||
|
||||
.mobile-search-more {
|
||||
height: 64px;
|
||||
border-bottom: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.mobile-search-more:active {
|
||||
background-color: lightgrey;
|
||||
}
|
||||
|
||||
.mobile-track {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mobile-artist {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.vertical-align {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mobile-audio-footer {
|
||||
background-color: #f5f5f5;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.mobile-audio-player-cover {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -27,16 +27,17 @@
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
AvConvTranscodeStreamResource::AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent)
|
||||
AvConvTranscodeStreamResource::AvConvTranscodeStreamResource(boost::filesystem::path p, Av::TranscodeParameters parameters, Wt::WObject *parent)
|
||||
: Wt::WResource(parent),
|
||||
_parameters( parameters )
|
||||
_filePath(p),
|
||||
_parameters( parameters )
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "CONSTRUCTING RESOURCE";
|
||||
LMS_LOG(UI, DEBUG) << "CONSTRUCTING RESOURCE";
|
||||
}
|
||||
|
||||
AvConvTranscodeStreamResource::~AvConvTranscodeStreamResource()
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "DESTRUCTING RESOURCE";
|
||||
LMS_LOG(UI, DEBUG) << "DESTRUCTING RESOURCE";
|
||||
beingDeleted();
|
||||
}
|
||||
|
||||
@@ -47,19 +48,25 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
|
||||
// see if this request is for a continuation:
|
||||
Wt::Http::ResponseContinuation *continuation = request.continuation();
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Handling new request. Continuation = " << continuation;
|
||||
LMS_LOG(UI, DEBUG) << "Handling new request. Continuation = " << continuation;
|
||||
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder;
|
||||
std::shared_ptr<Av::Transcoder> transcoder;
|
||||
if (continuation)
|
||||
transcoder = boost::any_cast<std::shared_ptr<Transcode::AvConvTranscoder> >(continuation->data());
|
||||
transcoder = boost::any_cast<std::shared_ptr<Av::Transcoder> >(continuation->data());
|
||||
|
||||
if (!transcoder)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Launching transcoder";
|
||||
transcoder = std::make_shared<Transcode::AvConvTranscoder>( _parameters);
|
||||
LMS_LOG(UI, DEBUG) << "Launching transcoder";
|
||||
transcoder = std::make_shared<Av::Transcoder>( _filePath, _parameters);
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Mime type set to '" << _parameters.getOutputFormat().getMimeType() << "'";
|
||||
response.setMimeType(_parameters.getOutputFormat().getMimeType());
|
||||
LMS_LOG(UI, DEBUG) << "Mime type set to '" << Av::encoding_to_mimetype(_parameters.getEncoding());
|
||||
response.setMimeType( Av::encoding_to_mimetype(_parameters.getEncoding()) );
|
||||
|
||||
if (!transcoder->start())
|
||||
{
|
||||
LMS_LOG(UI, ERROR) << "Cannot start transcoder";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!transcoder->isComplete())
|
||||
@@ -72,10 +79,10 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
|
||||
// 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();
|
||||
LMS_LOG(UI, DEBUG) << "Written " << data.size() << " bytes! complete = " << std::boolalpha << transcoder->isComplete();
|
||||
|
||||
if (!response.out())
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Write failed!";
|
||||
LMS_LOG(UI, ERROR) << "Write failed!";
|
||||
}
|
||||
|
||||
if (!transcoder->isComplete() && response.out()) {
|
||||
@@ -83,7 +90,7 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
|
||||
continuation->setData(transcoder);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "No more data!";
|
||||
LMS_LOG(UI, DEBUG) << "No more data!";
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -25,23 +25,22 @@
|
||||
|
||||
#include <Wt/WResource>
|
||||
|
||||
#include "transcode/AvConvTranscoder.hpp"
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "av/AvTranscoder.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class AvConvTranscodeStreamResource : public Wt::WResource
|
||||
{
|
||||
public:
|
||||
AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent = 0);
|
||||
AvConvTranscodeStreamResource(boost::filesystem::path p, Av::TranscodeParameters parameters, Wt::WObject *parent = 0);
|
||||
~AvConvTranscodeStreamResource();
|
||||
|
||||
void handleRequest(const Wt::Http::Request& request,
|
||||
Wt::Http::Response& response);
|
||||
|
||||
private:
|
||||
Transcode::Parameters _parameters;
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> _transcoder;
|
||||
boost::filesystem::path _filePath;
|
||||
Av::TranscodeParameters _parameters;
|
||||
|
||||
static const std::size_t _bufferSize = 8192*16;
|
||||
};
|
||||
|
||||
@@ -41,29 +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;
|
||||
|
||||
CoverArt::CoverArt defaultCover;
|
||||
if (!image.load( Wt::WApplication::instance()->docRoot() + unknownCoverPath ))
|
||||
throw std::runtime_error("Cannot read default cover file");
|
||||
|
||||
std::vector<unsigned char> data;
|
||||
{
|
||||
std::ifstream ist(Wt::WApplication::instance()->docRoot() + unknownCoverPath);
|
||||
char c;
|
||||
while(ist.get(c))
|
||||
data.push_back(c);
|
||||
}
|
||||
image.scale(size);
|
||||
|
||||
defaultCover.setData(data);
|
||||
defaultCover.setMimeType("image/jpeg");
|
||||
defaultCover.scale(size);
|
||||
|
||||
auto res = _defaultCovers.insert(std::make_pair(size, defaultCover));
|
||||
auto res = _defaultCovers.insert(std::make_pair(size, image));
|
||||
itCover = res.first;
|
||||
}
|
||||
|
||||
@@ -89,11 +81,14 @@ 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( cover.getMimeType() );
|
||||
BOOST_FOREACH(unsigned char c, cover.getData())
|
||||
response.out().put( c );
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
cover.save(data, Image::Format::JPEG);
|
||||
|
||||
response.setMimeType( Image::format_to_mimeType(Image::Format::JPEG) );
|
||||
response.out().write(reinterpret_cast<const char *>(&data[0]), data.size());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -106,7 +101,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
||||
|
||||
try
|
||||
{
|
||||
std::vector<CoverArt::CoverArt> covers;
|
||||
std::vector<Image::Image> covers;
|
||||
|
||||
// Mandatory parameter size
|
||||
if (!sizeStr)
|
||||
@@ -142,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<std::mutex> lock(_mutex);
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
@@ -160,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))
|
||||
{
|
||||
@@ -175,7 +167,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
||||
}
|
||||
catch (std::invalid_argument& e)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Invalid argument: " << e.what();
|
||||
LMS_LOG(UI, ERROR) << "Invalid argument: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <Wt/WResource>
|
||||
|
||||
#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<std::size_t, CoverArt::CoverArt> _defaultCovers;
|
||||
std::map<std::size_t, Image::Image> _defaultCovers;
|
||||
|
||||
// TODO construct a cache for covers?
|
||||
};
|
||||
|
||||
@@ -91,7 +91,7 @@ Settings::Settings(Wt::WContainerWidget* parent)
|
||||
void
|
||||
Settings::handleDatabaseDirectoriesChanged()
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_NOTICE) << "Media directories have changed: requesting imediate scan";
|
||||
LMS_LOG(UI, INFO) << "Media directories have changed: requesting imediate scan";
|
||||
// On directory add or delete, request an immediate scan
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
@@ -107,7 +107,7 @@ Settings::restartDatabaseUpdateService()
|
||||
// Restarting the update service
|
||||
boost::lock_guard<boost::mutex> serviceLock (Service::ServiceManager::instance().mutex());
|
||||
|
||||
Service::DatabaseUpdateService::pointer service = Service::ServiceManager::instance().getService<Service::DatabaseUpdateService>();
|
||||
Service::DatabaseUpdateService::pointer service = Service::ServiceManager::instance().get<Service::DatabaseUpdateService>();
|
||||
if (service)
|
||||
service->restart();
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class AccountFormModel : public Wt::WFormModel
|
||||
}
|
||||
catch(Wt::Dbo::Exception& exception)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
|
||||
LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class AudioFormModel : public Wt::WFormModel
|
||||
}
|
||||
catch(Wt::Dbo::Exception& exception)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
|
||||
LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class DatabaseFormModel : public Wt::WFormModel
|
||||
}
|
||||
catch(Wt::Dbo::Exception& exception)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
|
||||
LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class FirstConnectionFormModel : public Wt::WFormModel
|
||||
// If it's the case, just do nothing
|
||||
if (!Database::User::getAll(DboSession()).empty())
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Admin user already created";
|
||||
LMS_LOG(UI, ERROR) << "Admin user already created";
|
||||
error = Wt::WString("Admin user already created!");
|
||||
return false;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ class FirstConnectionFormModel : public Wt::WFormModel
|
||||
}
|
||||
catch(Wt::Dbo::Exception& exception)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
|
||||
LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
|
||||
error = Wt::WString(exception.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class MediaDirectoryFormModel : public Wt::WFormModel
|
||||
}
|
||||
catch(Wt::Dbo::Exception& exception)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
|
||||
LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,12 +168,12 @@ class UserFormModel : public Wt::WFormModel
|
||||
|
||||
// user may have been deleted by someone else
|
||||
if (!authUser.isValid()) {
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "user identity does not exist!";
|
||||
LMS_LOG(UI, ERROR) << "user identity does not exist!";
|
||||
return false;
|
||||
}
|
||||
else if(!user)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "User not found!";
|
||||
LMS_LOG(UI, ERROR) << "User not found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ class UserFormModel : public Wt::WFormModel
|
||||
}
|
||||
catch(Wt::Dbo::Exception& exception)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
|
||||
LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,12 +96,12 @@ Users::refresh(void)
|
||||
}
|
||||
catch(Wt::Dbo::Exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception when getting userId=" << userId << ": " << e.code();
|
||||
LMS_LOG(UI, ERROR) << "Caught exception when getting userId=" << userId << ": " << e.code();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!authUser.isValid()) {
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Users::refresh: skipping invalid userId = " << userId;
|
||||
LMS_LOG(UI, ERROR) << "Users::refresh: skipping invalid userId = " << userId;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
#include <Wt/WMediaPlayer>
|
||||
#include <Wt/WFileResource>
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
#include "VideoDatabaseWidget.hpp"
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp> //include all types plus i/o
|
||||
|
||||
#include <Wt/WMediaPlayer>
|
||||
|
||||
@@ -29,25 +28,25 @@
|
||||
namespace UserInterface {
|
||||
|
||||
Wt::WMediaPlayer::Encoding
|
||||
convert(Transcode::Format format)
|
||||
AvEncoding_to_WtEncoding(Av::Encoding encoding)
|
||||
{
|
||||
switch( format.getEncoding() )
|
||||
switch( encoding )
|
||||
{
|
||||
case Transcode::Format::OGA: return Wt::WMediaPlayer::OGA;
|
||||
case Transcode::Format::OGV: return Wt::WMediaPlayer::OGV;
|
||||
case Transcode::Format::MP3: return Wt::WMediaPlayer::MP3;
|
||||
case Transcode::Format::WEBMA: return Wt::WMediaPlayer::WEBMA;
|
||||
case Transcode::Format::WEBMV: return Wt::WMediaPlayer::WEBMV;
|
||||
case Transcode::Format::FLA: return Wt::WMediaPlayer::FLA;
|
||||
case Transcode::Format::FLV: return Wt::WMediaPlayer::FLV;
|
||||
case Transcode::Format::M4A: return Wt::WMediaPlayer::M4A;
|
||||
case Transcode::Format::M4V: return Wt::WMediaPlayer::M4V;
|
||||
case Av::Encoding::OGA: return Wt::WMediaPlayer::OGA;
|
||||
case Av::Encoding::OGV: return Wt::WMediaPlayer::OGV;
|
||||
case Av::Encoding::MP3: return Wt::WMediaPlayer::MP3;
|
||||
case Av::Encoding::WEBMA: return Wt::WMediaPlayer::WEBMA;
|
||||
case Av::Encoding::WEBMV: return Wt::WMediaPlayer::WEBMV;
|
||||
case Av::Encoding::FLA: return Wt::WMediaPlayer::FLA;
|
||||
case Av::Encoding::FLV: return Wt::WMediaPlayer::FLV;
|
||||
case Av::Encoding::M4A: return Wt::WMediaPlayer::M4A;
|
||||
case Av::Encoding::M4V: return Wt::WMediaPlayer::M4V;
|
||||
}
|
||||
assert(0);
|
||||
}
|
||||
|
||||
|
||||
VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent)
|
||||
VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_mediaResource(nullptr),
|
||||
_currentParameters(parameters),
|
||||
@@ -102,11 +101,16 @@ VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Transcode::Parameters& par
|
||||
Wt::WPushButton* parametersButton = new Wt::WPushButton("Parameters", this);
|
||||
parametersButton->clicked().connect( this, &VideoMediaPlayerWidget::handleParametersEdit );
|
||||
|
||||
_currentFile = mediaFile.getPath();
|
||||
|
||||
load(parameters);
|
||||
|
||||
_timeSlider->setRange(0, mediaFile.getDuration().total_seconds() );
|
||||
_duration->setText( boost::posix_time::to_simple_string( mediaFile.getDuration() ));
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters)
|
||||
VideoMediaPlayerWidget::load(Av::TranscodeParameters parameters)
|
||||
{
|
||||
_mediaPlayer->clearSources();
|
||||
|
||||
@@ -116,15 +120,13 @@ VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters)
|
||||
if (_mediaResource)
|
||||
delete _mediaResource;
|
||||
|
||||
_mediaResource = new AvConvTranscodeStreamResource( parameters, this );
|
||||
_mediaResource = new AvConvTranscodeStreamResource( _currentFile, parameters, this );
|
||||
_mediaInternalLink.setResource( _mediaResource );
|
||||
|
||||
_mediaPlayer->addSource( convert(parameters.getOutputFormat()), _mediaInternalLink );
|
||||
_mediaPlayer->addSource( AvEncoding_to_WtEncoding(parameters.getEncoding()), _mediaInternalLink );
|
||||
|
||||
_timeSlider->setRange(0, parameters.getInputMediaFile().getDuration().total_seconds() );
|
||||
_timeSlider->setValue( parameters.getOffset().total_seconds() );
|
||||
_timeSlider->setValue( 0 );
|
||||
|
||||
_duration->setText( boost::posix_time::to_simple_string( parameters.getInputMediaFile().getDuration() ));
|
||||
|
||||
_mediaPlayer->play();
|
||||
}
|
||||
@@ -132,7 +134,6 @@ VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters)
|
||||
void
|
||||
VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)
|
||||
{
|
||||
std::cout << "Want to play at offset " << offsetSecs << std::endl;;
|
||||
_currentParameters.setOffset( boost::posix_time::seconds(offsetSecs) );
|
||||
load( _currentParameters );
|
||||
|
||||
@@ -142,22 +143,17 @@ VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)
|
||||
void
|
||||
VideoMediaPlayerWidget::handleSliderMoved(int value)
|
||||
{
|
||||
std::cout << "Slider moved to " << value << std::endl;
|
||||
_curTime->setText( boost::posix_time::to_simple_string( boost::posix_time::seconds( value ) ) );
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handleTimeUpdated(void)
|
||||
{
|
||||
std::cout << "Time updated to " << _mediaPlayer->currentTime() << std::endl;
|
||||
|
||||
if (_mediaPlayer->currentTime() > 0 && _mediaPlayer->currentTime() < _currentParameters.getInputMediaFile().getDuration().total_seconds())
|
||||
{
|
||||
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
|
||||
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
|
||||
|
||||
_timeSlider->setValue( currentTime.total_seconds() );
|
||||
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
|
||||
}
|
||||
_timeSlider->setValue( currentTime.total_seconds() );
|
||||
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
|
||||
}
|
||||
|
||||
void
|
||||
@@ -175,13 +171,14 @@ VideoMediaPlayerWidget::handleClose(void)
|
||||
void
|
||||
VideoMediaPlayerWidget::handleParametersEdit(void)
|
||||
{
|
||||
|
||||
/*
|
||||
_dialog = new VideoParametersDialog("Parameters");
|
||||
_dialog->load(_currentParameters);
|
||||
|
||||
_dialog->show();
|
||||
|
||||
_dialog->finished().connect(this, &VideoMediaPlayerWidget::handleParametersDone);
|
||||
*/
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
#include "VideoParametersDialog.hpp"
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "av/AvTranscoder.hpp"
|
||||
#include "resource/AvConvTranscodeStreamResource.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
@@ -37,18 +37,16 @@ class VideoMediaPlayerWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
|
||||
VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
Wt::Signal<void>& close() { return _close; };
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// Signals
|
||||
Wt::Signal<void> _close;
|
||||
|
||||
void load(const Transcode::Parameters& parameters);
|
||||
void load(Av::TranscodeParameters parameters);
|
||||
|
||||
// Player controls
|
||||
void handlePlayOffset(int offsetSecs);
|
||||
@@ -62,13 +60,15 @@ class VideoMediaPlayerWidget : public Wt::WContainerWidget
|
||||
void handleParametersEdit(void);
|
||||
void handleParametersDone(Wt::WDialog::DialogCode);
|
||||
|
||||
boost::filesystem::path _currentFile;
|
||||
|
||||
// Core
|
||||
Wt::WMediaPlayer* _mediaPlayer;
|
||||
AvConvTranscodeStreamResource* _mediaResource;
|
||||
Wt::WLink _mediaInternalLink;
|
||||
|
||||
// Controls
|
||||
Transcode::Parameters _currentParameters;
|
||||
Av::TranscodeParameters _currentParameters;
|
||||
Wt::WPushButton* _playBtn;
|
||||
Wt::WPushButton* _pauseBtn;
|
||||
Wt::WSlider* _timeSlider;
|
||||
|
||||
@@ -17,53 +17,25 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <Wt/WPushButton>
|
||||
#include <Wt/WLabel>
|
||||
#include <Wt/WTable>
|
||||
|
||||
#include "VideoParametersDialog.hpp"
|
||||
|
||||
using namespace Transcode;
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
static const std::list<Stream::Type> streamTypes = {Stream::Video, Stream::Audio, Stream::Subtitle};
|
||||
static const std::list<Av::Stream::Type> streamTypes = {Av::Stream::Type::Video, Av::Stream::Type::Audio, Av::Stream::Type::Subtitle};
|
||||
|
||||
VideoParametersDialog::VideoParametersDialog(const Wt::WString &windowTitle, Wt::WDialog* parent)
|
||||
VideoParametersDialog::VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent)
|
||||
: Wt::WDialog(windowTitle, parent)
|
||||
{
|
||||
|
||||
Wt::WTable* layout = new Wt::WTable(contents());
|
||||
int row = 0;
|
||||
|
||||
{
|
||||
Wt::WLabel* label = new Wt::WLabel("Format");
|
||||
_outputFormat = new Wt::WComboBox();
|
||||
label->setBuddy(_outputFormat);
|
||||
|
||||
layout->elementAt(row, 0)->addWidget(label);
|
||||
layout->elementAt(row, 1)->addWidget(_outputFormat);
|
||||
|
||||
_outputFormatModel = new Wt::WStringListModel(_outputFormat);
|
||||
|
||||
std::vector<Format> formats = Format::get( Format::Video );
|
||||
|
||||
for(std::size_t idFormat = 0; idFormat < formats.size(); ++idFormat)
|
||||
{
|
||||
_outputFormatModel->addString(formats[idFormat].getDesc());
|
||||
_outputFormatModel->setData(idFormat, 0, formats[idFormat].getEncoding(), Wt::UserRole);
|
||||
}
|
||||
|
||||
_outputFormat->setModel(_outputFormatModel);
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
createStreamWidgets("Video", Stream::Video, layout);
|
||||
createStreamWidgets("Audio", Stream::Audio, layout);
|
||||
createStreamWidgets("Subtitles", Stream::Subtitle, layout);
|
||||
createStreamWidgets("Video", Av::Stream::Type::Video, layout);
|
||||
createStreamWidgets("Audio", Av::Stream::Type::Audio, layout);
|
||||
createStreamWidgets("Subtitles", Av::Stream::Type::Subtitle, layout);
|
||||
|
||||
Wt::WPushButton *ok = new Wt::WPushButton("Apply", contents());
|
||||
ok->clicked().connect(this, &Wt::WDialog::accept);
|
||||
@@ -73,7 +45,7 @@ VideoParametersDialog::VideoParametersDialog(const Wt::WString &windowTitle, Wt:
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Transcode::Stream::Type type, Wt::WTable* layout)
|
||||
VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Av::Stream::Type type, Wt::WTable* layout)
|
||||
{
|
||||
int row = layout->rowCount();
|
||||
|
||||
@@ -99,29 +71,21 @@ VideoParametersDialog::handleApply()
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Stream>& streams)
|
||||
VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams)
|
||||
{
|
||||
for (std::size_t idStream = 0; idStream < streams.size(); ++idStream)
|
||||
for (const Av::Stream& stream : streams)
|
||||
{
|
||||
const Stream& stream = streams[idStream];
|
||||
|
||||
std::ostringstream oss;
|
||||
if (!stream.getLanguage().empty())
|
||||
oss << "[" << stream.getLanguage() << "] ";
|
||||
|
||||
oss << stream.getDesc();
|
||||
|
||||
model->addString(oss.str());
|
||||
model->setData(idStream, 0, stream.getId(), Wt::UserRole);
|
||||
model->addString(stream.desc);
|
||||
model->setData(model->rowCount(), 0, stream.id, Wt::UserRole);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::selectStream(const Wt::WStringListModel* model, Stream::Id streamId, Wt::WComboBox* combo)
|
||||
VideoParametersDialog::selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo)
|
||||
{
|
||||
for (int idStream = 0; idStream < model->rowCount(); ++idStream)
|
||||
{
|
||||
Stream::Id id = boost::any_cast<Stream::Id>( model->data( model->index(idStream, 0), Wt::UserRole));
|
||||
int id = boost::any_cast<int>( model->data( model->index(idStream, 0), Wt::UserRole));
|
||||
if (id == streamId)
|
||||
{
|
||||
combo->setCurrentIndex(idStream);
|
||||
@@ -132,55 +96,44 @@ VideoParametersDialog::selectStream(const Wt::WStringListModel* model, Stream::I
|
||||
|
||||
|
||||
void
|
||||
VideoParametersDialog::load(const Transcode::Parameters& parameters)
|
||||
VideoParametersDialog::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters)
|
||||
{
|
||||
// Select proper encoding
|
||||
for (int row = 0; row < _outputFormat->count(); ++row)
|
||||
{
|
||||
Format::Encoding encoding = boost::any_cast<Format::Encoding>( _outputFormatModel->data(_outputFormatModel->index(row,0), Wt::UserRole));
|
||||
if (encoding == parameters.getOutputFormat().getEncoding()) {
|
||||
_outputFormat->setCurrentIndex(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current selected input streams
|
||||
Parameters::StreamMap streamMap = parameters.getInputStreams();;
|
||||
|
||||
// Populate the combox with the available streams
|
||||
// And then show the selected one
|
||||
BOOST_FOREACH(Stream::Type streamType, streamTypes)
|
||||
for (Av::Stream::Type streamType : streamTypes)
|
||||
{
|
||||
Wt::WComboBox* combo = _streamSelection[streamType].first;
|
||||
Wt::WStringListModel* model = _streamSelection[streamType].second;
|
||||
|
||||
addStreams(model, parameters.getInputMediaFile().getStreams( streamType ) );
|
||||
addStreams(model, mediaFile.getStreams(streamType) );
|
||||
|
||||
selectStream(model, streamMap[streamType], combo);
|
||||
// Pre Select if necessary
|
||||
std::set<int> selectedStreams = currentParameters.getSelectedStreamIds();
|
||||
for (Av::Stream stream : mediaFile.getStreams(streamType))
|
||||
{
|
||||
if (selectedStreams.find(stream.id) != selectedStreams.end())
|
||||
{
|
||||
selectStream(model, stream.id, combo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::save(Transcode::Parameters& parameters)
|
||||
VideoParametersDialog::save(Av::TranscodeParameters& parameters)
|
||||
{
|
||||
std::cout << "Grabbing user input!" << std::endl;
|
||||
|
||||
// Get encoder used
|
||||
Format::Encoding encoding = boost::any_cast<Format::Encoding>( _outputFormatModel->data(_outputFormatModel->index(_outputFormat->currentIndex(), 0), Wt::UserRole));
|
||||
parameters.setOutputFormat( Format::get(encoding) );
|
||||
|
||||
// Get stream selected, if any
|
||||
BOOST_FOREACH(Stream::Type streamType, streamTypes)
|
||||
for (Av::Stream::Type streamType : streamTypes)
|
||||
{
|
||||
Wt::WComboBox* combo = _streamSelection[streamType].first;
|
||||
Wt::WStringListModel* model = _streamSelection[streamType].second;
|
||||
|
||||
if (combo->currentIndex() >= 0)
|
||||
{
|
||||
Stream::Id streamId = boost::any_cast<Stream::Id>( model->data(model->index(combo->currentIndex(), 0), Wt::UserRole));
|
||||
int streamId = boost::any_cast<int>( model->data(model->index(combo->currentIndex(), 0), Wt::UserRole));
|
||||
|
||||
parameters.selectInputStream(streamType, streamId);
|
||||
parameters.addStream(streamId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef VIDEO_PARAMETER_DIALOG
|
||||
#define VIDEO_PARAMETER_DIALOG
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -28,7 +27,7 @@
|
||||
#include <Wt/WStringListModel>
|
||||
#include <Wt/WString>
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "av/AvTranscoder.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
@@ -36,13 +35,13 @@ class VideoParametersDialog : public Wt::WDialog
|
||||
{
|
||||
public:
|
||||
// parameters to be edited
|
||||
VideoParametersDialog(const Wt::WString &windowTitle, Wt::WDialog* parent = 0);
|
||||
VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent = 0);
|
||||
|
||||
// Populates widget contents using these paramaters
|
||||
void load(const Transcode::Parameters& parameters);
|
||||
void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters);
|
||||
|
||||
// Save widgets contents into these parameters
|
||||
void save(Transcode::Parameters& parameters);
|
||||
void save(Av::TranscodeParameters& parameters);
|
||||
|
||||
// Signal to be emitted if parameters are changed
|
||||
Wt::Signal<void>& apply() { return _apply; }
|
||||
@@ -52,19 +51,14 @@ class VideoParametersDialog : public Wt::WDialog
|
||||
void handleApply(void);
|
||||
|
||||
// Stream handling
|
||||
void createStreamWidgets(const Wt::WString& label, Transcode::Stream::Type type, Wt::WTable* layout);
|
||||
void addStreams(Wt::WStringListModel* model, const std::vector<Transcode::Stream>& streams);
|
||||
void selectStream(const Wt::WStringListModel* model, Transcode::Stream::Id streamId, Wt::WComboBox* combo);
|
||||
void createStreamWidgets(const Wt::WString& label, Av::Stream::Type type, Wt::WTable* layout);
|
||||
void addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams);
|
||||
void selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo);
|
||||
|
||||
Wt::Signal<void> _apply;
|
||||
|
||||
Wt::WComboBox* _outputFormat;
|
||||
Wt::WStringListModel* _outputFormatModel;
|
||||
|
||||
std::map<Transcode::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;
|
||||
std::map<Av::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;
|
||||
};
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -49,49 +49,42 @@ VideoWidget::search(const std::string& searchText)
|
||||
void
|
||||
VideoWidget::playVideo(boost::filesystem::path p)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Want to play video " << p << "'" << std::endl;
|
||||
try {
|
||||
LMS_LOG(UI, DEBUG) << "Want to play video " << p << "'";
|
||||
|
||||
std::size_t audioBitrate = 0;
|
||||
std::size_t videoBitrate = 0;
|
||||
std::size_t audioBitrate = 0;
|
||||
std::size_t videoBitrate = 0;
|
||||
|
||||
// Get user preferences
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
// Get user preferences
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
|
||||
audioBitrate = CurrentUser()->getMaxAudioBitrate();
|
||||
videoBitrate = CurrentUser()->getMaxVideoBitrate();
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Max bitrate set to " << videoBitrate << "/" << audioBitrate;
|
||||
|
||||
Transcode::InputMediaFile inputFile(p);
|
||||
|
||||
Transcode::Format::Encoding encoding;
|
||||
|
||||
if (Wt::WApplication::instance()->environment().agentIsChrome())
|
||||
encoding = Transcode::Format::WEBMV;
|
||||
else
|
||||
encoding = Transcode::Format::FLV;
|
||||
|
||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding));
|
||||
|
||||
// TODO, make a quality button in order to choose...
|
||||
|
||||
parameters.setBitrate(Transcode::Stream::Audio, 0/*audioBitrate*/);
|
||||
parameters.setBitrate(Transcode::Stream::Video, 0/*videoBitrate*/);
|
||||
|
||||
VideoMediaPlayerWidget *mediaPlayer = new VideoMediaPlayerWidget(parameters, this);
|
||||
mediaPlayer->close().connect(std::bind([=] () {
|
||||
_videoDbWidget->setHidden(false);
|
||||
delete mediaPlayer;
|
||||
}));
|
||||
|
||||
_videoDbWidget->setHidden(true);
|
||||
}
|
||||
catch( std::exception& e) {
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception while loading '" << p << "': " << e.what() << std::endl;
|
||||
audioBitrate = CurrentUser()->getMaxAudioBitrate();
|
||||
videoBitrate = CurrentUser()->getMaxVideoBitrate();
|
||||
}
|
||||
|
||||
LMS_LOG(UI, DEBUG) << "Max bitrate set to " << videoBitrate << "/" << audioBitrate;
|
||||
|
||||
Av::MediaFile mediaFile(p);
|
||||
|
||||
if (!mediaFile.open())
|
||||
return;
|
||||
|
||||
if (!mediaFile.scan())
|
||||
return;
|
||||
|
||||
Av::TranscodeParameters parameters;
|
||||
|
||||
parameters.setEncoding( Av::Encoding::WEBMV );
|
||||
|
||||
VideoMediaPlayerWidget *mediaPlayer = new VideoMediaPlayerWidget(mediaFile, parameters, this);
|
||||
|
||||
mediaPlayer->close().connect(std::bind([=] ()
|
||||
{
|
||||
_videoDbWidget->setHidden(false);
|
||||
delete mediaPlayer;
|
||||
}));
|
||||
|
||||
_videoDbWidget->setHidden(true);
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
||||
!_TAG_FILE_SORTED 2 /0=unsorted, 1=sorted, 2=foldcase/
|
||||
!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
|
||||
!_TAG_PROGRAM_NAME Exuberant Ctags //
|
||||
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
|
||||
!_TAG_PROGRAM_VERSION 5.9~svn20110310 //
|
||||
addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addDirectory(const std::string& name, boost::filesystem::path path, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addDirectory(const std::string& name, boost::filesystem::path path, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addHeader(void)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addHeader(void);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void>& apply() { return _apply; }$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
AvEncoding_to_WtEncoding /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^AvEncoding_to_WtEncoding(Av::Encoding encoding)$/;" f language:C++ namespace:UserInterface
|
||||
backToList /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void backToList(void);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void>& close() { return _close; };$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Av::Stream::Type type, Wt::WTable* layout)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void createStreamWidgets(const Wt::WString& label, Av::Stream::Type type, Wt::WTable* layout);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::handleApply()$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void handleApply(void);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleClose(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleClose(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleFullscreen /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleFullscreen(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersDone(Wt::WDialog::DialogCode);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersEdit(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersEdit(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handlePlayOffset(int offsetSecs);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleTimeUpdated(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleTimeUpdated(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleVolumeSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleVolumeSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; }$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::playVideo(boost::filesystem::path p)$/;" f language:C++ class:UserInterface::VideoWidget
|
||||
playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void playVideo(boost::filesystem::path p);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::save(Av::TranscodeParameters& parameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void save(Av::TranscodeParameters& parameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::search(const std::string& searchText)$/;" f language:C++ class:UserInterface::VideoWidget
|
||||
search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void search(const std::string& searchText);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
streamTypes /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^static const std::list<Av::Stream::Type> streamTypes = {Av::Stream::Type::Video, Av::Stream::Type::Audio, Av::Stream::Type::Subtitle};$/;" m language:C++ namespace:UserInterface file:
|
||||
updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::updateView(boost::filesystem::path directory, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void updateView(boost::filesystem::path directory, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^namespace UserInterface {$/;" n language:C++ file:
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^namespace UserInterface {$/;" n language:C++
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^namespace UserInterface {$/;" n language:C++ file:
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^namespace UserInterface {$/;" n language:C++
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^namespace UserInterface {$/;" n language:C++ file:
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^namespace UserInterface {$/;" n language:C++
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^namespace UserInterface {$/;" n language:C++ file:
|
||||
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^namespace UserInterface {$/;" n language:C++
|
||||
UserInterface::AvEncoding_to_WtEncoding /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^AvEncoding_to_WtEncoding(Av::Encoding encoding)$/;" f language:C++ namespace:UserInterface
|
||||
UserInterface::streamTypes /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^static const std::list<Av::Stream::Type> streamTypes = {Av::Stream::Type::Video, Av::Stream::Type::Audio, Av::Stream::Type::Subtitle};$/;" m language:C++ namespace:UserInterface file:
|
||||
UserInterface::VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^class VideoDatabaseWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
|
||||
UserInterface::VideoDatabaseWidget::addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addDirectory(const std::string& name, boost::filesystem::path path, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addDirectory(const std::string& name, boost::filesystem::path path, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addHeader(void)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addHeader(void);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; }$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::updateView(boost::filesystem::path directory, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void updateView(boost::filesystem::path directory, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::VideoDatabaseWidget(Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ VideoDatabaseWidget(Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::_playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path > _playVideo;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoDatabaseWidget::_table /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::WTable* _table;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
UserInterface::VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^class VideoMediaPlayerWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
|
||||
UserInterface::VideoMediaPlayerWidget::close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void>& close() { return _close; };$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleClose(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleClose(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleFullscreen /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleFullscreen(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersDone(Wt::WDialog::DialogCode);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersEdit(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersEdit(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handlePlayOffset(int offsetSecs);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleTimeUpdated(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleTimeUpdated(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleVolumeSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleVolumeSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void> _close;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_currentParameters /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Av::TranscodeParameters _currentParameters;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_curTime /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _curTime;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_dialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoParametersDialog* _dialog;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_duration /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _duration;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_mediaInternalLink /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WLink _mediaInternalLink;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WMediaPlayer* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_mediaResource /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ AvConvTranscodeStreamResource* _mediaResource;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_pauseBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _pauseBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_playBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _playBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_timeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _timeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoMediaPlayerWidget::_volumeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _volumeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
UserInterface::VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^class VideoParametersDialog : public Wt::WDialog$/;" c language:C++ namespace:UserInterface
|
||||
UserInterface::VideoParametersDialog::addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void>& apply() { return _apply; }$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Av::Stream::Type type, Wt::WTable* layout)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void createStreamWidgets(const Wt::WString& label, Av::Stream::Type type, Wt::WTable* layout);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::handleApply()$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void handleApply(void);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::save(Av::TranscodeParameters& parameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void save(Av::TranscodeParameters& parameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent = 0);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::_apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void> _apply;$/;" m language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoParametersDialog::_streamSelection /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ std::map<Av::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;$/;" m language:C++ class:UserInterface::VideoParametersDialog
|
||||
UserInterface::VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^class VideoWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
|
||||
UserInterface::VideoWidget::backToList /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void backToList(void);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::playVideo(boost::filesystem::path p)$/;" f language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void playVideo(boost::filesystem::path p);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::search(const std::string& searchText)$/;" f language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void search(const std::string& searchText);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::VideoWidget(Wt::WContainerWidget* parent )$/;" f language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoWidget(Wt::WContainerWidget* parent = 0);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoMediaPlayerWidget* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoWidget
|
||||
UserInterface::VideoWidget::_videoDbWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoDatabaseWidget* _videoDbWidget;$/;" m language:C++ class:UserInterface::VideoWidget
|
||||
VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::VideoDatabaseWidget(Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ VideoDatabaseWidget(Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^class VideoDatabaseWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
|
||||
VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^class VideoMediaPlayerWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
|
||||
VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent)$/;" f language:C++ class:UserInterface::VideoParametersDialog
|
||||
VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent = 0);$/;" p language:C++ class:UserInterface::VideoParametersDialog
|
||||
VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^class VideoParametersDialog : public Wt::WDialog$/;" c language:C++ namespace:UserInterface
|
||||
VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::VideoWidget(Wt::WContainerWidget* parent )$/;" f language:C++ class:UserInterface::VideoWidget
|
||||
VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoWidget(Wt::WContainerWidget* parent = 0);$/;" p language:C++ class:UserInterface::VideoWidget
|
||||
VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^class VideoWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
|
||||
VIDEO_DB_WIDGET_HPP /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp 21;" d language:C++
|
||||
VIDEO_WIDGET_HPP /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp 21;" d language:C++
|
||||
_apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void> _apply;$/;" m language:C++ class:UserInterface::VideoParametersDialog
|
||||
_close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void> _close;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_currentParameters /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Av::TranscodeParameters _currentParameters;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_curTime /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _curTime;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_dialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoParametersDialog* _dialog;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_duration /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _duration;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_mediaInternalLink /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WLink _mediaInternalLink;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WMediaPlayer* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoMediaPlayerWidget* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoWidget
|
||||
_mediaResource /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ AvConvTranscodeStreamResource* _mediaResource;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_pauseBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _pauseBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_playBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _playBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path > _playVideo;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
_streamSelection /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ std::map<Av::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;$/;" m language:C++ class:UserInterface::VideoParametersDialog
|
||||
_table /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::WTable* _table;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
|
||||
_timeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _timeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
_videoDbWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoDatabaseWidget* _videoDbWidget;$/;" m language:C++ class:UserInterface::VideoWidget
|
||||
_volumeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _volumeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
|
||||
__VIDEO_MEDIA_PLAYER_WIDGET_HPP /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp 21;" d language:C++
|
||||
Reference in New Issue
Block a user