From 4749fd7548dd59758b19d79ec2898491fd413f4d Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 20 Apr 2020 20:54:16 +0200 Subject: [PATCH] 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 --- approot/admin-subsonic.xml | 40 --- approot/admin-user.xml | 15 -- approot/messages.xml | 35 +-- approot/messages_fr.xml | 39 +-- approot/settings.xml | 85 ++++++- conf/lms.conf | 3 + docroot/js/mediaplayer.js | 66 ++++- src/libs/av/impl/AvTranscoder.cpp | 14 +- src/libs/av/impl/AvTypes.cpp | 14 +- src/libs/av/include/av/AvTranscoder.hpp | 2 +- src/libs/av/include/av/AvTypes.hpp | 14 +- src/libs/database/CMakeLists.txt | 1 - src/libs/database/impl/Session.cpp | 43 +++- src/libs/database/impl/SubsonicSettings.cpp | 48 ---- src/libs/database/impl/User.cpp | 68 +---- .../include/database/SubsonicSettings.hpp | 69 ----- src/libs/database/include/database/User.hpp | 63 +++-- src/libs/subsonic/impl/Stream.cpp | 30 +-- src/libs/subsonic/impl/SubsonicResource.cpp | 23 +- src/lms/CMakeLists.txt | 1 - src/lms/main.cpp | 10 +- src/lms/ui/LmsApplication.cpp | 21 +- src/lms/ui/LmsApplication.hpp | 5 + src/lms/ui/MediaPlayer.cpp | 81 +++++- src/lms/ui/MediaPlayer.hpp | 34 +++ src/lms/ui/SettingsView.cpp | 240 +++++++++++++----- src/lms/ui/admin/SubsonicView.cpp | 180 ------------- src/lms/ui/admin/SubsonicView.hpp | 37 --- src/lms/ui/admin/UserView.cpp | 52 +--- .../ui/resource/AudioTranscodeResource.cpp | 33 +-- .../ui/resource/AudioTranscodeResource.hpp | 2 +- 31 files changed, 615 insertions(+), 753 deletions(-) delete mode 100644 approot/admin-subsonic.xml delete mode 100644 src/libs/database/impl/SubsonicSettings.cpp delete mode 100644 src/libs/database/include/database/SubsonicSettings.hpp delete mode 100644 src/lms/ui/admin/SubsonicView.cpp delete mode 100644 src/lms/ui/admin/SubsonicView.hpp diff --git a/approot/admin-subsonic.xml b/approot/admin-subsonic.xml deleted file mode 100644 index f7ce118d..00000000 --- a/approot/admin-subsonic.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - ${tr:Lms.Admin.Subsonic.subsonic} -
- -
- -
-
-
-
- ${enable-info} -
-
-
- -
- ${artist-list-mode} -
-
- ${artist-list-mode-info} -
-
- -
-
- ${apply-btn class="btn-primary"} ${discard-btn} -
-
-
-
- -
diff --git a/approot/admin-user.xml b/approot/admin-user.xml index f5d732cd..2274c278 100644 --- a/approot/admin-user.xml +++ b/approot/admin-user.xml @@ -42,21 +42,6 @@ ${password-info} - -
- -
-
- ${audio-transcode-bitrate-limit} - kbps -
-
-
- ${audio-transcode-bitrate-limit-info} -
-
${}
${tr:Lms.Settings.audio} +
+
+
+ ${tr:Lms.Settings.audio-settings-are-local} +
+
+
-
-
-
+ ${} + ${tr:Lms.Settings.subsonic-api} +
+ +
+ +
+
+
+
+ ${subsonic-transcode-enable-info} +
+
+
+ +
+ ${subsonic-transcode-format} +
+
+ ${subsonic-transcode-format-info} +
+
+
+ +
+
+ ${subsonic-transcode-bitrate} + kbps +
+
+
+ ${subsonic-transcode-bitrate-info} +
+
+
+ +
+ ${subsonic-artist-list-mode} +
+
+ ${subsonic-artist-list-mode-info} +
+
+ +
+ ${
} ${tr:Lms.Settings.change-password}
${} diff --git a/conf/lms.conf b/conf/lms.conf index e84bfbdf..e1666f60 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -34,6 +34,9 @@ deploy-path = "/"; # Acoustic brainz's root API acousticbrainz-api-url = "https://acousticbrainz.org/api/v1/"; +# API +api-subsonic = true; + # Turn on this option to allow the demo account creation/use demo = false; diff --git a/docroot/js/mediaplayer.js b/docroot/js/mediaplayer.js index 68039c38..6135c86a 100644 --- a/docroot/js/mediaplayer.js +++ b/docroot/js/mediaplayer.js @@ -2,6 +2,13 @@ var LMS = LMS || {}; +// Keep in sync with MediaPlayer::TranscodeMode cpp +const TranscodeMode = { + Never: 0, + Always: 1, + IfFormatNotSupported: 2, +} + const Mode = { Transcode: 1, File: 2, @@ -16,6 +23,9 @@ LMS.mediaplayer = function () { var _duration = 0; var _audioNativeSrc; var _audioTranscodedSrc; + var _transcodeMode = TranscodeMode.Never; + var _transcodeFormat = 0; + var _transcodeBitrate = 0; var _updateControls = function() { if (_elems.audio.paused) { @@ -71,6 +81,29 @@ LMS.mediaplayer = function () { _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) { _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; _elems.audio = document.getElementById("lms-mp-audio"); @@ -139,7 +172,7 @@ LMS.mediaplayer = function () { case Mode.Transcode: _offset = selectedOffset; _removeAudioSources(); - _addAudioSourceIfSet(_audioTranscodeSrc + "&offset=" + _offset); + _addAudioSource(_audioTranscodeSrc + "&offset=" + _offset); _elems.audio.load(); _elems.audio.currentTime = 0; _playTrack(); @@ -166,6 +199,7 @@ LMS.mediaplayer = function () { }); _initVolume(); + _initTranscodeSettings(defaultTranscodeMode, defaultTranscodeFormat, defaultTranscodeBitrate); _elems.volumeslider.addEventListener("input", function() { _setVolume(_elems.volumeslider.value); @@ -197,7 +231,7 @@ LMS.mediaplayer = function () { } } - var _addAudioSourceIfSet = function(audioSrc) { + var _addAudioSource = function(audioSrc) { let source = document.createElement('source'); source.src = audioSrc; _elems.audio.appendChild(source); @@ -218,14 +252,21 @@ LMS.mediaplayer = function () { _offset = 0; _duration = params.duration; _audioNativeSrc = params.native_resource; - _audioTranscodeSrc = params.transcode_resource; + _audioTranscodeSrc = params.transcode_resource + "&bitrate=" + _transcodeBitrate + "&format=" + _transcodeFormat; _elems.seek.max = _duration; _removeAudioSources(); // ! order is important - _addAudioSourceIfSet(_audioNativeSrc); - _addAudioSourceIfSet(_audioTranscodeSrc); + if (_transcodeMode == TranscodeMode.Never || _transcodeMode == TranscodeMode.IfFormatNotSupported) + { + _addAudioSource(_audioNativeSrc); + } + if (_transcodeMode == TranscodeMode.Always || _transcodeMode == TranscodeMode.IfFormatNotSupported) + { + _addAudioSource(_audioTranscodeSrc); + } + _elems.audio.load(); _elems.curtime.innerHTML = _durationToString(_offset); @@ -248,10 +289,23 @@ LMS.mediaplayer = function () { _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 { init: init, loadTrack: loadTrack, stop: stop, + setSettings: setSettings, }; }(); diff --git a/src/libs/av/impl/AvTranscoder.cpp b/src/libs/av/impl/AvTranscoder.cpp index fd6576f2..50e7991d 100644 --- a/src/libs/av/impl/AvTranscoder.cpp +++ b/src/libs/av/impl/AvTranscoder.cpp @@ -104,35 +104,35 @@ Transcoder::start() args.emplace_back(std::to_string(_parameters.bitrate)); // Codecs and formats - switch (_parameters.encoding) + switch (_parameters.format) { - case Encoding::MP3: + case Format::MP3: args.emplace_back("-f"); args.emplace_back("mp3"); break; - case Encoding::OGG_OPUS: + case Format::OGG_OPUS: args.emplace_back("-acodec"); args.emplace_back("libopus"); args.emplace_back("-f"); args.emplace_back("ogg"); break; - case Encoding::MATROSKA_OPUS: + case Format::MATROSKA_OPUS: args.emplace_back("-acodec"); args.emplace_back("libopus"); args.emplace_back("-f"); args.emplace_back("matroska"); break; - case Encoding::OGG_VORBIS: + case Format::OGG_VORBIS: args.emplace_back("-acodec"); args.emplace_back("libvorbis"); args.emplace_back("-f"); args.emplace_back("ogg"); break; - case Encoding::WEBM_VORBIS: + case Format::WEBM_VORBIS: args.emplace_back("-acodec"); args.emplace_back("libvorbis"); args.emplace_back("-f"); @@ -143,7 +143,7 @@ Transcoder::start() return false; } - _outputMimeType = encodingToMimetype(_parameters.encoding); + _outputMimeType = formatToMimetype(_parameters.format); args.emplace_back("pipe:1"); diff --git a/src/libs/av/impl/AvTypes.cpp b/src/libs/av/impl/AvTypes.cpp index 7e556294..ca83978f 100644 --- a/src/libs/av/impl/AvTypes.cpp +++ b/src/libs/av/impl/AvTypes.cpp @@ -21,15 +21,15 @@ namespace Av { -const char* encodingToMimetype(Encoding encoding) +const char* formatToMimetype(Format format) { - switch (encoding) + switch (format) { - case Encoding::MP3: return "audio/mpeg"; - case Encoding::OGG_OPUS: return "audio/opus"; - case Encoding::MATROSKA_OPUS: return "audio/x-matroska"; - case Encoding::OGG_VORBIS: return "audio/ogg"; - case Encoding::WEBM_VORBIS: return "audio/webm"; + case Format::MP3: return "audio/mpeg"; + case Format::OGG_OPUS: return "audio/opus"; + case Format::MATROSKA_OPUS: return "audio/x-matroska"; + case Format::OGG_VORBIS: return "audio/ogg"; + case Format::WEBM_VORBIS: return "audio/webm"; } throw AvException {"Invalid encoding"}; diff --git a/src/libs/av/include/av/AvTranscoder.hpp b/src/libs/av/include/av/AvTranscoder.hpp index 60093ebb..4dfff1bb 100644 --- a/src/libs/av/include/av/AvTranscoder.hpp +++ b/src/libs/av/include/av/AvTranscoder.hpp @@ -33,7 +33,7 @@ namespace Av { struct TranscodeParameters { - Encoding encoding; + Format format; std::size_t bitrate {128000}; std::optional stream; // Id of the stream to be transcoded (auto detect by default) std::optional offset; diff --git a/src/libs/av/include/av/AvTypes.hpp b/src/libs/av/include/av/AvTypes.hpp index 3eca84d3..f2c942a9 100644 --- a/src/libs/av/include/av/AvTypes.hpp +++ b/src/libs/av/include/av/AvTypes.hpp @@ -31,17 +31,17 @@ class AvException : public LmsException AvException(const std::string& msg) : LmsException(msg) {} }; -enum class Encoding +enum class Format { // Values are important and must not be changed - MP3, - OGG_OPUS, - MATROSKA_OPUS, - OGG_VORBIS, - WEBM_VORBIS, + MP3 = 0, + OGG_OPUS = 1, + MATROSKA_OPUS = 2, + OGG_VORBIS = 3, + WEBM_VORBIS = 4, }; -const char* encodingToMimetype(Encoding encoding); +const char* formatToMimetype(Format encoding); } diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index cb6fc22e..a7eba7e2 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -11,7 +11,6 @@ add_library(lmsdatabase SHARED impl/Session.cpp impl/SessionPool.cpp impl/SqlQuery.cpp - impl/SubsonicSettings.cpp impl/Track.cpp impl/TrackBookmark.cpp impl/User.cpp diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 3aa85a27..22a7b250 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -31,7 +31,6 @@ #include "database/Db.hpp" #include "database/Release.hpp" #include "database/ScanSettings.hpp" -#include "database/SubsonicSettings.hpp" #include "database/Track.hpp" #include "database/TrackBookmark.hpp" #include "database/TrackArtistLink.hpp" @@ -41,7 +40,7 @@ namespace Database { -#define LMS_DATABASE_VERSION 19 +#define LMS_DATABASE_VERSION 21 using Version = std::size_t; @@ -198,11 +197,8 @@ CREATE TABLE "release_backup" ( "mbid" text not null ))"); _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("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 ScanSettings::get(*this).modify()->incScanVersion(); @@ -217,6 +213,39 @@ CREATE TABLE IF NOT EXISTS "subsonic_settings" ( "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(User::defaultSubsonicTranscodeFormat)) + + ", " + std::to_string(User::defaultSubsonicTranscodeBitrate) + + ", " + std::to_string(static_cast(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 { LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; @@ -239,7 +268,6 @@ Session::Session(Db& db) _session.mapClass("cluster_type"); _session.mapClass("release"); _session.mapClass("scan_settings"); - _session.mapClass("subsonic_settings"); _session.mapClass("track"); _session.mapClass("track_bookmark"); _session.mapClass("track_artist_link"); @@ -365,7 +393,6 @@ Session::prepareTables() auto uniqueTransaction {createUniqueTransaction()}; ScanSettings::init(*this); - SubsonicSettings::init(*this); } } diff --git a/src/libs/database/impl/SubsonicSettings.cpp b/src/libs/database/impl/SubsonicSettings.cpp deleted file mode 100644 index 3393afe6..00000000 --- a/src/libs/database/impl/SubsonicSettings.cpp +++ /dev/null @@ -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 . - */ - -#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::pointer -SubsonicSettings::get(Session& session) -{ - session.checkSharedLocked(); - - return session.getDboSession().find(); -} - - -} // ns Database - diff --git a/src/libs/database/impl/User.cpp b/src/libs/database/impl/User.cpp index c471b40b..97fa06ab 100644 --- a/src/libs/database/impl/User.cpp +++ b/src/libs/database/impl/User.cpp @@ -70,28 +70,11 @@ AuthToken::getByValue(Session& session, const std::string& value) static const std::string playedListName {"__played_tracks__"}; static const std::string queuedListName {"__queued_tracks__"}; -const std::set -User::audioTranscodeAllowedBitrates = -{ - 64000, - 96000, - 128000, - 192000, - 320000, -}; - -User::User() -: _maxAudioTranscodeBitrate {static_cast(*audioTranscodeAllowedBitrates.rbegin())} -{ - -} - 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 @@ -141,27 +124,10 @@ User::getByLoginName(Session& session, const std::string& name) } void -User::setAudioTranscodeBitrate(Bitrate bitrate) +User::setSubsonicTranscodeBitrate(Bitrate bitrate) { - _audioTranscodeBitrate = std::min(bitrate, static_cast(_maxAudioTranscodeBitrate)); -} - -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; + assert(audioTranscodeAllowedBitrates.find(bitrate) != audioTranscodeAllowedBitrates.cend()); + _subsonicTranscodeBitrate = bitrate; } void @@ -170,18 +136,6 @@ User::clearAuthTokens() _authTokens.clear(); } -Bitrate -User::getAudioTranscodeBitrate(void) const -{ - return _audioTranscodeBitrate; -} - -std::size_t -User::getMaxAudioTranscodeBitrate(void) const -{ - return _maxAudioTranscodeBitrate; -} - Wt::Dbo::ptr User::getPlayedTrackList(Session& session) const { @@ -283,16 +237,6 @@ User::getStarredTracks() const return std::vector>(_starredTracks.begin(), _starredTracks.end()); } - -bool -User::checkBitrate(Database::Bitrate bitrate) const -{ - if (audioTranscodeAllowedBitrates.find(bitrate) == std::cend(audioTranscodeAllowedBitrates)) - return false; - - return static_cast(_maxAudioTranscodeBitrate) >= bitrate; -} - } // namespace Database diff --git a/src/libs/database/include/database/SubsonicSettings.hpp b/src/libs/database/include/database/SubsonicSettings.hpp deleted file mode 100644 index 55154186..00000000 --- a/src/libs/database/include/database/SubsonicSettings.hpp +++ /dev/null @@ -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 . - */ - -#pragma once - -#include - -namespace Database { - -class Session; - -class SubsonicSettings : public Wt::Dbo::Dbo -{ - public: - using pointer = Wt::Dbo::ptr; - - 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 - 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 - diff --git a/src/libs/database/include/database/User.hpp b/src/libs/database/include/database/User.hpp index c7c0f888..24410c47 100644 --- a/src/libs/database/include/database/User.hpp +++ b/src/libs/database/include/database/User.hpp @@ -36,7 +36,7 @@ class Session; class TrackList; class Track; -// User selectable audio formats +// User selectable audio file formats // Do not change values enum class AudioFormat { @@ -113,16 +113,33 @@ class User : public Wt::Dbo::Dbo Dark = 1, }; - static const std::set audioTranscodeAllowedBitrates; + // Do not remove values! + static inline const std::set 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 MaxNameLength {15}; - static inline const bool defaultAudioTranscodeEnable {true}; - static inline const AudioFormat defaultAudioTranscodeFormat {AudioFormat::OGG_OPUS}; - static inline const Bitrate defaultAudioTranscodeBitrate {128000}; + static inline const bool defaultSubsonicTranscodeEnable {true}; + static inline const AudioFormat defaultSubsonicTranscodeFormat {AudioFormat::OGG_OPUS}; + static inline const Bitrate defaultSubsonicTranscodeBitrate {128000}; 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); // utility @@ -143,27 +160,27 @@ class User : public Wt::Dbo::Dbo void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; } void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; } void setType(Type type) { _type = type; } - void setAudioTranscodeEnable(bool value) { _audioTranscodeEnable = value; } - void setAudioTranscodeFormat(AudioFormat format) { _audioTranscodeFormat = format; } - void setAudioTranscodeBitrate(Bitrate bitrate); - void setMaxAudioTranscodeBitrate(Bitrate bitrate); + void setSubsonicTranscodeEnable(bool value) { _subsonicTranscodeEnable = value; } + void setSubsonicTranscodeFormat(AudioFormat encoding) { _subsonicTranscodeFormat = encoding; } + void setSubsonicTranscodeBitrate(Bitrate bitrate); void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; } void setRadio(bool val) { _radio = val; } void setRepeatAll(bool val) { _repeatAll = val; } void setUITheme(UITheme uiTheme) { _uiTheme = uiTheme; } void clearAuthTokens(); + void setSubsonicArtistListMode(SubsonicArtistListMode mode) { _subsonicArtistListMode = mode; } // read bool isAdmin() const { return _type == Type::ADMIN; } bool isDemo() const { return _type == Type::DEMO; } - bool getAudioTranscodeEnable() const { return _audioTranscodeEnable; } - Bitrate getAudioTranscodeBitrate() const; - AudioFormat getAudioTranscodeFormat() const { return _audioTranscodeFormat; } - Bitrate getMaxAudioTranscodeBitrate() const; + bool getSubsonicTranscodeEnable() const { return _subsonicTranscodeEnable; } + AudioFormat getSubsonicTranscodeFormat() const { return _subsonicTranscodeFormat; } + Bitrate getSubsonicTranscodeBitrate() const { return _subsonicTranscodeBitrate; } std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } bool isRepeatAllSet() const { return _repeatAll; } bool isRadioSet() const { return _radio; } UITheme getUITheme() const { return _uiTheme; } + SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; } Wt::Dbo::ptr getPlayedTrackList(Session& session) const; Wt::Dbo::ptr getQueuedTrackList(Session& session) const; @@ -184,8 +201,6 @@ class User : public Wt::Dbo::Dbo bool hasStarredTrack(Wt::Dbo::ptr track) const; std::vector> getStarredTracks() const; - bool checkBitrate(Bitrate bitrate) const; - template void persist(Action& a) { @@ -194,10 +209,10 @@ class User : public Wt::Dbo::Dbo Wt::Dbo::field(a, _passwordSalt, "password_salt"); Wt::Dbo::field(a, _passwordHash, "password_hash"); Wt::Dbo::field(a, _lastLogin, "last_login"); - Wt::Dbo::field(a, _maxAudioTranscodeBitrate, "max_audio_bitrate"); - Wt::Dbo::field(a, _audioTranscodeEnable, "audio_transcode_enable"); - Wt::Dbo::field(a, _audioTranscodeBitrate, "audio_transcode_bitrate"); - Wt::Dbo::field(a, _audioTranscodeFormat, "audio_transcode_format"); + Wt::Dbo::field(a, _subsonicTranscodeEnable, "subsonic_transcode_enable"); + Wt::Dbo::field(a, _subsonicTranscodeFormat, "subsonic_transcode_format"); + Wt::Dbo::field(a, _subsonicTranscodeBitrate, "subsonic_transcode_bitrate"); + Wt::Dbo::field(a, _subsonicArtistListMode, "subsonic_artist_list_mode"); Wt::Dbo::field(a, _uiTheme, "ui_theme"); // User's dynamic data Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos"); @@ -219,13 +234,13 @@ class User : public Wt::Dbo::Dbo UITheme _uiTheme {defaultUITheme}; // Admin defined settings - int _maxAudioTranscodeBitrate; Type _type {Type::REGULAR}; // User defined settings - bool _audioTranscodeEnable {defaultAudioTranscodeEnable}; - AudioFormat _audioTranscodeFormat {defaultAudioTranscodeFormat}; - int _audioTranscodeBitrate {defaultAudioTranscodeBitrate}; + SubsonicArtistListMode _subsonicArtistListMode {defaultSubsonicArtistListMode}; + bool _subsonicTranscodeEnable {defaultSubsonicTranscodeEnable}; + AudioFormat _subsonicTranscodeFormat {defaultSubsonicTranscodeFormat}; + int _subsonicTranscodeBitrate {defaultSubsonicTranscodeBitrate}; // User's dynamic data (UI) int _curPlayingTrackPos {}; // Current track position in queue diff --git a/src/libs/subsonic/impl/Stream.cpp b/src/libs/subsonic/impl/Stream.cpp index a87ad6e5..cd7645be 100644 --- a/src/libs/subsonic/impl/Stream.cpp +++ b/src/libs/subsonic/impl/Stream.cpp @@ -37,17 +37,17 @@ namespace API::Subsonic::Stream { static -Av::Encoding -userTranscodeFormatToAvEncoding(AudioFormat format) +Av::Format +userTranscodeFormatToAvFormat(AudioFormat format) { switch (format) { - case AudioFormat::MP3: return Av::Encoding::MP3; - case AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS; - case AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS; - case AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS; - case AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS; - default: return Av::Encoding::OGG_OPUS; + case AudioFormat::MP3: return Av::Format::MP3; + case AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS; + case AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS; + case AudioFormat::OGG_VORBIS: return Av::Format::OGG_VORBIS; + case AudioFormat::WEBM_VORBIS: return Av::Format::WEBM_VORBIS; + default: return Av::Format::OGG_OPUS; } } @@ -87,19 +87,19 @@ getStreamParameters(RequestContext& context) throw UserNotAuthorizedError {}; // 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 set to zero, no limit is imposed" - if (!maxBitRate || *maxBitRate == 0) - maxBitRate = user->getAudioTranscodeBitrate() / 1000; + std::size_t bitRate {user->getSubsonicTranscodeBitrate() / 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; - transcodeParameters.bitrate = *maxBitRate * 1000; - transcodeParameters.encoding = userTranscodeFormatToAvEncoding(user->getAudioTranscodeFormat()); + transcodeParameters.bitrate = bitRate * 1000; + transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicTranscodeFormat()); transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) parameters.transcodeParameters = std::move(transcodeParameters); diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 5b97899b..b34211a9 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -30,7 +30,6 @@ #include "database/Db.hpp" #include "database/Release.hpp" #include "database/Session.hpp" -#include "database/SubsonicSettings.hpp" #include "database/Track.hpp" #include "database/TrackBookmark.hpp" #include "database/TrackList.hpp" @@ -579,7 +578,6 @@ handleCreateUserRequest(RequestContext& context) throw UserAlreadyExistsGenericError {}; User::pointer user {User::create(context.dbSession, username, hash)}; - user.modify()->setMaxAudioTranscodeBitrate(128000); return Response::createOkResponse(); } @@ -903,11 +901,11 @@ handleGetArtistsRequest(RequestContext& context) throw UserNotAuthorizedError {}; std::optional linkType; - switch (SubsonicSettings::get(context.dbSession)->getArtistListMode()) + switch (user->getSubsonicArtistListMode()) { - case SubsonicSettings::ArtistListMode::AllArtists: + case User::SubsonicArtistListMode::AllArtists: break; - case SubsonicSettings::ArtistListMode::ReleaseArtists: + case User::SubsonicArtistListMode::ReleaseArtists: linkType = TrackArtistLink::Type::ReleaseArtist; break; } @@ -1046,11 +1044,11 @@ handleGetIndexesRequest(RequestContext& context) throw UserNotAuthorizedError {}; std::optional linkType; - switch (SubsonicSettings::get(context.dbSession)->getArtistListMode()) + switch (user->getSubsonicArtistListMode()) { - case SubsonicSettings::ArtistListMode::AllArtists: + case User::SubsonicArtistListMode::AllArtists: break; - case SubsonicSettings::ArtistListMode::ReleaseArtists: + case User::SubsonicArtistListMode::ReleaseArtists: linkType = TrackArtistLink::Type::ReleaseArtist; break; } @@ -1520,7 +1518,6 @@ handleUpdateUserRequest(RequestContext& context) { std::string username {getMandatoryParameterAs(context.parameters, "username")}; std::optional password {getParameterAs(context.parameters, "password")}; - std::optional maxBitRate {getParameterAs(context.parameters, "maxBitRate")}; User::PasswordHash hash; if (password) @@ -1538,14 +1535,6 @@ handleUpdateUserRequest(RequestContext& context) if (!user) throw UserNotAuthorizedError {}; - if (maxBitRate) - { - if (*maxBitRate == 0) - *maxBitRate = 320; - - user.modify()->setMaxAudioTranscodeBitrate(*maxBitRate * 1000); - } - if (password) { user.modify()->setPasswordHash(hash); diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index a3f94268..bac11ebd 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -13,7 +13,6 @@ add_executable(lms ui/admin/DatabaseSettingsView.cpp ui/admin/DatabaseStatus.cpp ui/admin/InitWizardView.cpp - ui/admin/SubsonicView.cpp ui/admin/UserView.cpp ui/admin/UsersView.cpp ui/common/Validators.cpp diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 44e3ea62..bbaa1985 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -28,7 +28,6 @@ #include "av/AvTranscoder.hpp" #include "cover/ICoverArtGrabber.hpp" #include "database/Db.hpp" -#include "database/SubsonicSettings.hpp" #include "scanner/IMediaScanner.hpp" #include "recommendation/IEngine.hpp" #include "subsonic/SubsonicResource.hpp" @@ -170,13 +169,8 @@ int main(int argc, char* argv[]) API::Subsonic::SubsonicResource subsonicResource {database}; // bind API resources - { - Database::Session session {database}; - auto transaction {session.createSharedTransaction()}; - - if (Database::SubsonicSettings::get(session)->isAPIEnabled()) - server.addResource(&subsonicResource, subsonicResource.getPath()); - } + if (ServiceProvider::get()->getBool("api-subsonic", true)) + server.addResource(&subsonicResource, subsonicResource.getPath()); // bind UI entry point server.addEntryPoint(Wt::EntryPointType::Application, diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index c27171e8..eb57811b 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -43,7 +43,6 @@ #include "admin/DatabaseSettingsView.hpp" #include "admin/UserView.hpp" #include "admin/UsersView.hpp" -#include "admin/SubsonicView.hpp" #include "resource/AudioFileResource.hpp" #include "resource/AudioTranscodeResource.hpp" #include "resource/ImageResource.hpp" @@ -123,7 +122,6 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, // Add a resource bundle messageResourceBundle().use(appRoot() + "admin-database"); messageResourceBundle().use(appRoot() + "admin-initwizard"); - messageResourceBundle().use(appRoot() + "admin-subsonic"); messageResourceBundle().use(appRoot() + "admin-user"); messageResourceBundle().use(appRoot() + "admin-users"); messageResourceBundle().use(appRoot() + "artist"); @@ -344,7 +342,6 @@ enum IdxRoot IdxAdminDatabase, IdxAdminUsers, IdxAdminUser, - IdxAdminSubsonic, }; static void @@ -368,7 +365,6 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin) { "/admin/database", IdxAdminDatabase, true }, { "/admin/users", IdxAdminUsers, true }, { "/admin/user", IdxAdminUser, true }, - { "/admin/subsonic", IdxAdminSubsonic, true }, }; 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->setResponsive(true); + // MediaPlayer + _mediaPlayer = main->bindNew("player"); + Wt::WMenu* menu {navbar->addMenu(std::make_unique())}; { 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->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)); } @@ -522,7 +517,6 @@ LmsApplication::createHome() mainStack->addNew(); mainStack->addNew(); mainStack->addNew(); - mainStack->addNew(); } explore->tracksAdd.connect([=] (const std::vector& trackIds) @@ -536,19 +530,16 @@ LmsApplication::createHome() }); - // MediaPlayer - MediaPlayer* player = main->bindNew("player"); - // Events from MediaPlayer - player->playNext.connect([=] + _mediaPlayer->playNext.connect([=] { playqueue->playNext(); }); - player->playPrevious.connect([=] + _mediaPlayer->playPrevious.connect([=] { playqueue->playPrevious(); }); - player->playbackEnded.connect([=] + _mediaPlayer->playbackEnded.connect([=] { playqueue->playNext(); }); diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp index 19928d31..9ec6968f 100644 --- a/src/lms/ui/LmsApplication.hpp +++ b/src/lms/ui/LmsApplication.hpp @@ -44,6 +44,7 @@ class AudioFileResource; class Auth; class ImageResource; class LmsApplicationException; +class MediaPlayer; // Events that can be listen from anywhere in the application struct Events @@ -55,6 +56,7 @@ struct Events // A track is being loaded Wt::Signal trackLoaded; std::optional lastLoadedTrackId; + Wt::Signal<> mediaPlayerSettingsAvailable; // Unload current track Wt::Signal<> trackUnloaded; @@ -105,6 +107,8 @@ class LmsApplication : public Wt::WApplication static std::unique_ptr createReleaseAnchor(Wt::Dbo::ptr release, bool addText = true); static std::unique_ptr createCluster(Wt::Dbo::ptr cluster, bool canDelete = false); + MediaPlayer* getMediaPlayer() const { return _mediaPlayer; } + // Signal emitted just before the session ends (user may already be logged out) Wt::Signal<>& preQuit() { return _preQuit; } @@ -133,6 +137,7 @@ class LmsApplication : public Wt::WApplication std::shared_ptr _audioTranscodeResource; std::shared_ptr _audioFileResource; std::shared_ptr _imageResource; + MediaPlayer* _mediaPlayer {}; }; diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index a87489d3..6127b2d5 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -36,17 +36,64 @@ namespace UserInterface { + MediaPlayer::MediaPlayer() -: Wt::WTemplate(Wt::WString::tr("Lms.MediaPlayer.template")), - playbackEnded(this, "playbackEnded"), - playPrevious(this, "playPrevious"), - playNext(this, "playNext") +: Wt::WTemplate {Wt::WString::tr("Lms.MediaPlayer.template")}, + playbackEnded {this, "playbackEnded"}, + playPrevious {this, "playPrevious"}, + playNext {this, "playNext"}, + _settingsLoaded {this, "settingsLoaded"} { _title = bindNew("title"); _artist = bindNew("artist"); _release = bindNew("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(mode)) + { + case TranscodeMode::Always: + case TranscodeMode::Never: + case TranscodeMode::IfFormatNotSupported: + settings.mode = static_cast(mode); + break; + } + + switch (static_cast(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); + 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(defaultTranscodeMode) + << ", " << static_cast(defaultTranscodeFormat) + << ", " << static_cast(defaultTranscodeBitrate) + << ")"; + + LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'"; + doJavaScript(oss.str()); + } LmsApp->getEvents().trackLoaded.connect(this, &MediaPlayer::loadTrack); 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 std::string imgResourceMimeType {LmsApp->getImageResource()->getMimeType()}; - std::string nativeResource; - const std::string transcodeResource {LmsApp->getAudioTranscodeResource()->getUrlForUser(trackId, LmsApp->getUser())}; -// if (!LmsApp->getUser()->getAudioTranscodeEnable()) - nativeResource = LmsApp->getAudioFileResource()->getUrl(trackId); + const std::string transcodeResource {LmsApp->getAudioTranscodeResource()->getUrl(trackId)}; + const std::string nativeResource {LmsApp->getAudioFileResource()->getUrl(trackId)}; const auto artists {track->getArtists()}; @@ -124,5 +169,23 @@ MediaPlayer::stop() wApp->doJavaScript("LMS.mediaplayer.stop()"); } +void +MediaPlayer::setSettings(const Settings& settings) +{ + _settings = settings; + + { + std::ostringstream oss; + oss << "LMS.mediaplayer.setSettings(" + << static_cast(_settings->mode) + << ", " << static_cast(_settings->format) + << ", " << static_cast(_settings->bitrate) + << ")"; + + LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'"; + doJavaScript(oss.str()); + } +} + } // namespace UserInterface diff --git a/src/lms/ui/MediaPlayer.hpp b/src/lms/ui/MediaPlayer.hpp index 538d64b1..bfb02c5f 100644 --- a/src/lms/ui/MediaPlayer.hpp +++ b/src/lms/ui/MediaPlayer.hpp @@ -25,14 +25,45 @@ #include #include "database/Types.hpp" +#include "database/User.hpp" namespace UserInterface { class MediaPlayer : public Wt::WTemplate { 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(const MediaPlayer&) = delete; + MediaPlayer(MediaPlayer&&) = delete; + MediaPlayer& operator=(const MediaPlayer&) = delete; + MediaPlayer& operator=(MediaPlayer&&) = delete; + + std::optional getSettings() const { return _settings; } + void setSettings(const Settings& settings); + // Signals Wt::JSignal<> playbackEnded; Wt::JSignal<> playPrevious; @@ -42,6 +73,9 @@ class MediaPlayer : public Wt::WTemplate void stop(); void loadTrack(Database::IdType trackId, bool play); + std::optional _settings; + + Wt::JSignal _settingsLoaded; Wt::WText* _title; Wt::WAnchor* _release; Wt::WAnchor* _artist; diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp index 52367564..73bd3b6b 100644 --- a/src/lms/ui/SettingsView.cpp +++ b/src/lms/ui/SettingsView.cpp @@ -31,11 +31,13 @@ #include "common/ValueStringModel.hpp" #include "auth/IPasswordService.hpp" +#include "utils/IConfig.hpp" #include "utils/Logger.hpp" #include "utils/Service.hpp" #include "LmsApplication.hpp" #include "LmsTheme.hpp" +#include "MediaPlayer.hpp" namespace UserInterface { @@ -46,22 +48,31 @@ class SettingsModel : public Wt::WFormModel public: // Associate each field with a unique string literal. static inline const Field DarkModeField {"dark-mode"}; - static inline const Field TranscodeEnableField {"transcoding-enable"}; - static inline const Field TranscodeFormatField {"transcoding-format"}; - static inline const Field TranscodeBitrateField {"transcoding-bitrate"}; + static inline const Field TranscodeModeField {"transcode-mode"}; + static inline const Field TranscodeFormatField {"transcode-format"}; + 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 PasswordField {"password"}; static inline const Field PasswordConfirmField {"password-confirm"}; + using TranscodeModeModel = ValueStringModel; + SettingsModel(bool withOldPassword) : _withOldPassword {withOldPassword} { initializeModels(); addField(DarkModeField); - addField(TranscodeEnableField); + addField(TranscodeModeField); addField(TranscodeBitrateField); addField(TranscodeFormatField); + addField(SubsonicTranscodeEnableField); + addField(SubsonicTranscodeBitrateField); + addField(SubsonicTranscodeFormatField); if (_withOldPassword) addField(PasswordOldField); @@ -69,14 +80,19 @@ class SettingsModel : public Wt::WFormModel addField(PasswordField); addField(PasswordConfirmField); + setValidator(TranscodeModeField, createMandatoryValidator()); setValidator(TranscodeBitrateField, createMandatoryValidator()); setValidator(TranscodeFormatField, createMandatoryValidator()); + setValidator(SubsonicTranscodeBitrateField, createMandatoryValidator()); + setValidator(SubsonicTranscodeFormatField, createMandatoryValidator()); loadData(); } - std::shared_ptr transcodeBitrateModel() { return _transcodeBitrateModel; } - std::shared_ptr transcodeFormatModel() { return _transcodeFormatModel; } + std::shared_ptr getTranscodeModeModel() { return _transcodeModeModel; } + std::shared_ptr getTranscodeBitrateModel() { return _transcodeBitrateModel; } + std::shared_ptr getTranscodeFormatModel() { return _transcodeFormatModel; } + std::shared_ptr getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; } void saveData() { @@ -97,43 +113,89 @@ class SettingsModel : public Wt::WFormModel user.modify()->setUITheme(newTheme); } - user.modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField))); + { + MediaPlayer::Settings settings; - auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(TranscodeBitrateField))}; - if (transcodeBitrateRow) - user.modify()->setAudioTranscodeBitrate(_transcodeBitrateModel->getValue(*transcodeBitrateRow)); + auto transcodeModeRow {_transcodeModeModel->getRowFromString(valueText(TranscodeModeField))}; + if (transcodeModeRow) + settings.mode = _transcodeModeModel->getValue(*transcodeModeRow); - auto transcodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(TranscodeFormatField))}; - if (transcodeFormatRow) - user.modify()->setAudioTranscodeFormat(_transcodeFormatModel->getValue(*transcodeFormatRow)); + auto transcodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(TranscodeFormatField))}; + if (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()) { user.modify()->setPasswordHash(passwordHash); user.modify()->clearAuthTokens(); } + + auto subsonicArtistListModeRow {_subsonicArtistListModeModel->getRowFromString(valueText(SubsonicArtistListModeField))}; + if (subsonicArtistListModeRow) + user.modify()->setSubsonicArtistListMode(_subsonicArtistListModeModel->getValue(*subsonicArtistListModeRow)); } void loadData() { 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); - setReadOnly(TranscodeBitrateField, true); + const auto& settings {*LmsApp->getMediaPlayer()->getSettings()}; + + 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())}; - if (transcodeBitrateRow) - setValue(TranscodeBitrateField, _transcodeBitrateModel->getString(*transcodeBitrateRow)); + setValue(SubsonicTranscodeEnableField, LmsApp->getUser()->getSubsonicTranscodeEnable()); + if (!LmsApp->getUser()->getSubsonicTranscodeEnable()) + { + setReadOnly(SubsonicTranscodeFormatField, true); + setReadOnly(SubsonicTranscodeBitrateField, true); + } - auto transcodeFormatRow {_transcodeFormatModel->getRowFromValue(LmsApp->getUser()->getAudioTranscodeFormat())}; - if (transcodeFormatRow) - setValue(TranscodeFormatField, _transcodeFormatModel->getString(*transcodeFormatRow)); + auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromValue(user->getSubsonicTranscodeBitrate())}; + if (subsonicTranscodeBitrateRow) + 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: @@ -207,33 +269,36 @@ class SettingsModel : public Wt::WFormModel void initializeModels() { - Bitrate maxAudioBitrate; - { - auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - maxAudioBitrate = LmsApp->getUser()->getMaxAudioTranscodeBitrate(); - } + + _transcodeModeModel = std::make_shared(); + _transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.always"), MediaPlayer::TranscodeMode::Always); + _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>(); + _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>(); for (const Bitrate bitrate : User::audioTranscodeAllowedBitrates) { - if (bitrate > maxAudioBitrate) - break; - _transcodeBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate); } _transcodeFormatModel = std::make_shared>(); - _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding.mp3"), AudioFormat::MP3); - _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding.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.transcoding.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.mp3"), AudioFormat::MP3); + _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.ogg_opus"), AudioFormat::OGG_OPUS); + _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.matroska_opus"), AudioFormat::MATROSKA_OPUS); + _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.ogg_vorbis"), AudioFormat::OGG_VORBIS); + _transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.webm_vorbis"), AudioFormat::WEBM_VORBIS); } bool _withOldPassword {}; - std::shared_ptr> _transcodeBitrateModel; - std::shared_ptr> _transcodeFormatModel; + std::shared_ptr _transcodeModeModel; + std::shared_ptr> _transcodeBitrateModel; + std::shared_ptr> _transcodeFormatModel; + std::shared_ptr> _subsonicArtistListModeModel; }; SettingsView::SettingsView() @@ -243,6 +308,11 @@ SettingsView::SettingsView() refreshView(); })); + LmsApp->getEvents().mediaPlayerSettingsAvailable.connect([=]() + { + refreshView(); + }); + refreshView(); } @@ -254,6 +324,10 @@ SettingsView::refreshView() clear(); + // Hack to wait for the audio player know the settings applied + if (!LmsApp->getMediaPlayer()->getSettings()) + return; + auto t {addNew(Wt::WString::tr("Lms.Settings.template"))}; auto model {std::make_shared(!LmsApp->isUserAuthStrong())}; @@ -268,6 +342,7 @@ SettingsView::refreshView() if (!LmsApp->isUserAuthStrong()) { t->setCondition("if-has-old-password", true); + auto oldPassword {std::make_unique()}; oldPassword->setEchoMode(Wt::EchoMode::Password); t->setFormWidget(SettingsModel::PasswordOldField, std::move(oldPassword)); @@ -283,35 +358,74 @@ SettingsView::refreshView() passwordConfirm->setEchoMode(Wt::EchoMode::Password); t->setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm)); - // Transcoding - auto transcode {std::make_unique()}; - auto* transcodeRaw {transcode.get()}; - t->setFormWidget(SettingsModel::TranscodeEnableField, std::move(transcode)); - - // Format - auto transcodeFormat {std::make_unique()}; - transcodeFormat->setModel(model->transcodeFormatModel()); - t->setFormWidget(SettingsModel::TranscodeFormatField, std::move(transcodeFormat)); - - // Bitrate - auto transcodeBitrate {std::make_unique()}; - transcodeBitrate->setModel(model->transcodeBitrateModel()); - t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodeBitrate)); - - transcodeRaw->changed().connect([=]() + // Audio { - bool enable {transcodeRaw->checkState() == Wt::CheckState::Checked}; - model->setReadOnly(SettingsModel::TranscodeFormatField, !enable); - model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable); - t->updateModel(model.get()); - t->updateView(model.get()); - }); + // Transcode + auto transcodeMode {std::make_unique()}; + auto* transcodeModeRaw {transcodeMode.get()}; + transcodeMode->setModel(model->getTranscodeModeModel()); + t->setFormWidget(SettingsModel::TranscodeModeField, std::move(transcodeMode)); + + // Format + auto transcodeFormat {std::make_unique()}; + transcodeFormat->setModel(model->getTranscodeFormatModel()); + t->setFormWidget(SettingsModel::TranscodeFormatField, std::move(transcodeFormat)); + + // Bitrate + auto transcodeBitrate {std::make_unique()}; + 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::get()->getBool("api-subsonic", true)); + + // Transcode + auto transcode {std::make_unique()}; + auto* transcodeRaw {transcode.get()}; + t->setFormWidget(SettingsModel::SubsonicTranscodeEnableField, std::move(transcode)); + + // Format + auto transcodeFormat {std::make_unique()}; + transcodeFormat->setModel(model->getTranscodeFormatModel()); + t->setFormWidget(SettingsModel::SubsonicTranscodeFormatField, std::move(transcodeFormat)); + + // Bitrate + auto transcodeBitrate {std::make_unique()}; + transcodeBitrate->setModel(model->getTranscodeBitrateModel()); + t->setFormWidget(SettingsModel::SubsonicTranscodeBitrateField, std::move(transcodeBitrate)); + + // Artist list mode + auto artistListMode = std::make_unique(); + 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 Wt::WPushButton *saveBtn {t->bindWidget("apply-btn", std::make_unique(Wt::WString::tr("Lms.apply")))}; Wt::WPushButton *discardBtn {t->bindWidget("discard-btn", std::make_unique(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. t->updateView(model.get()); - })); + }); discardBtn->clicked().connect(std::bind([=] () { diff --git a/src/lms/ui/admin/SubsonicView.cpp b/src/lms/ui/admin/SubsonicView.cpp deleted file mode 100644 index 9e289eb7..00000000 --- a/src/lms/ui/admin/SubsonicView.cpp +++ /dev/null @@ -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 . - */ - -#include "SubsonicView.hpp" - -#include -#include -#include -#include - -#include - -#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 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>(); - - _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> _artistListModeModel; -}; - -SubsonicView::SubsonicView() -{ - wApp->internalPathChanged().connect([this]() - { - refreshView(); - }); - - refreshView(); -} - -void -SubsonicView::refreshView() -{ - if (!wApp->internalPathMatches("/admin/subsonic")) - return; - - clear(); - - Wt::WTemplateFormView* t {addNew(Wt::WString::tr("Lms.Admin.Subsonic.template"))}; - - auto model {std::make_shared()}; - - // Enable - auto enable {std::make_unique()}; - auto* enableRaw {enable.get()}; - t->setFormWidget(SubsonicModel::EnableField, std::move(enable)); - - // Artis tlist mode - auto artistListMode = std::make_unique(); - 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("apply-btn", Wt::WString::tr("Lms.apply")); - Wt::WPushButton *discardBtn {t->bindWidget("discard-btn", std::make_unique(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 - - diff --git a/src/lms/ui/admin/SubsonicView.hpp b/src/lms/ui/admin/SubsonicView.hpp deleted file mode 100644 index 775057eb..00000000 --- a/src/lms/ui/admin/SubsonicView.hpp +++ /dev/null @@ -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 . - */ - -#pragma once - -#include - -namespace UserInterface { - -class SubsonicView : public Wt::WContainerWidget -{ - public: - SubsonicView(); - - private: - void refreshView(); -}; - -} // namespace UserInterface - - diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp index 154c64f5..3bb52e6d 100644 --- a/src/lms/ui/admin/UserView.cpp +++ b/src/lms/ui/admin/UserView.cpp @@ -48,10 +48,9 @@ class UserModel : public Wt::WFormModel { public: - static const Field LoginField; - static const Field PasswordField; - static const Field AudioTranscodeBitrateLimitField; - static const Field DemoField; + static inline const Field LoginField {"login"}; + static inline const Field PasswordField {"password"}; + static inline const Field DemoField {"demo"}; UserModel(std::optional userId) : _userId {userId} @@ -63,20 +62,15 @@ class UserModel : public Wt::WFormModel } addField(PasswordField); - addField(AudioTranscodeBitrateLimitField); addField(DemoField); if (!_userId) setValidator(PasswordField, createMandatoryValidator()); - initializeModels(); - // populate the model with initial data loadData(); } - std::shared_ptr bitrateModel() { return _bitrateModel; } - void saveData() { std::optional passwordHash; @@ -96,20 +90,12 @@ class UserModel : public Wt::WFormModel user.modify()->setPasswordHash(*passwordHash); user.modify()->clearAuthTokens(); } - - auto transcodeBitrateLimitRow {_bitrateModel->getRowFromString(valueText(AudioTranscodeBitrateLimitField))}; - if (transcodeBitrateLimitRow) - user.modify()->setMaxAudioTranscodeBitrate(_bitrateModel->getValue(*transcodeBitrateLimitRow)); } else { // Create user 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))) user.modify()->setType(Database::User::Type::DEMO); } @@ -129,10 +115,6 @@ class UserModel : public Wt::WFormModel throw UserNotFoundException {*_userId}; else if (user == LmsApp->getUser()) throw UserNotAllowedException {}; - - auto transcodeBitrateLimitRow {_bitrateModel->getRowFromValue(user->getMaxAudioTranscodeBitrate())}; - if (transcodeBitrateLimitRow) - setValue(AudioTranscodeBitrateLimitField, _bitrateModel->getString(*transcodeBitrateLimitRow)); } std::string getLoginName() const @@ -194,30 +176,15 @@ class UserModel : public Wt::WFormModel return false; } - - void initializeModels() - { - _bitrateModel = std::make_shared>(); - - for (auto bitrate : Database::User::audioTranscodeAllowedBitrates) - _bitrateModel->add( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate ); - } - - std::shared_ptr> _bitrateModel; std::optional _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() { - wApp->internalPathChanged().connect(std::bind([=] + wApp->internalPathChanged().connect([this]() { refreshView(); - })); + }); refreshView(); } @@ -262,18 +229,13 @@ UserView::refreshView() passwordEdit->setEchoMode(Wt::EchoMode::Password); t->setFormWidget(UserModel::PasswordField, std::move(passwordEdit)); - // Transcode bitrate limit - auto bitrate = std::make_unique(); - bitrate->setModel(model->bitrateModel()); - t->setFormWidget(UserModel::AudioTranscodeBitrateLimitField, std::move(bitrate)); - // Demo account t->setFormWidget(UserModel::DemoField, std::make_unique()); if (!userId && ServiceProvider::get()->getBool("demo", false)) t->setCondition("if-demo", true); Wt::WPushButton* saveBtn = t->bindNew("save-btn", Wt::WString::tr(userId ? "Lms.save" : "Lms.create")); - saveBtn->clicked().connect(std::bind([=] + saveBtn->clicked().connect([=]() { t->updateModel(model.get()); @@ -287,7 +249,7 @@ UserView::refreshView() { t->updateView(model.get()); } - })); + }); t->updateView(model.get()); } diff --git a/src/lms/ui/resource/AudioTranscodeResource.cpp b/src/lms/ui/resource/AudioTranscodeResource.cpp index ece62937..8288247c 100644 --- a/src/lms/ui/resource/AudioTranscodeResource.cpp +++ b/src/lms/ui/resource/AudioTranscodeResource.cpp @@ -73,32 +73,27 @@ AudioTranscodeResource:: ~AudioTranscodeResource() } static -std::optional -AudioFormatToAvEncoding(Database::AudioFormat format) +std::optional +AudioFormatToAvFormat(Database::AudioFormat format) { switch (format) { - case Database::AudioFormat::MP3: return Av::Encoding::MP3; - case Database::AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS; - case Database::AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS; - case Database::AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS; - case Database::AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS; + case Database::AudioFormat::MP3: return Av::Format::MP3; + case Database::AudioFormat::OGG_OPUS: return Av::Format::OGG_OPUS; + case Database::AudioFormat::MATROSKA_OPUS: return Av::Format::MATROSKA_OPUS; + case Database::AudioFormat::OGG_VORBIS: return Av::Format::OGG_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; } 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)}; - - computedUrl += "&format=" + std::to_string(static_cast(user->getAudioTranscodeFormat())); - computedUrl += "&bitrate=" + std::to_string(LmsApp->getUser()->getAudioTranscodeBitrate()); - - return computedUrl; + return url() + "&trackid=" + std::to_string(trackId); } template @@ -144,8 +139,8 @@ AudioTranscodeResource::handleRequest(const Wt::Http::Request& request, if (!trackId || !format || !bitrate) return; - auto encoding {AudioFormatToAvEncoding(*format)}; - if (!encoding) + auto avFormat {AudioFormatToAvFormat(*format)}; + if (!avFormat) return; // optional parameter @@ -166,7 +161,7 @@ AudioTranscodeResource::handleRequest(const Wt::Http::Request& request, 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"; return; @@ -175,7 +170,7 @@ AudioTranscodeResource::handleRequest(const Wt::Http::Request& request, Av::TranscodeParameters parameters {}; parameters.stripMetadata = true; - parameters.encoding = *encoding; + parameters.format = *avFormat; parameters.bitrate = *bitrate; parameters.offset = std::chrono::seconds {offset ? *offset : 0}; diff --git a/src/lms/ui/resource/AudioTranscodeResource.hpp b/src/lms/ui/resource/AudioTranscodeResource.hpp index fa5d2ed7..a23e6602 100644 --- a/src/lms/ui/resource/AudioTranscodeResource.hpp +++ b/src/lms/ui/resource/AudioTranscodeResource.hpp @@ -37,7 +37,7 @@ class AudioTranscodeResource : public Wt::WResource ~AudioTranscodeResource(); // Url depends on the user since settings are used in parameters - std::string getUrlForUser(Database::IdType trackId, Wt::Dbo::ptr user) const; + std::string getUrl(Database::IdType trackId) const; void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response);