From 87d70465e9613cd740d6db547a1edcad7f727521 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 12 Nov 2023 20:51:42 +0100 Subject: [PATCH] Renamed some transcoding stuff + now retrieve decoder codecs --- src/libs/av/CMakeLists.txt | 3 +- src/libs/av/impl/AudioFile.cpp | 32 +- src/libs/av/impl/TranscodeResourceHandler.cpp | 106 ------- src/libs/av/impl/Transcoder.cpp | 294 +++++++++--------- src/libs/av/impl/Transcoder.hpp | 58 ++-- .../av/impl/TranscodingResourceHandler.cpp | 103 ++++++ ...ler.hpp => TranscodingResourceHandler.hpp} | 8 +- src/libs/av/impl/Types.cpp | 41 --- src/libs/av/include/av/IAudioFile.hpp | 26 +- ...rameters.hpp => TranscodingParameters.hpp} | 41 ++- ... => TranscodingResourceHandlerCreator.hpp} | 9 +- src/libs/av/include/av/Types.hpp | 28 +- src/libs/services/database/impl/User.cpp | 4 +- .../include/services/database/Types.hpp | 4 +- .../include/services/database/User.hpp | 20 +- .../impl/entrypoints/MediaRetrieval.cpp | 64 ++-- src/libs/subsonic/impl/responses/Song.cpp | 14 +- src/lms/ui/MediaPlayer.hpp | 162 +++++----- src/lms/ui/SettingsView.cpp | 120 +++---- src/lms/ui/explore/ReleaseView.cpp | 2 +- src/lms/ui/explore/TrackListHelpers.cpp | 2 +- .../ui/resource/AudioTranscodeResource.cpp | 282 ++++++++--------- 22 files changed, 711 insertions(+), 712 deletions(-) delete mode 100644 src/libs/av/impl/TranscodeResourceHandler.cpp create mode 100644 src/libs/av/impl/TranscodingResourceHandler.cpp rename src/libs/av/impl/{TranscodeResourceHandler.hpp => TranscodingResourceHandler.hpp} (82%) delete mode 100644 src/libs/av/impl/Types.cpp rename src/libs/av/include/av/{TranscodeParameters.hpp => TranscodingParameters.hpp} (51%) rename src/libs/av/include/av/{TranscodeResourceHandlerCreator.hpp => TranscodingResourceHandlerCreator.hpp} (75%) diff --git a/src/libs/av/CMakeLists.txt b/src/libs/av/CMakeLists.txt index 7c556cb3..fee1201e 100644 --- a/src/libs/av/CMakeLists.txt +++ b/src/libs/av/CMakeLists.txt @@ -3,8 +3,7 @@ add_library(lmsav SHARED impl/AudioFile.cpp impl/RawResourceHandlerCreator.cpp impl/Transcoder.cpp - impl/TranscodeResourceHandler.cpp - impl/Types.cpp + impl/TranscodingResourceHandler.cpp ) target_include_directories(lmsav INTERFACE diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index fd29b3f0..346fe074 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -65,6 +65,31 @@ namespace Av res[StringUtils::stringToUpper(tag->key)] = tag->value; } } + + DecodingCodec avcodecToDecodingCodec(AVCodecID codec) + { + switch (codec) + { + case AV_CODEC_ID_MP3: return DecodingCodec::MP3; + case AV_CODEC_ID_AAC: return DecodingCodec::AAC; + case AV_CODEC_ID_AC3: return DecodingCodec::AC3; + case AV_CODEC_ID_VORBIS: return DecodingCodec::VORBIS; + case AV_CODEC_ID_WMAV1: return DecodingCodec::WMAV1; + case AV_CODEC_ID_WMAV2: return DecodingCodec::WMAV2; + case AV_CODEC_ID_FLAC: return DecodingCodec::FLAC; + case AV_CODEC_ID_ALAC: return DecodingCodec::ALAC; + case AV_CODEC_ID_WAVPACK: return DecodingCodec::WAVPACK; + case AV_CODEC_ID_MUSEPACK7: return DecodingCodec::MUSEPACK7; + case AV_CODEC_ID_MUSEPACK8: return DecodingCodec::MUSEPACK8; + case AV_CODEC_ID_APE: return DecodingCodec::APE; + case AV_CODEC_ID_EAC3: return DecodingCodec::EAC3; + case AV_CODEC_ID_MP4ALS: return DecodingCodec::MP4ALS; + case AV_CODEC_ID_OPUS: return DecodingCodec::OPUS; + case AV_CODEC_ID_SHORTEN: return DecodingCodec::SHORTEN; + default: + return DecodingCodec::UNKNOWN; + } + } } std::unique_ptr parseAudioFile(const std::filesystem::path& p) @@ -254,14 +279,17 @@ namespace Av res.emplace(); res->index = streamIndex; res->bitrate = static_cast(avstream->codecpar->bit_rate); - res->codec = ::avcodec_get_name(avstream->codecpar->codec_id); - assert(!res->codec.empty()); + res->codec = avcodecToDecodingCodec(avstream->codecpar->codec_id); + res->codecName = ::avcodec_get_name(avstream->codecpar->codec_id); + assert(!res->codecName.empty()); // doc says it is never NULL return res; } std::string_view getMimeType(const std::filesystem::path& fileExtension) { + // List should be sync with the demuxers shipped in the lms's docker version + // + the _audioFileExtensions in ScanSettings static const std::unordered_map entries { {".mp3", "audio/mpeg"}, diff --git a/src/libs/av/impl/TranscodeResourceHandler.cpp b/src/libs/av/impl/TranscodeResourceHandler.cpp deleted file mode 100644 index 753213c4..00000000 --- a/src/libs/av/impl/TranscodeResourceHandler.cpp +++ /dev/null @@ -1,106 +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 "TranscodeResourceHandler.hpp" -#include "utils/Logger.hpp" - -namespace Av -{ - namespace - { - std::size_t - doEstimateContentLength(const InputFileParameters& inputFileParameters, const TranscodeParameters& transcodeParameters) - { - const std::size_t estimatedContentLength {transcodeParameters.bitrate / 8 * static_cast(std::chrono::duration_cast(inputFileParameters.duration).count()) / 1000}; - return estimatedContentLength; - } - } - - std::unique_ptr - createTranscodeResourceHandler(const InputFileParameters& inputFileParameters, const TranscodeParameters& transcodeParameters, bool estimateContentLength) - { - return std::make_unique(inputFileParameters, transcodeParameters, estimateContentLength); - } - - // TODO set some nice HTTP return code - - TranscodeResourceHandler::TranscodeResourceHandler(const InputFileParameters& inputFileParameters, const TranscodeParameters& transcodeParameters, bool estimateContentLength) - : _estimatedContentLength {estimateContentLength ? std::make_optional(doEstimateContentLength(inputFileParameters, transcodeParameters)) : std::nullopt} - , _transcoder {inputFileParameters, transcodeParameters} - { - if (_estimatedContentLength) - LMS_LOG(TRANSCODE, DEBUG) << "Estimated content length = " << *_estimatedContentLength; - else - LMS_LOG(TRANSCODE, DEBUG) << "Not using estimated content length"; - } - - Wt::Http::ResponseContinuation* - TranscodeResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response) - { - if (_estimatedContentLength) - response.setContentLength(*_estimatedContentLength); - response.setMimeType(_transcoder.getOutputMimeType()); - LMS_LOG(TRANSCODE, DEBUG) << "Transcoder finished = " << _transcoder.finished() << ", total served bytes = " << _totalServedByteCount << ", mime type = " << _transcoder.getOutputMimeType(); - - if (_bytesReadyCount > 0) - { - LMS_LOG(TRANSCODE, DEBUG) << "Writing " << _bytesReadyCount << " bytes back to client"; - - response.out().write(reinterpret_cast(&_buffer[0]), _bytesReadyCount); - _totalServedByteCount += _bytesReadyCount; - _bytesReadyCount = 0; - } - - if (!_transcoder.finished()) - { - Wt::Http::ResponseContinuation *continuation {response.createContinuation()}; - continuation->waitForMoreData(); - _transcoder.asyncRead(_buffer.data(), _buffer.size(), [=](std::size_t nbBytesRead) - { - LMS_LOG(TRANSCODE, DEBUG) << "Have " << nbBytesRead << " more bytes to send back"; - - assert(_bytesReadyCount == 0); - _bytesReadyCount = nbBytesRead; - continuation->haveMoreData(); - }); - - return continuation; - } - else - { - // pad with 0 if necessary as duration may not be accurate - if (_estimatedContentLength && *_estimatedContentLength > _totalServedByteCount) - { - const std::size_t padSize {*_estimatedContentLength - _totalServedByteCount}; - - LMS_LOG(TRANSCODE, DEBUG) << "Adding " << padSize << " padding bytes"; - - for (std::size_t i {}; i < padSize; ++i) - response.out().put(0); - - _totalServedByteCount += padSize; - } - - LMS_LOG(TRANSCODE, DEBUG) << "Transcoding finished. Total served byte count = " << _totalServedByteCount; - } - - return {}; - } -} - diff --git a/src/libs/av/impl/Transcoder.cpp b/src/libs/av/impl/Transcoder.cpp index 5519021d..ff932e55 100644 --- a/src/libs/av/impl/Transcoder.cpp +++ b/src/libs/av/impl/Transcoder.cpp @@ -28,181 +28,191 @@ #include "utils/Logger.hpp" #include "utils/Service.hpp" -namespace Av { +namespace Av::Transcoding +{ #define LOG(sev) LMS_LOG(TRANSCODE, sev) << "[" << _debugId << "] - " -static std::atomic globalId {}; -static std::filesystem::path ffmpegPath; + static std::atomic globalId{}; + static std::filesystem::path ffmpegPath; -void -Transcoder::init() -{ - ffmpegPath = Service::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg"); - if (!std::filesystem::exists(ffmpegPath)) - throw Exception {"File '" + ffmpegPath.string() + "' does not exist!"}; -} + std::string_view formatToMimetype(OutputFormat format) + { + switch (format) + { + case OutputFormat::MP3: return "audio/mpeg"; + case OutputFormat::OGG_OPUS: return "audio/opus"; + case OutputFormat::MATROSKA_OPUS: return "audio/x-matroska"; + case OutputFormat::OGG_VORBIS: return "audio/ogg"; + case OutputFormat::WEBM_VORBIS: return "audio/webm"; + } -Transcoder::Transcoder(const InputFileParameters& inputFileParameters, const TranscodeParameters& transcodeParameters) -: _debugId {globalId++} -, _inputFileParameters {inputFileParameters} -, _transcodeParameters {transcodeParameters} -{ - start(); -} + throw Exception{ "Invalid encoding" }; + } -Transcoder::~Transcoder() = default; + void Transcoder::init() + { + ffmpegPath = Service::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg"); + if (!std::filesystem::exists(ffmpegPath)) + throw Exception{ "File '" + ffmpegPath.string() + "' does not exist!" }; + } -void -Transcoder::start() -{ - if (ffmpegPath.empty()) - init(); + Transcoder::Transcoder(const InputParameters& inputParameters, const OutputParameters& outputParameters) + : _debugId{ globalId++ } + , _inputParameters{ inputParameters } + , _outputParameters{ outputParameters } + { + start(); + } - try - { - if (!std::filesystem::exists(_inputFileParameters.trackPath)) - throw Exception {"File '" + _inputFileParameters.trackPath.string() + "' does not exist!"}; - else if (!std::filesystem::is_regular_file( _inputFileParameters.trackPath) ) - throw Exception {"File '" + _inputFileParameters.trackPath.string() + "' is not regular!"}; - } - catch (const std::filesystem::filesystem_error& e) - { - throw Exception {"File error '" + _inputFileParameters.trackPath.string() + "': " + e.what()}; - } + Transcoder::~Transcoder() = default; - LOG(INFO) << "Transcoding file '" << _inputFileParameters.trackPath.string() << "'"; + void Transcoder::start() + { + if (ffmpegPath.empty()) + init(); - std::vector args; + try + { + if (!std::filesystem::exists(_inputParameters.trackPath)) + throw Exception{ "File '" + _inputParameters.trackPath.string() + "' does not exist!" }; + else if (!std::filesystem::is_regular_file(_inputParameters.trackPath)) + throw Exception{ "File '" + _inputParameters.trackPath.string() + "' is not regular!" }; + } + catch (const std::filesystem::filesystem_error& e) + { + throw Exception{ "File error '" + _inputParameters.trackPath.string() + "': " + e.what() }; + } - args.emplace_back(ffmpegPath.string()); + LOG(INFO) << "Transcoding file '" << _inputParameters.trackPath.string() << "'"; - // Make sure: - // - we do not produce anything in the stderr output - // - we do not rely on input - // in order not to block the whole forked process - args.emplace_back("-loglevel"); - args.emplace_back("quiet"); - args.emplace_back("-nostdin"); + std::vector args; - // input Offset - { - args.emplace_back("-ss"); + args.emplace_back(ffmpegPath.string()); - std::ostringstream oss; - oss << std::fixed << std::showpoint << std::setprecision(3) << (_transcodeParameters.offset.count() / float {1000}); - args.emplace_back(oss.str()); - } + // Make sure: + // - we do not produce anything in the stderr output + // - we do not rely on input + // in order not to block the whole forked process + args.emplace_back("-loglevel"); + args.emplace_back("quiet"); + args.emplace_back("-nostdin"); - // Input file - args.emplace_back("-i"); - args.emplace_back(_inputFileParameters.trackPath.string()); + // input Offset + { + args.emplace_back("-ss"); - // Stream mapping, if set - if (_transcodeParameters.stream) - { - args.emplace_back("-map"); - args.emplace_back("0:" + std::to_string(*_transcodeParameters.stream)); - } + std::ostringstream oss; + oss << std::fixed << std::showpoint << std::setprecision(3) << (_outputParameters.offset.count() / float{ 1000 }); + args.emplace_back(oss.str()); + } - if (_transcodeParameters.stripMetadata) - { - // Strip metadata - args.emplace_back("-map_metadata"); - args.emplace_back("-1"); - } + // Input file + args.emplace_back("-i"); + args.emplace_back(_inputParameters.trackPath.string()); - // Skip video flows (including covers) - args.emplace_back("-vn"); + // Stream mapping, if set + if (_outputParameters.stream) + { + args.emplace_back("-map"); + args.emplace_back("0:" + std::to_string(*_outputParameters.stream)); + } - // Output bitrates - args.emplace_back("-b:a"); - args.emplace_back(std::to_string(_transcodeParameters.bitrate)); + if (_outputParameters.stripMetadata) + { + // Strip metadata + args.emplace_back("-map_metadata"); + args.emplace_back("-1"); + } - // Codecs and formats - switch (_transcodeParameters.format) - { - case Format::MP3: - args.emplace_back("-f"); - args.emplace_back("mp3"); - break; + // Skip video flows (including covers) + args.emplace_back("-vn"); - case Format::OGG_OPUS: - args.emplace_back("-acodec"); - args.emplace_back("libopus"); - args.emplace_back("-f"); - args.emplace_back("ogg"); - break; + // Output bitrates + args.emplace_back("-b:a"); + args.emplace_back(std::to_string(_outputParameters.bitrate)); - case Format::MATROSKA_OPUS: - args.emplace_back("-acodec"); - args.emplace_back("libopus"); - args.emplace_back("-f"); - args.emplace_back("matroska"); - break; + // Codecs and formats + switch (_outputParameters.format) + { + case OutputFormat::MP3: + args.emplace_back("-f"); + args.emplace_back("mp3"); + break; - case Format::OGG_VORBIS: - args.emplace_back("-acodec"); - args.emplace_back("libvorbis"); - args.emplace_back("-f"); - args.emplace_back("ogg"); - break; + case OutputFormat::OGG_OPUS: + args.emplace_back("-acodec"); + args.emplace_back("libopus"); + args.emplace_back("-f"); + args.emplace_back("ogg"); + break; - case Format::WEBM_VORBIS: - args.emplace_back("-acodec"); - args.emplace_back("libvorbis"); - args.emplace_back("-f"); - args.emplace_back("webm"); - break; + case OutputFormat::MATROSKA_OPUS: + args.emplace_back("-acodec"); + args.emplace_back("libopus"); + args.emplace_back("-f"); + args.emplace_back("matroska"); + break; - default: - throw Exception {"Unhandled format (" + std::to_string(static_cast(_transcodeParameters.format)) + ")"}; - } + case OutputFormat::OGG_VORBIS: + args.emplace_back("-acodec"); + args.emplace_back("libvorbis"); + args.emplace_back("-f"); + args.emplace_back("ogg"); + break; - _outputMimeType = formatToMimetype(_transcodeParameters.format); + case OutputFormat::WEBM_VORBIS: + args.emplace_back("-acodec"); + args.emplace_back("libvorbis"); + args.emplace_back("-f"); + args.emplace_back("webm"); + break; - args.emplace_back("pipe:1"); + default: + throw Exception{ "Unhandled format (" + std::to_string(static_cast(_outputParameters.format)) + ")" }; + } - LOG(DEBUG) << "Dumping args (" << args.size() << ")"; - for (const std::string& arg : args) - LOG(DEBUG) << "Arg = '" << arg << "'"; + _outputMimeType = formatToMimetype(_outputParameters.format); - // Caution: stdin must have been closed before - try - { - _childProcess = Service::get()->spawnChildProcess(ffmpegPath, args); - } - catch (ChildProcessException& exception) - { - throw Exception {"Cannot execute '" + ffmpegPath.string() + "': " + exception.what()}; - } -} + args.emplace_back("pipe:1"); -void -Transcoder::asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback readCallback) -{ - assert(_childProcess); + LOG(DEBUG) << "Dumping args (" << args.size() << ")"; + for (const std::string& arg : args) + LOG(DEBUG) << "Arg = '" << arg << "'"; - return _childProcess->asyncRead(buffer, bufferSize, [readCallback {std::move(readCallback)}](IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) - { - readCallback(nbBytesRead); - }); -} + // Caution: stdin must have been closed before + try + { + _childProcess = Service::get()->spawnChildProcess(ffmpegPath, args); + } + catch (ChildProcessException& exception) + { + throw Exception{ "Cannot execute '" + ffmpegPath.string() + "': " + exception.what() }; + } + } -std::size_t -Transcoder::readSome(std::byte* buffer, std::size_t bufferSize) -{ - assert(_childProcess); + void Transcoder::asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback readCallback) + { + assert(_childProcess); - return _childProcess->readSome(buffer, bufferSize); -} + return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) + { + readCallback(nbBytesRead); + }); + } -bool -Transcoder::finished() const -{ - assert(_childProcess); + std::size_t Transcoder::readSome(std::byte* buffer, std::size_t bufferSize) + { + assert(_childProcess); - return _childProcess->finished(); -} + return _childProcess->readSome(buffer, bufferSize); + } -} // namespace Transcode + bool Transcoder::finished() const + { + assert(_childProcess); + + return _childProcess->finished(); + } + +} // namespace Av::Transcoding diff --git a/src/libs/av/impl/Transcoder.hpp b/src/libs/av/impl/Transcoder.hpp index 0f6cf4c7..0bd3e1d2 100644 --- a/src/libs/av/impl/Transcoder.hpp +++ b/src/libs/av/impl/Transcoder.hpp @@ -22,47 +22,45 @@ #include #include -#include "av/TranscodeParameters.hpp" +#include "av/TranscodingParameters.hpp" #include "av/Types.hpp" class IChildProcess; -namespace Av +namespace Av::Transcoding { - class Transcoder - { - public: - Transcoder(const InputFileParameters& inputFileParameters, const TranscodeParameters& transcodeParameters); - ~Transcoder(); + class Transcoder + { + public: + Transcoder(const InputParameters& inputParameters, const OutputParameters& outputParameters); + ~Transcoder(); - Transcoder(const Transcoder&) = delete; - Transcoder& operator=(const Transcoder&) = delete; - Transcoder(Transcoder&&) = delete; - Transcoder& operator=(Transcoder&&) = delete; + Transcoder(const Transcoder&) = delete; + Transcoder& operator=(const Transcoder&) = delete; + Transcoder(Transcoder&&) = delete; + Transcoder& operator=(Transcoder&&) = delete; - // non blocking calls - using ReadCallback = std::function; - void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback); - std::size_t readSome(std::byte* buffer, std::size_t bufferSize); + // non blocking calls + using ReadCallback = std::function; + void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback); + std::size_t readSome(std::byte* buffer, std::size_t bufferSize); - const std::string& getOutputMimeType() const { return _outputMimeType; } - const TranscodeParameters& getParameters() const { return _transcodeParameters; } + const std::string& getOutputMimeType() const { return _outputMimeType; } + const OutputParameters& getOutputParameters() const { return _outputParameters; } - bool finished() const; + bool finished() const; - private: - static void init(); + private: + static void init(); - void start(); + void start(); - const std::size_t _debugId {}; - const InputFileParameters _inputFileParameters; - const TranscodeParameters _transcodeParameters; + const std::size_t _debugId{}; + const InputParameters _inputParameters; + const OutputParameters _outputParameters; + std::string _outputMimeType; - std::unique_ptr _childProcess; - - std::string _outputMimeType; - }; - -} // namespace Av + std::unique_ptr _childProcess; + }; +} // namespace Av::Transcoding diff --git a/src/libs/av/impl/TranscodingResourceHandler.cpp b/src/libs/av/impl/TranscodingResourceHandler.cpp new file mode 100644 index 00000000..e52552c3 --- /dev/null +++ b/src/libs/av/impl/TranscodingResourceHandler.cpp @@ -0,0 +1,103 @@ +/* + * 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 "TranscodingResourceHandler.hpp" +#include "utils/Logger.hpp" + +namespace Av::Transcoding +{ + namespace + { + std::size_t doEstimateContentLength(const InputParameters& inputParameters, const OutputParameters& outputParameters) + { + const std::size_t estimatedContentLength{ outputParameters.bitrate / 8 * static_cast(std::chrono::duration_cast(inputParameters.duration).count()) / 1000 }; + return estimatedContentLength; + } + } + + std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) + { + return std::make_unique(inputParameters, outputParameters, estimateContentLength); + } + + // TODO set some nice HTTP return code + + TranscodingResourceHandler::TranscodingResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) + : _estimatedContentLength{ estimateContentLength ? std::make_optional(doEstimateContentLength(inputParameters, outputParameters)) : std::nullopt } + , _transcoder{ inputParameters, outputParameters } + { + if (_estimatedContentLength) + LMS_LOG(TRANSCODE, DEBUG) << "Estimated content length = " << *_estimatedContentLength; + else + LMS_LOG(TRANSCODE, DEBUG) << "Not using estimated content length"; + } + + Wt::Http::ResponseContinuation* TranscodingResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response) + { + if (_estimatedContentLength) + response.setContentLength(*_estimatedContentLength); + response.setMimeType(_transcoder.getOutputMimeType()); + LMS_LOG(TRANSCODE, DEBUG) << "Transcoder finished = " << _transcoder.finished() << ", total served bytes = " << _totalServedByteCount << ", mime type = " << _transcoder.getOutputMimeType(); + + if (_bytesReadyCount > 0) + { + LMS_LOG(TRANSCODE, DEBUG) << "Writing " << _bytesReadyCount << " bytes back to client"; + + response.out().write(reinterpret_cast(&_buffer[0]), _bytesReadyCount); + _totalServedByteCount += _bytesReadyCount; + _bytesReadyCount = 0; + } + + if (!_transcoder.finished()) + { + Wt::Http::ResponseContinuation* continuation{ response.createContinuation() }; + continuation->waitForMoreData(); + _transcoder.asyncRead(_buffer.data(), _buffer.size(), [=](std::size_t nbBytesRead) + { + LMS_LOG(TRANSCODE, DEBUG) << "Have " << nbBytesRead << " more bytes to send back"; + + assert(_bytesReadyCount == 0); + _bytesReadyCount = nbBytesRead; + continuation->haveMoreData(); + }); + + return continuation; + } + else + { + // pad with 0 if necessary as duration may not be accurate + if (_estimatedContentLength && *_estimatedContentLength > _totalServedByteCount) + { + const std::size_t padSize{ *_estimatedContentLength - _totalServedByteCount }; + + LMS_LOG(TRANSCODE, DEBUG) << "Adding " << padSize << " padding bytes"; + + for (std::size_t i{}; i < padSize; ++i) + response.out().put(0); + + _totalServedByteCount += padSize; + } + + LMS_LOG(TRANSCODE, DEBUG) << "Transcoding finished. Total served byte count = " << _totalServedByteCount; + } + + return {}; + } +} + diff --git a/src/libs/av/impl/TranscodeResourceHandler.hpp b/src/libs/av/impl/TranscodingResourceHandler.hpp similarity index 82% rename from src/libs/av/impl/TranscodeResourceHandler.hpp rename to src/libs/av/impl/TranscodingResourceHandler.hpp index 71056bc2..6821d42a 100644 --- a/src/libs/av/impl/TranscodeResourceHandler.hpp +++ b/src/libs/av/impl/TranscodingResourceHandler.hpp @@ -23,16 +23,16 @@ #include #include -#include "av/TranscodeParameters.hpp" +#include "av/TranscodingParameters.hpp" #include "utils/IResourceHandler.hpp" #include "Transcoder.hpp" -namespace Av +namespace Av::Transcoding { - class TranscodeResourceHandler final : public IResourceHandler + class TranscodingResourceHandler final : public IResourceHandler { public: - TranscodeResourceHandler(const InputFileParameters& inputFileParameters, const TranscodeParameters& parameters, bool estimateContentLength); + TranscodingResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength); private: Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override; diff --git a/src/libs/av/impl/Types.cpp b/src/libs/av/impl/Types.cpp deleted file mode 100644 index 713491bc..00000000 --- a/src/libs/av/impl/Types.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019 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 "av/Types.hpp" - -namespace Av -{ - - std::string_view - formatToMimetype(Format format) - { - switch (format) - { - 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 Exception {"Invalid encoding"}; - } - -} - diff --git a/src/libs/av/include/av/IAudioFile.hpp b/src/libs/av/include/av/IAudioFile.hpp index b7f92a7b..1ce66e57 100644 --- a/src/libs/av/include/av/IAudioFile.hpp +++ b/src/libs/av/include/av/IAudioFile.hpp @@ -34,6 +34,29 @@ namespace Av { + // List should be sync with the codecs shipped in the lms's docker version + enum class DecodingCodec + { + UNKNOWN, + MP3, + AAC, + AC3, + VORBIS, + WMAV1, + WMAV2, + FLAC, // Flac + ALAC, // Apple Lossless Audio Codec (ALAC) + WAVPACK, // WavPack + MUSEPACK7, // Musepack + MUSEPACK8, + APE, // // Monkey's Audio + EAC3, // Enhanced AC-3 + MP4ALS, // MPEG-4 Audio Lossless Coding + OPUS, // Opus + SHORTEN, // Shorten (shn) + // TODO add PCM codecs + }; + struct Picture { std::string mimeType; @@ -52,7 +75,8 @@ namespace Av { size_t index{}; std::size_t bitrate{}; - std::string codec; + DecodingCodec codec; + std::string codecName; }; class IAudioFile diff --git a/src/libs/av/include/av/TranscodeParameters.hpp b/src/libs/av/include/av/TranscodingParameters.hpp similarity index 51% rename from src/libs/av/include/av/TranscodeParameters.hpp rename to src/libs/av/include/av/TranscodingParameters.hpp index f709fef8..13d8f961 100644 --- a/src/libs/av/include/av/TranscodeParameters.hpp +++ b/src/libs/av/include/av/TranscodingParameters.hpp @@ -25,21 +25,32 @@ #include "Types.hpp" -namespace Av +namespace Av::Transcoding { - struct InputFileParameters - { - std::filesystem::path trackPath; - std::chrono::milliseconds duration; - }; + struct InputParameters + { + std::filesystem::path trackPath; + std::chrono::milliseconds duration; // used to estimate content length + }; - struct TranscodeParameters - { - Format format; - std::size_t bitrate {128000}; - std::optional stream; // Id of the stream to be transcoded (auto detect by default) - std::chrono::milliseconds offset {0}; - bool stripMetadata {true}; - }; -} // namespace Av + enum class OutputFormat + { + MP3, + OGG_OPUS, + MATROSKA_OPUS, + OGG_VORBIS, + WEBM_VORBIS, + }; + + std::string_view toMimetype(OutputFormat format); + + struct OutputParameters + { + OutputFormat format; + std::size_t bitrate{ 128000 }; + std::optional stream; // Id of the stream to be transcoded (auto detect by default) + std::chrono::milliseconds offset{ 0 }; + bool stripMetadata{ true }; + }; +} // namespace Av::Transcoding diff --git a/src/libs/av/include/av/TranscodeResourceHandlerCreator.hpp b/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp similarity index 75% rename from src/libs/av/include/av/TranscodeResourceHandlerCreator.hpp rename to src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp index 8b232d8a..16e0fcbf 100644 --- a/src/libs/av/include/av/TranscodeResourceHandlerCreator.hpp +++ b/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp @@ -23,11 +23,10 @@ #include "utils/IResourceHandler.hpp" -namespace Av +namespace Av::Transcoding { - struct InputFileParameters; - struct TranscodeParameters; + struct InputParameters; + struct OutputParameters; - std::unique_ptr createTranscodeResourceHandler(const InputFileParameters& inputFileParameters, const TranscodeParameters& parameters, bool estimateContentLength); + std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength); } - diff --git a/src/libs/av/include/av/Types.hpp b/src/libs/av/include/av/Types.hpp index 166a19da..249529e3 100644 --- a/src/libs/av/include/av/Types.hpp +++ b/src/libs/av/include/av/Types.hpp @@ -19,27 +19,13 @@ #pragma once -#include - #include "utils/Exception.hpp" -namespace Av { - - class Exception : public LmsException - { - public: - using LmsException::LmsException; - }; - - enum class Format - { - MP3, - OGG_OPUS, - MATROSKA_OPUS, - OGG_VORBIS, - WEBM_VORBIS, - }; - - std::string_view formatToMimetype(Format format); +namespace Av +{ + class Exception : public LmsException + { + public: + using LmsException::LmsException; + }; } - diff --git a/src/libs/services/database/impl/User.cpp b/src/libs/services/database/impl/User.cpp index ca833435..5831b86b 100644 --- a/src/libs/services/database/impl/User.cpp +++ b/src/libs/services/database/impl/User.cpp @@ -80,10 +80,10 @@ namespace Database { .resultValue(); } - void User::setSubsonicDefaultTranscodeBitrate(Bitrate bitrate) + void User::setSubsonicDefaultTranscodingOutputBitrate(Bitrate bitrate) { assert(isAudioBitrateAllowed(bitrate)); - _subsonicDefaultTranscodeBitrate = bitrate; + _subsonicDefaultTranscodingOutputBitrate = bitrate; } void User::clearAuthTokens() diff --git a/src/libs/services/database/include/services/database/Types.hpp b/src/libs/services/database/include/services/database/Types.hpp index 5cc16877..fc261058 100644 --- a/src/libs/services/database/include/services/database/Types.hpp +++ b/src/libs/services/database/include/services/database/Types.hpp @@ -161,8 +161,8 @@ namespace Database Writer = 10, }; - // User selectable audio file formats - enum class AudioFormat + // User selectable transcoding output formats + enum class TranscodingOutputFormat { MP3 = 1, OGG_OPUS = 2, diff --git a/src/libs/services/database/include/services/database/User.hpp b/src/libs/services/database/include/services/database/User.hpp index 42938e4f..b0bc3870 100644 --- a/src/libs/services/database/include/services/database/User.hpp +++ b/src/libs/services/database/include/services/database/User.hpp @@ -58,8 +58,8 @@ namespace Database { static inline constexpr std::size_t MinNameLength{ 3 }; static inline constexpr std::size_t MaxNameLength{ 15 }; - static inline constexpr AudioFormat defaultSubsonicTranscodeFormat{ AudioFormat::OGG_OPUS }; - static inline constexpr Bitrate defaultSubsonicTranscodeBitrate{ 128000 }; + static inline constexpr TranscodingOutputFormat defaultSubsonicTranscodingOutputFormat{ TranscodingOutputFormat::OGG_OPUS }; + static inline constexpr Bitrate defaultSubsonicTranscodingOutputBitrate{ 128000 }; static inline constexpr UITheme defaultUITheme{ UITheme::Dark }; static inline constexpr SubsonicArtistListMode defaultSubsonicArtistListMode{ SubsonicArtistListMode::AllArtists }; static inline constexpr ScrobblingBackend defaultScrobblingBackend{ ScrobblingBackend::Internal }; @@ -83,8 +83,8 @@ namespace Database { 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 setSubsonicDefaultTranscodintOutputFormat(TranscodingOutputFormat encoding) { _subsonicDefaultTranscodingOutputFormat = encoding; } + void setSubsonicDefaultTranscodingOutputBitrate(Bitrate bitrate); void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; } void setRadio(bool val) { _radio = val; } void setRepeatAll(bool val) { _repeatAll = val; } @@ -99,8 +99,8 @@ namespace Database { 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; } + TranscodingOutputFormat getSubsonicDefaultTranscodingOutputFormat() const { return _subsonicDefaultTranscodingOutputFormat; } + Bitrate getSubsonicDefaultTranscodingOutputBitrate() const { return _subsonicDefaultTranscodingOutputBitrate; } std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } bool isRepeatAllSet() const { return _repeatAll; } bool isRadioSet() const { return _radio; } @@ -118,8 +118,8 @@ namespace Database { 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, _subsonicDefaultTranscodingOutputFormat, "subsonic_default_transcode_format"); + Wt::Dbo::field(a, _subsonicDefaultTranscodingOutputBitrate, "subsonic_default_transcode_bitrate"); Wt::Dbo::field(a, _subsonicArtistListMode, "subsonic_artist_list_mode"); Wt::Dbo::field(a, _uiTheme, "ui_theme"); Wt::Dbo::field(a, _feedbackBackend, "feedback_backend"); @@ -153,8 +153,8 @@ namespace Database { // User defined settings SubsonicArtistListMode _subsonicArtistListMode{ defaultSubsonicArtistListMode }; - AudioFormat _subsonicDefaultTranscodeFormat{ defaultSubsonicTranscodeFormat }; - int _subsonicDefaultTranscodeBitrate{ defaultSubsonicTranscodeBitrate }; + TranscodingOutputFormat _subsonicDefaultTranscodingOutputFormat{ defaultSubsonicTranscodingOutputFormat }; + int _subsonicDefaultTranscodingOutputBitrate{ defaultSubsonicTranscodingOutputBitrate }; // User's dynamic data (UI) int _curPlayingTrackPos{}; // Current track position in queue diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp index ce3d3fab..80aaf1de 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp @@ -21,8 +21,8 @@ #include "av/IAudioFile.hpp" #include "av/RawResourceHandlerCreator.hpp" -#include "av/TranscodeParameters.hpp" -#include "av/TranscodeResourceHandlerCreator.hpp" +#include "av/TranscodingParameters.hpp" +#include "av/TranscodingResourceHandlerCreator.hpp" #include "av/Types.hpp" #include "services/cover/ICoverService.hpp" #include "services/database/Session.hpp" @@ -41,37 +41,37 @@ namespace API::Subsonic using namespace Database; namespace { - std::optional subsonicStreamFormatToAvFormat(std::string_view format) + std::optional subsonicStreamFormatToAvFormat(std::string_view format) { - for (const auto& [str, avFormat] : std::initializer_list>{ - {"mp3", Av::Format::MP3}, - {"opus", Av::Format::OGG_OPUS}, - {"vorbis", Av::Format::OGG_VORBIS}, + for (const auto& [str, avFormat] : std::initializer_list>{ + {"mp3", Av::Transcoding::OutputFormat::MP3}, + {"opus", Av::Transcoding::OutputFormat::OGG_OPUS}, + {"vorbis", Av::Transcoding::OutputFormat::OGG_VORBIS}, }) { - if (StringUtils::stringCaseInsensitiveEqual("str", format)) + if (StringUtils::stringCaseInsensitiveEqual(str, format)) return avFormat; } return std::nullopt; } - Av::Format userTranscodeFormatToAvFormat(AudioFormat format) + Av::Transcoding::OutputFormat userTranscodeFormatToAvFormat(Database::TranscodingOutputFormat format) { switch (format) { - 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; + case Database::TranscodingOutputFormat::MP3: return Av::Transcoding::OutputFormat::MP3; + case Database::TranscodingOutputFormat::OGG_OPUS: return Av::Transcoding::OutputFormat::OGG_OPUS; + case Database::TranscodingOutputFormat::MATROSKA_OPUS: return Av::Transcoding::OutputFormat::MATROSKA_OPUS; + case Database::TranscodingOutputFormat::OGG_VORBIS: return Av::Transcoding::OutputFormat::OGG_VORBIS; + case Database::TranscodingOutputFormat::WEBM_VORBIS: return Av::Transcoding::OutputFormat::WEBM_VORBIS; } - return Av::Format::OGG_OPUS; + return Av::Transcoding::OutputFormat::OGG_OPUS; } struct StreamParameters { - Av::InputFileParameters inputFileParameters; - std::optional transcodeParameters; + Av::Transcoding::InputParameters inputParameters; + std::optional outputParameters; bool estimateContentLength{}; }; @@ -98,22 +98,22 @@ namespace API::Subsonic if (!track) throw RequestedDataNotFoundError{}; - parameters.inputFileParameters.trackPath = track->getPath(); - parameters.inputFileParameters.duration = track->getDuration(); + parameters.inputParameters.trackPath = track->getPath(); + parameters.inputParameters.duration = track->getDuration(); bitrate = track->getBitrate() / 1000; } if (format == "raw") // raw => no transcode return parameters; - const auto audioFile{ Av::parseAudioFile(parameters.inputFileParameters.trackPath) }; + const auto audioFile{ Av::parseAudioFile(parameters.inputParameters.trackPath) }; // check if transcode is really needed or not // same format as requested, bitrate is lower than requested => no need to transcode if (const auto streamInfo{ audioFile->getBestStreamInfo() }) { // assume reported codec is "mp3", "opus", "vorbis", etc. - if (StringUtils::stringCaseInsensitiveEqual(streamInfo->codec, format) && (maxBitRate == 0 || (bitrate != 0 && bitrate <= maxBitRate))) + if (StringUtils::stringCaseInsensitiveEqual(streamInfo->codecName, format) && (maxBitRate == 0 || (bitrate != 0 && bitrate <= maxBitRate))) { LMS_LOG(API_SUBSONIC, DEBUG) << "stream parameters are compatible with actual file: no transcode"; return parameters; @@ -126,19 +126,19 @@ namespace API::Subsonic if (!user) throw UserNotAuthorizedError{}; - Av::TranscodeParameters& transcodeParameters{ parameters.transcodeParameters.emplace() }; + Av::Transcoding::OutputParameters& outputParameters{ parameters.outputParameters.emplace() }; - transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) - transcodeParameters.offset = std::chrono::seconds{ timeOffset }; + outputParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) + outputParameters.offset = std::chrono::seconds{ timeOffset }; - if (std::optional requestedFormat{ subsonicStreamFormatToAvFormat(format) }) - transcodeParameters.format = *requestedFormat; + if (std::optional requestedFormat{ subsonicStreamFormatToAvFormat(format) }) + outputParameters.format = *requestedFormat; else - transcodeParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicDefaultTranscodeFormat()); + outputParameters.format = userTranscodeFormatToAvFormat(user->getSubsonicDefaultTranscodingOutputFormat()); - transcodeParameters.bitrate = user->getSubsonicDefaultTranscodeBitrate(); + outputParameters.bitrate = user->getSubsonicDefaultTranscodingOutputBitrate(); if (maxBitRate != 0) - transcodeParameters.bitrate = Utils::clamp(transcodeParameters.bitrate, std::size_t{ 48000 }, maxBitRate * 1000); + outputParameters.bitrate = Utils::clamp(outputParameters.bitrate, std::size_t{ 48000 }, maxBitRate * 1000); return parameters; } @@ -187,10 +187,10 @@ namespace API::Subsonic if (!continuation) { StreamParameters streamParameters{ getStreamParameters(context) }; - if (streamParameters.transcodeParameters) - resourceHandler = Av::createTranscodeResourceHandler(streamParameters.inputFileParameters, *streamParameters.transcodeParameters, streamParameters.estimateContentLength); + if (streamParameters.outputParameters) + resourceHandler = Av::Transcoding::createResourceHandler(streamParameters.inputParameters, *streamParameters.outputParameters, streamParameters.estimateContentLength); else - resourceHandler = Av::createRawResourceHandler(streamParameters.inputFileParameters.trackPath); + resourceHandler = Av::createRawResourceHandler(streamParameters.inputParameters.trackPath); } else { diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 4544ea70..66ef8f57 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -46,15 +46,15 @@ namespace API::Subsonic namespace { - std::string_view formatToSuffix(AudioFormat format) + std::string_view formatToSuffix(TranscodingOutputFormat format) { switch (format) { - case AudioFormat::MP3: return "mp3"; - case AudioFormat::OGG_OPUS: return "opus"; - case AudioFormat::MATROSKA_OPUS: return "mka"; - case AudioFormat::OGG_VORBIS: return "ogg"; - case AudioFormat::WEBM_VORBIS: return "webm"; + case TranscodingOutputFormat::MP3: return "mp3"; + case TranscodingOutputFormat::OGG_OPUS: return "opus"; + case TranscodingOutputFormat::MATROSKA_OPUS: return "mka"; + case TranscodingOutputFormat::OGG_VORBIS: return "ogg"; + case TranscodingOutputFormat::WEBM_VORBIS: return "webm"; } return ""; @@ -125,7 +125,7 @@ namespace API::Subsonic } { - const std::string fileSuffix{ formatToSuffix(user->getSubsonicDefaultTranscodeFormat()) }; + const std::string fileSuffix{ formatToSuffix(user->getSubsonicDefaultTranscodingOutputFormat()) }; trackResponse.setAttribute("transcodedSuffix", fileSuffix); trackResponse.setAttribute("transcodedContentType", Av::getMimeType(std::filesystem::path{ "." + fileSuffix })); } diff --git a/src/lms/ui/MediaPlayer.hpp b/src/lms/ui/MediaPlayer.hpp index 544a307e..e83806a4 100644 --- a/src/lms/ui/MediaPlayer.hpp +++ b/src/lms/ui/MediaPlayer.hpp @@ -19,118 +19,118 @@ #pragma once -#include #include #include #include +#include #include #include #include "services/database/TrackId.hpp" #include "services/database/Types.hpp" -namespace UserInterface { - -class AudioFileResource; -class AudioTranscodeResource; - -class MediaPlayer : public Wt::WTemplate +namespace UserInterface { - public: - using Bitrate = Database::Bitrate; - using Format = Database::AudioFormat; - using Gain = float; + class AudioFileResource; + class AudioTranscodeResource; - // Do not change enum values as they may be stored locally in browser - // Keep it sync with LMS.mediaplayer js + class MediaPlayer : public Wt::WTemplate + { + public: + using Bitrate = Database::Bitrate; + using Format = Database::TranscodingOutputFormat; + using Gain = float; - struct Settings - { - struct Transcode - { - enum class Mode - { - Never = 0, - Always = 1, - IfFormatNotSupported = 2, - }; - static inline constexpr Mode defaultMode {Mode::IfFormatNotSupported}; - static inline constexpr Format defaultFormat {Format::OGG_OPUS}; - static inline constexpr Bitrate defaultBitrate {128000}; + // Do not change enum values as they may be stored locally in browser + // Keep it sync with LMS.mediaplayer js - Mode mode {defaultMode}; - Format format {defaultFormat}; - Bitrate bitrate {defaultBitrate}; - }; + struct Settings + { + struct Transcode + { + enum class Mode + { + Never = 0, + Always = 1, + IfFormatNotSupported = 2, + }; + static inline constexpr Mode defaultMode{ Mode::IfFormatNotSupported }; + static inline constexpr Format defaultFormat{ Format::OGG_OPUS }; + static inline constexpr Bitrate defaultBitrate{ 128000 }; - struct ReplayGain - { - enum class Mode - { - None = 0, - Auto = 1, - Track = 2, - Release = 3, - }; + Mode mode{ defaultMode }; + Format format{ defaultFormat }; + Bitrate bitrate{ defaultBitrate }; + }; - static inline constexpr Mode defaultMode {Mode::None}; - static inline constexpr Gain defaultPreAmpGain {}; - static inline constexpr Gain minPreAmpGain {-15}; - static inline constexpr Gain maxPreAmpGain {15}; + struct ReplayGain + { + enum class Mode + { + None = 0, + Auto = 1, + Track = 2, + Release = 3, + }; - Mode mode {defaultMode}; - Gain preAmpGain {defaultPreAmpGain}; - Gain preAmpGainIfNoInfo {defaultPreAmpGain}; - }; + static inline constexpr Mode defaultMode{ Mode::None }; + static inline constexpr Gain defaultPreAmpGain{}; + static inline constexpr Gain minPreAmpGain{ -15 }; + static inline constexpr Gain maxPreAmpGain{ 15 }; - Transcode transcode; - ReplayGain replayGain; - }; + Mode mode{ defaultMode }; + Gain preAmpGain{ defaultPreAmpGain }; + Gain preAmpGainIfNoInfo{ defaultPreAmpGain }; + }; - MediaPlayer(); + Transcode transcode; + ReplayGain replayGain; + }; - MediaPlayer(const MediaPlayer&) = delete; - MediaPlayer(MediaPlayer&&) = delete; - MediaPlayer& operator=(const MediaPlayer&) = delete; - MediaPlayer& operator=(MediaPlayer&&) = delete; + MediaPlayer(); - std::optional getTrackLoaded() const { return _trackIdLoaded; } + MediaPlayer(const MediaPlayer&) = delete; + MediaPlayer(MediaPlayer&&) = delete; + MediaPlayer& operator=(const MediaPlayer&) = delete; + MediaPlayer& operator=(MediaPlayer&&) = delete; - void loadTrack(Database::TrackId trackId, bool play, float replayGain); - void stop(); + std::optional getTrackLoaded() const { return _trackIdLoaded; } - std::optional getSettings() const { return _settings; } - void setSettings(const Settings& settings); + void loadTrack(Database::TrackId trackId, bool play, float replayGain); + void stop(); - void onPlayQueueUpdated(std::size_t trackCount); + std::optional getSettings() const { return _settings; } + void setSettings(const Settings& settings); - // Signals - Wt::JSignal<> playPrevious; - Wt::JSignal<> playNext; - Wt::Signal trackLoaded; - Wt::Signal<> settingsLoaded; + void onPlayQueueUpdated(std::size_t trackCount); - Wt::JSignal scrobbleListenNow; - Wt::JSignal scrobbleListenFinished; + // Signals + Wt::JSignal<> playPrevious; + Wt::JSignal<> playNext; + Wt::Signal trackLoaded; + Wt::Signal<> settingsLoaded; - Wt::JSignal<> playbackEnded; + Wt::JSignal scrobbleListenNow; + Wt::JSignal scrobbleListenFinished; - private: - std::unique_ptr _audioFileResource; - std::unique_ptr _audioTranscodeResource; + Wt::JSignal<> playbackEnded; - std::optional _trackIdLoaded; - std::optional _settings; + private: + std::unique_ptr _audioFileResource; + std::unique_ptr _audioTranscodeResource; - Wt::JSignal _settingsLoaded; + std::optional _trackIdLoaded; + std::optional _settings; - Wt::WText* _title {}; - Wt::WAnchor* _release {}; - Wt::WText* _separator {}; - Wt::WAnchor* _artist {}; - Wt::WPushButton* _playQueue {}; -}; + Wt::JSignal _settingsLoaded; + + Wt::WText* _title{}; + Wt::WAnchor* _release{}; + Wt::WText* _separator{}; + Wt::WAnchor* _artist{}; + Wt::WPushButton* _playQueue{}; + }; } // namespace UserInterface diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp index 03224b66..713d827a 100644 --- a/src/lms/ui/SettingsView.cpp +++ b/src/lms/ui/SettingsView.cpp @@ -59,8 +59,8 @@ namespace UserInterface { static inline const Field ReplayGainPreAmpGainField{ "replaygain-preamp" }; static inline const Field ReplayGainPreAmpGainIfNoInfoField{ "replaygain-preamp-no-rg-info" }; static inline const Field SubsonicArtistListModeField{ "subsonic-artist-list-mode" }; - static inline const Field SubsonicTranscodeFormatField{ "subsonic-transcode-format" }; - static inline const Field SubsonicTranscodeBitrateField{ "subsonic-transcode-bitrate" }; + static inline const Field SubsonicTranscodingOutputFormatField{ "subsonic-transcode-format" }; + static inline const Field SubsonicTranscodingOutputBitrateField{ "subsonic-transcode-bitrate" }; static inline const Field FeedbackBackendField{ "feedback-backend" }; static inline const Field ScrobblingBackendField{ "scrobbling-backend" }; static inline const Field ListenBrainzTokenField{ "listenbrainz-token" }; @@ -68,7 +68,7 @@ namespace UserInterface { static inline const Field PasswordField{ "password" }; static inline const Field PasswordConfirmField{ "password-confirm" }; - using TranscodeModeModel = ValueStringModel; + using TranscodingModeModel = ValueStringModel; using ReplayGainModeModel = ValueStringModel; using FeedbackBackendModel = ValueStringModel; using ScrobblingBackendModel = ValueStringModel; @@ -85,8 +85,8 @@ namespace UserInterface { addField(ReplayGainModeField); addField(ReplayGainPreAmpGainField); addField(ReplayGainPreAmpGainIfNoInfoField); - addField(SubsonicTranscodeBitrateField); - addField(SubsonicTranscodeFormatField); + addField(SubsonicTranscodingOutputBitrateField); + addField(SubsonicTranscodingOutputFormatField); addField(FeedbackBackendField); addField(ScrobblingBackendField); addField(ListenBrainzTokenField); @@ -116,15 +116,15 @@ namespace UserInterface { setValidator(ReplayGainPreAmpGainField, createPreAmpValidator()); setValidator(ReplayGainPreAmpGainIfNoInfoField, createPreAmpValidator()); - setValidator(SubsonicTranscodeBitrateField, createMandatoryValidator()); - setValidator(SubsonicTranscodeFormatField, createMandatoryValidator()); + setValidator(SubsonicTranscodingOutputBitrateField, createMandatoryValidator()); + setValidator(SubsonicTranscodingOutputFormatField, createMandatoryValidator()); loadData(); } - std::shared_ptr getTranscodeModeModel() { return _transcodeModeModel; } - std::shared_ptr getTranscodeBitrateModel() { return _transcodeBitrateModel; } - std::shared_ptr getTranscodeFormatModel() { return _transcodeFormatModel; } + std::shared_ptr getTranscodingModeModel() { return _transcodingModeModeModel; } + std::shared_ptr getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; } + std::shared_ptr getTranscodeFormatModel() { return _transcodingOutputFormatModel; } std::shared_ptr getReplayGainModeModel() { return _replayGainModeModel; } std::shared_ptr getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; } std::shared_ptr getFeedbackBackendModel() { return _feedbackBackendModel; } @@ -139,17 +139,17 @@ namespace UserInterface { { MediaPlayer::Settings settings; - auto transcodeModeRow{ _transcodeModeModel->getRowFromString(valueText(TranscodeModeField)) }; + auto transcodeModeRow{ _transcodingModeModeModel->getRowFromString(valueText(TranscodeModeField)) }; if (transcodeModeRow) - settings.transcode.mode = _transcodeModeModel->getValue(*transcodeModeRow); + settings.transcode.mode = _transcodingModeModeModel->getValue(*transcodeModeRow); - auto transcodeFormatRow{ _transcodeFormatModel->getRowFromString(valueText(TranscodeFormatField)) }; + auto transcodeFormatRow{ _transcodingOutputFormatModel->getRowFromString(valueText(TranscodeFormatField)) }; if (transcodeFormatRow) - settings.transcode.format = _transcodeFormatModel->getValue(*transcodeFormatRow); + settings.transcode.format = _transcodingOutputFormatModel->getValue(*transcodeFormatRow); - auto transcodeBitrateRow{ _transcodeBitrateModel->getRowFromString(valueText(TranscodeBitrateField)) }; + auto transcodeBitrateRow{ _transcodingOutputBitrateModel->getRowFromString(valueText(TranscodeBitrateField)) }; if (transcodeBitrateRow) - settings.transcode.bitrate = _transcodeBitrateModel->getValue(*transcodeBitrateRow); + settings.transcode.bitrate = _transcodingOutputBitrateModel->getValue(*transcodeBitrateRow); auto replayGainModeRow{ _replayGainModeModel->getRowFromString(valueText(ReplayGainModeField)) }; if (replayGainModeRow) @@ -162,13 +162,13 @@ namespace UserInterface { } { - auto subsonicTranscodeBitrateRow{ _transcodeBitrateModel->getRowFromString(valueText(SubsonicTranscodeBitrateField)) }; - if (subsonicTranscodeBitrateRow) - user.modify()->setSubsonicDefaultTranscodeBitrate(_transcodeBitrateModel->getValue(*subsonicTranscodeBitrateRow)); + auto subsonicTranscodingOutputBitrateRow{ _transcodingOutputBitrateModel->getRowFromString(valueText(SubsonicTranscodingOutputBitrateField)) }; + if (subsonicTranscodingOutputBitrateRow) + user.modify()->setSubsonicDefaultTranscodingOutputBitrate(_transcodingOutputBitrateModel->getValue(*subsonicTranscodingOutputBitrateRow)); - auto subsonicTranscodeFormatRow{ _transcodeFormatModel->getRowFromString(valueText(SubsonicTranscodeFormatField)) }; - if (subsonicTranscodeFormatRow) - user.modify()->setSubsonicDefaultTranscodeFormat(_transcodeFormatModel->getValue(*subsonicTranscodeFormatRow)); + auto subsonicTranscodingOutputFormatRow{ _transcodingOutputFormatModel->getRowFromString(valueText(SubsonicTranscodingOutputFormatField)) }; + if (subsonicTranscodingOutputFormatRow) + user.modify()->setSubsonicDefaultTranscodintOutputFormat(_transcodingOutputFormatModel->getValue(*subsonicTranscodingOutputFormatRow)); auto subsonicArtistListModeRow{ _subsonicArtistListModeModel->getRowFromString(valueText(SubsonicArtistListModeField)) }; if (subsonicArtistListModeRow) @@ -205,17 +205,17 @@ namespace UserInterface { { const auto settings{ *LmsApp->getMediaPlayer().getSettings() }; - auto transcodeModeRow{ _transcodeModeModel->getRowFromValue(settings.transcode.mode) }; + auto transcodeModeRow{ _transcodingModeModeModel->getRowFromValue(settings.transcode.mode) }; if (transcodeModeRow) - setValue(TranscodeModeField, _transcodeModeModel->getString(*transcodeModeRow)); + setValue(TranscodeModeField, _transcodingModeModeModel->getString(*transcodeModeRow)); - auto transcodeFormatRow{ _transcodeFormatModel->getRowFromValue(settings.transcode.format) }; + auto transcodeFormatRow{ _transcodingOutputFormatModel->getRowFromValue(settings.transcode.format) }; if (transcodeFormatRow) - setValue(TranscodeFormatField, _transcodeFormatModel->getString(*transcodeFormatRow)); + setValue(TranscodeFormatField, _transcodingOutputFormatModel->getString(*transcodeFormatRow)); - auto transcodeBitrateRow{ _transcodeBitrateModel->getRowFromValue(settings.transcode.bitrate) }; + auto transcodeBitrateRow{ _transcodingOutputBitrateModel->getRowFromValue(settings.transcode.bitrate) }; if (transcodeBitrateRow) - setValue(TranscodeBitrateField, _transcodeBitrateModel->getString(*transcodeBitrateRow)); + setValue(TranscodeBitrateField, _transcodingOutputBitrateModel->getString(*transcodeBitrateRow)); { const bool usesTranscode{ settings.transcode.mode != MediaPlayer::Settings::Transcode::Mode::Never }; @@ -232,13 +232,13 @@ namespace UserInterface { } { - auto subsonicTranscodeBitrateRow{ _transcodeBitrateModel->getRowFromValue(user->getSubsonicDefaultTranscodeBitrate()) }; - if (subsonicTranscodeBitrateRow) - setValue(SubsonicTranscodeBitrateField, _transcodeBitrateModel->getString(*subsonicTranscodeBitrateRow)); + auto subsonicTranscodingOutputBitrateRow{ _transcodingOutputBitrateModel->getRowFromValue(user->getSubsonicDefaultTranscodingOutputBitrate()) }; + if (subsonicTranscodingOutputBitrateRow) + setValue(SubsonicTranscodingOutputBitrateField, _transcodingOutputBitrateModel->getString(*subsonicTranscodingOutputBitrateRow)); - auto subsonicTranscodeFormatRow{ _transcodeFormatModel->getRowFromValue(user->getSubsonicDefaultTranscodeFormat()) }; - if (subsonicTranscodeFormatRow) - setValue(SubsonicTranscodeFormatField, _transcodeFormatModel->getString(*subsonicTranscodeFormatRow)); + auto subsonicTranscodingOutputFormatRow{ _transcodingOutputFormatModel->getRowFromValue(user->getSubsonicDefaultTranscodingOutputFormat()) }; + if (subsonicTranscodingOutputFormatRow) + setValue(SubsonicTranscodingOutputFormatField, _transcodingOutputFormatModel->getString(*subsonicTranscodingOutputFormatRow)); auto subsonicArtistListModeRow{ _subsonicArtistListModeModel->getRowFromValue(user->getSubsonicArtistListMode()) }; if (subsonicArtistListModeRow) @@ -259,7 +259,7 @@ namespace UserInterface { } { - const bool usesListenBrainz{ user->getScrobblingBackend() == ScrobblingBackend::ListenBrainz || user->getFeedbackBackend() == FeedbackBackend::ListenBrainz}; + const bool usesListenBrainz{ user->getScrobblingBackend() == ScrobblingBackend::ListenBrainz || user->getFeedbackBackend() == FeedbackBackend::ListenBrainz }; setReadOnly(SettingsModel::ListenBrainzTokenField, !usesListenBrainz); validator(SettingsModel::ListenBrainzTokenField)->setMandatory(usesListenBrainz); } @@ -315,23 +315,23 @@ namespace UserInterface { void initializeModels() { - _transcodeModeModel = std::make_shared(); - _transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.always"), MediaPlayer::Settings::Transcode::Mode::Always); - _transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.never"), MediaPlayer::Settings::Transcode::Mode::Never); - _transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.if-format-not-supported"), MediaPlayer::Settings::Transcode::Mode::IfFormatNotSupported); + _transcodingModeModeModel = std::make_shared(); + _transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.always"), MediaPlayer::Settings::Transcode::Mode::Always); + _transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.never"), MediaPlayer::Settings::Transcode::Mode::Never); + _transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.if-format-not-supported"), MediaPlayer::Settings::Transcode::Mode::IfFormatNotSupported); - _transcodeBitrateModel = std::make_shared>(); + _transcodingOutputBitrateModel = std::make_shared>(); visitAllowedAudioBitrates([&](const Bitrate bitrate) { - _transcodeBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate); + _transcodingOutputBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate); }); - _transcodeFormatModel = std::make_shared>(); - _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); + _transcodingOutputFormatModel = std::make_shared>(); + _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.mp3"), TranscodingOutputFormat::MP3); + _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.ogg_opus"), TranscodingOutputFormat::OGG_OPUS); + _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.matroska_opus"), TranscodingOutputFormat::MATROSKA_OPUS); + _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.ogg_vorbis"), TranscodingOutputFormat::OGG_VORBIS); + _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.webm_vorbis"), TranscodingOutputFormat::WEBM_VORBIS); _replayGainModeModel = std::make_shared(); _replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.none"), MediaPlayer::Settings::ReplayGain::Mode::None); @@ -356,13 +356,13 @@ namespace UserInterface { ::Auth::IPasswordService* _authPasswordService{}; bool _withOldPassword{}; - std::shared_ptr _transcodeModeModel; - std::shared_ptr> _transcodeBitrateModel; - std::shared_ptr> _transcodeFormatModel; - std::shared_ptr _replayGainModeModel; - std::shared_ptr> _subsonicArtistListModeModel; - std::shared_ptr _feedbackBackendModel; - std::shared_ptr _scrobblingBackendModel; + std::shared_ptr _transcodingModeModeModel; + std::shared_ptr> _transcodingOutputBitrateModel; + std::shared_ptr> _transcodingOutputFormatModel; + std::shared_ptr _replayGainModeModel; + std::shared_ptr> _subsonicArtistListModeModel; + std::shared_ptr _feedbackBackendModel; + std::shared_ptr _scrobblingBackendModel; }; SettingsView::SettingsView() @@ -433,7 +433,7 @@ namespace UserInterface { // Transcode auto transcodeMode{ std::make_unique() }; auto* transcodeModeRaw{ transcodeMode.get() }; - transcodeMode->setModel(model->getTranscodeModeModel()); + transcodeMode->setModel(model->getTranscodingModeModel()); t->setFormWidget(SettingsModel::TranscodeModeField, std::move(transcodeMode)); // Format @@ -443,12 +443,12 @@ namespace UserInterface { // Bitrate auto transcodeBitrate{ std::make_unique() }; - transcodeBitrate->setModel(model->getTranscodeBitrateModel()); + transcodeBitrate->setModel(model->getTranscodingOutputBitrateModel()); t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodeBitrate)); transcodeModeRaw->activated().connect([=](int row) { - const bool enable{ model->getTranscodeModeModel()->getValue(row) != MediaPlayer::Settings::Transcode::Mode::Never }; + const bool enable{ model->getTranscodingModeModel()->getValue(row) != MediaPlayer::Settings::Transcode::Mode::Never }; model->setReadOnly(SettingsModel::TranscodeFormatField, !enable); model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable); t->updateModel(model.get()); @@ -493,12 +493,12 @@ namespace UserInterface { // Format auto transcodeFormat{ std::make_unique() }; transcodeFormat->setModel(model->getTranscodeFormatModel()); - t->setFormWidget(SettingsModel::SubsonicTranscodeFormatField, std::move(transcodeFormat)); + t->setFormWidget(SettingsModel::SubsonicTranscodingOutputFormatField, std::move(transcodeFormat)); // Bitrate auto transcodeBitrate{ std::make_unique() }; - transcodeBitrate->setModel(model->getTranscodeBitrateModel()); - t->setFormWidget(SettingsModel::SubsonicTranscodeBitrateField, std::move(transcodeBitrate)); + transcodeBitrate->setModel(model->getTranscodingOutputBitrateModel()); + t->setFormWidget(SettingsModel::SubsonicTranscodingOutputBitrateField, std::move(transcodeBitrate)); // Artist list mode auto artistListMode{ std::make_unique() }; diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 3e42f776..f9f61291 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -146,7 +146,7 @@ namespace UserInterface if (audioStream) { releaseInfo->setCondition("if-has-codec", true); - releaseInfo->bindString("codec", audioStream->codec); + releaseInfo->bindString("codec", audioStream->codecName); break; } } diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp index c755a6af..ecb11c96 100644 --- a/src/lms/ui/explore/TrackListHelpers.cpp +++ b/src/lms/ui/explore/TrackListHelpers.cpp @@ -130,7 +130,7 @@ namespace UserInterface::TrackListHelpers if (audioStream) { trackInfo->setCondition("if-has-codec", true); - trackInfo->bindString("codec", audioStream->codec); + trackInfo->bindString("codec", audioStream->codecName); } } diff --git a/src/lms/ui/resource/AudioTranscodeResource.cpp b/src/lms/ui/resource/AudioTranscodeResource.cpp index b7797724..b8dfa6cb 100644 --- a/src/lms/ui/resource/AudioTranscodeResource.cpp +++ b/src/lms/ui/resource/AudioTranscodeResource.cpp @@ -22,8 +22,8 @@ #include #include -#include "av/TranscodeParameters.hpp" -#include "av/TranscodeResourceHandlerCreator.hpp" +#include "av/TranscodingParameters.hpp" +#include "av/TranscodingResourceHandlerCreator.hpp" #include "av/Types.hpp" #include "services/database/Session.hpp" #include "services/database/Track.hpp" @@ -37,178 +37,166 @@ namespace StringUtils { - template <> - std::optional - readAs(std::string_view str) - { - auto encodedFormat {readAs(str)}; - if (!encodedFormat) - return std::nullopt; + template <> + std::optional readAs(std::string_view str) + { + auto encodedFormat{ readAs(str) }; + if (!encodedFormat) + return std::nullopt; - Database::AudioFormat format {static_cast(*encodedFormat)}; + Database::TranscodingOutputFormat format{ static_cast(*encodedFormat) }; - // check - switch (static_cast(*encodedFormat)) - { - case Database::AudioFormat::MP3: - [[fallthrough]]; - case Database::AudioFormat::OGG_OPUS: - [[fallthrough]]; - case Database::AudioFormat::MATROSKA_OPUS: - [[fallthrough]]; - case Database::AudioFormat::OGG_VORBIS: - [[fallthrough]]; - case Database::AudioFormat::WEBM_VORBIS: - return format; - } + // check + switch (static_cast(*encodedFormat)) + { + case Database::TranscodingOutputFormat::MP3: + [[fallthrough]]; + case Database::TranscodingOutputFormat::OGG_OPUS: + [[fallthrough]]; + case Database::TranscodingOutputFormat::MATROSKA_OPUS: + [[fallthrough]]; + case Database::TranscodingOutputFormat::OGG_VORBIS: + [[fallthrough]]; + case Database::TranscodingOutputFormat::WEBM_VORBIS: + return format; + } - LOG(ERROR) << "Cannot determine audio format from value '" << str << "'"; + LOG(ERROR) << "Cannot determine audio format from value '" << str << "'"; - return std::nullopt; - } + return std::nullopt; + } } -static -std::optional -AudioFormatToAvFormat(Database::AudioFormat format) +namespace UserInterface { - switch (format) - { - 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; - } + namespace + { + std::optional AudioFormatToAvFormat(Database::TranscodingOutputFormat format) + { + switch (format) + { + case Database::TranscodingOutputFormat::MP3: return Av::Transcoding::OutputFormat::MP3; + case Database::TranscodingOutputFormat::OGG_OPUS: return Av::Transcoding::OutputFormat::OGG_OPUS; + case Database::TranscodingOutputFormat::MATROSKA_OPUS: return Av::Transcoding::OutputFormat::MATROSKA_OPUS; + case Database::TranscodingOutputFormat::OGG_VORBIS: return Av::Transcoding::OutputFormat::OGG_VORBIS; + case Database::TranscodingOutputFormat::WEBM_VORBIS: return Av::Transcoding::OutputFormat::WEBM_VORBIS; + } - LOG(ERROR) << "Cannot convert from audio format to AV format"; + LOG(ERROR) << "Cannot convert from audio format to AV format"; - return std::nullopt; -} + return std::nullopt; + } + template + std::optional readParameterAs(const Wt::Http::Request& request, const std::string& parameterName) + { + auto paramStr{ request.getParameter(parameterName) }; + if (!paramStr) + { + LOG(DEBUG) << "Missing parameter '" << parameterName << "'"; + return std::nullopt; + } -namespace UserInterface { + auto res{ StringUtils::readAs(*paramStr) }; + if (!res) + LOG(ERROR) << "Cannot parse parameter '" << parameterName << "' from value '" << *paramStr << "'"; -AudioTranscodeResource:: ~AudioTranscodeResource() -{ - beingDeleted(); -} + return res; + } -std::string -AudioTranscodeResource::getUrl(Database::TrackId trackId) const -{ - return url() + "&trackid=" + trackId.toString(); -} + struct TranscodingParameters + { + Av::Transcoding::InputParameters inputParameters; + Av::Transcoding::OutputParameters outputParameters; + }; -template -std::optional -readParameterAs(const Wt::Http::Request& request, const std::string& parameterName) -{ - auto paramStr {request.getParameter(parameterName)}; - if (!paramStr) - { - LOG(DEBUG) << "Missing parameter '" << parameterName << "'"; - return std::nullopt; - } + std::optional readTranscodingParameters(const Wt::Http::Request& request) + { + TranscodingParameters parameters; - auto res {StringUtils::readAs(*paramStr)}; - if (!res) - LOG(ERROR) << "Cannot parse parameter '" << parameterName << "' from value '" << *paramStr << "'"; + // mandatory parameters + const std::optional trackId{ readParameterAs(request, "trackid") }; + const auto format{ readParameterAs(request, "format") }; + const auto bitrate{ readParameterAs(request, "bitrate") }; - return res; -} + if (!trackId || !format || !bitrate) + return std::nullopt; -namespace -{ - struct TranscodeParameters - { - Av::InputFileParameters inputFileParameters; - Av::TranscodeParameters transcodeParameters; - }; + if (!Database::isAudioBitrateAllowed(*bitrate)) + { + LOG(ERROR) << "Bitrate '" << *bitrate << "' is not allowed"; + return std::nullopt; + } - std::optional - readTranscodeParameters(const Wt::Http::Request& request) - { - TranscodeParameters parameters; + const std::optional avFormat{ AudioFormatToAvFormat(*format) }; + if (!avFormat) + return std::nullopt; - // mandatory parameters - const std::optional trackId {readParameterAs(request, "trackid")}; - const auto format {readParameterAs(request, "format")}; - const auto bitrate {readParameterAs(request, "bitrate")}; + // optional parameter + std::size_t offset{ readParameterAs(request, "offset").value_or(0) }; - if (!trackId || !format || !bitrate) - return std::nullopt; + std::filesystem::path trackPath; + { + auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; - if (!Database::isAudioBitrateAllowed(*bitrate)) - { - LOG(ERROR) << "Bitrate '" << *bitrate << "' is not allowed"; - return std::nullopt; - } + const Database::Track::pointer track{ Database::Track::find(LmsApp->getDbSession(), *trackId) }; + if (!track) + { + LOG(ERROR) << "Missing track"; + return std::nullopt; + } - const std::optional avFormat {AudioFormatToAvFormat(*format)}; - if (!avFormat) - return std::nullopt; + parameters.inputParameters.trackPath = track->getPath(); + parameters.inputParameters.duration = track->getDuration(); + } - // optional parameter - std::size_t offset {readParameterAs(request, "offset").value_or(0)}; + parameters.outputParameters.stripMetadata = true; + parameters.outputParameters.format = *avFormat; + parameters.outputParameters.bitrate = *bitrate; + parameters.outputParameters.offset = std::chrono::seconds{ offset }; - std::filesystem::path trackPath; - { - auto transaction {LmsApp->getDbSession().createSharedTransaction()}; + return parameters; + } + } - const Database::Track::pointer track {Database::Track::find(LmsApp->getDbSession(), *trackId)}; - if (!track) - { - LOG(ERROR) << "Missing track"; - return std::nullopt; - } + AudioTranscodeResource:: ~AudioTranscodeResource() + { + beingDeleted(); + } - parameters.inputFileParameters.trackPath = track->getPath(); - parameters.inputFileParameters.duration = track->getDuration(); - } + std::string AudioTranscodeResource::getUrl(Database::TrackId trackId) const + { + return url() + "&trackid=" + trackId.toString(); + } - parameters.transcodeParameters.stripMetadata = true; - parameters.transcodeParameters.format = *avFormat; - parameters.transcodeParameters.bitrate = *bitrate; - parameters.transcodeParameters.offset = std::chrono::seconds {offset}; + void AudioTranscodeResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) + { + std::shared_ptr resourceHandler; - return parameters; - } -} - -void -AudioTranscodeResource::handleRequest(const Wt::Http::Request& request, - Wt::Http::Response& response) -{ - std::shared_ptr resourceHandler; - - try - { - Wt::Http::ResponseContinuation* continuation {request.continuation()}; - if (!continuation) - { - const std::optional& parameters {readTranscodeParameters(request)}; - if (parameters) - resourceHandler = Av::createTranscodeResourceHandler(parameters->inputFileParameters, parameters->transcodeParameters, false /* estimate content length */); - } - else - { - resourceHandler = Wt::cpp17::any_cast>(continuation->data()); - } - - if (resourceHandler) - { - continuation = resourceHandler->processRequest(request, response); - if (continuation) - continuation->setData(resourceHandler); - } - } - catch (const Av::Exception& e) - { - LOG(ERROR) << "Caught Av exception: " << e.what(); - } -} - -} // namespace UserInterface + try + { + Wt::Http::ResponseContinuation* continuation{ request.continuation() }; + if (!continuation) + { + if (const auto& parameters{ readTranscodingParameters(request) }) + resourceHandler = Av::Transcoding::createResourceHandler(parameters->inputParameters, parameters->outputParameters, false /* estimate content length */); + } + else + { + resourceHandler = Wt::cpp17::any_cast>(continuation->data()); + } + if (resourceHandler) + { + continuation = resourceHandler->processRequest(request, response); + if (continuation) + continuation->setData(resourceHandler); + } + } + catch (const Av::Exception& e) + { + LOG(ERROR) << "Caught Av exception: " << e.what(); + } + } +} // namespace UserInterface \ No newline at end of file