Renamed some transcoding stuff + now retrieve decoder codecs

This commit is contained in:
emeric
2023-11-12 20:51:42 +01:00
parent 53943c4118
commit 87d70465e9
22 changed files with 711 additions and 712 deletions
+1 -2
View File
@@ -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
+30 -2
View File
@@ -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<IAudioFile> parseAudioFile(const std::filesystem::path& p)
@@ -254,14 +279,17 @@ namespace Av
res.emplace();
res->index = streamIndex;
res->bitrate = static_cast<std::size_t>(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<std::filesystem::path, std::string_view> entries
{
{".mp3", "audio/mpeg"},
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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::size_t>(std::chrono::duration_cast<std::chrono::milliseconds>(inputFileParameters.duration).count()) / 1000};
return estimatedContentLength;
}
}
std::unique_ptr<IResourceHandler>
createTranscodeResourceHandler(const InputFileParameters& inputFileParameters, const TranscodeParameters& transcodeParameters, bool estimateContentLength)
{
return std::make_unique<TranscodeResourceHandler>(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<const char *>(&_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 {};
}
}
+152 -142
View File
@@ -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<size_t> globalId {};
static std::filesystem::path ffmpegPath;
static std::atomic<size_t> globalId{};
static std::filesystem::path ffmpegPath;
void
Transcoder::init()
{
ffmpegPath = Service<IConfig>::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<IConfig>::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<std::string> 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<std::string> 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<int>(_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<int>(_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<IChildProcessManager>::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<IChildProcessManager>::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
+28 -30
View File
@@ -22,47 +22,45 @@
#include <filesystem>
#include <functional>
#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(std::size_t nbReadBytes)>;
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(std::size_t nbReadBytes)>;
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<IChildProcess> _childProcess;
std::string _outputMimeType;
};
} // namespace Av
std::unique_ptr<IChildProcess> _childProcess;
};
} // namespace Av::Transcoding
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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::size_t>(std::chrono::duration_cast<std::chrono::milliseconds>(inputParameters.duration).count()) / 1000 };
return estimatedContentLength;
}
}
std::unique_ptr<IResourceHandler> createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength)
{
return std::make_unique<TranscodingResourceHandler>(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<const char*>(&_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 {};
}
}
@@ -23,16 +23,16 @@
#include <filesystem>
#include <optional>
#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;
-41
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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"};
}
}
+25 -1
View File
@@ -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
@@ -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<std::size_t> 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<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default)
std::chrono::milliseconds offset{ 0 };
bool stripMetadata{ true };
};
} // namespace Av::Transcoding
@@ -23,11 +23,10 @@
#include "utils/IResourceHandler.hpp"
namespace Av
namespace Av::Transcoding
{
struct InputFileParameters;
struct TranscodeParameters;
struct InputParameters;
struct OutputParameters;
std::unique_ptr<IResourceHandler> createTranscodeResourceHandler(const InputFileParameters& inputFileParameters, const TranscodeParameters& parameters, bool estimateContentLength);
std::unique_ptr<IResourceHandler> createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength);
}
+7 -21
View File
@@ -19,27 +19,13 @@
#pragma once
#include <string_view>
#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;
};
}
+2 -2
View File
@@ -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()
@@ -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,
@@ -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
@@ -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<Av::Format> subsonicStreamFormatToAvFormat(std::string_view format)
std::optional<Av::Transcoding::OutputFormat> 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},
for (const auto& [str, avFormat] : std::initializer_list<std::pair<std::string_view, Av::Transcoding::OutputFormat>>{
{"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<Av::TranscodeParameters> transcodeParameters;
Av::Transcoding::InputParameters inputParameters;
std::optional<Av::Transcoding::OutputParameters> 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<Av::Format> requestedFormat{ subsonicStreamFormatToAvFormat(format) })
transcodeParameters.format = *requestedFormat;
if (std::optional<Av::Transcoding::OutputFormat> 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
{
+7 -7
View File
@@ -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 }));
}
+81 -81
View File
@@ -19,118 +19,118 @@
#pragma once
#include <chrono>
#include <optional>
#include <Wt/WAnchor.h>
#include <Wt/WJavaScript.h>
#include <Wt/WPushButton.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#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<Database::TrackId> 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<Database::TrackId> getTrackLoaded() const { return _trackIdLoaded; }
std::optional<Settings> 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<Settings> getSettings() const { return _settings; }
void setSettings(const Settings& settings);
// Signals
Wt::JSignal<> playPrevious;
Wt::JSignal<> playNext;
Wt::Signal<Database::TrackId> trackLoaded;
Wt::Signal<> settingsLoaded;
void onPlayQueueUpdated(std::size_t trackCount);
Wt::JSignal<Database::TrackId::ValueType> scrobbleListenNow;
Wt::JSignal<Database::TrackId::ValueType, unsigned /* ms */> scrobbleListenFinished;
// Signals
Wt::JSignal<> playPrevious;
Wt::JSignal<> playNext;
Wt::Signal<Database::TrackId> trackLoaded;
Wt::Signal<> settingsLoaded;
Wt::JSignal<> playbackEnded;
Wt::JSignal<Database::TrackId::ValueType> scrobbleListenNow;
Wt::JSignal<Database::TrackId::ValueType, unsigned /* ms */> scrobbleListenFinished;
private:
std::unique_ptr<AudioFileResource> _audioFileResource;
std::unique_ptr<AudioTranscodeResource> _audioTranscodeResource;
Wt::JSignal<> playbackEnded;
std::optional<Database::TrackId> _trackIdLoaded;
std::optional<Settings> _settings;
private:
std::unique_ptr<AudioFileResource> _audioFileResource;
std::unique_ptr<AudioTranscodeResource> _audioTranscodeResource;
Wt::JSignal<std::string> _settingsLoaded;
std::optional<Database::TrackId> _trackIdLoaded;
std::optional<Settings> _settings;
Wt::WText* _title {};
Wt::WAnchor* _release {};
Wt::WText* _separator {};
Wt::WAnchor* _artist {};
Wt::WPushButton* _playQueue {};
};
Wt::JSignal<std::string> _settingsLoaded;
Wt::WText* _title{};
Wt::WAnchor* _release{};
Wt::WText* _separator{};
Wt::WAnchor* _artist{};
Wt::WPushButton* _playQueue{};
};
} // namespace UserInterface
+60 -60
View File
@@ -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<MediaPlayer::Settings::Transcode::Mode>;
using TranscodingModeModel = ValueStringModel<MediaPlayer::Settings::Transcode::Mode>;
using ReplayGainModeModel = ValueStringModel<MediaPlayer::Settings::ReplayGain::Mode>;
using FeedbackBackendModel = ValueStringModel<FeedbackBackend>;
using ScrobblingBackendModel = ValueStringModel<ScrobblingBackend>;
@@ -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<TranscodeModeModel> getTranscodeModeModel() { return _transcodeModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodeBitrateModel() { return _transcodeBitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodeFormatModel() { return _transcodeFormatModel; }
std::shared_ptr<TranscodingModeModel> getTranscodingModeModel() { return _transcodingModeModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodeFormatModel() { return _transcodingOutputFormatModel; }
std::shared_ptr<ReplayGainModeModel> getReplayGainModeModel() { return _replayGainModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; }
std::shared_ptr<FeedbackBackendModel> 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>();
_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<TranscodingModeModel>();
_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<ValueStringModel<Bitrate>>();
_transcodingOutputBitrateModel = std::make_shared<ValueStringModel<Bitrate>>();
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<ValueStringModel<AudioFormat>>();
_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<ValueStringModel<TranscodingOutputFormat>>();
_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>();
_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> _transcodeModeModel;
std::shared_ptr<ValueStringModel<Bitrate>> _transcodeBitrateModel;
std::shared_ptr<ValueStringModel<AudioFormat>> _transcodeFormatModel;
std::shared_ptr<ReplayGainModeModel> _replayGainModeModel;
std::shared_ptr<ValueStringModel<SubsonicArtistListMode>> _subsonicArtistListModeModel;
std::shared_ptr<FeedbackBackendModel> _feedbackBackendModel;
std::shared_ptr<ScrobblingBackendModel> _scrobblingBackendModel;
std::shared_ptr<TranscodingModeModel> _transcodingModeModeModel;
std::shared_ptr<ValueStringModel<Bitrate>> _transcodingOutputBitrateModel;
std::shared_ptr<ValueStringModel<TranscodingOutputFormat>> _transcodingOutputFormatModel;
std::shared_ptr<ReplayGainModeModel> _replayGainModeModel;
std::shared_ptr<ValueStringModel<SubsonicArtistListMode>> _subsonicArtistListModeModel;
std::shared_ptr<FeedbackBackendModel> _feedbackBackendModel;
std::shared_ptr<ScrobblingBackendModel> _scrobblingBackendModel;
};
SettingsView::SettingsView()
@@ -433,7 +433,7 @@ namespace UserInterface {
// Transcode
auto transcodeMode{ std::make_unique<Wt::WComboBox>() };
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<Wt::WComboBox>() };
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<Wt::WComboBox>() };
transcodeFormat->setModel(model->getTranscodeFormatModel());
t->setFormWidget(SettingsModel::SubsonicTranscodeFormatField, std::move(transcodeFormat));
t->setFormWidget(SettingsModel::SubsonicTranscodingOutputFormatField, std::move(transcodeFormat));
// Bitrate
auto transcodeBitrate{ std::make_unique<Wt::WComboBox>() };
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<Wt::WComboBox>() };
+1 -1
View File
@@ -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;
}
}
+1 -1
View File
@@ -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);
}
}
+135 -147
View File
@@ -22,8 +22,8 @@
#include <optional>
#include <Wt/Http/Response.h>
#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<Database::AudioFormat>
readAs(std::string_view str)
{
auto encodedFormat {readAs<int>(str)};
if (!encodedFormat)
return std::nullopt;
template <>
std::optional<Database::TranscodingOutputFormat> readAs(std::string_view str)
{
auto encodedFormat{ readAs<int>(str) };
if (!encodedFormat)
return std::nullopt;
Database::AudioFormat format {static_cast<Database::AudioFormat>(*encodedFormat)};
Database::TranscodingOutputFormat format{ static_cast<Database::TranscodingOutputFormat>(*encodedFormat) };
// check
switch (static_cast<Database::AudioFormat>(*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<Database::TranscodingOutputFormat>(*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<Av::Format>
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<Av::Transcoding::OutputFormat> 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<typename T>
std::optional<T> 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<T>(*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<typename T>
std::optional<T>
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<TranscodingParameters> readTranscodingParameters(const Wt::Http::Request& request)
{
TranscodingParameters parameters;
auto res {StringUtils::readAs<T>(*paramStr)};
if (!res)
LOG(ERROR) << "Cannot parse parameter '" << parameterName << "' from value '" << *paramStr << "'";
// mandatory parameters
const std::optional<Database::TrackId> trackId{ readParameterAs<Database::TrackId::ValueType>(request, "trackid") };
const auto format{ readParameterAs<Database::TranscodingOutputFormat>(request, "format") };
const auto bitrate{ readParameterAs<Database::Bitrate>(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<TranscodeParameters>
readTranscodeParameters(const Wt::Http::Request& request)
{
TranscodeParameters parameters;
const std::optional<Av::Transcoding::OutputFormat> avFormat{ AudioFormatToAvFormat(*format) };
if (!avFormat)
return std::nullopt;
// mandatory parameters
const std::optional<Database::TrackId> trackId {readParameterAs<Database::TrackId::ValueType>(request, "trackid")};
const auto format {readParameterAs<Database::AudioFormat>(request, "format")};
const auto bitrate {readParameterAs<Database::Bitrate>(request, "bitrate")};
// optional parameter
std::size_t offset{ readParameterAs<std::size_t>(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<Av::Format> 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<std::size_t>(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<IResourceHandler> resourceHandler;
return parameters;
}
}
void
AudioTranscodeResource::handleRequest(const Wt::Http::Request& request,
Wt::Http::Response& response)
{
std::shared_ptr<IResourceHandler> resourceHandler;
try
{
Wt::Http::ResponseContinuation* continuation {request.continuation()};
if (!continuation)
{
const std::optional<TranscodeParameters>& parameters {readTranscodeParameters(request)};
if (parameters)
resourceHandler = Av::createTranscodeResourceHandler(parameters->inputFileParameters, parameters->transcodeParameters, false /* estimate content length */);
}
else
{
resourceHandler = Wt::cpp17::any_cast<std::shared_ptr<IResourceHandler>>(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<std::shared_ptr<IResourceHandler>>(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