diff --git a/src/libs/auth/impl/PasswordService.cpp b/src/libs/auth/impl/PasswordService.cpp
index 3ee4d932..0c58b410 100644
--- a/src/libs/auth/impl/PasswordService.cpp
+++ b/src/libs/auth/impl/PasswordService.cpp
@@ -82,7 +82,7 @@ checkUserPassword(Database::Session& session, const std::string& loginName, cons
case Database::User::AuthMode::Internal:
{
LMS_LOG(AUTH, DEBUG) << "Checking internal password for user '" << loginName << "'";
- const Wt::Auth::BCryptHashFunction hashFunc {6}; // TODO parametrize this
+ const Wt::Auth::BCryptHashFunction hashFunc {7}; // TODO parametrize this
return hashFunc.verify(password, passwordHash.salt, passwordHash.hash);
}
diff --git a/src/libs/av/CMakeLists.txt b/src/libs/av/CMakeLists.txt
index a5c24167..c42826bf 100644
--- a/src/libs/av/CMakeLists.txt
+++ b/src/libs/av/CMakeLists.txt
@@ -1,9 +1,9 @@
add_library(lmsav SHARED
- impl/AvInfo.cpp
- impl/AvTranscoder.cpp
- impl/AvTranscodeResourceHandler.cpp
- impl/AvTypes.cpp
+ impl/AudioFile.cpp
+ impl/Transcoder.cpp
+ impl/TranscodeResourceHandler.cpp
+ impl/Types.cpp
)
target_include_directories(lmsav INTERFACE
diff --git a/src/libs/av/impl/AvInfo.cpp b/src/libs/av/impl/AudioFile.cpp
similarity index 80%
rename from src/libs/av/impl/AvInfo.cpp
rename to src/libs/av/impl/AudioFile.cpp
index fa893871..b9b51524 100644
--- a/src/libs/av/impl/AvInfo.cpp
+++ b/src/libs/av/impl/AudioFile.cpp
@@ -17,7 +17,7 @@
* along with LMS. If not, see .
*/
-#include "av/AvInfo.hpp"
+#include "AudioFile.hpp"
extern "C"
{
@@ -44,20 +44,28 @@ static std::string averror_to_string(int error)
return "Unknown error";
}
-MediaFileException::MediaFileException(int avError)
-: AvException {"MediaFileException: " + averror_to_string(avError)}
+class AudioFileException : public Av::Exception
{
+ public:
+ AudioFileException(int avError)
+ : Av::Exception {"AudioFileException: " + averror_to_string(avError)}
+ {}
+};
+
+std::unique_ptr
+parseAudioFile(const std::filesystem::path& p)
+{
+ return std::make_unique(p);
}
-
-MediaFile::MediaFile(const std::filesystem::path& p)
+AudioFile::AudioFile(const std::filesystem::path& p)
: _p {p}
{
- int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr);
+ int error {avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr)};
if (error < 0)
{
LMS_LOG(AV, ERROR) << "Cannot open " << _p.string() << ": " << averror_to_string(error);
- throw MediaFileException(error);
+ throw AudioFileException {error};
}
error = avformat_find_stream_info(_context, nullptr);
@@ -65,32 +73,32 @@ MediaFile::MediaFile(const std::filesystem::path& p)
{
LMS_LOG(AV, ERROR) << "Cannot find stream information on " << _p.string() << ": " << averror_to_string(error);
avformat_close_input(&_context);
- throw MediaFileException(error);
+ throw AudioFileException {error};
}
}
-MediaFile::~MediaFile()
+AudioFile::~AudioFile()
{
avformat_close_input(&_context);
}
-std::string
-MediaFile::getFormatName() const
+const std::filesystem::path&
+AudioFile::getPath() const
{
- return _context->iformat->name;
+ return _p;
}
std::chrono::milliseconds
-MediaFile::getDuration() const
+AudioFile::getDuration() const
{
if (_context->duration == AV_NOPTS_VALUE)
- return std::chrono::milliseconds(0); // TODO estimate
+ return std::chrono::milliseconds {0}; // TODO estimate
- return std::chrono::milliseconds(_context->duration / AV_TIME_BASE * 1000);
+ return std::chrono::milliseconds {_context->duration / AV_TIME_BASE * 1000};
}
void
-getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map& res)
+getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& res)
{
if (!dictionnary)
return;
@@ -102,10 +110,10 @@ getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map
-MediaFile::getMetaData(void)
+AudioFile::MetadataMap
+AudioFile::getMetaData() const
{
- std::map res;
+ MetadataMap res;
getMetaDataFromDictionnary(_context->metadata, res);
@@ -113,7 +121,7 @@ MediaFile::getMetaData(void)
// If we did not find tags, search metadata in streams
if (res.empty())
{
- for (std::size_t i = 0; i < _context->nb_streams; ++i)
+ for (std::size_t i {}; i < _context->nb_streams; ++i)
{
getMetaDataFromDictionnary(_context->streams[i]->metadata, res);
@@ -126,7 +134,7 @@ MediaFile::getMetaData(void)
}
std::vector
-MediaFile::getStreamInfo() const
+AudioFile::getStreamInfo() const
{
std::vector res;
@@ -154,7 +162,7 @@ MediaFile::getStreamInfo() const
}
std::optional
-MediaFile::getBestStream() const
+AudioFile::getBestStream() const
{
int res = av_find_best_stream(_context,
AVMEDIA_TYPE_AUDIO,
@@ -170,7 +178,7 @@ MediaFile::getBestStream() const
}
bool
-MediaFile::hasAttachedPictures(void) const
+AudioFile::hasAttachedPictures(void) const
{
for (std::size_t i = 0; i < _context->nb_streams; ++i)
{
@@ -182,9 +190,9 @@ MediaFile::hasAttachedPictures(void) const
}
void
-MediaFile::visitAttachedPictures(std::function func) const
+AudioFile::visitAttachedPictures(std::function func) const
{
- static const std::map codecMimeMap =
+ static const std::unordered_map codecMimeMap =
{
{ AV_CODEC_ID_BMP, "image/x-bmp" },
{ AV_CODEC_ID_GIF, "image/gif" },
@@ -230,7 +238,7 @@ MediaFile::visitAttachedPictures(std::function func) const
}
}
-std::optional
+std::optional
guessMediaFileFormat(const std::filesystem::path& file)
{
AVOutputFormat* format {av_guess_format(NULL,file.string().c_str(),NULL)};
@@ -252,7 +260,7 @@ guessMediaFileFormat(const std::filesystem::path& file)
else if (mimeTypes.size() > 1)
LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several mime types: '" << format->mime_type << "'";
- MediaFileFormat res;
+ AudioFileFormat res;
res.format = formats.front();
res.mimeType = mimeTypes.empty() ? "application/octet-stream" : mimeTypes.front();
diff --git a/src/libs/av/impl/AudioFile.hpp b/src/libs/av/impl/AudioFile.hpp
new file mode 100644
index 00000000..82f1dfb5
--- /dev/null
+++ b/src/libs/av/impl/AudioFile.hpp
@@ -0,0 +1,57 @@
+/*
+ * 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 .
+ */
+
+/* This file contains some classes in order to get info from file using the libavconv */
+
+#pragma once
+
+#include "av/IAudioFile.hpp"
+
+struct AVFormatContext;
+
+namespace Av
+{
+
+ class AudioFile final : public IAudioFile
+ {
+ public:
+ AudioFile(const std::filesystem::path& p);
+ ~AudioFile();
+
+ AudioFile(const AudioFile&) = delete;
+ AudioFile(AudioFile&&) = delete;
+ AudioFile& operator=(const AudioFile&) = delete;
+ AudioFile& operator=(AudioFile&&) = delete;
+
+ const std::filesystem::path& getPath() const override;
+ std::chrono::milliseconds getDuration() const override;
+ MetadataMap getMetaData() const override;
+ std::vector getStreamInfo() const override;
+ std::optional getBestStream() const override;
+ bool hasAttachedPictures() const override;
+ void visitAttachedPictures(std::function func) const override;
+
+ private:
+
+ const std::filesystem::path _p;
+ AVFormatContext* _context {};
+ };
+
+} // namespace Av
+
diff --git a/src/libs/av/impl/AvTranscodeResourceHandler.cpp b/src/libs/av/impl/TranscodeResourceHandler.cpp
similarity index 69%
rename from src/libs/av/impl/AvTranscodeResourceHandler.cpp
rename to src/libs/av/impl/TranscodeResourceHandler.cpp
index fbea1f2f..7cbd99c6 100644
--- a/src/libs/av/impl/AvTranscodeResourceHandler.cpp
+++ b/src/libs/av/impl/TranscodeResourceHandler.cpp
@@ -17,7 +17,7 @@
* along with LMS. If not, see .
*/
-#include "AvTranscodeResourceHandler.hpp"
+#include "TranscodeResourceHandler.hpp"
namespace Av
{
@@ -36,24 +36,32 @@ namespace Av
_transcoder.start();
}
- void
+ Wt::Http::ResponseContinuation*
TranscodeResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
{
response.setMimeType(_transcoder.getOutputMimeType());
- if (!_transcoder.isComplete())
+ if (_nbBytesReady > 0)
{
- std::vector buffer;
-
- _transcoder.process(buffer, _chunkSize);
- response.out().write(reinterpret_cast(&buffer[0]), buffer.size());
+ response.out().write(reinterpret_cast(&_buffer[0]), _nbBytesReady);
+ _nbBytesReady = 0;
}
- }
- bool
- TranscodeResourceHandler::isFinished() const
- {
- return _transcoder.isComplete();
+ if (!_transcoder.finished())
+ {
+ Wt::Http::ResponseContinuation *continuation {response.createContinuation()};
+ continuation->waitForMoreData();
+ _transcoder.asyncRead(_buffer.data(), _buffer.size(), [=](std::size_t nbBytesRead)
+ {
+ assert(_nbBytesReady == 0);
+ _nbBytesReady = nbBytesRead;
+ continuation->haveMoreData();
+ });
+
+ return continuation;
+ }
+
+ return {};
}
}
diff --git a/src/libs/av/impl/AvTranscodeResourceHandler.hpp b/src/libs/av/impl/TranscodeResourceHandler.hpp
similarity index 76%
rename from src/libs/av/impl/AvTranscodeResourceHandler.hpp
rename to src/libs/av/impl/TranscodeResourceHandler.hpp
index 543ca74b..87a6f7e0 100644
--- a/src/libs/av/impl/AvTranscodeResourceHandler.hpp
+++ b/src/libs/av/impl/TranscodeResourceHandler.hpp
@@ -19,9 +19,12 @@
#pragma once
+#include
#include
-#include "av/AvTranscoder.hpp"
+
+#include "av/TranscodeParameters.hpp"
#include "utils/IResourceHandler.hpp"
+#include "Transcoder.hpp"
namespace Av
{
@@ -33,10 +36,11 @@ namespace Av
private:
- void processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
- bool isFinished() const override;
+ Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
- static constexpr std::size_t _chunkSize {262144};
+ static constexpr std::size_t _chunkSize {32768};
+ std::array _buffer;
+ std::size_t _nbBytesReady {};
const std::filesystem::path _trackPath;
Transcoder _transcoder;
};
diff --git a/src/libs/av/impl/AvTranscoder.cpp b/src/libs/av/impl/Transcoder.cpp
similarity index 65%
rename from src/libs/av/impl/AvTranscoder.cpp
rename to src/libs/av/impl/Transcoder.cpp
index 574f8299..bbbec080 100644
--- a/src/libs/av/impl/AvTranscoder.cpp
+++ b/src/libs/av/impl/Transcoder.cpp
@@ -17,12 +17,12 @@
* along with LMS. If not, see .
*/
-#include "av/AvTranscoder.hpp"
+#include "Transcoder.hpp"
#include
-#include
+#include
-#include "av/AvInfo.hpp"
+#include "utils/IChildProcessManager.hpp"
#include "utils/IConfig.hpp"
#include "utils/Path.hpp"
#include "utils/Logger.hpp"
@@ -32,7 +32,7 @@ namespace Av {
#define LOG(sev) LMS_LOG(TRANSCODE, sev) << "[" << _id << "] - "
-static std::atomic globalId {};
+static std::atomic globalId {};
static std::filesystem::path ffmpegPath;
void
@@ -40,39 +40,41 @@ Transcoder::init()
{
ffmpegPath = Service::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
if (!std::filesystem::exists(ffmpegPath))
- throw LmsException {"File '" + ffmpegPath.string() + "' does not exist!"};
+ throw Exception {"File '" + ffmpegPath.string() + "' does not exist!"};
}
Transcoder::Transcoder(const std::filesystem::path& filePath, const TranscodeParameters& parameters)
-: _filePath {filePath},
- _parameters {parameters},
- _id {globalId++}
+: _id {globalId++}
+, _filePath {filePath}
+, _parameters {parameters}
{
}
+Transcoder::~Transcoder() = default;
+
bool
Transcoder::start()
{
+ if (ffmpegPath.empty())
+ init();
+
try
{
if (!std::filesystem::exists(_filePath))
{
LOG(ERROR) << "File '" << _filePath << "' does not exist!";
- _isComplete = true;
return false;
}
else if (!std::filesystem::is_regular_file( _filePath) )
{
LOG(ERROR) << "File '" << _filePath << "' is not regular!";
- _isComplete = true;
return false;
}
}
catch (const std::filesystem::filesystem_error& e)
{
LOG(ERROR) << "File error on '" << _filePath.string() << "': " << e.what();
- _isComplete = true;
return false;
}
@@ -82,17 +84,21 @@ Transcoder::start()
args.emplace_back(ffmpegPath.string());
- // Make sure we do not produce anything in the stderr output
+ // 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 Offset
- if (_parameters.offset)
{
args.emplace_back("-ss");
- args.emplace_back(std::to_string((*_parameters.offset).count()));
+
+ std::ostringstream oss;
+ oss << std::fixed << std::showpoint << std::setprecision(3) << (_parameters.offset.count() / float {1000});
+ args.emplace_back(oss.str());
}
// Input file
@@ -120,6 +126,7 @@ Transcoder::start()
args.emplace_back("-b:a");
args.emplace_back(std::to_string(_parameters.bitrate));
+
// Codecs and formats
switch (_parameters.format)
{
@@ -157,7 +164,6 @@ Transcoder::start()
break;
default:
- _isComplete = true;
return false;
}
@@ -169,84 +175,56 @@ Transcoder::start()
for (const std::string& arg : args)
LOG(DEBUG) << "Arg = '" << arg << "'";
- // make sure only one thread is executing this part of code
+ // Caution: stdin must have been closed before
+ try
{
- static std::mutex transcoderMutex;
-
- std::lock_guard lock {transcoderMutex};
-
- _child = std::make_shared();
-
- // Caution: stdin must have been closed before
- _child->open(ffmpegPath.string(), args);
- if (!_child->is_open())
- {
- LOG(DEBUG) << "Exec failed!";
- _isComplete = true;
- return false;
- }
-
- if (_child->out().eof())
- {
- LOG(DEBUG) << "Early end of file!";
- _isComplete = true;
- return false;
- }
-
+ _childProcess = Service::get()->spawnChildProcess(ffmpegPath, args);
+ }
+ catch (ChildProcessException& exception)
+ {
+ LOG(ERROR) << "Cannot execute '" << ffmpegPath << "': " << exception.what();
+ return false;
}
- LOG(DEBUG) << "Stream opened!";
return true;
}
void
-Transcoder::process(std::vector& output, std::size_t maxSize)
+Transcoder::asyncWaitForData(WaitCallback cb)
{
- if (!_child || _isComplete)
- return;
+ assert(_childProcess);
- output.resize(maxSize);
+ LOG(DEBUG) << "Want to wait for data";
- //Read on the output stream
- _child->out().read(reinterpret_cast(&output[0]), maxSize);
- output.resize(_child->out().gcount());
-
- if (_child->out().fail())
+ _childProcess->asyncWaitForData([cb = std::move(cb)]
{
- LOG(DEBUG) << "Stdout FAILED";
- _isComplete = true;
- }
-
- if (_child->out().eof())
- {
- LOG(DEBUG) << "Stdout EOF!";
- _isComplete = true;
- }
-
- if (_isComplete)
- {
- LOG(DEBUG) << "Transcode complete!";
- _child->clear();
- _child.reset();
- }
-
- _total += output.size();
-
- LOG(DEBUG) << "nb bytes = " << output.size() << ", total = " << _total;
+ cb();
+ });
}
-Transcoder::~Transcoder()
+void
+Transcoder::asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback readCallback)
{
- LOG(DEBUG) << ", ~Transcoder called! Total produced bytes = " << _total;
+ assert(_childProcess);
- if (_child)
+ return _childProcess->asyncRead(buffer, bufferSize, [readCallback {std::move(readCallback)}](IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead)
{
- LOG(DEBUG) << "Child still here!";
- _child->rdbuf()->kill(SIGKILL);
- LOG(DEBUG) << "Closing...";
- _child->rdbuf()->close();
- LOG(DEBUG) << "Closing DONE";
- }
+ readCallback(nbBytesRead);
+ });
+}
+
+std::size_t
+Transcoder::readSome(std::byte* buffer, std::size_t bufferSize)
+{
+ assert(_childProcess);
+
+ return _childProcess->readSome(buffer, bufferSize);
+}
+
+bool
+Transcoder::finished() const
+{
+ return _childProcess->finished();
}
} // namespace Transcode
diff --git a/src/libs/av/impl/Transcoder.hpp b/src/libs/av/impl/Transcoder.hpp
new file mode 100644
index 00000000..aa9ace31
--- /dev/null
+++ b/src/libs/av/impl/Transcoder.hpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "av/TranscodeParameters.hpp"
+#include "av/Types.hpp"
+
+class IChildProcess;
+
+namespace Av
+{
+ class Transcoder
+ {
+ public:
+ Transcoder(const std::filesystem::path& file, const TranscodeParameters& parameters);
+ ~Transcoder();
+
+ Transcoder(const Transcoder&) = delete;
+ Transcoder& operator=(const Transcoder&) = delete;
+ Transcoder(Transcoder&&) = delete;
+ Transcoder& operator=(Transcoder&&) = delete;
+
+ bool start();
+
+ using WaitCallback = std::function;
+ void asyncWaitForData(WaitCallback cb);
+
+ // non blocking calls
+ using ReadCallback = std::function;
+ void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback);
+ std::size_t readSome(std::byte* buffer, std::size_t bufferSize);
+
+ const std::string& getOutputMimeType() const { return _outputMimeType; }
+ const TranscodeParameters& getParameters() const { return _parameters; }
+
+ bool finished() const;
+
+ private:
+ static void init();
+
+ const std::size_t _id {};
+ const std::filesystem::path _filePath;
+ const TranscodeParameters _parameters;
+
+ std::unique_ptr _childProcess;
+
+ bool _finished {};
+ std::string _outputMimeType;
+ };
+
+} // namespace Av
+
diff --git a/src/libs/av/impl/AvTypes.cpp b/src/libs/av/impl/Types.cpp
similarity index 63%
rename from src/libs/av/impl/AvTypes.cpp
rename to src/libs/av/impl/Types.cpp
index ca83978f..713491bc 100644
--- a/src/libs/av/impl/AvTypes.cpp
+++ b/src/libs/av/impl/Types.cpp
@@ -17,23 +17,25 @@
* along with LMS. If not, see .
*/
-#include "av/AvTypes.hpp"
+#include "av/Types.hpp"
-namespace Av {
-
-const char* formatToMimetype(Format format)
+namespace Av
{
- switch (format)
+
+ std::string_view
+ formatToMimetype(Format 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";
+ 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"};
}
- throw AvException {"Invalid encoding"};
-}
-
}
diff --git a/src/libs/av/include/av/AvInfo.hpp b/src/libs/av/include/av/AvInfo.hpp
deleted file mode 100644
index 60fe0844..00000000
--- a/src/libs/av/include/av/AvInfo.hpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2015 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 .
- */
-
-/* This file contains some classes in order to get info from file using the libavconv */
-
-#pragma once
-
-#include
-#include
-#include
-#include