Renamed some transcoding stuff + now retrieve decoder codecs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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;
|
||||
@@ -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"};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+26
-15
@@ -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
|
||||
|
||||
+4
-5
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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>() };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user