${title class="Lms-player-text Lms-trackname text-primary"}
- ${release class="Lms-btn Lms-player-text Lms-releasename text-primary"}${artist class="Lms-btn Lms-player-text Lms-artistname text-muted"}
+ ${release class="Lms-btn Lms-player-text Lms-releasename text-primary"}${artist class="Lms-btn Lms-player-text Lms-artistname text-muted"}
+
diff --git a/approot/messages.xml b/approot/messages.xml
index 82885ada..43389b1e 100644
--- a/approot/messages.xml
+++ b/approot/messages.xml
@@ -37,6 +37,7 @@
Daily
Music collection
+
Hourly
Scan now!
Monthly
diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml
index 8634953f..d4d1d5fe 100644
--- a/approot/messages_fr.xml
+++ b/approot/messages_fr.xml
@@ -37,6 +37,7 @@
Tous les jours
Collection de musiques
+
Toutes les heures
Scanner maintenant !
Tous les mois
diff --git a/approot/playqueue.xml b/approot/playqueue.xml
index c121fd6a..b1c4424e 100644
--- a/approot/playqueue.xml
+++ b/approot/playqueue.xml
@@ -2,16 +2,16 @@
-
-
-
-
+
+
+
+
${entries}
@@ -20,8 +20,9 @@
-
-
+
+
+
@@ -39,7 +40,7 @@
- ${play-btn}${del-btn}
+ ${play-btn class="Lms-playqueue-btn Lms-btn"}${del-btn class="Lms-playqueue-btn Lms-btn"}${more-btn}
${duration}
diff --git a/approot/release.xml b/approot/release.xml
index 1a720e34..ba7e36b3 100644
--- a/approot/release.xml
+++ b/approot/release.xml
@@ -17,7 +17,7 @@
${
}${artist class="Lms-btn Lms-artistname text-muted"}
${}
${}${year}${}${} (${orig-year})${}
${clusters}
- ${play-btn}${more-btn}
+ ${play-btn class="Lms-explore-btn Lms-btn"}${more-btn class="Lms-explore-btn Lms-btn"}
${container}
@@ -59,16 +59,16 @@
-
+
${}${track-number}${}
-
+
-
+
- ${play-btn}${more-btn}
+ ${play-btn class="Lms-explore-btn Lms-btn"}${more-btn class="Lms-explore-btn Lms-btn"}
${duration}
diff --git a/approot/releases.xml b/approot/releases.xml
index 091dcd34..65e3cb8a 100644
--- a/approot/releases.xml
+++ b/approot/releases.xml
@@ -5,7 +5,7 @@
${releases}
diff --git a/approot/tracks.xml b/approot/tracks.xml
index 846b8184..34384ee0 100644
--- a/approot/tracks.xml
+++ b/approot/tracks.xml
@@ -8,7 +8,7 @@
${tracks}
${loading-indicator class="Lms-horizontal-center Lms-loading-indicator"}
@@ -32,7 +32,7 @@
- ${play-btn}${more-btn}
+ ${play-btn class="Lms-explore-btn Lms-btn"}${more-btn class="Lms-explore-btn Lms-btn"}
${duration}
diff --git a/cmake/modules/FindPStreams.cmake b/cmake/modules/FindPStreams.cmake
deleted file mode 100644
index bff205cd..00000000
--- a/cmake/modules/FindPStreams.cmake
+++ /dev/null
@@ -1,17 +0,0 @@
-# If already in cache, be silent
-if(PSTREAMS_INCLUDE_DIRS)
- set (PSTREAMS_FIND_QUIETLY TRUE)
-endif()
-
-FIND_PATH(PSTREAMS_INCLUDE_DIR NAMES pstream.h
- PATH_SUFFIXES pstreams
- HINTS ${PSTREAMS_ROOT}/include $ENV{PSTREAMS_ROOT})
-
-set(PSTREAMS_INCLUDE_DIRS ${PSTREAMS_INCLUDE_DIR})
-
-# Handle the QUIETLY and REQUIRED arguments and set PSTREAMS_FOUND to TRUE if
-# all listed variables are TRUE.
-INCLUDE(FindPackageHandleStandardArgs)
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(Pstreams DEFAULT_MSG PSTREAMS_INCLUDE_DIRS)
-
-MARK_AS_ADVANCED(PSTREAMS_INCLUDE_DIRS)
diff --git a/cmake/modules/FindTaglib.cmake b/cmake/modules/FindTaglib.cmake
deleted file mode 100644
index 7c617918..00000000
--- a/cmake/modules/FindTaglib.cmake
+++ /dev/null
@@ -1,87 +0,0 @@
-# - Try to find the Taglib library
-# Once done this will define
-#
-# TAGLIB_FOUND - system has the taglib library
-# TAGLIB_CFLAGS - the taglib cflags
-# TAGLIB_LIBRARIES - The libraries needed to use taglib
-
-# Copyright (c) 2006, Laurent Montel,
-#
-# Redistribution and use is allowed according to the terms of the BSD license.
-# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
-
-IF(TAGLIB_FOUND)
- MESSAGE(STATUS "Using manually specified taglib locations")
-ELSE()
-
- if(NOT TAGLIB_MIN_VERSION)
- set(TAGLIB_MIN_VERSION "1.6")
- endif(NOT TAGLIB_MIN_VERSION)
-
- if(NOT WIN32)
- find_program(TAGLIBCONFIG_EXECUTABLE NAMES taglib-config PATHS
- ${BIN_INSTALL_DIR}
- )
- endif(NOT WIN32)
-
- #reset vars
- set(TAGLIB_LIBRARIES)
- set(TAGLIB_CFLAGS)
-
-# MESSAGE( STATUS "PATHS: ${PATHS}")
- # if taglib-config has been found
- if(TAGLIBCONFIG_EXECUTABLE)
-
- exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --version RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_VERSION)
-
- if(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}")
- message(STATUS "TagLib version not found: version searched :${TAGLIB_MIN_VERSION}, found ${TAGLIB_VERSION}")
- set(TAGLIB_FOUND FALSE)
- else(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}")
-
- exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --libs RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_LIBRARIES)
-
- exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_CFLAGS)
-
- if(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS)
- set(TAGLIB_FOUND TRUE)
-# message(STATUS "Found taglib: ${TAGLIB_LIBRARIES}")
- endif(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS)
- string(REGEX REPLACE " *-I" ";" TAGLIB_INCLUDES "${TAGLIB_CFLAGS}")
- endif(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}")
- mark_as_advanced(TAGLIB_CFLAGS TAGLIB_LIBRARIES TAGLIB_INCLUDES)
-
- else(TAGLIBCONFIG_EXECUTABLE)
-
- include(FindLibraryWithDebug)
- include(FindPackageHandleStandardArgs)
-
- find_path(TAGLIB_INCLUDES
- NAMES
- tag.h
- PATH_SUFFIXES taglib
- PATHS
- ${INCLUDE_INSTALL_DIR}
- )
-
- find_library_with_debug(TAGLIB_LIBRARIES
- WIN32_DEBUG_POSTFIX d
- NAMES tag
- PATHS
- ${LIB_INSTALL_DIR}
- )
-
- find_package_handle_standard_args(Taglib DEFAULT_MSG
- TAGLIB_INCLUDES TAGLIB_LIBRARIES)
- endif(TAGLIBCONFIG_EXECUTABLE)
-ENDIF()
-
-if(TAGLIB_FOUND)
- if(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE)
- message(STATUS "Found TagLib: ${TAGLIB_LIBRARIES}")
- endif(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE)
-else(TAGLIB_FOUND)
- if(Taglib_FIND_REQUIRED)
- message(FATAL_ERROR "Could not find Taglib")
- endif(Taglib_FIND_REQUIRED)
-endif(TAGLIB_FOUND)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 94aa0ccf..2d02c4c7 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -4,9 +4,8 @@ WORKDIR /tmp/workdir
ARG MAKEFLAGS="-j2"
ARG FFMPEG_VERSION=4.1.4
-ARG WT_VERSION=4.2.0
+ARG WT_VERSION=4.5.0
ARG STB_VERSION=b42009b3b9d4ca35bc703f5310eedc74f584be58
-ARG PSTREAMS_VERSION=1.0.1
ARG LMS_VERSION=v3.6.3
ARG PREFIX="/tmp/install"
@@ -102,13 +101,6 @@ RUN \
make && \
make install
-# libpstreams
-RUN \
- DIR=/tmp/libpstreams && mkdir -p ${DIR} && cd ${DIR} && \
- curl -sLO https://sourceforge.net/projects/pstreams/files/pstreams/Release%201.0/pstreams-${PSTREAMS_VERSION}.tar.gz && \
- tar -x --strip-components=1 -f pstreams-${PSTREAMS_VERSION}.tar.gz && \
- prefix=${PREFIX} make install prefix=/ DESTDIR=${PREFIX}
-
# LMS
RUN \
DIR=/tmp/lms && mkdir -p ${DIR} && cd ${DIR} && \
diff --git a/docroot/css/lms.css b/docroot/css/lms.css
index e0026384..d8020abc 100644
--- a/docroot/css/lms.css
+++ b/docroot/css/lms.css
@@ -112,9 +112,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
}
.Lms-explore-btn {
- font-size: 24px !important;
- text-align: left;
- width: 28px;
+ font-size: 24px;
}
.Lms-explore-clusters {
@@ -185,7 +183,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
}
.Lms-explore-release-entry {
- min-height: 32px;
+ min-height: 34px;
}
.Lms-explore-release-entry:hover .Lms-explore-release-entry-controls {
@@ -278,8 +276,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
}
.Lms-playqueue-btn {
- font-size: 24px !important;
- text-align: left;
+ font-size: 24px;
}
.Lms-playqueue-entry:hover .Lms-playqueue-entry-controls {
@@ -350,8 +347,6 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
.Lms-player-btn-controls {
font-size: 40px !important;
- text-align: center;
- width: 1.1em;
}
.Lms-player-controls {
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..a0253bd1
--- /dev/null
+++ b/src/libs/av/impl/AudioFile.hpp
@@ -0,0 +1,56 @@
+/*
+ * 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..2395a6cc 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
{
@@ -32,11 +35,11 @@ namespace Av
TranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters);
private:
+ Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
- void processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
- bool isFinished() const 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..caf719ba 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,40 @@ 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 +83,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 +125,7 @@ Transcoder::start()
args.emplace_back("-b:a");
args.emplace_back(std::to_string(_parameters.bitrate));
+
// Codecs and formats
switch (_parameters.format)
{
@@ -157,7 +163,6 @@ Transcoder::start()
break;
default:
- _isComplete = true;
return false;
}
@@ -169,84 +174,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