Made transcoding optional

This commit is contained in:
emeric
2019-05-20 12:55:17 +02:00
parent 49dc6c84f1
commit 234d9f80ef
22 changed files with 412 additions and 286 deletions
+4 -4
View File
@@ -42,17 +42,17 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" for="${id:audio-bitrate-limit}"> <label class="control-label col-sm-2" for="${id:audio-transcode-bitrate-limit}">
${tr:Lms.Admin.User.audio-bitrate-limit} ${tr:Lms.Admin.User.audio-transcode-bitrate-limit}
</label> </label>
<div class="col-sm-5"> <div class="col-sm-5">
<div class="input-group"> <div class="input-group">
${audio-bitrate-limit} ${audio-transcode-bitrate-limit}
<span class="input-group-addon">kbps</span> <span class="input-group-addon">kbps</span>
</div> </div>
</div> </div>
<div class="help-block col-sm-5"> <div class="help-block col-sm-5">
${audio-bitrate-limit-info} ${audio-transcode-bitrate-limit-info}
</div> </div>
</div> </div>
${<if-demo>} ${<if-demo>}
+9 -8
View File
@@ -45,7 +45,7 @@
<message id="Lms.Admin.Users.users">Users</message> <message id="Lms.Admin.Users.users">Users</message>
<!--User--> <!--User-->
<message id="Lms.Admin.User.audio-bitrate-limit">Audio bitrate limit</message> <message id="Lms.Admin.User.audio-transcode-bitrate-limit">Audio transcode bitrate limit</message>
<message id="Lms.Admin.User.demo-account">Demo account</message> <message id="Lms.Admin.User.demo-account">Demo account</message>
<message id="Lms.Admin.User.demo-account-already-exists">Demo account already exists!</message> <message id="Lms.Admin.User.demo-account-already-exists">Demo account already exists!</message>
<message id="Lms.Admin.User.demo-password-invalid">Demo password must be the login name!</message> <message id="Lms.Admin.User.demo-password-invalid">Demo password must be the login name!</message>
@@ -114,14 +114,15 @@
<!--Settings--> <!--Settings-->
<message id="Lms.Settings.account">Account</message> <message id="Lms.Settings.account">Account</message>
<message id="Lms.Settings.audio">Audio</message> <message id="Lms.Settings.audio">Audio</message>
<message id="Lms.Settings.bitrate">Bitrate</message>
<message id="Lms.Settings.demo-cannot-save">Cannot save using a demo account!</message> <message id="Lms.Settings.demo-cannot-save">Cannot save using a demo account!</message>
<message id="Lms.Settings.encoding">Encoding</message> <message id="Lms.Settings.transcoding">Transcoding</message>
<message id="Lms.Settings.encoding.auto">Auto</message> <message id="Lms.Settings.transcoding-bitrate">Transcode bitrate</message>
<message id="Lms.Settings.encoding.mp3">MP3</message> <message id="Lms.Settings.transcoding-format">Transcode format</message>
<message id="Lms.Settings.encoding.ogg_opus">Ogg/Opus</message> <message id="Lms.Settings.transcoding-enable">Enable transcoding</message>
<message id="Lms.Settings.encoding.ogg_vorbis">Ogg/Vorbis</message> <message id="Lms.Settings.transcoding.mp3">MP3</message>
<message id="Lms.Settings.encoding.webm_vorbis">WebM/Vorbis</message> <message id="Lms.Settings.transcoding.ogg_opus">Ogg/Opus</message>
<message id="Lms.Settings.transcoding.ogg_vorbis">Ogg/Vorbis</message>
<message id="Lms.Settings.transcoding.webm_vorbis">WebM/Vorbis</message>
<message id="Lms.Settings.settings">Settings</message> <message id="Lms.Settings.settings">Settings</message>
<message id="Lms.Settings.settings-saved">New settings saved!</message> <message id="Lms.Settings.settings-saved">New settings saved!</message>
+10 -9
View File
@@ -45,7 +45,7 @@
<message id="Lms.Admin.Users.users">Utilisateurs</message> <message id="Lms.Admin.Users.users">Utilisateurs</message>
<!--User--> <!--User-->
<message id="Lms.Admin.User.audio-bitrate-limit">Limite du bitrate</message> <message id="Lms.Admin.User.audio-transcode-bitrate-limit">Limite du bitrate pour le transcodage</message>
<message id="Lms.Admin.User.demo-account">Compte de démonstration</message> <message id="Lms.Admin.User.demo-account">Compte de démonstration</message>
<message id="Lms.Admin.User.demo-account-already-exists">Le compte de démonstration existe déjà !</message> <message id="Lms.Admin.User.demo-account-already-exists">Le compte de démonstration existe déjà !</message>
<message id="Lms.Admin.User.demo-password-invalid">Le password doit être égal au login !</message> <message id="Lms.Admin.User.demo-password-invalid">Le password doit être égal au login !</message>
@@ -101,7 +101,7 @@
<message id="Lms.PlayQueue.clear">Effacer</message> <message id="Lms.PlayQueue.clear">Effacer</message>
<message id="Lms.PlayQueue.nb-tracks">{1} pistes</message> <message id="Lms.PlayQueue.nb-tracks">{1} pistes</message>
<message id="Lms.PlayQueue.nb-tracks-added">{1} pistes ajoutées</message> <message id="Lms.PlayQueue.nb-tracks-added">{1} pistes ajoutées</message>
<message id="Lms.PlayQueue.nb-tracks-playing">{1} pistes ajouées</message> <message id="Lms.PlayQueue.nb-tracks-playing">{1} pistes ajoutées</message>
<message id="Lms.PlayQueue.playqueue">Liste de lecture</message> <message id="Lms.PlayQueue.playqueue">Liste de lecture</message>
<message id="Lms.PlayQueue.radio-mode">Mode radio</message> <message id="Lms.PlayQueue.radio-mode">Mode radio</message>
<message id="Lms.PlayQueue.repeat">Repéter</message> <message id="Lms.PlayQueue.repeat">Repéter</message>
@@ -115,14 +115,15 @@
<message id="Lms.Settings.account">Compte</message> <message id="Lms.Settings.account">Compte</message>
<message id="Lms.Settings.audio">Audio</message> <message id="Lms.Settings.audio">Audio</message>
<message id="Lms.Settings.auto">Auto</message> <message id="Lms.Settings.auto">Auto</message>
<message id="Lms.Settings.bitrate">Bitrate</message>
<message id="Lms.Settings.demo-cannot-save">Impossible de sauvegarder en utilisant un compte de démo !</message> <message id="Lms.Settings.demo-cannot-save">Impossible de sauvegarder en utilisant un compte de démo !</message>
<message id="Lms.Settings.encoding">Encodage</message> <message id="Lms.Settings.transcoding">Transcodage</message>
<message id="Lms.Settings.encoding.auto">Auto</message> <message id="Lms.Settings.transcoding-bitrate">Bitrate du transcodage</message>
<message id="Lms.Settings.encoding.mp3">MP3</message> <message id="Lms.Settings.transcoding-format">Format du transcodage</message>
<message id="Lms.Settings.encoding.ogg_opus">Ogg/Opus</message> <message id="Lms.Settings.transcoding-enable">Activer le transcodage</message>
<message id="Lms.Settings.encoding.ogg_vorbis">Ogg/Vorbis</message> <message id="Lms.Settings.transcoding.mp3">MP3</message>
<message id="Lms.Settings.encoding.webm_vorbis">WebM/Vorbis</message> <message id="Lms.Settings.transcoding.ogg_opus">Ogg/Opus</message>
<message id="Lms.Settings.transcoding.ogg_vorbis">Ogg/Vorbis</message>
<message id="Lms.Settings.transcoding.webm_vorbis">WebM/Vorbis</message>
<message id="Lms.Settings.settings">Paramètres</message> <message id="Lms.Settings.settings">Paramètres</message>
<message id="Lms.Settings.settings-saved">Paramètres sauvegardés !</message> <message id="Lms.Settings.settings-saved">Paramètres sauvegardés !</message>
+26 -15
View File
@@ -7,28 +7,39 @@
<legend>${tr:Lms.Settings.audio}</legend> <legend>${tr:Lms.Settings.audio}</legend>
<div class="form-horizontal"> <div class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" for="${id:bitrate}"> <label class="control-label col-sm-2" for="${id:transcoding-enable}">
${tr:Lms.Settings.bitrate} ${tr:Lms.Settings.transcoding-enable}
</label>
<div class="col-sm-5">
${transcoding-enable}
</div>
<div class="help-block col-sm-5">
${transcoding-enable-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:transcoding}">
${tr:Lms.Settings.transcoding-format}
</label>
<div class="col-sm-5">
${transcoding-format}
</div>
<div class="help-block col-sm-5">
${transcoding-format-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:transcoding-bitrate}">
${tr:Lms.Settings.transcoding-bitrate}
</label> </label>
<div class="col-sm-5"> <div class="col-sm-5">
<div class="input-group"> <div class="input-group">
${bitrate} ${transcoding-bitrate}
<span class="input-group-addon">kbps</span> <span class="input-group-addon">kbps</span>
</div> </div>
</div> </div>
<div class="help-block col-sm-5"> <div class="help-block col-sm-5">
${bitrate-info} ${transcoding-bitrate-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:encoding}">
${tr:Lms.Settings.encoding}
</label>
<div class="col-sm-5">
${encoding}
</div>
<div class="help-block col-sm-5">
${encoding-info}
</div> </div>
</div> </div>
</div> </div>
+2 -2
View File
@@ -1354,9 +1354,9 @@ createTranscoder(RequestContext& context)
// "If set to zero, no limit is imposed" // "If set to zero, no limit is imposed"
if (!maxBitRate || *maxBitRate == 0) if (!maxBitRate || *maxBitRate == 0)
maxBitRate = user->getAudioBitrate() / 1000; maxBitRate = user->getAudioTranscodeBitrate() / 1000;
*maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioBitrate() / 1000); *maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000);
auto track {Database::Track::getById(context.db.getSession(), id.value)}; auto track {Database::Track::getById(context.db.getSession(), id.value)};
if (!track) if (!track)
+36 -5
View File
@@ -24,6 +24,7 @@
#include <array> #include <array>
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/Utils.hpp"
namespace Av { namespace Av {
@@ -51,10 +52,8 @@ void AvInit()
} }
MediaFile::MediaFile(const boost::filesystem::path& p) MediaFile::MediaFile(const boost::filesystem::path& p)
: _p(p), _context(nullptr) : _p {p}
{ {
int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr); int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr);
if (error < 0) if (error < 0)
@@ -77,6 +76,12 @@ MediaFile::~MediaFile()
avformat_close_input(&_context); avformat_close_input(&_context);
} }
std::string
MediaFile::getFormatName() const
{
return _context->iformat->name;
}
std::chrono::milliseconds std::chrono::milliseconds
MediaFile::getDuration() const MediaFile::getDuration() const
{ {
@@ -127,9 +132,9 @@ MediaFile::getStreamInfo() const
{ {
std::vector<StreamInfo> res; std::vector<StreamInfo> res;
for (std::size_t i = 0; i < _context->nb_streams; ++i) for (std::size_t i {}; i < _context->nb_streams; ++i)
{ {
AVStream* avstream = _context->streams[i]; AVStream* avstream { _context->streams[i]};
// Skip attached pics // Skip attached pics
if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC) if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
@@ -233,4 +238,30 @@ MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const
return pictures; return pictures;
} }
boost::optional<MediaFileFormat> guessMediaFileFormat(const boost::filesystem::path& file)
{
AVOutputFormat* format {av_guess_format(NULL,file.string().c_str(),NULL)};
if (!format || !format->name)
return {};
auto formats {splitString(format->name, ",")};
if (formats.size() > 1)
LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several formats: '" << format->name << "'";
std::vector<std::string> mimeTypes;
if (format->mime_type)
mimeTypes = splitString(format->mime_type, ",");
if (mimeTypes.empty())
LMS_LOG(AV, INFO) << "File '" << file.string() << "', no mime type found!";
else if (mimeTypes.size() > 1)
LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several mime types: '" << format->mime_type << "'";
MediaFileFormat res;
res.format = formats.front();
res.mimeType = mimeTypes.empty() ? "application/octet-stream" : mimeTypes.front();
return res;
}
} // namespace Av } // namespace Av
+14 -3
View File
@@ -69,9 +69,12 @@ class MediaFile
MediaFile(const boost::filesystem::path& p); MediaFile(const boost::filesystem::path& p);
~MediaFile(); ~MediaFile();
// non copyable
MediaFile(const MediaFile&) = delete; MediaFile(const MediaFile&) = delete;
MediaFile& operator=(const MediaFile&) = delete; MediaFile& operator=(const MediaFile&) = delete;
MediaFile(MediaFile&&) = delete;
MediaFile& operator=(MediaFile&&) = delete;
std::string getFormatName() const;
const boost::filesystem::path& getPath() const {return _p;}; const boost::filesystem::path& getPath() const {return _p;};
@@ -84,11 +87,19 @@ class MediaFile
std::vector<Picture> getAttachedPictures(std::size_t nbMaxPictures) const; std::vector<Picture> getAttachedPictures(std::size_t nbMaxPictures) const;
private: private:
MediaFile();
boost::filesystem::path _p; boost::filesystem::path _p;
AVFormatContext* _context; AVFormatContext* _context {};
}; };
struct MediaFileFormat
{
std::string mimeType;
std::string format;
};
boost::optional<MediaFileFormat> guessMediaFileFormat(const boost::filesystem::path& file);
} // namespace Av } // namespace Av
+51 -32
View File
@@ -22,6 +22,7 @@
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include "AvInfo.hpp"
#include "utils/Path.hpp" #include "utils/Path.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
@@ -99,10 +100,6 @@ Transcoder::start()
args.push_back("-i"); args.push_back("-i");
args.push_back(_filePath.string()); args.push_back(_filePath.string());
// Output bitrates
args.push_back("-b:a");
args.push_back(std::to_string(_parameters.bitrate));
// Stream mapping, if set // Stream mapping, if set
if (_parameters.stream) if (_parameters.stream)
{ {
@@ -121,37 +118,63 @@ Transcoder::start()
args.push_back("-vn"); args.push_back("-vn");
// Codecs and formats // Codecs and formats
switch( _parameters.encoding) if (_parameters.encoding)
{ {
case Encoding::MP3: // Output bitrates
args.push_back("-f"); args.push_back("-b:a");
args.push_back("mp3"); args.push_back(std::to_string(_parameters.bitrate));
break;
case Encoding::OGG_OPUS: switch (*_parameters.encoding)
args.push_back("-acodec"); {
args.push_back("libopus"); case Encoding::MP3:
args.push_back("-f"); args.push_back("-f");
args.push_back("ogg"); args.push_back("mp3");
break; break;
case Encoding::OGG_VORBIS: case Encoding::OGG_OPUS:
args.push_back("-acodec"); args.push_back("-acodec");
args.push_back("libvorbis"); args.push_back("libopus");
args.push_back("-f"); args.push_back("-f");
args.push_back("ogg"); args.push_back("ogg");
break; break;
case Encoding::WEBM_VORBIS: case Encoding::OGG_VORBIS:
args.push_back("-acodec"); args.push_back("-acodec");
args.push_back("libvorbis"); args.push_back("libvorbis");
args.push_back("-f"); args.push_back("-f");
args.push_back("webm"); args.push_back("ogg");
break; break;
case Encoding::WEBM_VORBIS:
args.push_back("-acodec");
args.push_back("libvorbis");
args.push_back("-f");
args.push_back("webm");
break;
default: default:
return false;
}
_outputMimeType = encodingToMimetype(*_parameters.encoding);
}
else
{
auto mediaFileFormat {guessMediaFileFormat(_filePath)};
if (!mediaFileFormat)
{
LMS_LOG(AV, ERROR) << "Cannot guess media file format for '" << _filePath.string() << "'";
return false; return false;
}
args.push_back("-acodec");
args.push_back("copy");
args.push_back("-f");
args.push_back(mediaFileFormat->format);
_outputMimeType = mediaFileFormat->mimeType;
} }
args.push_back("pipe:1"); args.push_back("pipe:1");
@@ -204,14 +227,10 @@ Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
output.resize(maxSize); output.resize(maxSize);
LMS_LOG_TRANSCODE(DEBUG) << "Reading up to " << output.size() << " bytes";
//Read on the output stream //Read on the output stream
_child->out().read(reinterpret_cast<char*>(&output[0]), maxSize); _child->out().read(reinterpret_cast<char*>(&output[0]), maxSize);
output.resize(_child->out().gcount()); output.resize(_child->out().gcount());
LMS_LOG_TRANSCODE(DEBUG) << "Read " << output.size() << " bytes";
if (_child->out().fail()) if (_child->out().fail())
{ {
LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED"; LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED";
+9 -8
View File
@@ -19,15 +19,13 @@
#pragma once #pragma once
#include <map> #include <chrono>
#include <set>
#include <pstreams/pstream.h> #include <pstreams/pstream.h>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include "AvInfo.hpp"
#include "AvTypes.hpp" #include "AvTypes.hpp"
namespace Av { namespace Av {
@@ -36,11 +34,11 @@ namespace Av {
struct TranscodeParameters struct TranscodeParameters
{ {
Encoding encoding {Encoding::MP3}; boost::optional<Encoding> encoding; // If not set, no transcoding is performed
std::size_t bitrate {128000}; std::size_t bitrate {128000};
boost::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default) boost::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default)
boost::optional<std::chrono::seconds> offset; boost::optional<std::chrono::seconds> offset {};
bool stripMetadata {true}; bool stripMetadata {true};
}; };
class Transcoder class Transcoder
@@ -56,11 +54,13 @@ class Transcoder
Transcoder& operator=(const Transcoder&) = delete; Transcoder& operator=(const Transcoder&) = delete;
bool start(); bool start();
const std::string& getOutputMimeType() const { return _outputMimeType; }
void process(std::vector<unsigned char>& output, std::size_t maxSize); void process(std::vector<unsigned char>& output, std::size_t maxSize);
bool isComplete(void) const { return _isComplete; } bool isComplete(void) const { return _isComplete; }
const TranscodeParameters& getParameters() const { return _parameters; } const TranscodeParameters& getParameters() const { return _parameters; }
private: private:
Transcoder(); Transcoder();
@@ -72,6 +72,7 @@ class Transcoder
bool _isComplete = false; bool _isComplete = false;
std::size_t _total = 0; std::size_t _total = 0;
std::size_t _id; std::size_t _id;
std::string _outputMimeType;
}; };
} // namespace Av } // namespace Av
+5
View File
@@ -40,5 +40,10 @@ std::string encodingToMimetype(Encoding encoding)
return it->second; return it->second;
} }
Encoding guessEncoding(const boost::filesystem::path& file)
{
}
} }
+5 -4
View File
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <boost/filesystem.hpp>
#include "utils/Exception.hpp" #include "utils/Exception.hpp"
@@ -34,10 +35,10 @@ class AvException : public LmsException
enum class Encoding enum class Encoding
{ {
// Values are important and must not be changed // Values are important and must not be changed
MP3 = 0, MP3 = 0,
OGG_OPUS = 1, OGG_OPUS = 1,
OGG_VORBIS = 2, OGG_VORBIS = 2,
WEBM_VORBIS = 3, WEBM_VORBIS = 3,
}; };
std::string encodingToMimetype(Encoding encoding); std::string encodingToMimetype(Encoding encoding);
+1 -1
View File
@@ -46,7 +46,7 @@
namespace Database { namespace Database {
#define LMS_DATABASE_VERSION 1 #define LMS_DATABASE_VERSION 2
namespace { namespace {
Wt::Auth::AuthService authService; Wt::Auth::AuthService authService;
+2 -2
View File
@@ -211,9 +211,9 @@ Track::getByFilter(Wt::Dbo::Session& session,
.limit(size ? static_cast<int>(*size) + 1 : -1) .limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1); .offset(offset ? static_cast<int>(*offset) : -1);
auto res = std::vector<pointer>(collection.begin(), collection.end()); std::vector<pointer> res(collection.begin(), collection.end());
if (size && res.size() == static_cast<std::size_t>(*size) + 1) if (size && (res.size() == static_cast<std::size_t>(*size) + 1))
{ {
moreResults = true; moreResults = true;
res.pop_back(); res.pop_back();
+17 -18
View File
@@ -23,9 +23,8 @@
namespace Database { namespace Database {
// must be ordered const std::set<Bitrate>
const std::vector<std::size_t> User::audioTranscodeAllowedBitrates =
User::audioBitrates =
{ {
64000, 64000,
96000, 96000,
@@ -35,7 +34,7 @@ User::audioBitrates =
}; };
User::User() User::User()
: _maxAudioBitrate(audioBitrates.back()) : _maxAudioTranscodeBitrate{static_cast<int>(*audioTranscodeAllowedBitrates.rbegin())}
{ {
} }
@@ -67,29 +66,29 @@ User::getById(Wt::Dbo::Session& session, IdType id)
} }
void void
User::setAudioBitrate(std::size_t bitrate) User::setAudioTranscodeBitrate(Bitrate bitrate)
{ {
_audioBitrate = std::min(bitrate, static_cast<std::size_t>(_maxAudioBitrate)); _audioTranscodeBitrate = std::min(bitrate, static_cast<Bitrate>(_maxAudioTranscodeBitrate));
} }
void void
User::setMaxAudioBitrate(std::size_t bitrate) User::setMaxAudioTranscodeBitrate(Bitrate bitrate)
{ {
_maxAudioBitrate = std::min(bitrate, audioBitrates.back()); _maxAudioTranscodeBitrate = std::min(bitrate, *audioTranscodeAllowedBitrates.rbegin());
if (_audioBitrate > _maxAudioBitrate) if (_audioTranscodeBitrate > _maxAudioTranscodeBitrate)
_audioBitrate = _maxAudioBitrate; _audioTranscodeBitrate = _maxAudioTranscodeBitrate;
}
Bitrate
User::getAudioTranscodeBitrate(void) const
{
return _audioTranscodeBitrate;
} }
std::size_t std::size_t
User::getAudioBitrate(void) const User::getMaxAudioTranscodeBitrate(void) const
{ {
return _audioBitrate; return _maxAudioTranscodeBitrate;
}
std::size_t
User::getMaxAudioBitrate(void) const
{
return _maxAudioBitrate;
} }
Wt::Dbo::ptr<TrackList> Wt::Dbo::ptr<TrackList>
+37 -29
View File
@@ -34,15 +34,17 @@ using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
class TrackList; class TrackList;
// User selectable audio formats // User selectable audio formats
enum class AudioEncoding // Do not change values
enum class AudioFormat
{ {
AUTO = 0,
MP3 = 1, MP3 = 1,
OGG_OPUS = 2, OGG_OPUS = 2,
OGG_VORBIS = 3, OGG_VORBIS = 3,
WEBM_VORBIS = 4, WEBM_VORBIS = 4,
}; };
using Bitrate = std::size_t;
class User : public Wt::Dbo::Dbo<User> class User : public Wt::Dbo::Dbo<User>
{ {
public: public:
@@ -59,7 +61,7 @@ class User : public Wt::Dbo::Dbo<User>
}; };
// list of audio parameters // list of audio parameters
static const std::vector<std::size_t> audioBitrates; static const std::set<Bitrate> audioTranscodeAllowedBitrates;
User(); User();
@@ -72,23 +74,25 @@ class User : public Wt::Dbo::Dbo<User>
static pointer getDemo(Wt::Dbo::Session& session); static pointer getDemo(Wt::Dbo::Session& session);
// write // write
void setType(Type type) { _type = type; } void setType(Type type) { _type = type; }
void setAudioBitrate(std::size_t bitrate); void setAudioTranscodeEnable(bool value) { _audioTranscodeEnable = value; }
void setAudioEncoding(AudioEncoding encoding) { _audioEncoding = encoding; } void setAudioTranscodeFormat(AudioFormat format) { _audioTranscodeFormat = format; }
void setMaxAudioBitrate(std::size_t bitrate); void setAudioTranscodeBitrate(Bitrate bitrate);
void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; } void setMaxAudioTranscodeBitrate(Bitrate bitrate);
void setRadio(bool val) { _radio = val; } void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; }
void setRepeatAll(bool val) { _repeatAll = val; } void setRadio(bool val) { _radio = val; }
void setRepeatAll(bool val) { _repeatAll = val; }
// read // read
bool isAdmin() const { return _type == Type::ADMIN; } bool isAdmin() const { return _type == Type::ADMIN; }
bool isDemo() const { return _type == Type::DEMO; } bool isDemo() const { return _type == Type::DEMO; }
std::size_t getAudioBitrate() const; bool getAudioTranscodeEnable() const { return _audioTranscodeEnable; }
AudioEncoding getAudioEncoding() const { return _audioEncoding; } Bitrate getAudioTranscodeBitrate() const;
std::size_t getMaxAudioBitrate() const; AudioFormat getAudioTranscodeFormat() const { return _audioTranscodeFormat; }
std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } Bitrate getMaxAudioTranscodeBitrate() const;
bool isRepeatAllSet() const { return _repeatAll; } std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; }
bool isRadioSet() const { return _radio; } bool isRepeatAllSet() const { return _repeatAll; }
bool isRadioSet() const { return _radio; }
Wt::Dbo::ptr<TrackList> getQueuedTrackList() const; Wt::Dbo::ptr<TrackList> getQueuedTrackList() const;
Wt::Dbo::ptr<TrackList> getPlayedTrackList() const; Wt::Dbo::ptr<TrackList> getPlayedTrackList() const;
@@ -96,10 +100,11 @@ class User : public Wt::Dbo::Dbo<User>
template<class Action> template<class Action>
void persist(Action& a) void persist(Action& a)
{ {
Wt::Dbo::field(a, _maxAudioBitrate, "max_audio_bitrate");
Wt::Dbo::field(a, _type, "type"); Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _audioBitrate, "audio_bitrate"); Wt::Dbo::field(a, _maxAudioTranscodeBitrate, "max_audio_bitrate");
Wt::Dbo::field(a, _audioEncoding, "audio_encoding"); Wt::Dbo::field(a, _audioTranscodeEnable, "audio_transcode_enable");
Wt::Dbo::field(a, _audioTranscodeBitrate, "audio_transcode_bitrate");
Wt::Dbo::field(a, _audioTranscodeFormat, "audio_transcode_format");
// User's dynamic data // User's dynamic data
Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos"); Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos");
Wt::Dbo::field(a, _repeatAll, "repeat_all"); Wt::Dbo::field(a, _repeatAll, "repeat_all");
@@ -109,20 +114,23 @@ class User : public Wt::Dbo::Dbo<User>
private: private:
static const std::size_t defaultAudioBitrate = 128000; static const bool defaultAudioTranscodeEnable {true};
static const AudioFormat defaultAudioTranscodeFormat {AudioFormat::OGG_OPUS};
static const Bitrate defaultAudioTranscodeBitrate {128000};
// Admin defined settings // Admin defined settings
int _maxAudioBitrate; int _maxAudioTranscodeBitrate;
Type _type = Type::REGULAR; Type _type {Type::REGULAR};
// User defined settings // User defined settings
int _audioBitrate = defaultAudioBitrate; bool _audioTranscodeEnable {defaultAudioTranscodeEnable};
AudioEncoding _audioEncoding = AudioEncoding::AUTO; AudioFormat _audioTranscodeFormat {defaultAudioTranscodeFormat};
int _audioTranscodeBitrate {defaultAudioTranscodeBitrate};
// User's dynamic data // User's dynamic data
int _curPlayingTrackPos = 0; // Current track position in queue int _curPlayingTrackPos {}; // Current track position in queue
bool _repeatAll = false; bool _repeatAll {};
bool _radio = false; bool _radio {};
Wt::Dbo::collection< Wt::Dbo::ptr<TrackList> > _tracklists; Wt::Dbo::collection< Wt::Dbo::ptr<TrackList> > _tracklists;
+1 -1
View File
@@ -63,7 +63,7 @@ std::vector<std::string> generateWtConfig(std::string execPath)
args.push_back("--http-address=" + Config::instance().getString("listen-addr", "0.0.0.0")); args.push_back("--http-address=" + Config::instance().getString("listen-addr", "0.0.0.0"));
} }
args.push_back("--gdb"); // args.push_back("--gdb");
// Generate the wt_config.xml file // Generate the wt_config.xml file
boost::property_tree::ptree pt; boost::property_tree::ptree pt;
+106 -84
View File
@@ -21,6 +21,7 @@
#include <Wt/WString.h> #include <Wt/WString.h>
#include <Wt/WPushButton.h> #include <Wt/WPushButton.h>
#include <Wt/WCheckBox.h>
#include <Wt/WComboBox.h> #include <Wt/WComboBox.h>
#include <Wt/WLineEdit.h> #include <Wt/WLineEdit.h>
@@ -40,8 +41,9 @@ class SettingsModel : public Wt::WFormModel
{ {
public: public:
// Associate each field with a unique string literal. // Associate each field with a unique string literal.
static const Field BitrateField; static const Field TranscodeEnableField;
static const Field EncodingField; static const Field TranscodeFormatField;
static const Field TranscodeBitrateField;
static const Field PasswordField; static const Field PasswordField;
static const Field PasswordConfirmField; static const Field PasswordConfirmField;
@@ -50,49 +52,57 @@ class SettingsModel : public Wt::WFormModel
{ {
initializeModels(); initializeModels();
addField(BitrateField); addField(TranscodeEnableField);
addField(EncodingField); addField(TranscodeBitrateField);
addField(TranscodeFormatField);
addField(PasswordField); addField(PasswordField);
addField(PasswordConfirmField); addField(PasswordConfirmField);
setValidator(BitrateField, createMandatoryValidator()); setValidator(TranscodeBitrateField, createMandatoryValidator());
setValidator(EncodingField, createMandatoryValidator()); setValidator(TranscodeFormatField, createMandatoryValidator());
loadData(); loadData();
} }
std::shared_ptr<Wt::WAbstractItemModel> bitrateModel() { return _bitrateModel; } std::shared_ptr<Wt::WAbstractItemModel> transcodeBitrateModel() { return _transcodeBitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> encodingModel() { return _encodingModel; } std::shared_ptr<Wt::WAbstractItemModel> transcodeFormatModel() { return _transcodeFormatModel; }
void loadData() void loadData()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto bitrate = getBitrateRow(LmsApp->getUser()->getAudioBitrate()); setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable());
if (bitrate) if (!LmsApp->getUser()->getAudioTranscodeEnable())
setValue(BitrateField, bitrateString(*bitrate)); {
setReadOnly(TranscodeFormatField, true);
setReadOnly(TranscodeBitrateField, true);
}
auto encodingRow = getEncodingRow(LmsApp->getUser()->getAudioEncoding()); auto transcodeBitrateRow {getTranscodeBitrateRow(LmsApp->getUser()->getAudioTranscodeBitrate())};
if (encodingRow) if (transcodeBitrateRow)
setValue(EncodingField, encodingString(*encodingRow)); setValue(TranscodeBitrateField, transcodeBitrateString(*transcodeBitrateRow));
auto transcodeFormatRow {getTranscodeFormatRow(LmsApp->getUser()->getAudioTranscodeFormat())};
if (transcodeFormatRow)
setValue(TranscodeFormatField, transcodeFormatString(*transcodeFormatRow));
} }
void saveData() void saveData()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto bitrateRow = getBitrateRow(Wt::asString(value(BitrateField))); LmsApp->getUser().modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
assert(bitrateRow);
LmsApp->getUser().modify()->setAudioBitrate(bitrate(*bitrateRow));
auto encodingRow = getEncodingRow(Wt::asString(value(EncodingField))); auto transcodeBitrateRow {getTranscodeBitrateRow(Wt::asString(value(TranscodeBitrateField)))};
LmsApp->getUser().modify()->setAudioEncoding(encoding(*encodingRow)); if (transcodeBitrateRow)
LmsApp->getUser().modify()->setAudioTranscodeBitrate(transcodeBitrate(*transcodeBitrateRow));
auto transcodeFormatRow {getTranscodeFormatRow(Wt::asString(value(TranscodeFormatField)))};
if (transcodeFormatRow)
LmsApp->getUser().modify()->setAudioTranscodeFormat(transcodeFormat(*transcodeFormatRow));
if (!valueText(PasswordField).empty()) if (!valueText(PasswordField).empty())
{
Database::Handler::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField)); Database::Handler::getPasswordService().updatePassword(LmsApp->getAuthUser(), valueText(PasswordField));
}
} }
bool validateField(Field field) bool validateField(Field field)
@@ -130,72 +140,72 @@ class SettingsModel : public Wt::WFormModel
return (validation(field).state() == Wt::ValidationState::Valid); return (validation(field).state() == Wt::ValidationState::Valid);
} }
boost::optional<int> getBitrateRow(Wt::WString value) boost::optional<int> getTranscodeBitrateRow(Wt::WString value)
{ {
for (int i = 0; i < _bitrateModel->rowCount(); ++i) for (int i = 0; i < _transcodeBitrateModel->rowCount(); ++i)
{ {
if (bitrateString(i) == value) if (transcodeBitrateString(i) == value)
return i; return i;
} }
return boost::none; return boost::none;
} }
boost::optional<int> getBitrateRow(std::size_t value) boost::optional<int> getTranscodeBitrateRow(std::size_t value)
{ {
for (int i = 0; i < _bitrateModel->rowCount(); ++i) for (int i = 0; i < _transcodeBitrateModel->rowCount(); ++i)
{ {
if (bitrate(i) == value) if (transcodeBitrate(i) == value)
return i; return i;
} }
return boost::none; return boost::none;
} }
std::size_t bitrate(int row) std::size_t transcodeBitrate(int row)
{ {
return Wt::cpp17::any_cast<std::size_t> return Wt::cpp17::any_cast<std::size_t>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::User)); (_transcodeBitrateModel->data(_transcodeBitrateModel->index(row, 0), Wt::ItemDataRole::User));
} }
Wt::WString bitrateString(int row) Wt::WString transcodeBitrateString(int row)
{ {
return Wt::cpp17::any_cast<Wt::WString> return Wt::cpp17::any_cast<Wt::WString>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::Display)); (_transcodeBitrateModel->data(_transcodeBitrateModel->index(row, 0), Wt::ItemDataRole::Display));
} }
boost::optional<int> getEncodingRow(Wt::WString value) boost::optional<int> getTranscodeFormatRow(Wt::WString value)
{ {
for (int i = 0; i < _encodingModel->rowCount(); ++i) for (int i = 0; i < _transcodeFormatModel->rowCount(); ++i)
{ {
if (encodingString(i) == value) if (transcodeFormatString(i) == value)
return i; return i;
} }
return boost::none; return boost::none;
} }
boost::optional<int> getEncodingRow(Database::AudioEncoding enc) boost::optional<int> getTranscodeFormatRow(Database::AudioFormat format)
{ {
for (int i = 0; i < _encodingModel->rowCount(); ++i) for (int i = 0; i < _transcodeFormatModel->rowCount(); ++i)
{ {
if (encoding(i) == enc) if (transcodeFormat(i) == format)
return i; return i;
} }
return boost::none; return boost::none;
} }
Database::AudioEncoding encoding(int row) Database::AudioFormat transcodeFormat(int row)
{ {
return Wt::cpp17::any_cast<Database::AudioEncoding> return Wt::cpp17::any_cast<Database::AudioFormat>
(_encodingModel->data(_encodingModel->index(row, 0), Wt::ItemDataRole::User)); (_transcodeFormatModel->data(_transcodeFormatModel->index(row, 0), Wt::ItemDataRole::User));
} }
Wt::WString encodingString(int row) Wt::WString transcodeFormatString(int row)
{ {
return Wt::cpp17::any_cast<Wt::WString> return Wt::cpp17::any_cast<Wt::WString>
(_encodingModel->data(_encodingModel->index(row, 0), Wt::ItemDataRole::Display)); (_transcodeFormatModel->data(_transcodeFormatModel->index(row, 0), Wt::ItemDataRole::Display));
} }
@@ -203,52 +213,50 @@ class SettingsModel : public Wt::WFormModel
void initializeModels() void initializeModels()
{ {
_bitrateModel = std::make_shared<Wt::WStringListModel>(); _transcodeBitrateModel = std::make_shared<Wt::WStringListModel>();
std::size_t maxAudioBitrate; Database::Bitrate maxAudioBitrate;
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
maxAudioBitrate = LmsApp->getUser()->getMaxAudioBitrate(); maxAudioBitrate = LmsApp->getUser()->getMaxAudioTranscodeBitrate();
} }
std::size_t id = 0; std::size_t id = 0;
for (std::size_t bitrate : Database::User::audioBitrates) for (Database::Bitrate bitrate : Database::User::audioTranscodeAllowedBitrates)
{ {
if (bitrate > maxAudioBitrate) if (bitrate > maxAudioBitrate)
break; break;
_bitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) ); _transcodeBitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) );
_bitrateModel->setData( id, 0, bitrate, Wt::ItemDataRole::User); _transcodeBitrateModel->setData( id, 0, bitrate, Wt::ItemDataRole::User);
id++; id++;
} }
_encodingModel = std::make_shared<Wt::WStringListModel>(); _transcodeFormatModel = std::make_shared<Wt::WStringListModel>();
_encodingModel->addString(Wt::WString::tr("Lms.Settings.encoding.auto")); _transcodeFormatModel->addString(Wt::WString::tr("Lms.Settings.transcoding.mp3"));
_encodingModel->setData(0, 0, Database::AudioEncoding::AUTO, Wt::ItemDataRole::User); _transcodeFormatModel->setData(0, 0, Database::AudioFormat::MP3, Wt::ItemDataRole::User);
_encodingModel->addString(Wt::WString::tr("Lms.Settings.encoding.mp3")); _transcodeFormatModel->addString(Wt::WString::tr("Lms.Settings.transcoding.ogg_opus"));
_encodingModel->setData(1, 0, Database::AudioEncoding::MP3, Wt::ItemDataRole::User); _transcodeFormatModel->setData(1, 0, Database::AudioFormat::OGG_OPUS, Wt::ItemDataRole::User);
_encodingModel->addString(Wt::WString::tr("Lms.Settings.encoding.ogg_opus")); _transcodeFormatModel->addString(Wt::WString::tr("Lms.Settings.transcoding.ogg_vorbis"));
_encodingModel->setData(2, 0, Database::AudioEncoding::OGG_OPUS, Wt::ItemDataRole::User); _transcodeFormatModel->setData(2, 0, Database::AudioFormat::OGG_VORBIS, Wt::ItemDataRole::User);
_encodingModel->addString(Wt::WString::tr("Lms.Settings.encoding.ogg_vorbis")); _transcodeFormatModel->addString(Wt::WString::tr("Lms.Settings.transcoding.webm_vorbis"));
_encodingModel->setData(3, 0, Database::AudioEncoding::OGG_VORBIS, Wt::ItemDataRole::User); _transcodeFormatModel->setData(3, 0, Database::AudioFormat::WEBM_VORBIS, Wt::ItemDataRole::User);
_encodingModel->addString(Wt::WString::tr("Lms.Settings.encoding.webm_vorbis"));
_encodingModel->setData(4, 0, Database::AudioEncoding::WEBM_VORBIS, Wt::ItemDataRole::User);
} }
std::shared_ptr<Wt::WStringListModel> _bitrateModel; std::shared_ptr<Wt::WStringListModel> _transcodeBitrateModel;
std::shared_ptr<Wt::WStringListModel> _encodingModel; std::shared_ptr<Wt::WStringListModel> _transcodeFormatModel;
}; };
const Wt::WFormModel::Field SettingsModel::BitrateField = "bitrate"; const Wt::WFormModel::Field SettingsModel::TranscodeEnableField = "transcoding-enable";
const Wt::WFormModel::Field SettingsModel::EncodingField = "encoding"; const Wt::WFormModel::Field SettingsModel::TranscodeBitrateField = "transcoding-bitrate";
const Wt::WFormModel::Field SettingsModel::PasswordField = "password"; const Wt::WFormModel::Field SettingsModel::TranscodeFormatField = "transcoding-format";
const Wt::WFormModel::Field SettingsModel::PasswordConfirmField = "password-confirm"; const Wt::WFormModel::Field SettingsModel::PasswordField = "password";
const Wt::WFormModel::Field SettingsModel::PasswordConfirmField = "password-confirm";
SettingsView::SettingsView() SettingsView::SettingsView()
{ {
@@ -268,39 +276,53 @@ SettingsView::refreshView()
clear(); clear();
auto t = addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Settings.template")); auto t {addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Settings.template"))};
auto model = std::make_shared<SettingsModel>(); auto model {std::make_shared<SettingsModel>()};
// Password // Password
auto password = std::make_unique<Wt::WLineEdit>(); auto password {std::make_unique<Wt::WLineEdit>()};
password->setEchoMode(Wt::EchoMode::Password); password->setEchoMode(Wt::EchoMode::Password);
t->setFormWidget(SettingsModel::PasswordField, std::move(password)); t->setFormWidget(SettingsModel::PasswordField, std::move(password));
// Password confirm // Password confirm
auto passwordConfirm = std::make_unique<Wt::WLineEdit>(); auto passwordConfirm {std::make_unique<Wt::WLineEdit>()};
passwordConfirm->setEchoMode(Wt::EchoMode::Password); passwordConfirm->setEchoMode(Wt::EchoMode::Password);
t->setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm)); t->setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm));
// Bitrate // Transcoding
auto bitrate = std::make_unique<Wt::WComboBox>(); auto transcode {std::make_unique<Wt::WCheckBox>()};
bitrate->setModel(model->bitrateModel()); auto* transcodeRaw {transcode.get()};
t->setFormWidget(SettingsModel::BitrateField, std::move(bitrate)); t->setFormWidget(SettingsModel::TranscodeEnableField, std::move(transcode));
// Encoding // Format
auto encoding = std::make_unique<Wt::WComboBox>(); auto transcodeFormat {std::make_unique<Wt::WComboBox>()};
encoding->setModel(model->encodingModel()); transcodeFormat->setModel(model->transcodeFormatModel());
t->setFormWidget(SettingsModel::EncodingField, std::move(encoding)); t->setFormWidget(SettingsModel::TranscodeFormatField, std::move(transcodeFormat));
// Bitrate
auto transcodeBitrate {std::make_unique<Wt::WComboBox>()};
transcodeBitrate->setModel(model->transcodeBitrateModel());
t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodeBitrate));
transcodeRaw->changed().connect([=]()
{
bool enable {transcodeRaw->checkState() == Wt::CheckState::Checked};
model->setReadOnly(SettingsModel::TranscodeFormatField, !enable);
model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
// Buttons // Buttons
Wt::WPushButton *saveBtn = t->bindWidget("apply-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.apply"))); Wt::WPushButton *saveBtn {t->bindWidget("apply-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.apply")))};
Wt::WPushButton *discardBtn = t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard"))); Wt::WPushButton *discardBtn {t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")))};
saveBtn->clicked().connect(std::bind([=] () saveBtn->clicked().connect(std::bind([=] ()
{ {
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
if (LmsApp->getUser()->isDemo()) if (LmsApp->getUser()->isDemo())
{ {
+22 -22
View File
@@ -45,7 +45,7 @@ class UserModel : public Wt::WFormModel
public: public:
static const Field LoginField; static const Field LoginField;
static const Field PasswordField; static const Field PasswordField;
static const Field BitrateLimitField; static const Field AudioTranscodeBitrateLimitField;
static const Field DemoField; static const Field DemoField;
UserModel(boost::optional<Database::IdType> userId) UserModel(boost::optional<Database::IdType> userId)
@@ -59,7 +59,7 @@ class UserModel : public Wt::WFormModel
} }
addField(PasswordField); addField(PasswordField);
addField(BitrateLimitField); addField(AudioTranscodeBitrateLimitField);
addField(DemoField); addField(DemoField);
if (!_userId) if (!_userId)
@@ -76,7 +76,7 @@ class UserModel : public Wt::WFormModel
if (!_userId) if (!_userId)
return; return;
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) ); auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) );
auto user = LmsApp->getDb().getUser(authUser); auto user = LmsApp->getDb().getUser(authUser);
@@ -84,14 +84,14 @@ class UserModel : public Wt::WFormModel
if (user == LmsApp->getUser()) if (user == LmsApp->getUser())
throw LmsException("Cannot edit ourselves"); throw LmsException("Cannot edit ourselves");
auto bitrate = getBitrateLimitRow(user->getMaxAudioBitrate()); auto bitrate {getTranscodeBitrateLimitRow(user->getMaxAudioTranscodeBitrate())};
if (bitrate) if (bitrate)
setValue(BitrateLimitField, bitrateLimitString(*bitrate)); setValue(AudioTranscodeBitrateLimitField, transcodeBitrateLimitString(*bitrate));
} }
void saveData() void saveData()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
if (_userId) if (_userId)
{ {
@@ -103,9 +103,9 @@ class UserModel : public Wt::WFormModel
if (!valueText(PasswordField).empty()) if (!valueText(PasswordField).empty())
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
auto bitrateLimitRow = getBitrateLimitRow(Wt::asString(value(BitrateLimitField))); auto transcodeBitrateLimitRow = getTranscodeBitrateLimitRow(Wt::asString(value(AudioTranscodeBitrateLimitField)));
user.modify()->setMaxAudioBitrate(bitrateLimit(*bitrateLimitRow)); user.modify()->setMaxAudioTranscodeBitrate(transcodeBitrateLimit(*transcodeBitrateLimitRow));
LMS_LOG(UI, DEBUG) << "Max audio bitrate set to " << bitrateLimit(*bitrateLimitRow); LMS_LOG(UI, DEBUG) << "Max audio bitrate set to " << transcodeBitrateLimit(*transcodeBitrateLimitRow);
} }
else else
{ {
@@ -117,8 +117,8 @@ class UserModel : public Wt::WFormModel
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField)); authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField));
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
auto bitrateLimitRow = getBitrateLimitRow(Wt::asString(value(BitrateLimitField))); auto transcodeBitrateLimitRow = getTranscodeBitrateLimitRow(Wt::asString(value(AudioTranscodeBitrateLimitField)));
user.modify()->setMaxAudioBitrate(bitrateLimit(*bitrateLimitRow)); user.modify()->setMaxAudioTranscodeBitrate(transcodeBitrateLimit(*transcodeBitrateLimitRow));
if (Wt::asNumber(value(DemoField))) if (Wt::asNumber(value(DemoField)))
user.modify()->setType(Database::User::Type::DEMO); user.modify()->setType(Database::User::Type::DEMO);
@@ -168,7 +168,7 @@ class UserModel : public Wt::WFormModel
} }
else if (field == DemoField) else if (field == DemoField)
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDboSession())) if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDboSession()))
error = Wt::WString::tr("Lms.Admin.User.demo-account-already-exists"); error = Wt::WString::tr("Lms.Admin.User.demo-account-already-exists");
@@ -182,35 +182,35 @@ class UserModel : public Wt::WFormModel
return false; return false;
} }
boost::optional<int> getBitrateLimitRow(Wt::WString value) boost::optional<int> getTranscodeBitrateLimitRow(Wt::WString value)
{ {
for (int i = 0; i < _bitrateModel->rowCount(); ++i) for (int i = 0; i < _bitrateModel->rowCount(); ++i)
{ {
if (bitrateLimitString(i) == value) if (transcodeBitrateLimitString(i) == value)
return i; return i;
} }
return boost::none; return boost::none;
} }
boost::optional<int> getBitrateLimitRow(std::size_t value) boost::optional<int> getTranscodeBitrateLimitRow(std::size_t value)
{ {
for (int i = 0; i < _bitrateModel->rowCount(); ++i) for (int i = 0; i < _bitrateModel->rowCount(); ++i)
{ {
if (bitrateLimit(i) == value) if (transcodeBitrateLimit(i) == value)
return i; return i;
} }
return boost::none; return boost::none;
} }
std::size_t bitrateLimit(int row) std::size_t transcodeBitrateLimit(int row)
{ {
return Wt::cpp17::any_cast<std::size_t> return Wt::cpp17::any_cast<std::size_t>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::User)); (_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::User));
} }
Wt::WString bitrateLimitString(int row) Wt::WString transcodeBitrateLimitString(int row)
{ {
return Wt::cpp17::any_cast<Wt::WString> return Wt::cpp17::any_cast<Wt::WString>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::Display)); (_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::Display));
@@ -225,7 +225,7 @@ class UserModel : public Wt::WFormModel
_bitrateModel = std::make_shared<Wt::WStringListModel>(); _bitrateModel = std::make_shared<Wt::WStringListModel>();
std::size_t id = 0; std::size_t id = 0;
for (auto bitrate : Database::User::audioBitrates) for (auto bitrate : Database::User::audioTranscodeAllowedBitrates)
{ {
_bitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) ); _bitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) );
_bitrateModel->setData( id++, 0, bitrate, Wt::ItemDataRole::User); _bitrateModel->setData( id++, 0, bitrate, Wt::ItemDataRole::User);
@@ -240,7 +240,7 @@ class UserModel : public Wt::WFormModel
const Wt::WFormModel::Field UserModel::LoginField = "login"; const Wt::WFormModel::Field UserModel::LoginField = "login";
const Wt::WFormModel::Field UserModel::PasswordField = "password"; const Wt::WFormModel::Field UserModel::PasswordField = "password";
const Wt::WFormModel::Field UserModel::BitrateLimitField = "audio-bitrate-limit"; const Wt::WFormModel::Field UserModel::AudioTranscodeBitrateLimitField = "audio-transcode-bitrate-limit";
const Wt::WFormModel::Field UserModel::DemoField = "demo"; const Wt::WFormModel::Field UserModel::DemoField = "demo";
UserView::UserView() UserView::UserView()
@@ -293,10 +293,10 @@ UserView::refreshView()
passwordEdit->setEchoMode(Wt::EchoMode::Password); passwordEdit->setEchoMode(Wt::EchoMode::Password);
t->setFormWidget(UserModel::PasswordField, std::move(passwordEdit)); t->setFormWidget(UserModel::PasswordField, std::move(passwordEdit));
// Bitrate // Transcode bitrate limit
auto bitrate = std::make_unique<Wt::WComboBox>(); auto bitrate = std::make_unique<Wt::WComboBox>();
bitrate->setModel(model->bitrateModel()); bitrate->setModel(model->bitrateModel());
t->setFormWidget(UserModel::BitrateLimitField, std::move(bitrate)); t->setFormWidget(UserModel::AudioTranscodeBitrateLimitField, std::move(bitrate));
// Demo account // Demo account
t->setFormWidget(UserModel::DemoField, std::make_unique<Wt::WCheckBox>()); t->setFormWidget(UserModel::DemoField, std::make_unique<Wt::WCheckBox>());
+9 -9
View File
@@ -77,12 +77,12 @@ _filters(filters)
} }
std::vector<Database::Track::pointer> std::vector<Database::Track::pointer>
Tracks::getTracks(int offset, int size, bool& moreResults) Tracks::getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults)
{ {
auto searchKeywords = splitString(_search->text().toUTF8(), " "); auto searchKeywords {splitString(_search->text().toUTF8(), " ")};
auto clusterIds = _filters->getClusterIds(); auto clusterIds {_filters->getClusterIds()};
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
return Track::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, offset, size, moreResults); return Track::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, offset, size, moreResults);
} }
@@ -91,7 +91,7 @@ std::vector<Database::Track::pointer>
Tracks::getTracks() Tracks::getTracks()
{ {
bool moreResults; bool moreResults;
return getTracks(-1, -1, moreResults); return getTracks({}, {}, moreResults);
} }
void void
@@ -104,15 +104,15 @@ Tracks::refresh()
void void
Tracks::addSome() Tracks::addSome()
{ {
Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); Wt::Dbo::Transaction transaction {LmsApp->getDboSession()};
bool moreResults; bool moreResults;
auto tracks = getTracks(_tracksContainer->count(), 20, moreResults); auto tracks {getTracks(_tracksContainer->count(), 20, moreResults)};
for (auto track : tracks) for (auto track : tracks)
{ {
auto trackId = track.id(); auto trackId {track.id()};
Wt::WTemplate* entry = _tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry")); Wt::WTemplate* entry {_tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain); entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
+1 -1
View File
@@ -45,7 +45,7 @@ class Tracks : public Wt::WTemplate
void refresh(); void refresh();
void addSome(); void addSome();
std::vector<Database::Track::pointer> getTracks(int offset, int size, bool& moreResults); std::vector<Database::Track::pointer> getTracks(boost::optional<std::size_t> offset, boost::optional<std::size_t> size, bool& moreResults);
std::vector<Database::Track::pointer> getTracks(); std::vector<Database::Track::pointer> getTracks();
Wt::WContainerWidget* _tracksContainer; Wt::WContainerWidget* _tracksContainer;
+33 -29
View File
@@ -49,8 +49,6 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
{ {
std::shared_ptr<Av::Transcoder> transcoder; std::shared_ptr<Av::Transcoder> transcoder;
LMS_LOG(UI, DEBUG) << "Handling new request...";
// First, see if this request is for a continuation // First, see if this request is for a continuation
Wt::Http::ResponseContinuation *continuation = request.continuation(); Wt::Http::ResponseContinuation *continuation = request.continuation();
if (continuation) if (continuation)
@@ -62,8 +60,9 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
{ {
Database::IdType trackId; Database::IdType trackId;
Av::TranscodeParameters parameters {}; Av::TranscodeParameters parameters {};
parameters.stripMetadata = true;
LMS_LOG(UI, DEBUG) << "No continuation yet"; LMS_LOG(UI, DEBUG) << "First request: creating transcoder";
try try
{ {
auto trackIdStr = request.getParameter("trackid"); auto trackIdStr = request.getParameter("trackid");
@@ -75,8 +74,7 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
trackId = std::stol(*request.getParameter("trackid")); trackId = std::stol(*request.getParameter("trackid"));
auto offsetStr = request.getParameter("offset"); auto offsetStr = request.getParameter("offset");
if (offsetStr) parameters.offset = std::chrono::seconds(offsetStr ? std::stol(*offsetStr) : 0);
parameters.offset = std::chrono::seconds(std::stol(*offsetStr));
} }
catch (std::exception &e) catch (std::exception &e)
{ {
@@ -98,34 +96,35 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
return; return;
} }
parameters.bitrate = LmsApp->getUser()->getAudioBitrate(); if (LmsApp->getUser()->getAudioTranscodeEnable())
switch (LmsApp->getUser()->getAudioEncoding())
{ {
case Database::AudioEncoding::AUTO: parameters.bitrate = LmsApp->getUser()->getAudioTranscodeBitrate();
case Database::AudioEncoding::MP3:
parameters.encoding = Av::Encoding::MP3; switch (LmsApp->getUser()->getAudioTranscodeFormat())
break; {
case Database::AudioEncoding::OGG_OPUS: case Database::AudioFormat::MP3:
parameters.encoding = Av::Encoding::OGG_OPUS; parameters.encoding = Av::Encoding::MP3;
break; break;
case Database::AudioEncoding::OGG_VORBIS: case Database::AudioFormat::OGG_OPUS:
parameters.encoding = Av::Encoding::OGG_VORBIS; parameters.encoding = Av::Encoding::OGG_OPUS;
break; break;
case Database::AudioEncoding::WEBM_VORBIS: case Database::AudioFormat::OGG_VORBIS:
parameters.encoding = Av::Encoding::WEBM_VORBIS; parameters.encoding = Av::Encoding::OGG_VORBIS;
break; break;
default: case Database::AudioFormat::WEBM_VORBIS:
parameters.encoding = Av::Encoding::MP3; parameters.encoding = Av::Encoding::WEBM_VORBIS;
break; break;
default:
parameters.encoding = Av::Encoding::OGG_OPUS;
break;
}
} }
else
parameters.bitrate = 0;
transcoder = std::make_shared<Av::Transcoder>(track->getPath(), parameters); transcoder = std::make_shared<Av::Transcoder>(track->getPath(), parameters);
} }
std::string mimeType = Av::encodingToMimetype(transcoder->getParameters().encoding);
LMS_LOG(UI, DEBUG) << "Mime type set to '" << mimeType << "'";
response.setMimeType(mimeType);
if (!transcoder->start()) if (!transcoder->start())
{ {
@@ -134,6 +133,11 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
} }
LMS_LOG(UI, DEBUG) << "Transcoder started"; LMS_LOG(UI, DEBUG) << "Transcoder started";
std::string mimeType {transcoder->getOutputMimeType()};
response.setMimeType(mimeType);
LMS_LOG(UI, DEBUG) << "Mime type set to '" << mimeType << "'";
} }
if (!transcoder->isComplete()) if (!transcoder->isComplete())
@@ -144,7 +148,7 @@ AudioResource::handleRequest(const Wt::Http::Request& request,
transcoder->process(data, _chunkSize); transcoder->process(data, _chunkSize);
response.out().write(reinterpret_cast<char*>(&data[0]), data.size()); response.out().write(reinterpret_cast<char*>(&data[0]), data.size());
LMS_LOG(UI, DEBUG) << "Written " << data.size() << " bytes! complete = " << std::boolalpha << transcoder->isComplete(); LMS_LOG(UI, DEBUG) << "Written " << data.size() << " bytes! complete = " << (transcoder->isComplete() ? "true" : "false");
if (!response.out()) if (!response.out())
{ {
+12
View File
@@ -482,6 +482,10 @@ testSingleTrackSingleCluster(Wt::Dbo::Session& session)
auto tracks {Track::getByFilter(session, {clusterId})}; auto tracks {Track::getByFilter(session, {clusterId})};
CHECK(tracks.size() == 1); CHECK(tracks.size() == 1);
CHECK(tracks.front().id() == trackId); CHECK(tracks.front().id() == trackId);
tracks = Track::getByFilter(session, {});
CHECK(tracks.size() == 1);
CHECK(tracks.front().id() == trackId);
} }
{ {
@@ -535,6 +539,10 @@ testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session)
auto releases {Release::getByFilter(session, {clusterId})}; auto releases {Release::getByFilter(session, {clusterId})};
CHECK(releases.size() == 1); CHECK(releases.size() == 1);
CHECK(releases.front().id() == releaseId); CHECK(releases.front().id() == releaseId);
releases = Release::getByFilter(session, {clusterId});
CHECK(releases.size() == 1);
CHECK(releases.front().id() == releaseId);
} }
{ {
@@ -595,6 +603,10 @@ testSingleTrackSingleArtistMultiClusters(Wt::Dbo::Session& session)
CHECK(artists.size() == 1); CHECK(artists.size() == 1);
CHECK(artists.front().id() == artistId); CHECK(artists.front().id() == artistId);
artists = Artist::getByFilter(session, {});
CHECK(artists.size() == 1);
CHECK(artists.front().id() == artistId);
artists = Artist::getByFilter(session, {cluster2Id}); artists = Artist::getByFilter(session, {cluster2Id});
CHECK(artists.empty()); CHECK(artists.empty());