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);