Subsonic API: try to recognize some formats in stream endpoint (mp3, opus, vorbis)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -23,33 +23,33 @@
|
||||
|
||||
namespace Database
|
||||
{
|
||||
class Session;
|
||||
class Session;
|
||||
|
||||
using Version = std::size_t;
|
||||
static constexpr Version LMS_DATABASE_VERSION {41};
|
||||
class VersionInfo
|
||||
{
|
||||
public:
|
||||
using pointer = Wt::Dbo::ptr<VersionInfo>;
|
||||
using Version = std::size_t;
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 42 };
|
||||
class VersionInfo
|
||||
{
|
||||
public:
|
||||
using pointer = Wt::Dbo::ptr<VersionInfo>;
|
||||
|
||||
static VersionInfo::pointer getOrCreate(Session& session);
|
||||
static VersionInfo::pointer get(Session& session);
|
||||
static VersionInfo::pointer getOrCreate(Session& session);
|
||||
static VersionInfo::pointer get(Session& session);
|
||||
|
||||
Version getVersion() const { return _version; }
|
||||
void setVersion(Version version) { _version = static_cast<int>(version); }
|
||||
Version getVersion() const { return _version; }
|
||||
void setVersion(Version version) { _version = static_cast<int>(version); }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _version, "db_version");
|
||||
}
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _version, "db_version");
|
||||
}
|
||||
|
||||
private:
|
||||
int _version {LMS_DATABASE_VERSION};
|
||||
};
|
||||
private:
|
||||
int _version{ LMS_DATABASE_VERSION };
|
||||
};
|
||||
|
||||
namespace Migration
|
||||
{
|
||||
void doDbMigration(Session& session);
|
||||
}
|
||||
namespace Migration
|
||||
{
|
||||
void doDbMigration(Session& session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,73 +30,63 @@
|
||||
|
||||
namespace Database {
|
||||
|
||||
User::User(std::string_view loginName)
|
||||
: _loginName {loginName}
|
||||
{
|
||||
}
|
||||
User::User(std::string_view loginName)
|
||||
: _loginName{ loginName }
|
||||
{
|
||||
}
|
||||
|
||||
User::pointer
|
||||
User::create(Session& session, std::string_view loginName)
|
||||
{
|
||||
return session.getDboSession().add(std::unique_ptr<User> {new User {loginName}});
|
||||
}
|
||||
User::pointer User::create(Session& session, std::string_view loginName)
|
||||
{
|
||||
return session.getDboSession().add(std::unique_ptr<User> {new User{ loginName }});
|
||||
}
|
||||
|
||||
std::size_t
|
||||
User::getCount(Session& session)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
std::size_t User::getCount(Session& session)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
return session.getDboSession().query<int>("SELECT COUNT(*) FROM user");
|
||||
}
|
||||
return session.getDboSession().query<int>("SELECT COUNT(*) FROM user");
|
||||
}
|
||||
|
||||
RangeResults<UserId>
|
||||
User::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
RangeResults<UserId> User::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
auto query {session.getDboSession().query<UserId>("SELECT id FROM user")};
|
||||
auto query{ session.getDboSession().query<UserId>("SELECT id FROM user") };
|
||||
|
||||
if (params.scrobbler)
|
||||
query.where("scrobbler = ?").bind(*params.scrobbler);
|
||||
if (params.scrobbler)
|
||||
query.where("scrobbler = ?").bind(*params.scrobbler);
|
||||
|
||||
return Utils::execQuery(query, params.range);
|
||||
}
|
||||
return Utils::execQuery(query, params.range);
|
||||
}
|
||||
|
||||
User::pointer
|
||||
User::findDemoUser(Session& session)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
User::pointer User::findDemoUser(Session& session)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
|
||||
}
|
||||
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
|
||||
}
|
||||
|
||||
User::pointer
|
||||
User::find(Session& session, UserId id)
|
||||
{
|
||||
return session.getDboSession().find<User>().where("id = ?").bind(id).resultValue();
|
||||
}
|
||||
User::pointer User::find(Session& session, UserId id)
|
||||
{
|
||||
return session.getDboSession().find<User>().where("id = ?").bind(id).resultValue();
|
||||
}
|
||||
|
||||
User::pointer
|
||||
User::find(Session& session, std::string_view name)
|
||||
{
|
||||
return session.getDboSession().find<User>()
|
||||
.where("login_name = ?").bind(name)
|
||||
.resultValue();
|
||||
}
|
||||
User::pointer User::find(Session& session, std::string_view name)
|
||||
{
|
||||
return session.getDboSession().find<User>()
|
||||
.where("login_name = ?").bind(name)
|
||||
.resultValue();
|
||||
}
|
||||
|
||||
void
|
||||
User::setSubsonicTranscodeBitrate(Bitrate bitrate)
|
||||
{
|
||||
assert(isAudioBitrateAllowed(bitrate));
|
||||
_subsonicTranscodeBitrate = bitrate;
|
||||
}
|
||||
void User::setSubsonicDefaultTranscodeBitrate(Bitrate bitrate)
|
||||
{
|
||||
assert(isAudioBitrateAllowed(bitrate));
|
||||
_subsonicDefaultTranscodeBitrate = bitrate;
|
||||
}
|
||||
|
||||
void
|
||||
User::clearAuthTokens()
|
||||
{
|
||||
_authTokens.clear();
|
||||
}
|
||||
void User::clearAuthTokens()
|
||||
{
|
||||
_authTokens.clear();
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
|
||||
|
||||
|
||||
@@ -33,134 +33,128 @@
|
||||
|
||||
namespace Database {
|
||||
|
||||
class AuthToken;
|
||||
class Session;
|
||||
class AuthToken;
|
||||
class Session;
|
||||
|
||||
class User final : public Object<User, UserId>
|
||||
{
|
||||
public:
|
||||
struct PasswordHash
|
||||
{
|
||||
std::string salt;
|
||||
std::string hash;
|
||||
};
|
||||
class User final : public Object<User, UserId>
|
||||
{
|
||||
public:
|
||||
struct PasswordHash
|
||||
{
|
||||
std::string salt;
|
||||
std::string hash;
|
||||
};
|
||||
|
||||
struct FindParameters
|
||||
{
|
||||
std::optional<Scrobbler> scrobbler;
|
||||
Range range;
|
||||
struct FindParameters
|
||||
{
|
||||
std::optional<Scrobbler> scrobbler;
|
||||
Range range;
|
||||
|
||||
FindParameters& setScrobbler(Scrobbler _scrobbler) { scrobbler = _scrobbler; return *this; }
|
||||
FindParameters& setRange(Range _range) {range = _range; return *this; }
|
||||
};
|
||||
FindParameters& setScrobbler(Scrobbler _scrobbler) { scrobbler = _scrobbler; return *this; }
|
||||
FindParameters& setRange(Range _range) { range = _range; return *this; }
|
||||
};
|
||||
|
||||
static inline const std::size_t MinNameLength {3};
|
||||
static inline const std::size_t MaxNameLength {15};
|
||||
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};
|
||||
static inline const Scrobbler defaultScrobbler {Scrobbler::Internal};
|
||||
static inline const std::size_t MinNameLength{ 3 };
|
||||
static inline const std::size_t MaxNameLength{ 15 };
|
||||
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 };
|
||||
static inline const Scrobbler defaultScrobbler{ Scrobbler::Internal };
|
||||
|
||||
User() = default;
|
||||
User() = default;
|
||||
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, UserId id);
|
||||
static pointer find(Session& session, std::string_view loginName);
|
||||
static RangeResults<UserId> find(Session& session, const FindParameters& params);
|
||||
static pointer findDemoUser(Session& session);
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, UserId id);
|
||||
static pointer find(Session& session, std::string_view loginName);
|
||||
static RangeResults<UserId> find(Session& session, const FindParameters& params);
|
||||
static pointer findDemoUser(Session& session);
|
||||
|
||||
// accessors
|
||||
const std::string& getLoginName() const { return _loginName; }
|
||||
PasswordHash getPasswordHash() const { return PasswordHash {_passwordSalt, _passwordHash}; }
|
||||
const Wt::WDateTime& getLastLogin() const { return _lastLogin; }
|
||||
std::size_t getAuthTokensCount() const { return _authTokens.size(); }
|
||||
// accessors
|
||||
const std::string& getLoginName() const { return _loginName; }
|
||||
PasswordHash getPasswordHash() const { return PasswordHash{ _passwordSalt, _passwordHash }; }
|
||||
const Wt::WDateTime& getLastLogin() const { return _lastLogin; }
|
||||
std::size_t getAuthTokensCount() const { return _authTokens.size(); }
|
||||
|
||||
// write
|
||||
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
|
||||
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
|
||||
void setType(UserType type) { _type = type; }
|
||||
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; }
|
||||
void setScrobbler(Scrobbler scrobbler) { _scrobbler = scrobbler; }
|
||||
void setListenBrainzToken(const std::optional<UUID>& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; }
|
||||
// write
|
||||
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
|
||||
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
|
||||
void setType(UserType type) { _type = type; }
|
||||
void setSubsonicDefaultTranscodeFormat(AudioFormat encoding) { _subsonicDefaultTranscodeFormat = encoding; }
|
||||
void setSubsonicDefaultTranscodeBitrate(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; }
|
||||
void setScrobbler(Scrobbler scrobbler) { _scrobbler = scrobbler; }
|
||||
void setListenBrainzToken(const std::optional<UUID>& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; }
|
||||
|
||||
// read
|
||||
bool isAdmin() const { return _type == UserType::ADMIN; }
|
||||
bool isDemo() const { return _type == UserType::DEMO; }
|
||||
UserType getType() const { return _type; }
|
||||
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; }
|
||||
Scrobbler getScrobbler() const { return _scrobbler; }
|
||||
std::optional<UUID> getListenBrainzToken() const { return UUID::fromString(_listenbrainzToken); }
|
||||
// read
|
||||
bool isAdmin() const { return _type == UserType::ADMIN; }
|
||||
bool isDemo() const { return _type == UserType::DEMO; }
|
||||
UserType getType() const { return _type; }
|
||||
AudioFormat getSubsonicDefaultTranscodeFormat() const { return _subsonicDefaultTranscodeFormat; }
|
||||
Bitrate getSubsonicDefaultTranscodeBitrate() const { return _subsonicDefaultTranscodeBitrate; }
|
||||
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; }
|
||||
Scrobbler getScrobbler() const { return _scrobbler; }
|
||||
std::optional<UUID> getListenBrainzToken() const { return UUID::fromString(_listenbrainzToken); }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::field(a, _loginName, "login_name");
|
||||
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, _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");
|
||||
Wt::Dbo::field(a, _scrobbler, "scrobbler");
|
||||
Wt::Dbo::field(a, _listenbrainzToken, "listenbrainz_token");
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::field(a, _loginName, "login_name");
|
||||
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, _subsonicDefaultTranscodeFormat, "subsonic_default_transcode_format");
|
||||
Wt::Dbo::field(a, _subsonicDefaultTranscodeBitrate, "subsonic_default_transcode_bitrate");
|
||||
Wt::Dbo::field(a, _subsonicArtistListMode, "subsonic_artist_list_mode");
|
||||
Wt::Dbo::field(a, _uiTheme, "ui_theme");
|
||||
Wt::Dbo::field(a, _scrobbler, "scrobbler");
|
||||
Wt::Dbo::field(a, _listenbrainzToken, "listenbrainz_token");
|
||||
|
||||
// UI player settings
|
||||
Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos");
|
||||
Wt::Dbo::field(a, _repeatAll, "repeat_all");
|
||||
Wt::Dbo::field(a, _radio, "radio");
|
||||
// UI player settings
|
||||
Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos");
|
||||
Wt::Dbo::field(a, _repeatAll, "repeat_all");
|
||||
Wt::Dbo::field(a, _radio, "radio");
|
||||
|
||||
Wt::Dbo::hasMany(a, _authTokens, Wt::Dbo::ManyToOne, "user");
|
||||
}
|
||||
Wt::Dbo::hasMany(a, _authTokens, Wt::Dbo::ManyToOne, "user");
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
User(std::string_view loginName);
|
||||
static pointer create(Session& session, std::string_view loginName);
|
||||
private:
|
||||
friend class Session;
|
||||
User(std::string_view loginName);
|
||||
static pointer create(Session& session, std::string_view loginName);
|
||||
|
||||
std::string _loginName;
|
||||
std::string _passwordSalt;
|
||||
std::string _passwordHash;
|
||||
Wt::WDateTime _lastLogin;
|
||||
UITheme _uiTheme {defaultUITheme};
|
||||
Scrobbler _scrobbler {defaultScrobbler};
|
||||
std::string _listenbrainzToken; // Musicbrainz Identifier
|
||||
std::string _loginName;
|
||||
std::string _passwordSalt;
|
||||
std::string _passwordHash;
|
||||
Wt::WDateTime _lastLogin;
|
||||
UITheme _uiTheme{ defaultUITheme };
|
||||
Scrobbler _scrobbler{ defaultScrobbler };
|
||||
std::string _listenbrainzToken; // Musicbrainz Identifier
|
||||
|
||||
// Admin defined settings
|
||||
UserType _type {UserType::REGULAR};
|
||||
// Admin defined settings
|
||||
UserType _type{ UserType::REGULAR };
|
||||
|
||||
// User defined settings
|
||||
SubsonicArtistListMode _subsonicArtistListMode {defaultSubsonicArtistListMode};
|
||||
bool _subsonicTranscodeEnable {defaultSubsonicTranscodeEnable};
|
||||
AudioFormat _subsonicTranscodeFormat {defaultSubsonicTranscodeFormat};
|
||||
int _subsonicTranscodeBitrate {defaultSubsonicTranscodeBitrate};
|
||||
// User defined settings
|
||||
SubsonicArtistListMode _subsonicArtistListMode{ defaultSubsonicArtistListMode };
|
||||
AudioFormat _subsonicDefaultTranscodeFormat{ defaultSubsonicTranscodeFormat };
|
||||
int _subsonicDefaultTranscodeBitrate{ defaultSubsonicTranscodeBitrate };
|
||||
|
||||
// User's dynamic data (UI)
|
||||
int _curPlayingTrackPos {}; // Current track position in queue
|
||||
bool _repeatAll {};
|
||||
bool _radio {};
|
||||
// User's dynamic data (UI)
|
||||
int _curPlayingTrackPos{}; // Current track position in queue
|
||||
bool _repeatAll{};
|
||||
bool _radio{};
|
||||
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<AuthToken>> _authTokens;
|
||||
};
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<AuthToken>> _authTokens;
|
||||
};
|
||||
|
||||
} // namespace Databas'
|
||||
|
||||
|
||||
@@ -30,25 +30,40 @@
|
||||
#include "utils/Logger.hpp"
|
||||
#include "utils/FileResourceHandlerCreator.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
#include "utils/String.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
using namespace Database;
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
namespace {
|
||||
std::optional<Av::Format> subsonicStreamFormatToAvFormat(std::string_view format)
|
||||
{
|
||||
for (const auto& [str, avFormat] : std::initializer_list<std::pair<std::string_view, Av::Format>>{
|
||||
{"mp3", Av::Format::MP3},
|
||||
{"opus", Av::Format::OGG_OPUS},
|
||||
{"vorbis", Av::Format::OGG_VORBIS},
|
||||
})
|
||||
{
|
||||
if (StringUtils::stringCaseInsensitiveEqual("str", format))
|
||||
return avFormat;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Av::Format userTranscodeFormatToAvFormat(AudioFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
return Av::Format::OGG_OPUS;
|
||||
}
|
||||
|
||||
struct StreamParameters
|
||||
@@ -64,9 +79,9 @@ namespace API::Subsonic
|
||||
const TrackId id{ getMandatoryParameterAs<TrackId>(context.parameters, "id") };
|
||||
|
||||
// Optional params
|
||||
std::optional<std::size_t> maxBitRate{ getParameterAs<std::size_t>(context.parameters, "maxBitRate") };
|
||||
const std::optional<std::string> format{ getParameterAs<std::string>(context.parameters, "format") };
|
||||
const std::optional<std::size_t> timeOffset{ getParameterAs<std::size_t>(context.parameters, "timeOffset") };
|
||||
std::size_t maxBitRate{ getParameterAs<std::size_t>(context.parameters, "maxBitRate").value_or(0) }; // "If set to zero, no limit is imposed"
|
||||
const std::string format{ getParameterAs<std::string>(context.parameters, "format").value_or("") };
|
||||
std::size_t timeOffset{ getParameterAs<std::size_t>(context.parameters, "timeOffset").value_or(0) };
|
||||
bool estimateContentLength{ getParameterAs<bool>(context.parameters, "estimateContentLength").value_or(false) };
|
||||
|
||||
StreamParameters parameters;
|
||||
@@ -84,30 +99,25 @@ namespace API::Subsonic
|
||||
parameters.inputFileParameters.duration = track->getDuration();
|
||||
}
|
||||
|
||||
if (format != "raw") // raw => no transcode
|
||||
{
|
||||
const User::pointer user{ User::find(context.dbSession, context.userId) };
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError{};
|
||||
|
||||
// format = "raw" => no transcode. Other format values will be ignored
|
||||
const bool transcode{ (!format || (*format != "raw")) && user->getSubsonicTranscodeEnable() };
|
||||
if (transcode)
|
||||
{
|
||||
std::size_t bitRate{ user->getSubsonicTranscodeBitrate() / 1000 };
|
||||
Av::TranscodeParameters& transcodeParameters{ parameters.transcodeParameters.emplace() };
|
||||
|
||||
// "If set to zero, no limit is imposed"
|
||||
if (maxBitRate && *maxBitRate != 0)
|
||||
bitRate = Utils::clamp(*maxBitRate, std::size_t{ 48 }, bitRate);
|
||||
transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.)
|
||||
transcodeParameters.offset = std::chrono::seconds{ timeOffset };
|
||||
|
||||
Av::TranscodeParameters transcodeParameters;
|
||||
if (std::optional<Av::Format> requestedFormat{ subsonicStreamFormatToAvFormat(format) })
|
||||
transcodeParameters.format = *requestedFormat;
|
||||
else
|
||||
transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicDefaultTranscodeFormat());
|
||||
|
||||
transcodeParameters.bitrate = bitRate * 1000;
|
||||
transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicTranscodeFormat());
|
||||
transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.)
|
||||
transcodeParameters.offset = std::chrono::seconds{ timeOffset ? *timeOffset : 0 };
|
||||
|
||||
parameters.transcodeParameters = std::move(transcodeParameters);
|
||||
}
|
||||
transcodeParameters.bitrate = user->getSubsonicDefaultTranscodeBitrate();
|
||||
if (maxBitRate != 0)
|
||||
transcodeParameters.bitrate = Utils::clamp(transcodeParameters.bitrate, std::size_t{ 48 }, maxBitRate);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
|
||||
@@ -120,8 +120,7 @@ namespace API::Subsonic
|
||||
trackResponse.setAttribute("suffix", extension.string().substr(1));
|
||||
}
|
||||
|
||||
if (user->getSubsonicTranscodeEnable())
|
||||
trackResponse.setAttribute("transcodedSuffix", formatToSuffix(user->getSubsonicTranscodeFormat()));
|
||||
trackResponse.setAttribute("transcodedSuffix", formatToSuffix(user->getSubsonicDefaultTranscodeFormat()));
|
||||
|
||||
trackResponse.setAttribute("coverArt", idToString(track->getId()));
|
||||
|
||||
|
||||
@@ -177,6 +177,20 @@ namespace StringUtils
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool stringCaseInsensitiveEqual(std::string_view strA, std::string_view strB)
|
||||
{
|
||||
if (strA.size() != strB.size())
|
||||
return false;
|
||||
|
||||
for (std::size_t i{}; i < strA.size(); ++i)
|
||||
{
|
||||
if (std::tolower(strA[i]) != std::tolower(strB[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void capitalize(std::string& str)
|
||||
{
|
||||
for (auto it{ std::begin(str) }; it != std::end(str); ++it)
|
||||
|
||||
@@ -55,6 +55,8 @@ namespace StringUtils {
|
||||
|
||||
[[nodiscard]] std::string bufferToString(const std::vector<unsigned char>& data);
|
||||
|
||||
[[nodiscard]] bool stringCaseInsensitiveEqual(std::string_view strA, std::string_view strB);
|
||||
|
||||
void capitalize(std::string& str);
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -160,15 +160,13 @@ class SettingsModel : public Wt::WFormModel
|
||||
}
|
||||
|
||||
{
|
||||
user.modify()->setSubsonicTranscodeEnable(Wt::asNumber(value(SubsonicTranscodeEnableField)));
|
||||
|
||||
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(SubsonicTranscodeBitrateField))};
|
||||
if (subsonicTranscodeBitrateRow)
|
||||
user.modify()->setSubsonicTranscodeBitrate(_transcodeBitrateModel->getValue(*subsonicTranscodeBitrateRow));
|
||||
user.modify()->setSubsonicDefaultTranscodeBitrate(_transcodeBitrateModel->getValue(*subsonicTranscodeBitrateRow));
|
||||
|
||||
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromString(valueText(SubsonicTranscodeFormatField))};
|
||||
if (subsonicTranscodeFormatRow)
|
||||
user.modify()->setSubsonicTranscodeFormat(_transcodeFormatModel->getValue(*subsonicTranscodeFormatRow));
|
||||
user.modify()->setSubsonicDefaultTranscodeFormat(_transcodeFormatModel->getValue(*subsonicTranscodeFormatRow));
|
||||
|
||||
auto subsonicArtistListModeRow {_subsonicArtistListModeModel->getRowFromString(valueText(SubsonicArtistListModeField))};
|
||||
if (subsonicArtistListModeRow)
|
||||
@@ -224,19 +222,12 @@ class SettingsModel : public Wt::WFormModel
|
||||
setValue(ReplayGainPreAmpGainIfNoInfoField, settings.replayGain.preAmpGainIfNoInfo);
|
||||
}
|
||||
|
||||
setValue(SubsonicTranscodeEnableField, LmsApp->getUser()->getSubsonicTranscodeEnable());
|
||||
{
|
||||
const bool usesTranscode {LmsApp->getUser()->getSubsonicTranscodeEnable()};
|
||||
setReadOnly(SubsonicTranscodeFormatField, !usesTranscode);
|
||||
setReadOnly(SubsonicTranscodeBitrateField, !usesTranscode);
|
||||
}
|
||||
|
||||
{
|
||||
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromValue(user->getSubsonicTranscodeBitrate())};
|
||||
auto subsonicTranscodeBitrateRow {_transcodeBitrateModel->getRowFromValue(user->getSubsonicDefaultTranscodeBitrate())};
|
||||
if (subsonicTranscodeBitrateRow)
|
||||
setValue(SubsonicTranscodeBitrateField, _transcodeBitrateModel->getString(*subsonicTranscodeBitrateRow));
|
||||
|
||||
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromValue(user->getSubsonicTranscodeFormat())};
|
||||
auto subsonicTranscodeFormatRow {_transcodeFormatModel->getRowFromValue(user->getSubsonicDefaultTranscodeFormat())};
|
||||
if (subsonicTranscodeFormatRow)
|
||||
setValue(SubsonicTranscodeFormatField, _transcodeFormatModel->getString(*subsonicTranscodeFormatRow));
|
||||
|
||||
@@ -482,11 +473,6 @@ SettingsView::refreshView()
|
||||
{
|
||||
t->setCondition("if-has-subsonic-api", Service<IConfig>::get()->getBool("api-subsonic", true));
|
||||
|
||||
// Transcode
|
||||
auto transcode {std::make_unique<Wt::WCheckBox>()};
|
||||
auto* transcodeRaw {transcode.get()};
|
||||
t->setFormWidget(SettingsModel::SubsonicTranscodeEnableField, std::move(transcode));
|
||||
|
||||
// Format
|
||||
auto transcodeFormat {std::make_unique<Wt::WComboBox>()};
|
||||
transcodeFormat->setModel(model->getTranscodeFormatModel());
|
||||
@@ -501,15 +487,6 @@ SettingsView::refreshView()
|
||||
auto artistListMode {std::make_unique<Wt::WComboBox>()};
|
||||
artistListMode->setModel(model->getSubsonicArtistListModeModel());
|
||||
t->setFormWidget(SettingsModel::SubsonicArtistListModeField, std::move(artistListMode));
|
||||
|
||||
transcodeRaw->changed().connect([=]
|
||||
{
|
||||
const bool enable {transcodeRaw->checkState() == Wt::CheckState::Checked};
|
||||
model->setReadOnly(SettingsModel::SubsonicTranscodeFormatField, !enable);
|
||||
model->setReadOnly(SettingsModel::SubsonicTranscodeBitrateField, !enable);
|
||||
t->updateModel(model.get());
|
||||
t->updateView(model.get());
|
||||
});
|
||||
}
|
||||
|
||||
// Scrobbling
|
||||
|
||||
Reference in New Issue
Block a user