Added a new transcoding mode: transcode if and only if the format is not handled by the browser. Made the subsonic API configurable again in conf file. Moved the subsonic's artist list mode in the per-user settings page. fixes #46

This commit is contained in:
emeric
2020-04-20 20:54:16 +02:00
parent 7d8ec6ff1e
commit 4749fd7548
31 changed files with 615 additions and 753 deletions
-40
View File
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<!--FORMS message blocks-->
<message id="Lms.Admin.Subsonic.template">
<legend>${tr:Lms.Admin.Subsonic.subsonic}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:enable}">
${tr:Lms.Admin.Subsonic.enable-api}
</label>
<div class="col-sm-5">
<div class="checkbox"><label>${enable}</label></div>
</div>
<div class="help-block col-sm-5">
${enable-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:artist-list-mode}">
${tr:Lms.Admin.Subsonic.artist-list-mode}
</label>
<div class="col-sm-5">
${artist-list-mode}
</div>
<div class="help-block col-sm-5">
${artist-list-mode-info}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${apply-btn class="btn-primary"} ${discard-btn}
</div>
</div>
</div>
</message>
</messages>
-15
View File
@@ -42,21 +42,6 @@
${password-info} ${password-info}
</div> </div>
</div> </div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:audio-transcode-bitrate-limit}">
${tr:Lms.Admin.User.audio-transcode-bitrate-limit}
</label>
<div class="col-sm-5">
<div class="input-group">
${audio-transcode-bitrate-limit}
<span class="input-group-addon">kbps</span>
</div>
</div>
<div class="help-block col-sm-5">
${audio-transcode-bitrate-limit-info}
</div>
</div>
${<if-demo>} ${<if-demo>}
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" for="${id:demo}"> <label class="control-label col-sm-2" for="${id:demo}">
+18 -17
View File
@@ -68,14 +68,6 @@
<message id="Lms.Admin.Database.Status.status-scheduled">Scheduled on {1}</message> <message id="Lms.Admin.Database.Status.status-scheduled">Scheduled on {1}</message>
<message id="Lms.Admin.Database.Status.status-in-progress">Scanning {1} of {2} files ({3} %)</message> <message id="Lms.Admin.Database.Status.status-in-progress">Scanning {1} of {2} files ({3} %)</message>
<!--Subsonic-->
<message id="Lms.Admin.Subsonic.all-artists">All artists</message>
<message id="Lms.Admin.Subsonic.release-artists">Album artists</message>
<message id="Lms.Admin.Subsonic.enable-api">Enable</message>
<message id="Lms.Admin.Subsonic.artist-list-mode">Artist list mode</message>
<message id="Lms.Admin.Subsonic.need-restart">You must restart the service for this change to take effect</message>
<message id="Lms.Admin.Subsonic.subsonic">Subsonic API</message>
<!--Users--> <!--Users-->
<message id="Lms.Admin.Users.add">New user</message> <message id="Lms.Admin.Users.add">New user</message>
<message id="Lms.Admin.Users.admin">Admin</message> <message id="Lms.Admin.Users.admin">Admin</message>
@@ -166,20 +158,29 @@
<!--Settings--> <!--Settings-->
<message id="Lms.Settings.appearance">Appearance</message> <message id="Lms.Settings.appearance">Appearance</message>
<message id="Lms.Settings.audio">Audio</message> <message id="Lms.Settings.audio">Audio</message>
<message id="Lms.Settings.audio-settings-are-local">These audio settings are local to your browser!</message>
<message id="Lms.Settings.change-password">Change password</message> <message id="Lms.Settings.change-password">Change password</message>
<message id="Lms.Settings.dark-mode">Dark mode</message> <message id="Lms.Settings.dark-mode">Dark mode</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.password-bad">Bad password</message> <message id="Lms.Settings.password-bad">Bad password</message>
<message id="Lms.Settings.password-must-fill-old-password">Old password must be filled in</message> <message id="Lms.Settings.password-must-fill-old-password">Old password must be filled in</message>
<message id="Lms.Settings.transcoding">Transcoding</message> <message id="Lms.Settings.subsonic-artist-list-mode">Artist list mode</message>
<message id="Lms.Settings.transcoding-bitrate">Transcode bitrate</message> <message id="Lms.Settings.subsonic-artist-list-mode.all-artists">All artists</message>
<message id="Lms.Settings.transcoding-format">Transcode format</message> <message id="Lms.Settings.subsonic-artist-list-mode.release-artists">Album artists</message>
<message id="Lms.Settings.transcoding-enable">Enable transcoding</message> <message id="Lms.Settings.subsonic-api">Subsonic API</message>
<message id="Lms.Settings.transcoding.matroska_opus">Matroska/Opus</message> <message id="Lms.Settings.transcode">Transcoding</message>
<message id="Lms.Settings.transcoding.mp3">MP3</message> <message id="Lms.Settings.transcode-bitrate">Transcode bitrate</message>
<message id="Lms.Settings.transcoding.ogg_opus">Ogg/Opus</message> <message id="Lms.Settings.transcode-enable">Enable transcoding</message>
<message id="Lms.Settings.transcoding.ogg_vorbis">Ogg/Vorbis</message> <message id="Lms.Settings.transcode-format">Transcode format</message>
<message id="Lms.Settings.transcoding.webm_vorbis">WebM/Vorbis</message> <message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
<message id="Lms.Settings.transcode-format.ogg_opus">Ogg/Opus</message>
<message id="Lms.Settings.transcode-format.ogg_vorbis">Ogg/Vorbis</message>
<message id="Lms.Settings.transcode-format.webm_vorbis">WebM/Vorbis</message>
<message id="Lms.Settings.transcode-mode">Enable transcoding</message>
<message id="Lms.Settings.transcode-mode.always">Always</message>
<message id="Lms.Settings.transcode-mode.if-format-not-supported">Only when format is not supported</message>
<message id="Lms.Settings.transcode-mode.never">Never</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>
+20 -19
View File
@@ -68,14 +68,6 @@
<message id="Lms.Admin.Database.Status.status-scheduled">Planifié le {1}</message> <message id="Lms.Admin.Database.Status.status-scheduled">Planifié le {1}</message>
<message id="Lms.Admin.Database.Status.status-in-progress">Scan de {1} fichiers sur {2} ({3} %)</message> <message id="Lms.Admin.Database.Status.status-in-progress">Scan de {1} fichiers sur {2} ({3} %)</message>
<!--Subsonic-->
<message id="Lms.Admin.Subsonic.all-artists">Tous les artistes</message>
<message id="Lms.Admin.Subsonic.release-artists">Artistes d'album</message>
<message id="Lms.Admin.Subsonic.enable-api">Activer</message>
<message id="Lms.Admin.Subsonic.artist-list-mode">Mode de listage des artistes</message>
<message id="Lms.Admin.Subsonic.need-restart">Vous devez redémarrer le service pour que cette option soit prise en compte</message>
<message id="Lms.Admin.Subsonic.subsonic">API Subsonic</message>
<!--Users--> <!--Users-->
<message id="Lms.Admin.Users.add">Ajouter</message> <message id="Lms.Admin.Users.add">Ajouter</message>
<message id="Lms.Admin.Users.admin">Admin</message> <message id="Lms.Admin.Users.admin">Admin</message>
@@ -146,11 +138,11 @@
<!--Playqueue--> <!--Playqueue-->
<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-playing"> <message id="Lms.PlayQueue.nb-tracks-added">
<plural case="0">{1} piste ajoutée</plural> <plural case="0">{1} piste ajoutée</plural>
<plural case="1">{1} pistes ajoutées</plural> <plural case="1">{1} pistes ajoutées</plural>
</message> </message>
<message id="Lms.PlayQueue.nb-tracks-added"> <message id="Lms.PlayQueue.nb-tracks-playing">
<plural case="0">{1} piste ajoutée</plural> <plural case="0">{1} piste ajoutée</plural>
<plural case="1">{1} pistes ajoutées</plural> <plural case="1">{1} pistes ajoutées</plural>
</message> </message>
@@ -166,20 +158,29 @@
<!--Settings--> <!--Settings-->
<message id="Lms.Settings.appearance">Apparence</message> <message id="Lms.Settings.appearance">Apparence</message>
<message id="Lms.Settings.audio">Audio</message> <message id="Lms.Settings.audio">Audio</message>
<message id="Lms.Settings.audio-settings-are-local">Ces paramètres audio sont locaux à votre navigateur !</message>
<message id="Lms.Settings.change-password">Changement de mot de passe</message> <message id="Lms.Settings.change-password">Changement de mot de passe</message>
<message id="Lms.Settings.dark-mode">Mode sombre</message> <message id="Lms.Settings.dark-mode">Mode sombre</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.password-bad">Mauvais mot de passe</message> <message id="Lms.Settings.password-bad">Mauvais mot de passe</message>
<message id="Lms.Settings.password-must-fill-old-password">L'ancien mot de passe doit être renseigné</message> <message id="Lms.Settings.password-must-fill-old-password">L'ancien mot de passe doit être renseigné</message>
<message id="Lms.Settings.transcoding">Transcodage</message> <message id="Lms.Settings.subsonic-artist-list-mode">Mode de listage des artistes</message>
<message id="Lms.Settings.transcoding-bitrate">Bitrate du transcodage</message> <message id="Lms.Settings.subsonic-artist-list-mode.all-artists">Tous les artistes</message>
<message id="Lms.Settings.transcoding-format">Format du transcodage</message> <message id="Lms.Settings.subsonic-artist-list-mode.release-artists">Tous les artistes d'album</message>
<message id="Lms.Settings.transcoding-enable">Activer le transcodage</message> <message id="Lms.Settings.subsonic-api">API Subsonic</message>
<message id="Lms.Settings.transcoding.matroska_opus">Matroska/Opus</message> <message id="Lms.Settings.transcode">Transcodage</message>
<message id="Lms.Settings.transcoding.mp3">MP3</message> <message id="Lms.Settings.transcode-bitrate">Bitrate du transcodage</message>
<message id="Lms.Settings.transcoding.ogg_opus">Ogg/Opus</message> <message id="Lms.Settings.transcode-enable">Activer le transcodage</message>
<message id="Lms.Settings.transcoding.ogg_vorbis">Ogg/Vorbis</message> <message id="Lms.Settings.transcode-format">Format du transcodage</message>
<message id="Lms.Settings.transcoding.webm_vorbis">WebM/Vorbis</message> <message id="Lms.Settings.transcode-format.matroska_opus">Matroska/Opus</message>
<message id="Lms.Settings.transcode-format.mp3">MP3</message>
<message id="Lms.Settings.transcode-format.ogg_opus">Ogg/Opus</message>
<message id="Lms.Settings.transcode-format.ogg_vorbis">Ogg/Vorbis</message>
<message id="Lms.Settings.transcode-format.webm_vorbis">WebM/Vorbis</message>
<message id="Lms.Settings.transcode-mode">Appliquer le transcodage</message>
<message id="Lms.Settings.transcode-mode.always">Toujours</message>
<message id="Lms.Settings.transcode-mode.if-format-not-supported">Seulement lorsque le format n'est pas pris en charge</message>
<message id="Lms.Settings.transcode-mode.never">Jamais</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>
+73 -12
View File
@@ -19,44 +19,105 @@
</div> </div>
</div> </div>
<legend>${tr:Lms.Settings.audio}</legend> <legend>${tr:Lms.Settings.audio}</legend>
<div class="col-sm-12">
<div class="row">
<div class="alert alert-info col-sm-7">
${tr:Lms.Settings.audio-settings-are-local}
</div>
</div>
</div>
<div class="form-horizontal"> <div class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label" for="${id:transcoding-enable}"> <label class="col-sm-2 control-label" for="${id:transcode-mode}">
${tr:Lms.Settings.transcoding-enable} ${tr:Lms.Settings.transcode-mode}
</label> </label>
<div class="col-sm-5"> <div class="col-sm-5">
<div class="checkbox"><label>${transcoding-enable}</label></div> ${transcode-mode}
</div> </div>
<div class="col-sm-5 help-block"> <div class="col-sm-5 help-block">
${transcoding-enable-info} ${transcode-mode-info}
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" for="${id:transcoding-format}"> <label class="control-label col-sm-2" for="${id:transcode-format}">
${tr:Lms.Settings.transcoding-format} ${tr:Lms.Settings.transcode-format}
</label> </label>
<div class="col-sm-5"> <div class="col-sm-5">
${transcoding-format} ${transcode-format}
</div> </div>
<div class="help-block col-sm-5"> <div class="help-block col-sm-5">
${transcoding-format-info} ${transcode-format-info}
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" for="${id:transcoding-bitrate}"> <label class="control-label col-sm-2" for="${id:transcode-bitrate}">
${tr:Lms.Settings.transcoding-bitrate} ${tr:Lms.Settings.transcode-bitrate}
</label> </label>
<div class="col-sm-5"> <div class="col-sm-5">
<div class="input-group"> <div class="input-group">
${transcoding-bitrate} ${transcode-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">
${transcoding-bitrate-info} ${transcode-bitrate-info}
</div> </div>
</div> </div>
</div> </div>
${<if-has-subsonic-api>}
<legend>${tr:Lms.Settings.subsonic-api}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label" for="${id:subsonic-transcode-enable}">
${tr:Lms.Settings.transcode-enable}
</label>
<div class="col-sm-5">
<div class="checkbox"><label>${subsonic-transcode-enable}</label></div>
</div>
<div class="col-sm-5 help-block">
${subsonic-transcode-enable-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:subsonic-transcode-format}">
${tr:Lms.Settings.transcode-format}
</label>
<div class="col-sm-5">
${subsonic-transcode-format}
</div>
<div class="col-sm-5 help-block">
${subsonic-transcode-format-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:subsonic-transcode-bitrate}">
${tr:Lms.Settings.transcode-bitrate}
</label>
<div class="col-sm-5">
<div class="input-group">
${subsonic-transcode-bitrate}
<span class="input-group-addon">kbps</span>
</div>
</div>
<div class="help-block col-sm-5">
${subsonic-transcode-bitrate-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:subsonic-artist-list-mode}">
${tr:Lms.Settings.subsonic-artist-list-mode}
</label>
<div class="col-sm-5">
${subsonic-artist-list-mode}
</div>
<div class="help-block col-sm-5">
${subsonic-artist-list-mode-info}
</div>
</div>
</div>
${</if-has-subsonic-api>}
<legend>${tr:Lms.Settings.change-password}</legend> <legend>${tr:Lms.Settings.change-password}</legend>
<div class="form-horizontal"> <div class="form-horizontal">
${<if-has-old-password>} ${<if-has-old-password>}
+3
View File
@@ -34,6 +34,9 @@ deploy-path = "/";
# Acoustic brainz's root API # Acoustic brainz's root API
acousticbrainz-api-url = "https://acousticbrainz.org/api/v1/"; acousticbrainz-api-url = "https://acousticbrainz.org/api/v1/";
# API
api-subsonic = true;
# Turn on this option to allow the demo account creation/use # Turn on this option to allow the demo account creation/use
demo = false; demo = false;
+60 -6
View File
@@ -2,6 +2,13 @@
var LMS = LMS || {}; var LMS = LMS || {};
// Keep in sync with MediaPlayer::TranscodeMode cpp
const TranscodeMode = {
Never: 0,
Always: 1,
IfFormatNotSupported: 2,
}
const Mode = { const Mode = {
Transcode: 1, Transcode: 1,
File: 2, File: 2,
@@ -16,6 +23,9 @@ LMS.mediaplayer = function () {
var _duration = 0; var _duration = 0;
var _audioNativeSrc; var _audioNativeSrc;
var _audioTranscodedSrc; var _audioTranscodedSrc;
var _transcodeMode = TranscodeMode.Never;
var _transcodeFormat = 0;
var _transcodeBitrate = 0;
var _updateControls = function() { var _updateControls = function() {
if (_elems.audio.paused) { if (_elems.audio.paused) {
@@ -71,6 +81,29 @@ LMS.mediaplayer = function () {
_setVolume(_elems.volumeslider.value); _setVolume(_elems.volumeslider.value);
} }
var _initTranscodeSettings = function(defaultTranscodeMode, defaultTranscodeFormat, defaultTranscodeBitrate) {
if (typeof(Storage) !== "undefined" && localStorage.transcodeMode) {
_transcodeMode = Number(localStorage.transcodeMode);
}
else {
_transcodeMode = defaultTranscodeMode;
}
if (typeof(Storage) !== "undefined" && localStorage.transcodeFormat) {
_transcodeFormat = Number(localStorage.transcodeFormat);
}
else {
_transcodeFormat = defaultTranscodeFormat;
}
if (typeof(Storage) !== "undefined" && localStorage.transcodeBitrate) {
_transcodeBitrate = Number(localStorage.transcodeBitrate);
}
else {
_transcodeBitrate = defaultTranscodeBitrate;
}
Wt.emit(_root, "settingsLoaded", _transcodeMode, _transcodeFormat, _transcodeBitrate);
}
var _setVolume = function(volume) { var _setVolume = function(volume) {
_elems.lastvolume = _elems.audio.volume; _elems.lastvolume = _elems.audio.volume;
@@ -99,7 +132,7 @@ LMS.mediaplayer = function () {
} }
} }
var init = function(root) { var init = function(root, defaultTranscodeMode, defaultTranscodeFormat, defaultTranscodeBitrate) {
_root = root; _root = root;
_elems.audio = document.getElementById("lms-mp-audio"); _elems.audio = document.getElementById("lms-mp-audio");
@@ -139,7 +172,7 @@ LMS.mediaplayer = function () {
case Mode.Transcode: case Mode.Transcode:
_offset = selectedOffset; _offset = selectedOffset;
_removeAudioSources(); _removeAudioSources();
_addAudioSourceIfSet(_audioTranscodeSrc + "&offset=" + _offset); _addAudioSource(_audioTranscodeSrc + "&offset=" + _offset);
_elems.audio.load(); _elems.audio.load();
_elems.audio.currentTime = 0; _elems.audio.currentTime = 0;
_playTrack(); _playTrack();
@@ -166,6 +199,7 @@ LMS.mediaplayer = function () {
}); });
_initVolume(); _initVolume();
_initTranscodeSettings(defaultTranscodeMode, defaultTranscodeFormat, defaultTranscodeBitrate);
_elems.volumeslider.addEventListener("input", function() { _elems.volumeslider.addEventListener("input", function() {
_setVolume(_elems.volumeslider.value); _setVolume(_elems.volumeslider.value);
@@ -197,7 +231,7 @@ LMS.mediaplayer = function () {
} }
} }
var _addAudioSourceIfSet = function(audioSrc) { var _addAudioSource = function(audioSrc) {
let source = document.createElement('source'); let source = document.createElement('source');
source.src = audioSrc; source.src = audioSrc;
_elems.audio.appendChild(source); _elems.audio.appendChild(source);
@@ -218,14 +252,21 @@ LMS.mediaplayer = function () {
_offset = 0; _offset = 0;
_duration = params.duration; _duration = params.duration;
_audioNativeSrc = params.native_resource; _audioNativeSrc = params.native_resource;
_audioTranscodeSrc = params.transcode_resource; _audioTranscodeSrc = params.transcode_resource + "&bitrate=" + _transcodeBitrate + "&format=" + _transcodeFormat;
_elems.seek.max = _duration; _elems.seek.max = _duration;
_removeAudioSources(); _removeAudioSources();
// ! order is important // ! order is important
_addAudioSourceIfSet(_audioNativeSrc); if (_transcodeMode == TranscodeMode.Never || _transcodeMode == TranscodeMode.IfFormatNotSupported)
_addAudioSourceIfSet(_audioTranscodeSrc); {
_addAudioSource(_audioNativeSrc);
}
if (_transcodeMode == TranscodeMode.Always || _transcodeMode == TranscodeMode.IfFormatNotSupported)
{
_addAudioSource(_audioTranscodeSrc);
}
_elems.audio.load(); _elems.audio.load();
_elems.curtime.innerHTML = _durationToString(_offset); _elems.curtime.innerHTML = _durationToString(_offset);
@@ -248,10 +289,23 @@ LMS.mediaplayer = function () {
_elems.audio.pause(); _elems.audio.pause();
} }
var setSettings = function(transcodeMode, transcodeFormat, transcodeBitrate) {
_transcodeMode = transcodeMode;
_transcodeFormat = transcodeFormat;
_transcodeBitrate = transcodeBitrate;
if (typeof(Storage) !== "undefined") {
localStorage.transcodeMode = _transcodeMode;
localStorage.transcodeFormat = _transcodeFormat;
localStorage.transcodeBitrate = _transcodeBitrate;
}
}
return { return {
init: init, init: init,
loadTrack: loadTrack, loadTrack: loadTrack,
stop: stop, stop: stop,
setSettings: setSettings,
}; };
}(); }();
+7 -7
View File
@@ -104,35 +104,35 @@ Transcoder::start()
args.emplace_back(std::to_string(_parameters.bitrate)); args.emplace_back(std::to_string(_parameters.bitrate));
// Codecs and formats // Codecs and formats
switch (_parameters.encoding) switch (_parameters.format)
{ {
case Encoding::MP3: case Format::MP3:
args.emplace_back("-f"); args.emplace_back("-f");
args.emplace_back("mp3"); args.emplace_back("mp3");
break; break;
case Encoding::OGG_OPUS: case Format::OGG_OPUS:
args.emplace_back("-acodec"); args.emplace_back("-acodec");
args.emplace_back("libopus"); args.emplace_back("libopus");
args.emplace_back("-f"); args.emplace_back("-f");
args.emplace_back("ogg"); args.emplace_back("ogg");
break; break;
case Encoding::MATROSKA_OPUS: case Format::MATROSKA_OPUS:
args.emplace_back("-acodec"); args.emplace_back("-acodec");
args.emplace_back("libopus"); args.emplace_back("libopus");
args.emplace_back("-f"); args.emplace_back("-f");
args.emplace_back("matroska"); args.emplace_back("matroska");
break; break;
case Encoding::OGG_VORBIS: case Format::OGG_VORBIS:
args.emplace_back("-acodec"); args.emplace_back("-acodec");
args.emplace_back("libvorbis"); args.emplace_back("libvorbis");
args.emplace_back("-f"); args.emplace_back("-f");
args.emplace_back("ogg"); args.emplace_back("ogg");
break; break;
case Encoding::WEBM_VORBIS: case Format::WEBM_VORBIS:
args.emplace_back("-acodec"); args.emplace_back("-acodec");
args.emplace_back("libvorbis"); args.emplace_back("libvorbis");
args.emplace_back("-f"); args.emplace_back("-f");
@@ -143,7 +143,7 @@ Transcoder::start()
return false; return false;
} }
_outputMimeType = encodingToMimetype(_parameters.encoding); _outputMimeType = formatToMimetype(_parameters.format);
args.emplace_back("pipe:1"); args.emplace_back("pipe:1");
+7 -7
View File
@@ -21,15 +21,15 @@
namespace Av { namespace Av {
const char* encodingToMimetype(Encoding encoding) const char* formatToMimetype(Format format)
{ {
switch (encoding) switch (format)
{ {
case Encoding::MP3: return "audio/mpeg"; case Format::MP3: return "audio/mpeg";
case Encoding::OGG_OPUS: return "audio/opus"; case Format::OGG_OPUS: return "audio/opus";
case Encoding::MATROSKA_OPUS: return "audio/x-matroska"; case Format::MATROSKA_OPUS: return "audio/x-matroska";
case Encoding::OGG_VORBIS: return "audio/ogg"; case Format::OGG_VORBIS: return "audio/ogg";
case Encoding::WEBM_VORBIS: return "audio/webm"; case Format::WEBM_VORBIS: return "audio/webm";
} }
throw AvException {"Invalid encoding"}; throw AvException {"Invalid encoding"};
+1 -1
View File
@@ -33,7 +33,7 @@ namespace Av {
struct TranscodeParameters struct TranscodeParameters
{ {
Encoding encoding; Format format;
std::size_t bitrate {128000}; std::size_t bitrate {128000};
std::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default) std::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default)
std::optional<std::chrono::seconds> offset; std::optional<std::chrono::seconds> offset;
+7 -7
View File
@@ -31,17 +31,17 @@ class AvException : public LmsException
AvException(const std::string& msg) : LmsException(msg) {} AvException(const std::string& msg) : LmsException(msg) {}
}; };
enum class Encoding enum class Format
{ {
// Values are important and must not be changed // Values are important and must not be changed
MP3, MP3 = 0,
OGG_OPUS, OGG_OPUS = 1,
MATROSKA_OPUS, MATROSKA_OPUS = 2,
OGG_VORBIS, OGG_VORBIS = 3,
WEBM_VORBIS, WEBM_VORBIS = 4,
}; };
const char* encodingToMimetype(Encoding encoding); const char* formatToMimetype(Format encoding);
} }
-1
View File
@@ -11,7 +11,6 @@ add_library(lmsdatabase SHARED
impl/Session.cpp impl/Session.cpp
impl/SessionPool.cpp impl/SessionPool.cpp
impl/SqlQuery.cpp impl/SqlQuery.cpp
impl/SubsonicSettings.cpp
impl/Track.cpp impl/Track.cpp
impl/TrackBookmark.cpp impl/TrackBookmark.cpp
impl/User.cpp impl/User.cpp
+35 -8
View File
@@ -31,7 +31,6 @@
#include "database/Db.hpp" #include "database/Db.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/ScanSettings.hpp" #include "database/ScanSettings.hpp"
#include "database/SubsonicSettings.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/TrackBookmark.hpp" #include "database/TrackBookmark.hpp"
#include "database/TrackArtistLink.hpp" #include "database/TrackArtistLink.hpp"
@@ -41,7 +40,7 @@
namespace Database { namespace Database {
#define LMS_DATABASE_VERSION 19 #define LMS_DATABASE_VERSION 21
using Version = std::size_t; using Version = std::size_t;
@@ -198,11 +197,8 @@ CREATE TABLE "release_backup" (
"mbid" text not null "mbid" text not null
))"); ))");
_session.execute("INSERT INTO release_backup SELECT id,version,name,mbid FROM release"); _session.execute("INSERT INTO release_backup SELECT id,version,name,mbid FROM release");
_session.execute("DROP TABLE release;"); _session.execute("DROP TABLE release");
_session.execute("ALTER TABLE release_backup RENAME TO release"); _session.execute("ALTER TABLE release_backup RENAME TO release");
_session.execute("CREATE INDEX release_name_idx ON release(name)");
_session.execute("CREATE INDEX release_name_nocase_idx ON release(name COLLATE NOCASE)");
_session.execute("CREATE INDEX release_mbid_idx ON release(mbid)");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion(); ScanSettings::get(*this).modify()->incScanVersion();
@@ -217,6 +213,39 @@ CREATE TABLE IF NOT EXISTS "subsonic_settings" (
"artist_list_mode" integer not null "artist_list_mode" integer not null
))"); ))");
} }
else if (version == 19)
{
_session.execute(R"(
CREATE TABLE "user_backup" (
"id" integer primary key autoincrement,
"version" integer not null,
"type" integer not null,
"login_name" text not null,
"password_salt" text not null,
"password_hash" text not null,
"last_login" text,
"subsonic_transcode_enable" boolean not null,
"subsonic_transcode_format" integer not null,
"subsonic_transcode_bitrate" integer not null,
"subsonic_artist_list_mode" integer not null,
"ui_theme" integer not null,
"cur_playing_track_pos" integer not null,
"repeat_all" boolean not null,
"radio" boolean not null
))");
_session.execute(std::string {"INSERT INTO user_backup SELECT id, version, type, login_name, password_salt, password_hash, last_login, "}
+ (User::defaultSubsonicTranscodeEnable ? "1" : "0")
+ ", " + std::to_string(static_cast<int>(User::defaultSubsonicTranscodeFormat))
+ ", " + std::to_string(User::defaultSubsonicTranscodeBitrate)
+ ", " + std::to_string(static_cast<int>(User::defaultSubsonicArtistListMode))
+ ", ui_theme, cur_playing_track_pos, repeat_all, radio FROM user");
_session.execute("DROP TABLE user");
_session.execute("ALTER TABLE user_backup RENAME TO user");
}
else if (version == 20)
{
_session.execute("DROP TABLE subsonic_settings");
}
else else
{ {
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
@@ -239,7 +268,6 @@ Session::Session(Db& db)
_session.mapClass<ClusterType>("cluster_type"); _session.mapClass<ClusterType>("cluster_type");
_session.mapClass<Release>("release"); _session.mapClass<Release>("release");
_session.mapClass<ScanSettings>("scan_settings"); _session.mapClass<ScanSettings>("scan_settings");
_session.mapClass<SubsonicSettings>("subsonic_settings");
_session.mapClass<Track>("track"); _session.mapClass<Track>("track");
_session.mapClass<TrackBookmark>("track_bookmark"); _session.mapClass<TrackBookmark>("track_bookmark");
_session.mapClass<TrackArtistLink>("track_artist_link"); _session.mapClass<TrackArtistLink>("track_artist_link");
@@ -365,7 +393,6 @@ Session::prepareTables()
auto uniqueTransaction {createUniqueTransaction()}; auto uniqueTransaction {createUniqueTransaction()};
ScanSettings::init(*this); ScanSettings::init(*this);
SubsonicSettings::init(*this);
} }
} }
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2020 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 "database/SubsonicSettings.hpp"
#include "database/Session.hpp"
namespace Database {
void
SubsonicSettings::init(Session& session)
{
session.checkUniqueLocked();
pointer settings {get(session)};
if (settings)
return;
settings = session.getDboSession().add(std::make_unique<SubsonicSettings>());
}
SubsonicSettings::pointer
SubsonicSettings::get(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<SubsonicSettings>();
}
} // ns Database
+6 -62
View File
@@ -70,28 +70,11 @@ AuthToken::getByValue(Session& session, const std::string& value)
static const std::string playedListName {"__played_tracks__"}; static const std::string playedListName {"__played_tracks__"};
static const std::string queuedListName {"__queued_tracks__"}; static const std::string queuedListName {"__queued_tracks__"};
const std::set<Bitrate>
User::audioTranscodeAllowedBitrates =
{
64000,
96000,
128000,
192000,
320000,
};
User::User()
: _maxAudioTranscodeBitrate {static_cast<int>(*audioTranscodeAllowedBitrates.rbegin())}
{
}
User::User(const std::string& loginName, const PasswordHash& passwordHash) User::User(const std::string& loginName, const PasswordHash& passwordHash)
: User() : _loginName {loginName}
, _passwordSalt {passwordHash.salt}
, _passwordHash {passwordHash.hash}
{ {
_loginName = loginName;
_passwordHash = passwordHash.hash;
_passwordSalt = passwordHash.salt;
} }
std::vector<User::pointer> std::vector<User::pointer>
@@ -141,27 +124,10 @@ User::getByLoginName(Session& session, const std::string& name)
} }
void void
User::setAudioTranscodeBitrate(Bitrate bitrate) User::setSubsonicTranscodeBitrate(Bitrate bitrate)
{ {
_audioTranscodeBitrate = std::min(bitrate, static_cast<Bitrate>(_maxAudioTranscodeBitrate)); assert(audioTranscodeAllowedBitrates.find(bitrate) != audioTranscodeAllowedBitrates.cend());
} _subsonicTranscodeBitrate = bitrate;
void
User::setMaxAudioTranscodeBitrate(Bitrate requestedBitrate)
{
Bitrate bitrate {*audioTranscodeAllowedBitrates.begin()};
for (auto allowedBitrate : audioTranscodeAllowedBitrates)
{
if (requestedBitrate < allowedBitrate)
break;
bitrate = allowedBitrate;
}
_maxAudioTranscodeBitrate = bitrate;
if (_audioTranscodeBitrate > _maxAudioTranscodeBitrate)
_audioTranscodeBitrate = _maxAudioTranscodeBitrate;
} }
void void
@@ -170,18 +136,6 @@ User::clearAuthTokens()
_authTokens.clear(); _authTokens.clear();
} }
Bitrate
User::getAudioTranscodeBitrate(void) const
{
return _audioTranscodeBitrate;
}
std::size_t
User::getMaxAudioTranscodeBitrate(void) const
{
return _maxAudioTranscodeBitrate;
}
Wt::Dbo::ptr<TrackList> Wt::Dbo::ptr<TrackList>
User::getPlayedTrackList(Session& session) const User::getPlayedTrackList(Session& session) const
{ {
@@ -283,16 +237,6 @@ User::getStarredTracks() const
return std::vector<Wt::Dbo::ptr<Track>>(_starredTracks.begin(), _starredTracks.end()); return std::vector<Wt::Dbo::ptr<Track>>(_starredTracks.begin(), _starredTracks.end());
} }
bool
User::checkBitrate(Database::Bitrate bitrate) const
{
if (audioTranscodeAllowedBitrates.find(bitrate) == std::cend(audioTranscodeAllowedBitrates))
return false;
return static_cast<Bitrate>(_maxAudioTranscodeBitrate) >= bitrate;
}
} // namespace Database } // namespace Database
@@ -1,69 +0,0 @@
/*
* Copyright (C) 2020 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 <Wt/Dbo/Dbo.h>
namespace Database {
class Session;
class SubsonicSettings : public Wt::Dbo::Dbo<SubsonicSettings>
{
public:
using pointer = Wt::Dbo::ptr<SubsonicSettings>;
enum class ArtistListMode
{
AllArtists,
ReleaseArtists,
};
static inline constexpr bool defaultSubsonicAPIEnabled {true};
static inline constexpr ArtistListMode defaultArtistListMode {ArtistListMode::AllArtists};
static void init(Session& session);
static pointer get(Session& session);
// Getters
bool isAPIEnabled() const { return _isAPIEnabled; }
ArtistListMode getArtistListMode() const { return _artistListMode; }
// Setters
void setAPIEnabled(bool enabled) { _isAPIEnabled = enabled; }
void setArtistListMode(ArtistListMode mode) {_artistListMode = mode; }
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, _isAPIEnabled, "api_enabled");
Wt::Dbo::field(a, _artistListMode, "artist_list_mode");
}
private:
bool _isAPIEnabled {defaultSubsonicAPIEnabled};
ArtistListMode _artistListMode {defaultArtistListMode};
};
} // namespace Database
+39 -24
View File
@@ -36,7 +36,7 @@ class Session;
class TrackList; class TrackList;
class Track; class Track;
// User selectable audio formats // User selectable audio file formats
// Do not change values // Do not change values
enum class AudioFormat enum class AudioFormat
{ {
@@ -113,16 +113,33 @@ class User : public Wt::Dbo::Dbo<User>
Dark = 1, Dark = 1,
}; };
static const std::set<Bitrate> audioTranscodeAllowedBitrates; // Do not remove values!
static inline const std::set<Bitrate> audioTranscodeAllowedBitrates
{
64000,
96000,
128000,
192000,
320000,
};
// Do not change enum values!
enum class SubsonicArtistListMode
{
AllArtists = 0,
ReleaseArtists = 1,
};
static inline const std::size_t MinNameLength {3}; static inline const std::size_t MinNameLength {3};
static inline const std::size_t MaxNameLength {15}; static inline const std::size_t MaxNameLength {15};
static inline const bool defaultAudioTranscodeEnable {true}; static inline const bool defaultSubsonicTranscodeEnable {true};
static inline const AudioFormat defaultAudioTranscodeFormat {AudioFormat::OGG_OPUS}; static inline const AudioFormat defaultSubsonicTranscodeFormat {AudioFormat::OGG_OPUS};
static inline const Bitrate defaultAudioTranscodeBitrate {128000}; static inline const Bitrate defaultSubsonicTranscodeBitrate {128000};
static inline const UITheme defaultUITheme {UITheme::Dark}; static inline const UITheme defaultUITheme {UITheme::Dark};
static inline const SubsonicArtistListMode defaultSubsonicArtistListMode {SubsonicArtistListMode::AllArtists};
User(); User() = default;
User(const std::string& loginName, const PasswordHash& passwordHash); User(const std::string& loginName, const PasswordHash& passwordHash);
// utility // utility
@@ -143,27 +160,27 @@ class User : public Wt::Dbo::Dbo<User>
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; } void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; } void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
void setType(Type type) { _type = type; } void setType(Type type) { _type = type; }
void setAudioTranscodeEnable(bool value) { _audioTranscodeEnable = value; } void setSubsonicTranscodeEnable(bool value) { _subsonicTranscodeEnable = value; }
void setAudioTranscodeFormat(AudioFormat format) { _audioTranscodeFormat = format; } void setSubsonicTranscodeFormat(AudioFormat encoding) { _subsonicTranscodeFormat = encoding; }
void setAudioTranscodeBitrate(Bitrate bitrate); void setSubsonicTranscodeBitrate(Bitrate bitrate);
void setMaxAudioTranscodeBitrate(Bitrate bitrate);
void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; } void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; }
void setRadio(bool val) { _radio = val; } void setRadio(bool val) { _radio = val; }
void setRepeatAll(bool val) { _repeatAll = val; } void setRepeatAll(bool val) { _repeatAll = val; }
void setUITheme(UITheme uiTheme) { _uiTheme = uiTheme; } void setUITheme(UITheme uiTheme) { _uiTheme = uiTheme; }
void clearAuthTokens(); void clearAuthTokens();
void setSubsonicArtistListMode(SubsonicArtistListMode mode) { _subsonicArtistListMode = mode; }
// 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; }
bool getAudioTranscodeEnable() const { return _audioTranscodeEnable; } bool getSubsonicTranscodeEnable() const { return _subsonicTranscodeEnable; }
Bitrate getAudioTranscodeBitrate() const; AudioFormat getSubsonicTranscodeFormat() const { return _subsonicTranscodeFormat; }
AudioFormat getAudioTranscodeFormat() const { return _audioTranscodeFormat; } Bitrate getSubsonicTranscodeBitrate() const { return _subsonicTranscodeBitrate; }
Bitrate getMaxAudioTranscodeBitrate() const;
std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; }
bool isRepeatAllSet() const { return _repeatAll; } bool isRepeatAllSet() const { return _repeatAll; }
bool isRadioSet() const { return _radio; } bool isRadioSet() const { return _radio; }
UITheme getUITheme() const { return _uiTheme; } UITheme getUITheme() const { return _uiTheme; }
SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; }
Wt::Dbo::ptr<TrackList> getPlayedTrackList(Session& session) const; Wt::Dbo::ptr<TrackList> getPlayedTrackList(Session& session) const;
Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const; Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const;
@@ -184,8 +201,6 @@ class User : public Wt::Dbo::Dbo<User>
bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const; bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const;
std::vector<Wt::Dbo::ptr<Track>> getStarredTracks() const; std::vector<Wt::Dbo::ptr<Track>> getStarredTracks() const;
bool checkBitrate(Bitrate bitrate) const;
template<class Action> template<class Action>
void persist(Action& a) void persist(Action& a)
{ {
@@ -194,10 +209,10 @@ class User : public Wt::Dbo::Dbo<User>
Wt::Dbo::field(a, _passwordSalt, "password_salt"); Wt::Dbo::field(a, _passwordSalt, "password_salt");
Wt::Dbo::field(a, _passwordHash, "password_hash"); Wt::Dbo::field(a, _passwordHash, "password_hash");
Wt::Dbo::field(a, _lastLogin, "last_login"); Wt::Dbo::field(a, _lastLogin, "last_login");
Wt::Dbo::field(a, _maxAudioTranscodeBitrate, "max_audio_bitrate"); Wt::Dbo::field(a, _subsonicTranscodeEnable, "subsonic_transcode_enable");
Wt::Dbo::field(a, _audioTranscodeEnable, "audio_transcode_enable"); Wt::Dbo::field(a, _subsonicTranscodeFormat, "subsonic_transcode_format");
Wt::Dbo::field(a, _audioTranscodeBitrate, "audio_transcode_bitrate"); Wt::Dbo::field(a, _subsonicTranscodeBitrate, "subsonic_transcode_bitrate");
Wt::Dbo::field(a, _audioTranscodeFormat, "audio_transcode_format"); Wt::Dbo::field(a, _subsonicArtistListMode, "subsonic_artist_list_mode");
Wt::Dbo::field(a, _uiTheme, "ui_theme"); Wt::Dbo::field(a, _uiTheme, "ui_theme");
// 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");
@@ -219,13 +234,13 @@ class User : public Wt::Dbo::Dbo<User>
UITheme _uiTheme {defaultUITheme}; UITheme _uiTheme {defaultUITheme};
// Admin defined settings // Admin defined settings
int _maxAudioTranscodeBitrate;
Type _type {Type::REGULAR}; Type _type {Type::REGULAR};
// User defined settings // User defined settings
bool _audioTranscodeEnable {defaultAudioTranscodeEnable}; SubsonicArtistListMode _subsonicArtistListMode {defaultSubsonicArtistListMode};
AudioFormat _audioTranscodeFormat {defaultAudioTranscodeFormat}; bool _subsonicTranscodeEnable {defaultSubsonicTranscodeEnable};
int _audioTranscodeBitrate {defaultAudioTranscodeBitrate}; AudioFormat _subsonicTranscodeFormat {defaultSubsonicTranscodeFormat};
int _subsonicTranscodeBitrate {defaultSubsonicTranscodeBitrate};
// User's dynamic data (UI) // User's dynamic data (UI)
int _curPlayingTrackPos {}; // Current track position in queue int _curPlayingTrackPos {}; // Current track position in queue
+15 -15
View File
@@ -37,17 +37,17 @@ namespace API::Subsonic::Stream
{ {
static static
Av::Encoding Av::Format
userTranscodeFormatToAvEncoding(AudioFormat format) userTranscodeFormatToAvFormat(AudioFormat format)
{ {
switch (format) switch (format)
{ {
case AudioFormat::MP3: return Av::Encoding::MP3; case AudioFormat::MP3: return Av::Format::MP3;
case AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS; case AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS;
case AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS; case AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS;
case AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS; case AudioFormat::OGG_VORBIS: return Av::Format::OGG_VORBIS;
case AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS; case AudioFormat::WEBM_VORBIS: return Av::Format::WEBM_VORBIS;
default: return Av::Encoding::OGG_OPUS; default: return Av::Format::OGG_OPUS;
} }
} }
@@ -87,19 +87,19 @@ getStreamParameters(RequestContext& context)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
// format = "raw" => no transcode. Other format values will be ignored // format = "raw" => no transcode. Other format values will be ignored
const bool transcode {(!format || (*format != "raw")) && user->getAudioTranscodeEnable()}; const bool transcode {(!format || (*format != "raw")) && user->getSubsonicTranscodeEnable()};
if (transcode) if (transcode)
{ {
// "If set to zero, no limit is imposed" std::size_t bitRate {user->getSubsonicTranscodeBitrate() / 1000};
if (!maxBitRate || *maxBitRate == 0)
maxBitRate = user->getAudioTranscodeBitrate() / 1000;
*maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000); // "If set to zero, no limit is imposed"
if (maxBitRate && *maxBitRate != 0)
bitRate = clamp(*maxBitRate, std::size_t {48}, bitRate);
Av::TranscodeParameters transcodeParameters; Av::TranscodeParameters transcodeParameters;
transcodeParameters.bitrate = *maxBitRate * 1000; transcodeParameters.bitrate = bitRate * 1000;
transcodeParameters.encoding = userTranscodeFormatToAvEncoding(user->getAudioTranscodeFormat()); transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicTranscodeFormat());
transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.)
parameters.transcodeParameters = std::move(transcodeParameters); parameters.transcodeParameters = std::move(transcodeParameters);
+6 -17
View File
@@ -30,7 +30,6 @@
#include "database/Db.hpp" #include "database/Db.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/SubsonicSettings.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/TrackBookmark.hpp" #include "database/TrackBookmark.hpp"
#include "database/TrackList.hpp" #include "database/TrackList.hpp"
@@ -579,7 +578,6 @@ handleCreateUserRequest(RequestContext& context)
throw UserAlreadyExistsGenericError {}; throw UserAlreadyExistsGenericError {};
User::pointer user {User::create(context.dbSession, username, hash)}; User::pointer user {User::create(context.dbSession, username, hash)};
user.modify()->setMaxAudioTranscodeBitrate(128000);
return Response::createOkResponse(); return Response::createOkResponse();
} }
@@ -903,11 +901,11 @@ handleGetArtistsRequest(RequestContext& context)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
std::optional<TrackArtistLink::Type> linkType; std::optional<TrackArtistLink::Type> linkType;
switch (SubsonicSettings::get(context.dbSession)->getArtistListMode()) switch (user->getSubsonicArtistListMode())
{ {
case SubsonicSettings::ArtistListMode::AllArtists: case User::SubsonicArtistListMode::AllArtists:
break; break;
case SubsonicSettings::ArtistListMode::ReleaseArtists: case User::SubsonicArtistListMode::ReleaseArtists:
linkType = TrackArtistLink::Type::ReleaseArtist; linkType = TrackArtistLink::Type::ReleaseArtist;
break; break;
} }
@@ -1046,11 +1044,11 @@ handleGetIndexesRequest(RequestContext& context)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
std::optional<TrackArtistLink::Type> linkType; std::optional<TrackArtistLink::Type> linkType;
switch (SubsonicSettings::get(context.dbSession)->getArtistListMode()) switch (user->getSubsonicArtistListMode())
{ {
case SubsonicSettings::ArtistListMode::AllArtists: case User::SubsonicArtistListMode::AllArtists:
break; break;
case SubsonicSettings::ArtistListMode::ReleaseArtists: case User::SubsonicArtistListMode::ReleaseArtists:
linkType = TrackArtistLink::Type::ReleaseArtist; linkType = TrackArtistLink::Type::ReleaseArtist;
break; break;
} }
@@ -1520,7 +1518,6 @@ handleUpdateUserRequest(RequestContext& context)
{ {
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")}; std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
std::optional<std::string> password {getParameterAs<std::string>(context.parameters, "password")}; std::optional<std::string> password {getParameterAs<std::string>(context.parameters, "password")};
std::optional<Bitrate> maxBitRate {getParameterAs<Bitrate>(context.parameters, "maxBitRate")};
User::PasswordHash hash; User::PasswordHash hash;
if (password) if (password)
@@ -1538,14 +1535,6 @@ handleUpdateUserRequest(RequestContext& context)
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
if (maxBitRate)
{
if (*maxBitRate == 0)
*maxBitRate = 320;
user.modify()->setMaxAudioTranscodeBitrate(*maxBitRate * 1000);
}
if (password) if (password)
{ {
user.modify()->setPasswordHash(hash); user.modify()->setPasswordHash(hash);
-1
View File
@@ -13,7 +13,6 @@ add_executable(lms
ui/admin/DatabaseSettingsView.cpp ui/admin/DatabaseSettingsView.cpp
ui/admin/DatabaseStatus.cpp ui/admin/DatabaseStatus.cpp
ui/admin/InitWizardView.cpp ui/admin/InitWizardView.cpp
ui/admin/SubsonicView.cpp
ui/admin/UserView.cpp ui/admin/UserView.cpp
ui/admin/UsersView.cpp ui/admin/UsersView.cpp
ui/common/Validators.cpp ui/common/Validators.cpp
+2 -8
View File
@@ -28,7 +28,6 @@
#include "av/AvTranscoder.hpp" #include "av/AvTranscoder.hpp"
#include "cover/ICoverArtGrabber.hpp" #include "cover/ICoverArtGrabber.hpp"
#include "database/Db.hpp" #include "database/Db.hpp"
#include "database/SubsonicSettings.hpp"
#include "scanner/IMediaScanner.hpp" #include "scanner/IMediaScanner.hpp"
#include "recommendation/IEngine.hpp" #include "recommendation/IEngine.hpp"
#include "subsonic/SubsonicResource.hpp" #include "subsonic/SubsonicResource.hpp"
@@ -170,13 +169,8 @@ int main(int argc, char* argv[])
API::Subsonic::SubsonicResource subsonicResource {database}; API::Subsonic::SubsonicResource subsonicResource {database};
// bind API resources // bind API resources
{ if (ServiceProvider<IConfig>::get()->getBool("api-subsonic", true))
Database::Session session {database}; server.addResource(&subsonicResource, subsonicResource.getPath());
auto transaction {session.createSharedTransaction()};
if (Database::SubsonicSettings::get(session)->isAPIEnabled())
server.addResource(&subsonicResource, subsonicResource.getPath());
}
// bind UI entry point // bind UI entry point
server.addEntryPoint(Wt::EntryPointType::Application, server.addEntryPoint(Wt::EntryPointType::Application,
+6 -15
View File
@@ -43,7 +43,6 @@
#include "admin/DatabaseSettingsView.hpp" #include "admin/DatabaseSettingsView.hpp"
#include "admin/UserView.hpp" #include "admin/UserView.hpp"
#include "admin/UsersView.hpp" #include "admin/UsersView.hpp"
#include "admin/SubsonicView.hpp"
#include "resource/AudioFileResource.hpp" #include "resource/AudioFileResource.hpp"
#include "resource/AudioTranscodeResource.hpp" #include "resource/AudioTranscodeResource.hpp"
#include "resource/ImageResource.hpp" #include "resource/ImageResource.hpp"
@@ -123,7 +122,6 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
// Add a resource bundle // Add a resource bundle
messageResourceBundle().use(appRoot() + "admin-database"); messageResourceBundle().use(appRoot() + "admin-database");
messageResourceBundle().use(appRoot() + "admin-initwizard"); messageResourceBundle().use(appRoot() + "admin-initwizard");
messageResourceBundle().use(appRoot() + "admin-subsonic");
messageResourceBundle().use(appRoot() + "admin-user"); messageResourceBundle().use(appRoot() + "admin-user");
messageResourceBundle().use(appRoot() + "admin-users"); messageResourceBundle().use(appRoot() + "admin-users");
messageResourceBundle().use(appRoot() + "artist"); messageResourceBundle().use(appRoot() + "artist");
@@ -344,7 +342,6 @@ enum IdxRoot
IdxAdminDatabase, IdxAdminDatabase,
IdxAdminUsers, IdxAdminUsers,
IdxAdminUser, IdxAdminUser,
IdxAdminSubsonic,
}; };
static void static void
@@ -368,7 +365,6 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin)
{ "/admin/database", IdxAdminDatabase, true }, { "/admin/database", IdxAdminDatabase, true },
{ "/admin/users", IdxAdminUsers, true }, { "/admin/users", IdxAdminUsers, true },
{ "/admin/user", IdxAdminUser, true }, { "/admin/user", IdxAdminUser, true },
{ "/admin/subsonic", IdxAdminSubsonic, true },
}; };
LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
@@ -445,6 +441,9 @@ LmsApplication::createHome()
navbar->setTitle("LMS", Wt::WLink {Wt::LinkType::InternalPath, "/artists"}); navbar->setTitle("LMS", Wt::WLink {Wt::LinkType::InternalPath, "/artists"});
navbar->setResponsive(true); navbar->setResponsive(true);
// MediaPlayer
_mediaPlayer = main->bindNew<MediaPlayer>("player");
Wt::WMenu* menu {navbar->addMenu(std::make_unique<Wt::WMenu>())}; Wt::WMenu* menu {navbar->addMenu(std::make_unique<Wt::WMenu>())};
{ {
auto menuItem = menu->insertItem(0, Wt::WString::tr("Lms.Explore.artists")); auto menuItem = menu->insertItem(0, Wt::WString::tr("Lms.Explore.artists"));
@@ -488,10 +487,6 @@ LmsApplication::createHome()
usersSettings->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/admin/users")); usersSettings->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/admin/users"));
usersSettings->setSelectable(false); usersSettings->setSelectable(false);
auto subsonicSettings = admin->insertItem(2, Wt::WString::tr("Lms.Admin.Subsonic.subsonic"));
subsonicSettings->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/admin/subsonic"));
subsonicSettings->setSelectable(false);
menuItem->setMenu(std::move(admin)); menuItem->setMenu(std::move(admin));
} }
@@ -522,7 +517,6 @@ LmsApplication::createHome()
mainStack->addNew<DatabaseSettingsView>(); mainStack->addNew<DatabaseSettingsView>();
mainStack->addNew<UsersView>(); mainStack->addNew<UsersView>();
mainStack->addNew<UserView>(); mainStack->addNew<UserView>();
mainStack->addNew<SubsonicView>();
} }
explore->tracksAdd.connect([=] (const std::vector<Database::IdType>& trackIds) explore->tracksAdd.connect([=] (const std::vector<Database::IdType>& trackIds)
@@ -536,19 +530,16 @@ LmsApplication::createHome()
}); });
// MediaPlayer
MediaPlayer* player = main->bindNew<MediaPlayer>("player");
// Events from MediaPlayer // Events from MediaPlayer
player->playNext.connect([=] _mediaPlayer->playNext.connect([=]
{ {
playqueue->playNext(); playqueue->playNext();
}); });
player->playPrevious.connect([=] _mediaPlayer->playPrevious.connect([=]
{ {
playqueue->playPrevious(); playqueue->playPrevious();
}); });
player->playbackEnded.connect([=] _mediaPlayer->playbackEnded.connect([=]
{ {
playqueue->playNext(); playqueue->playNext();
}); });
+5
View File
@@ -44,6 +44,7 @@ class AudioFileResource;
class Auth; class Auth;
class ImageResource; class ImageResource;
class LmsApplicationException; class LmsApplicationException;
class MediaPlayer;
// Events that can be listen from anywhere in the application // Events that can be listen from anywhere in the application
struct Events struct Events
@@ -55,6 +56,7 @@ struct Events
// A track is being loaded // A track is being loaded
Wt::Signal<Database::IdType /* trackId */, bool /* play */> trackLoaded; Wt::Signal<Database::IdType /* trackId */, bool /* play */> trackLoaded;
std::optional<Database::IdType> lastLoadedTrackId; std::optional<Database::IdType> lastLoadedTrackId;
Wt::Signal<> mediaPlayerSettingsAvailable;
// Unload current track // Unload current track
Wt::Signal<> trackUnloaded; Wt::Signal<> trackUnloaded;
@@ -105,6 +107,8 @@ class LmsApplication : public Wt::WApplication
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Wt::Dbo::ptr<Database::Release> release, bool addText = true); static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Wt::Dbo::ptr<Database::Release> release, bool addText = true);
static std::unique_ptr<Wt::WText> createCluster(Wt::Dbo::ptr<Database::Cluster> cluster, bool canDelete = false); static std::unique_ptr<Wt::WText> createCluster(Wt::Dbo::ptr<Database::Cluster> cluster, bool canDelete = false);
MediaPlayer* getMediaPlayer() const { return _mediaPlayer; }
// Signal emitted just before the session ends (user may already be logged out) // Signal emitted just before the session ends (user may already be logged out)
Wt::Signal<>& preQuit() { return _preQuit; } Wt::Signal<>& preQuit() { return _preQuit; }
@@ -133,6 +137,7 @@ class LmsApplication : public Wt::WApplication
std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource; std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource;
std::shared_ptr<AudioFileResource> _audioFileResource; std::shared_ptr<AudioFileResource> _audioFileResource;
std::shared_ptr<ImageResource> _imageResource; std::shared_ptr<ImageResource> _imageResource;
MediaPlayer* _mediaPlayer {};
}; };
+72 -9
View File
@@ -36,17 +36,64 @@
namespace UserInterface { namespace UserInterface {
MediaPlayer::MediaPlayer() MediaPlayer::MediaPlayer()
: Wt::WTemplate(Wt::WString::tr("Lms.MediaPlayer.template")), : Wt::WTemplate {Wt::WString::tr("Lms.MediaPlayer.template")},
playbackEnded(this, "playbackEnded"), playbackEnded {this, "playbackEnded"},
playPrevious(this, "playPrevious"), playPrevious {this, "playPrevious"},
playNext(this, "playNext") playNext {this, "playNext"},
_settingsLoaded {this, "settingsLoaded"}
{ {
_title = bindNew<Wt::WText>("title"); _title = bindNew<Wt::WText>("title");
_artist = bindNew<Wt::WAnchor>("artist"); _artist = bindNew<Wt::WAnchor>("artist");
_release = bindNew<Wt::WAnchor>("release"); _release = bindNew<Wt::WAnchor>("release");
wApp->doJavaScript("LMS.mediaplayer.init(" + jsRef() + ")"); _settingsLoaded.connect([this](int mode, int format, int bitrate)
{
LMS_LOG(UI, DEBUG) << "Settings loaded! mode = " << mode << ", format = " << format << ", bitrate = " << bitrate;
Settings settings;
switch (static_cast<TranscodeMode>(mode))
{
case TranscodeMode::Always:
case TranscodeMode::Never:
case TranscodeMode::IfFormatNotSupported:
settings.mode = static_cast<TranscodeMode>(mode);
break;
}
switch (static_cast<Format>(format))
{
case Format::MP3:
case Format::OGG_OPUS:
case Format::MATROSKA_OPUS:
case Format::OGG_VORBIS:
case Format::WEBM_VORBIS:
settings.format = static_cast<Format>(format);
break;
}
if (Database::User::audioTranscodeAllowedBitrates.find(bitrate) != std::cend(Database::User::audioTranscodeAllowedBitrates))
settings.bitrate = bitrate;
_settings = settings;
LmsApp->getEvents().mediaPlayerSettingsAvailable.emit();
});
{
std::ostringstream oss;
oss << "LMS.mediaplayer.init("
<< jsRef()
<< ", " << static_cast<int>(defaultTranscodeMode)
<< ", " << static_cast<int>(defaultTranscodeFormat)
<< ", " << static_cast<int>(defaultTranscodeBitrate)
<< ")";
LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
doJavaScript(oss.str());
}
LmsApp->getEvents().trackLoaded.connect(this, &MediaPlayer::loadTrack); LmsApp->getEvents().trackLoaded.connect(this, &MediaPlayer::loadTrack);
LmsApp->getEvents().trackUnloaded.connect(this, &MediaPlayer::stop); LmsApp->getEvents().trackUnloaded.connect(this, &MediaPlayer::stop);
@@ -62,10 +109,8 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play)
const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)}; const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
const std::string imgResourceMimeType {LmsApp->getImageResource()->getMimeType()}; const std::string imgResourceMimeType {LmsApp->getImageResource()->getMimeType()};
std::string nativeResource; const std::string transcodeResource {LmsApp->getAudioTranscodeResource()->getUrl(trackId)};
const std::string transcodeResource {LmsApp->getAudioTranscodeResource()->getUrlForUser(trackId, LmsApp->getUser())}; const std::string nativeResource {LmsApp->getAudioFileResource()->getUrl(trackId)};
// if (!LmsApp->getUser()->getAudioTranscodeEnable())
nativeResource = LmsApp->getAudioFileResource()->getUrl(trackId);
const auto artists {track->getArtists()}; const auto artists {track->getArtists()};
@@ -124,5 +169,23 @@ MediaPlayer::stop()
wApp->doJavaScript("LMS.mediaplayer.stop()"); wApp->doJavaScript("LMS.mediaplayer.stop()");
} }
void
MediaPlayer::setSettings(const Settings& settings)
{
_settings = settings;
{
std::ostringstream oss;
oss << "LMS.mediaplayer.setSettings("
<< static_cast<int>(_settings->mode)
<< ", " << static_cast<int>(_settings->format)
<< ", " << static_cast<int>(_settings->bitrate)
<< ")";
LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
doJavaScript(oss.str());
}
}
} // namespace UserInterface } // namespace UserInterface
+34
View File
@@ -25,14 +25,45 @@
#include <Wt/WText.h> #include <Wt/WText.h>
#include "database/Types.hpp" #include "database/Types.hpp"
#include "database/User.hpp"
namespace UserInterface { namespace UserInterface {
class MediaPlayer : public Wt::WTemplate class MediaPlayer : public Wt::WTemplate
{ {
public: public:
using Bitrate = Database::Bitrate;
using Format = Database::AudioFormat;
// Do not change this enum as it may be stored locally in browser
// Keep it sync with LMS.mediaplayer js
enum class TranscodeMode
{
Never = 0,
Always = 1,
IfFormatNotSupported = 2,
};
static inline constexpr TranscodeMode defaultTranscodeMode {TranscodeMode::IfFormatNotSupported};
static inline constexpr Format defaultTranscodeFormat {Format::OGG_OPUS};
static inline constexpr Bitrate defaultTranscodeBitrate {128000};
struct Settings
{
TranscodeMode mode {defaultTranscodeMode};
Format format {defaultTranscodeFormat};
Bitrate bitrate {defaultTranscodeBitrate};
};
MediaPlayer(); MediaPlayer();
MediaPlayer(const MediaPlayer&) = delete;
MediaPlayer(MediaPlayer&&) = delete;
MediaPlayer& operator=(const MediaPlayer&) = delete;
MediaPlayer& operator=(MediaPlayer&&) = delete;
std::optional<Settings> getSettings() const { return _settings; }
void setSettings(const Settings& settings);
// Signals // Signals
Wt::JSignal<> playbackEnded; Wt::JSignal<> playbackEnded;
Wt::JSignal<> playPrevious; Wt::JSignal<> playPrevious;
@@ -42,6 +73,9 @@ class MediaPlayer : public Wt::WTemplate
void stop(); void stop();
void loadTrack(Database::IdType trackId, bool play); void loadTrack(Database::IdType trackId, bool play);
std::optional<Settings> _settings;
Wt::JSignal<int, int, int> _settingsLoaded;
Wt::WText* _title; Wt::WText* _title;
Wt::WAnchor* _release; Wt::WAnchor* _release;
Wt::WAnchor* _artist; Wt::WAnchor* _artist;
+177 -63
View File
@@ -31,11 +31,13 @@
#include "common/ValueStringModel.hpp" #include "common/ValueStringModel.hpp"
#include "auth/IPasswordService.hpp" #include "auth/IPasswordService.hpp"
#include "utils/IConfig.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/Service.hpp" #include "utils/Service.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
#include "LmsTheme.hpp" #include "LmsTheme.hpp"
#include "MediaPlayer.hpp"
namespace UserInterface { namespace UserInterface {
@@ -46,22 +48,31 @@ class SettingsModel : public Wt::WFormModel
public: public:
// Associate each field with a unique string literal. // Associate each field with a unique string literal.
static inline const Field DarkModeField {"dark-mode"}; static inline const Field DarkModeField {"dark-mode"};
static inline const Field TranscodeEnableField {"transcoding-enable"}; static inline const Field TranscodeModeField {"transcode-mode"};
static inline const Field TranscodeFormatField {"transcoding-format"}; static inline const Field TranscodeFormatField {"transcode-format"};
static inline const Field TranscodeBitrateField {"transcoding-bitrate"}; static inline const Field TranscodeBitrateField {"transcode-bitrate"};
static inline const Field SubsonicArtistListModeField {"subsonic-artist-list-mode"};
static inline const Field SubsonicTranscodeEnableField {"subsonic-transcode-enable"};
static inline const Field SubsonicTranscodeFormatField {"subsonic-transcode-format"};
static inline const Field SubsonicTranscodeBitrateField {"subsonic-transcode-bitrate"};
static inline const Field PasswordOldField {"password-old"}; static inline const Field PasswordOldField {"password-old"};
static inline const Field PasswordField {"password"}; static inline const Field PasswordField {"password"};
static inline const Field PasswordConfirmField {"password-confirm"}; static inline const Field PasswordConfirmField {"password-confirm"};
using TranscodeModeModel = ValueStringModel<MediaPlayer::TranscodeMode>;
SettingsModel(bool withOldPassword) SettingsModel(bool withOldPassword)
: _withOldPassword {withOldPassword} : _withOldPassword {withOldPassword}
{ {
initializeModels(); initializeModels();
addField(DarkModeField); addField(DarkModeField);
addField(TranscodeEnableField); addField(TranscodeModeField);
addField(TranscodeBitrateField); addField(TranscodeBitrateField);
addField(TranscodeFormatField); addField(TranscodeFormatField);
addField(SubsonicTranscodeEnableField);
addField(SubsonicTranscodeBitrateField);
addField(SubsonicTranscodeFormatField);
if (_withOldPassword) if (_withOldPassword)
addField(PasswordOldField); addField(PasswordOldField);
@@ -69,14 +80,19 @@ class SettingsModel : public Wt::WFormModel
addField(PasswordField); addField(PasswordField);
addField(PasswordConfirmField); addField(PasswordConfirmField);
setValidator(TranscodeModeField, createMandatoryValidator());
setValidator(TranscodeBitrateField, createMandatoryValidator()); setValidator(TranscodeBitrateField, createMandatoryValidator());
setValidator(TranscodeFormatField, createMandatoryValidator()); setValidator(TranscodeFormatField, createMandatoryValidator());
setValidator(SubsonicTranscodeBitrateField, createMandatoryValidator());
setValidator(SubsonicTranscodeFormatField, createMandatoryValidator());
loadData(); loadData();
} }
std::shared_ptr<Wt::WAbstractItemModel> transcodeBitrateModel() { return _transcodeBitrateModel; } std::shared_ptr<TranscodeModeModel> getTranscodeModeModel() { return _transcodeModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> transcodeFormatModel() { return _transcodeFormatModel; } std::shared_ptr<Wt::WAbstractItemModel> getTranscodeBitrateModel() { return _transcodeBitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodeFormatModel() { return _transcodeFormatModel; }
std::shared_ptr<Wt::WAbstractItemModel> getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; }
void saveData() void saveData()
{ {
@@ -97,43 +113,89 @@ class SettingsModel : public Wt::WFormModel
user.modify()->setUITheme(newTheme); user.modify()->setUITheme(newTheme);
} }
user.modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField))); {
MediaPlayer::Settings settings;
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(TranscodeBitrateField))}; auto transcodeModeRow {_transcodeModeModel->getRowFromString(valueText(TranscodeModeField))};
if (transcodeBitrateRow) if (transcodeModeRow)
user.modify()->setAudioTranscodeBitrate(_transcodeBitrateModel->getValue(*transcodeBitrateRow)); settings.mode = _transcodeModeModel->getValue(*transcodeModeRow);
auto transcodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(TranscodeFormatField))}; auto transcodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(TranscodeFormatField))};
if (transcodeFormatRow) if (transcodeFormatRow)
user.modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow)); settings.format = _transcodeFormatModel->getValue(*transcodeFormatRow);
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(TranscodeBitrateField))};
if (transcodeBitrateRow)
settings.bitrate = _transcodeBitrateModel->getValue(*transcodeBitrateRow);
LmsApp->getMediaPlayer()->setSettings(settings);
}
{
user.modify()->setSubsonicTranscodeEnable(Wt::asNumber(value(SubsonicTranscodeEnableField)));
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(SubsonicTranscodeBitrateField))};
if (subsonicTranscodeBitrateRow)
user.modify()->setSubsonicTranscodeBitrate(_transcodeBitrateModel->getValue(*subsonicTranscodeBitrateRow));
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(SubsonicTranscodeFormatField))};
if (subsonicTranscodeFormatRow)
user.modify()->setSubsonicTranscodeFormat(_transcodeFormatModel->getValue(*subsonicTranscodeFormatRow));
}
if (!valueText(PasswordField).empty()) if (!valueText(PasswordField).empty())
{ {
user.modify()->setPasswordHash(passwordHash); user.modify()->setPasswordHash(passwordHash);
user.modify()->clearAuthTokens(); user.modify()->clearAuthTokens();
} }
auto subsonicArtistListModeRow {_subsonicArtistListModeModel->getRowFromString(valueText(SubsonicArtistListModeField))};
if (subsonicArtistListModeRow)
user.modify()->setSubsonicArtistListMode(_subsonicArtistListModeModel->getValue(*subsonicArtistListModeRow));
} }
void loadData() void loadData()
{ {
auto transaction {LmsApp->getDbSession().createSharedTransaction()}; auto transaction {LmsApp->getDbSession().createSharedTransaction()};
setValue(DarkModeField, LmsApp->getUser()->getUITheme() == User::UITheme::Dark); User::pointer user {LmsApp->getUser()};
setValue(DarkModeField, user->getUITheme() == User::UITheme::Dark);
setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable());
if (!LmsApp->getUser()->getAudioTranscodeEnable())
{ {
setReadOnly(TranscodeFormatField, true); const auto& settings {*LmsApp->getMediaPlayer()->getSettings()};
setReadOnly(TranscodeBitrateField, true);
auto transcodeModeRow {_transcodeModeModel->getRowFromValue(settings.mode)};
if (transcodeModeRow)
setValue(TranscodeModeField, _transcodeModeModel->getString(*transcodeModeRow));
auto transcodeFormatRow {_transcodeFormatModel->getRowFromValue(settings.format)};
if (transcodeFormatRow)
setValue(TranscodeFormatField, _transcodeFormatModel->getString(*transcodeFormatRow));
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromValue(settings.bitrate)};
if (transcodeBitrateRow)
setValue(TranscodeBitrateField, _transcodeBitrateModel->getString(*transcodeBitrateRow));
} }
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromValue(LmsApp->getUser()->getAudioTranscodeBitrate())}; setValue(SubsonicTranscodeEnableField, LmsApp->getUser()->getSubsonicTranscodeEnable());
if (transcodeBitrateRow) if (!LmsApp->getUser()->getSubsonicTranscodeEnable())
setValue(TranscodeBitrateField, _transcodeBitrateModel->getString(*transcodeBitrateRow)); {
setReadOnly(SubsonicTranscodeFormatField, true);
setReadOnly(SubsonicTranscodeBitrateField, true);
}
auto transcodeFormatRow {_transcodeFormatModel->getRowFromValue(LmsApp->getUser()->getAudioTranscodeFormat())}; auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromValue(user->getSubsonicTranscodeBitrate())};
if (transcodeFormatRow) if (subsonicTranscodeBitrateRow)
setValue(TranscodeFormatField, _transcodeFormatModel->getString(*transcodeFormatRow)); setValue(SubsonicTranscodeBitrateField, _transcodeBitrateModel->getString(*subsonicTranscodeBitrateRow));
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromValue(user->getSubsonicTranscodeFormat())};
if (subsonicTranscodeFormatRow)
setValue(SubsonicTranscodeFormatField, _transcodeFormatModel->getString(*subsonicTranscodeFormatRow));
auto subsonicArtistListModeRow {_subsonicArtistListModeModel->getRowFromValue(user->getSubsonicArtistListMode())};
if (subsonicArtistListModeRow)
setValue(SubsonicArtistListModeField, _subsonicArtistListModeModel->getString(*subsonicArtistListModeRow));
} }
private: private:
@@ -207,33 +269,36 @@ class SettingsModel : public Wt::WFormModel
void initializeModels() void initializeModels()
{ {
Bitrate maxAudioBitrate;
{ _transcodeModeModel = std::make_shared<TranscodeModeModel>();
auto transaction {LmsApp->getDbSession().createSharedTransaction()}; _transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.always"), MediaPlayer::TranscodeMode::Always);
maxAudioBitrate = LmsApp->getUser()->getMaxAudioTranscodeBitrate(); _transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.never"), MediaPlayer::TranscodeMode::Never);
} _transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.if-format-not-supported"), MediaPlayer::TranscodeMode::IfFormatNotSupported);
_subsonicArtistListModeModel = std::make_shared<ValueStringModel<User::SubsonicArtistListMode>>();
_subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.all-artists"), User::SubsonicArtistListMode::AllArtists);
_subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.release-artists"), User::SubsonicArtistListMode::ReleaseArtists);
_transcodeBitrateModel = std::make_shared<ValueStringModel<Bitrate>>(); _transcodeBitrateModel = std::make_shared<ValueStringModel<Bitrate>>();
for (const Bitrate bitrate : User::audioTranscodeAllowedBitrates) for (const Bitrate bitrate : User::audioTranscodeAllowedBitrates)
{ {
if (bitrate > maxAudioBitrate)
break;
_transcodeBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate); _transcodeBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate);
} }
_transcodeFormatModel = std::make_shared<ValueStringModel<AudioFormat>>(); _transcodeFormatModel = std::make_shared<ValueStringModel<AudioFormat>>();
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding.mp3"), AudioFormat::MP3); _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.mp3"), AudioFormat::MP3);
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding.ogg_opus"), AudioFormat::OGG_OPUS); _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.ogg_opus"), AudioFormat::OGG_OPUS);
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding.matroska_opus"), AudioFormat::MATROSKA_OPUS); _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.matroska_opus"), AudioFormat::MATROSKA_OPUS);
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding.ogg_vorbis"), AudioFormat::OGG_VORBIS); _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.ogg_vorbis"), AudioFormat::OGG_VORBIS);
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding.webm_vorbis"), AudioFormat::WEBM_VORBIS); _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.webm_vorbis"), AudioFormat::WEBM_VORBIS);
} }
bool _withOldPassword {}; bool _withOldPassword {};
std::shared_ptr<ValueStringModel<Bitrate>> _transcodeBitrateModel; std::shared_ptr<TranscodeModeModel> _transcodeModeModel;
std::shared_ptr<ValueStringModel<AudioFormat>> _transcodeFormatModel; std::shared_ptr<ValueStringModel<Bitrate>> _transcodeBitrateModel;
std::shared_ptr<ValueStringModel<AudioFormat>> _transcodeFormatModel;
std::shared_ptr<ValueStringModel<User::SubsonicArtistListMode>> _subsonicArtistListModeModel;
}; };
SettingsView::SettingsView() SettingsView::SettingsView()
@@ -243,6 +308,11 @@ SettingsView::SettingsView()
refreshView(); refreshView();
})); }));
LmsApp->getEvents().mediaPlayerSettingsAvailable.connect([=]()
{
refreshView();
});
refreshView(); refreshView();
} }
@@ -254,6 +324,10 @@ SettingsView::refreshView()
clear(); clear();
// Hack to wait for the audio player know the settings applied
if (!LmsApp->getMediaPlayer()->getSettings())
return;
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>(!LmsApp->isUserAuthStrong())}; auto model {std::make_shared<SettingsModel>(!LmsApp->isUserAuthStrong())};
@@ -268,6 +342,7 @@ SettingsView::refreshView()
if (!LmsApp->isUserAuthStrong()) if (!LmsApp->isUserAuthStrong())
{ {
t->setCondition("if-has-old-password", true); t->setCondition("if-has-old-password", true);
auto oldPassword {std::make_unique<Wt::WLineEdit>()}; auto oldPassword {std::make_unique<Wt::WLineEdit>()};
oldPassword->setEchoMode(Wt::EchoMode::Password); oldPassword->setEchoMode(Wt::EchoMode::Password);
t->setFormWidget(SettingsModel::PasswordOldField, std::move(oldPassword)); t->setFormWidget(SettingsModel::PasswordOldField, std::move(oldPassword));
@@ -283,35 +358,74 @@ SettingsView::refreshView()
passwordConfirm->setEchoMode(Wt::EchoMode::Password); passwordConfirm->setEchoMode(Wt::EchoMode::Password);
t->setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm)); t->setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm));
// Transcoding // Audio
auto transcode {std::make_unique<Wt::WCheckBox>()};
auto* transcodeRaw {transcode.get()};
t->setFormWidget(SettingsModel::TranscodeEnableField, std::move(transcode));
// Format
auto transcodeFormat {std::make_unique<Wt::WComboBox>()};
transcodeFormat->setModel(model->transcodeFormatModel());
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}; // Transcode
model->setReadOnly(SettingsModel::TranscodeFormatField, !enable); auto transcodeMode {std::make_unique<Wt::WComboBox>()};
model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable); auto* transcodeModeRaw {transcodeMode.get()};
t->updateModel(model.get()); transcodeMode->setModel(model->getTranscodeModeModel());
t->updateView(model.get()); t->setFormWidget(SettingsModel::TranscodeModeField, std::move(transcodeMode));
});
// Format
auto transcodeFormat {std::make_unique<Wt::WComboBox>()};
transcodeFormat->setModel(model->getTranscodeFormatModel());
t->setFormWidget(SettingsModel::TranscodeFormatField, std::move(transcodeFormat));
// Bitrate
auto transcodeBitrate {std::make_unique<Wt::WComboBox>()};
transcodeBitrate->setModel(model->getTranscodeBitrateModel());
t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodeBitrate));
transcodeModeRaw->sactivated().connect([=]()
{
auto row {model->getTranscodeModeModel()->getRowFromString(model->valueText(SettingsModel::TranscodeModeField))};
const bool enable = (row && (model->getTranscodeModeModel()->getValue(*row) != MediaPlayer::TranscodeMode::Never));
model->setReadOnly(SettingsModel::TranscodeFormatField, !enable);
model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
}
// Subsonic
{
t->setCondition("if-has-subsonic-api", ServiceProvider<IConfig>::get()->getBool("api-subsonic", true));
// Transcode
auto transcode {std::make_unique<Wt::WCheckBox>()};
auto* transcodeRaw {transcode.get()};
t->setFormWidget(SettingsModel::SubsonicTranscodeEnableField, std::move(transcode));
// Format
auto transcodeFormat {std::make_unique<Wt::WComboBox>()};
transcodeFormat->setModel(model->getTranscodeFormatModel());
t->setFormWidget(SettingsModel::SubsonicTranscodeFormatField, std::move(transcodeFormat));
// Bitrate
auto transcodeBitrate {std::make_unique<Wt::WComboBox>()};
transcodeBitrate->setModel(model->getTranscodeBitrateModel());
t->setFormWidget(SettingsModel::SubsonicTranscodeBitrateField, std::move(transcodeBitrate));
// Artist list mode
auto artistListMode = std::make_unique<Wt::WComboBox>();
artistListMode->setModel(model->getSubsonicArtistListModeModel());
t->setFormWidget(SettingsModel::SubsonicArtistListModeField, std::move(artistListMode));
transcodeRaw->changed().connect([=]()
{
const bool enable {transcodeRaw->checkState() == Wt::CheckState::Checked};
model->setReadOnly(SettingsModel::SubsonicTranscodeFormatField, !enable);
model->setReadOnly(SettingsModel::SubsonicTranscodeBitrateField, !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([=]()
{ {
{ {
@@ -334,7 +448,7 @@ SettingsView::refreshView()
// Udate the view: Delete any validation message in the view, etc. // Udate the view: Delete any validation message in the view, etc.
t->updateView(model.get()); t->updateView(model.get());
})); });
discardBtn->clicked().connect(std::bind([=] () discardBtn->clicked().connect(std::bind([=] ()
{ {
-180
View File
@@ -1,180 +0,0 @@
/*
* Copyright (C) 2018 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 "SubsonicView.hpp"
#include <Wt/WCheckBox.h>
#include <Wt/WComboBox.h>
#include <Wt/WPushButton.h>
#include <Wt/WTemplateFormView.h>
#include <Wt/WFormModel.h>
#include "database/SubsonicSettings.hpp"
#include "utils/Logger.hpp"
#include "common/ValueStringModel.hpp"
#include "LmsApplication.hpp"
namespace UserInterface {
using namespace Database;
class SubsonicModel : public Wt::WFormModel
{
public:
static inline const Field EnableField {"enable"};
static inline const Field ArtistListModeField {"artist-list-mode"};
SubsonicModel()
{
addField(EnableField);
addField(ArtistListModeField);
initializeModels();
loadData();
}
std::shared_ptr<Wt::WAbstractItemModel> getArtistListModeModel() { return _artistListModeModel; }
void saveData()
{
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto settings {SubsonicSettings::get(LmsApp->getDbSession())};
settings.modify()->setAPIEnabled(Wt::asNumber(value(EnableField)));
auto artistListModeRow {_artistListModeModel->getRowFromString(valueText(ArtistListModeField))};
if (artistListModeRow)
settings.modify()->setArtistListMode(_artistListModeModel->getValue(*artistListModeRow));
}
void loadData()
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const auto settings {SubsonicSettings::get(LmsApp->getDbSession())};
setValue(EnableField, settings->isAPIEnabled());
auto artistListModeRow {_artistListModeModel->getRowFromValue(settings->getArtistListMode())};
if (artistListModeRow)
setValue(ArtistListModeField, _artistListModeModel->getString(*artistListModeRow));
}
void initializeModels()
{
_artistListModeModel = std::make_shared<ValueStringModel<SubsonicSettings::ArtistListMode>>();
_artistListModeModel->add(Wt::WString::tr("Lms.Admin.Subsonic.all-artists"), SubsonicSettings::ArtistListMode::AllArtists);
_artistListModeModel->add(Wt::WString::tr("Lms.Admin.Subsonic.release-artists"), SubsonicSettings::ArtistListMode::ReleaseArtists);
}
private:
bool validateField(Field field) override
{
if (field == EnableField)
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
if (SubsonicSettings::get(LmsApp->getDbSession())->isAPIEnabled() != Wt::asNumber(value(EnableField)))
setValidation(field, Wt::WValidator::Result( Wt::ValidationState::Valid, Wt::WString::tr("Lms.Admin.Subsonic.need-restart")));
else
setValidation(field, Wt::WValidator::Result( Wt::ValidationState::Valid));
return true;
}
return Wt::WFormModel::validateField(field);
}
std::shared_ptr<ValueStringModel<SubsonicSettings::ArtistListMode>> _artistListModeModel;
};
SubsonicView::SubsonicView()
{
wApp->internalPathChanged().connect([this]()
{
refreshView();
});
refreshView();
}
void
SubsonicView::refreshView()
{
if (!wApp->internalPathMatches("/admin/subsonic"))
return;
clear();
Wt::WTemplateFormView* t {addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.Subsonic.template"))};
auto model {std::make_shared<SubsonicModel>()};
// Enable
auto enable {std::make_unique<Wt::WCheckBox>()};
auto* enableRaw {enable.get()};
t->setFormWidget(SubsonicModel::EnableField, std::move(enable));
// Artis tlist mode
auto artistListMode = std::make_unique<Wt::WComboBox>();
artistListMode->setModel(model->getArtistListModeModel());
t->setFormWidget(SubsonicModel::ArtistListModeField, std::move(artistListMode));
enableRaw->changed().connect([=]()
{
bool enable {enableRaw->checkState() == Wt::CheckState::Checked};
model->setReadOnly(SubsonicModel::ArtistListModeField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
Wt::WPushButton* saveBtn = t->bindNew<Wt::WPushButton>("apply-btn", Wt::WString::tr("Lms.apply"));
Wt::WPushButton *discardBtn {t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")))};
saveBtn->clicked().connect([=]()
{
t->updateModel(model.get());
if (model->validate())
{
model->saveData();
LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr("Lms.Settings.settings-saved"));
}
t->updateView(model.get());
});
discardBtn->clicked().connect([=]()
{
model->loadData();
model->validate();
t->updateView(model.get());
});
t->updateView(model.get());
}
} // namespace UserInterface
-37
View File
@@ -1,37 +0,0 @@
/*
* Copyright (C) 2018 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 <Wt/WContainerWidget.h>
namespace UserInterface {
class SubsonicView : public Wt::WContainerWidget
{
public:
SubsonicView();
private:
void refreshView();
};
} // namespace UserInterface
+7 -45
View File
@@ -48,10 +48,9 @@ class UserModel : public Wt::WFormModel
{ {
public: public:
static const Field LoginField; static inline const Field LoginField {"login"};
static const Field PasswordField; static inline const Field PasswordField {"password"};
static const Field AudioTranscodeBitrateLimitField; static inline const Field DemoField {"demo"};
static const Field DemoField;
UserModel(std::optional<Database::IdType> userId) UserModel(std::optional<Database::IdType> userId)
: _userId {userId} : _userId {userId}
@@ -63,20 +62,15 @@ class UserModel : public Wt::WFormModel
} }
addField(PasswordField); addField(PasswordField);
addField(AudioTranscodeBitrateLimitField);
addField(DemoField); addField(DemoField);
if (!_userId) if (!_userId)
setValidator(PasswordField, createMandatoryValidator()); setValidator(PasswordField, createMandatoryValidator());
initializeModels();
// populate the model with initial data // populate the model with initial data
loadData(); loadData();
} }
std::shared_ptr<Wt::WAbstractItemModel> bitrateModel() { return _bitrateModel; }
void saveData() void saveData()
{ {
std::optional<Database::User::PasswordHash> passwordHash; std::optional<Database::User::PasswordHash> passwordHash;
@@ -96,20 +90,12 @@ class UserModel : public Wt::WFormModel
user.modify()->setPasswordHash(*passwordHash); user.modify()->setPasswordHash(*passwordHash);
user.modify()->clearAuthTokens(); user.modify()->clearAuthTokens();
} }
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
if (transcodeBitrateLimitRow)
user.modify()->setMaxAudioTranscodeBitrate(_bitrateModel->getValue(*transcodeBitrateLimitRow));
} }
else else
{ {
// Create user // Create user
Database::User::pointer user {Database::User::create(LmsApp->getDbSession(), valueText(LoginField).toUTF8(), *passwordHash)}; Database::User::pointer user {Database::User::create(LmsApp->getDbSession(), valueText(LoginField).toUTF8(), *passwordHash)};
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))};
if (transcodeBitrateLimitRow )
user.modify()->setMaxAudioTranscodeBitrate(_bitrateModel->getValue(*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);
} }
@@ -129,10 +115,6 @@ class UserModel : public Wt::WFormModel
throw UserNotFoundException {*_userId}; throw UserNotFoundException {*_userId};
else if (user == LmsApp->getUser()) else if (user == LmsApp->getUser())
throw UserNotAllowedException {}; throw UserNotAllowedException {};
auto transcodeBitrateLimitRow {_bitrateModel->getRowFromValue(user->getMaxAudioTranscodeBitrate())};
if (transcodeBitrateLimitRow)
setValue(AudioTranscodeBitrateLimitField, _bitrateModel->getString(*transcodeBitrateLimitRow));
} }
std::string getLoginName() const std::string getLoginName() const
@@ -194,30 +176,15 @@ class UserModel : public Wt::WFormModel
return false; return false;
} }
void initializeModels()
{
_bitrateModel = std::make_shared<ValueStringModel<Bitrate>>();
for (auto bitrate : Database::User::audioTranscodeAllowedBitrates)
_bitrateModel->add( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate );
}
std::shared_ptr<ValueStringModel<Bitrate>> _bitrateModel;
std::optional<Database::IdType> _userId; std::optional<Database::IdType> _userId;
}; };
const Wt::WFormModel::Field UserModel::LoginField = "login";
const Wt::WFormModel::Field UserModel::PasswordField = "password";
const Wt::WFormModel::Field UserModel::AudioTranscodeBitrateLimitField = "audio-transcode-bitrate-limit";
const Wt::WFormModel::Field UserModel::DemoField = "demo";
UserView::UserView() UserView::UserView()
{ {
wApp->internalPathChanged().connect(std::bind([=] wApp->internalPathChanged().connect([this]()
{ {
refreshView(); refreshView();
})); });
refreshView(); refreshView();
} }
@@ -262,18 +229,13 @@ 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));
// Transcode bitrate limit
auto bitrate = std::make_unique<Wt::WComboBox>();
bitrate->setModel(model->bitrateModel());
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>());
if (!userId && ServiceProvider<IConfig>::get()->getBool("demo", false)) if (!userId && ServiceProvider<IConfig>::get()->getBool("demo", false))
t->setCondition("if-demo", true); t->setCondition("if-demo", true);
Wt::WPushButton* saveBtn = t->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr(userId ? "Lms.save" : "Lms.create")); Wt::WPushButton* saveBtn = t->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr(userId ? "Lms.save" : "Lms.create"));
saveBtn->clicked().connect(std::bind([=] saveBtn->clicked().connect([=]()
{ {
t->updateModel(model.get()); t->updateModel(model.get());
@@ -287,7 +249,7 @@ UserView::refreshView()
{ {
t->updateView(model.get()); t->updateView(model.get());
} }
})); });
t->updateView(model.get()); t->updateView(model.get());
} }
+14 -19
View File
@@ -73,32 +73,27 @@ AudioTranscodeResource:: ~AudioTranscodeResource()
} }
static static
std::optional<Av::Encoding> std::optional<Av::Format>
AudioFormatToAvEncoding(Database::AudioFormat format) AudioFormatToAvFormat(Database::AudioFormat format)
{ {
switch (format) switch (format)
{ {
case Database::AudioFormat::MP3: return Av::Encoding::MP3; case Database::AudioFormat::MP3: return Av::Format::MP3;
case Database::AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS; case Database::AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS;
case Database::AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS; case Database::AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS;
case Database::AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS; case Database::AudioFormat::OGG_VORBIS: return Av::Format::OGG_VORBIS;
case Database::AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS; case Database::AudioFormat::WEBM_VORBIS: return Av::Format::WEBM_VORBIS;
} }
LOG(ERROR) << "Cannot convert from audio format to encoding"; LOG(ERROR) << "Cannot convert from audio format to AV format";
return std::nullopt; return std::nullopt;
} }
std::string std::string
AudioTranscodeResource::getUrlForUser(Database::IdType trackId, Database::User::pointer user) const AudioTranscodeResource::getUrl(Database::IdType trackId) const
{ {
std::string computedUrl {url() + "&trackid=" + std::to_string(trackId)}; return url() + "&trackid=" + std::to_string(trackId);
computedUrl += "&format=" + std::to_string(static_cast<std::size_t>(user->getAudioTranscodeFormat()));
computedUrl += "&bitrate=" + std::to_string(LmsApp->getUser()->getAudioTranscodeBitrate());
return computedUrl;
} }
template<typename T> template<typename T>
@@ -144,8 +139,8 @@ AudioTranscodeResource::handleRequest(const Wt::Http::Request& request,
if (!trackId || !format || !bitrate) if (!trackId || !format || !bitrate)
return; return;
auto encoding {AudioFormatToAvEncoding(*format)}; auto avFormat {AudioFormatToAvFormat(*format)};
if (!encoding) if (!avFormat)
return; return;
// optional parameter // optional parameter
@@ -166,7 +161,7 @@ AudioTranscodeResource::handleRequest(const Wt::Http::Request& request,
trackPath = track->getPath(); trackPath = track->getPath();
if (!LmsApp->getUser()->checkBitrate(*bitrate)) if (Database::User::audioTranscodeAllowedBitrates.find(*bitrate) == std::cend(Database::User::audioTranscodeAllowedBitrates))
{ {
LOG(ERROR) << "Bitrate '" << *bitrate << "' is not allowed"; LOG(ERROR) << "Bitrate '" << *bitrate << "' is not allowed";
return; return;
@@ -175,7 +170,7 @@ AudioTranscodeResource::handleRequest(const Wt::Http::Request& request,
Av::TranscodeParameters parameters {}; Av::TranscodeParameters parameters {};
parameters.stripMetadata = true; parameters.stripMetadata = true;
parameters.encoding = *encoding; parameters.format = *avFormat;
parameters.bitrate = *bitrate; parameters.bitrate = *bitrate;
parameters.offset = std::chrono::seconds {offset ? *offset : 0}; parameters.offset = std::chrono::seconds {offset ? *offset : 0};
@@ -37,7 +37,7 @@ class AudioTranscodeResource : public Wt::WResource
~AudioTranscodeResource(); ~AudioTranscodeResource();
// Url depends on the user since settings are used in parameters // Url depends on the user since settings are used in parameters
std::string getUrlForUser(Database::IdType trackId, Wt::Dbo::ptr<Database::User> user) const; std::string getUrl(Database::IdType trackId) const;
void handleRequest(const Wt::Http::Request& request, void handleRequest(const Wt::Http::Request& request,
Wt::Http::Response& response); Wt::Http::Response& response);