diff --git a/src/libs/av/CMakeLists.txt b/src/libs/av/CMakeLists.txt index 5c63d843..0833bad7 100644 --- a/src/libs/av/CMakeLists.txt +++ b/src/libs/av/CMakeLists.txt @@ -2,9 +2,7 @@ pkg_check_modules(LIBAV IMPORTED_TARGET libavcodec libavutil libavformat) add_library(lmsav STATIC impl/AudioFile.cpp - impl/RawResourceHandlerCreator.cpp impl/Transcoder.cpp - impl/TranscodingResourceHandler.cpp ) target_include_directories(lmsav INTERFACE diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index 5432bfc6..614c6cde 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -34,7 +34,7 @@ extern "C" #include "core/ILogger.hpp" #include "core/String.hpp" -#include "av/Types.hpp" +#include "av/Exception.hpp" namespace lms::av { @@ -321,42 +321,4 @@ namespace lms::av 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 - // std::filesystem::path does not seem to have std::hash specialization on freebsd - static const std::unordered_map entries{ - { ".mp3", "audio/mpeg" }, - { ".ogg", "audio/ogg" }, - { ".oga", "audio/ogg" }, - { ".opus", "audio/opus" }, - { ".aac", "audio/aac" }, - { ".alac", "audio/mp4" }, - { ".m4a", "audio/mp4" }, - { ".m4b", "audio/mp4" }, - { ".flac", "audio/flac" }, - { ".webm", "audio/webm" }, - { ".wav", "audio/x-wav" }, - { ".wma", "audio/x-ms-wma" }, - { ".ape", "audio/x-monkeys-audio" }, - { ".mpc", "audio/x-musepack" }, - { ".shn", "audio/x-shn" }, - { ".aif", "audio/x-aiff" }, - { ".aiff", "audio/x-aiff" }, - { ".m3u", "audio/x-mpegurl" }, - { ".pls", "audio/x-scpls" }, - { ".dsf", "audio/x-dsd" }, - { ".wv", "audio/x-wavpack" }, - { ".wvp", "audio/x-wavpack" }, - { ".mka", "audio/x-matroska" }, - }; - - auto it{ entries.find(core::stringUtils::stringToLower(fileExtension.c_str())) }; - if (it == std::cend(entries)) - return ""; - - return it->second; - } } // namespace lms::av diff --git a/src/libs/av/impl/RawResourceHandlerCreator.cpp b/src/libs/av/impl/RawResourceHandlerCreator.cpp deleted file mode 100644 index 42c200b8..00000000 --- a/src/libs/av/impl/RawResourceHandlerCreator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 Emeric Poupon - * - * This file is part of LMS. - * - * LMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LMS. If not, see . - */ - -#include "av/RawResourceHandlerCreator.hpp" - -#include "av/IAudioFile.hpp" -#include "core/FileResourceHandlerCreator.hpp" - -namespace lms::av -{ - std::unique_ptr createRawResourceHandler(const std::filesystem::path& path) - { - std::string_view mimeType{ getMimeType(path.extension()) }; - return createFileResourceHandler(path, mimeType.empty() ? "application/octet-stream" : mimeType); - } -} // namespace lms::av diff --git a/src/libs/av/impl/Transcoder.cpp b/src/libs/av/impl/Transcoder.cpp index efc0a304..8dc4d59d 100644 --- a/src/libs/av/impl/Transcoder.cpp +++ b/src/libs/av/impl/Transcoder.cpp @@ -27,35 +27,20 @@ #include "core/ILogger.hpp" #include "core/Service.hpp" -#include "av/Types.hpp" +#include "av/Exception.hpp" -namespace lms::av::transcoding +namespace lms::av { - #define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message) + std::unique_ptr createTranscoder(const InputParameters& inputParameters, const OutputParameters& outputParameters) + { + return std::make_unique(inputParameters, outputParameters); + } + static std::atomic globalId{}; static std::filesystem::path ffmpegPath; - 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"; - } - - throw Exception{ "Invalid encoding" }; - } - void Transcoder::init() { ffmpegPath = core::Service::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg"); @@ -63,10 +48,10 @@ namespace lms::av::transcoding throw Exception{ "File '" + ffmpegPath.string() + "' does not exist!" }; } - Transcoder::Transcoder(const InputParameters& inputParameters, const OutputParameters& outputParameters) + Transcoder::Transcoder(const InputParameters& inputParams, const OutputParameters& outputParams) : _debugId{ globalId++ } - , _inputParameters{ inputParameters } - , _outputParameters{ outputParameters } + , _inputParams{ inputParams } + , _outputParams{ outputParams } { start(); } @@ -80,17 +65,18 @@ namespace lms::av::transcoding try { - if (!std::filesystem::exists(_inputParameters.trackPath)) - throw Exception{ "File '" + _inputParameters.trackPath.string() + "' does not exist!" }; - if (!std::filesystem::is_regular_file(_inputParameters.trackPath)) - throw Exception{ "File '" + _inputParameters.trackPath.string() + "' is not regular!" }; + if (!std::filesystem::exists(_inputParams.file)) + throw Exception{ "File " + _inputParams.file.string() + " does not exist!" }; + if (!std::filesystem::is_regular_file(_inputParams.file)) + throw Exception{ "File " + _inputParams.file.string() + " is not regular!" }; } catch (const std::filesystem::filesystem_error& e) { - throw Exception{ "File error '" + _inputParameters.trackPath.string() + "': " + e.what() }; + // TODO store/raise e.code() + throw Exception{ "File error '" + _inputParams.file.string() + "': " + e.what() }; } - LOG(INFO, "Transcoding file " << _inputParameters.trackPath); + LOG(INFO, "Transcoding file " << _inputParams.file); std::vector args; @@ -109,22 +95,22 @@ namespace lms::av::transcoding args.emplace_back("-ss"); std::ostringstream oss; - oss << std::fixed << std::showpoint << std::setprecision(3) << (_outputParameters.offset.count() / float{ 1'000 }); + oss << std::fixed << std::showpoint << std::setprecision(3) << (_inputParams.offset.count() / float{ 1'000 }); args.emplace_back(oss.str()); } // Input file args.emplace_back("-i"); - args.emplace_back(_inputParameters.trackPath.string()); + args.emplace_back(_inputParams.file.string()); // Stream mapping, if set - if (_outputParameters.stream) + if (_inputParams.streamIndex) { args.emplace_back("-map"); - args.emplace_back("0:" + std::to_string(*_outputParameters.stream)); + args.emplace_back("0:" + std::to_string(*_inputParams.streamIndex)); } - if (_outputParameters.stripMetadata) + if (_outputParams.stripMetadata) { // Strip metadata args.emplace_back("-map_metadata"); @@ -136,10 +122,10 @@ namespace lms::av::transcoding // Output bitrates args.emplace_back("-b:a"); - args.emplace_back(std::to_string(_outputParameters.bitrate)); + args.emplace_back(std::to_string(_outputParams.bitrate)); // Codecs and formats - switch (_outputParameters.format) + switch (_outputParams.format) { case OutputFormat::MP3: args.emplace_back("-f"); @@ -175,11 +161,9 @@ namespace lms::av::transcoding break; default: - throw Exception{ "Unhandled format (" + std::to_string(static_cast(_outputParameters.format)) + ")" }; + throw Exception{ "Unhandled format (" + std::to_string(static_cast(_outputParams.format)) + ")" }; } - _outputMimeType = formatToMimetype(_outputParameters.format); - args.emplace_back("pipe:1"); LOG(DEBUG, "Dumping args (" << args.size() << ")"); @@ -213,6 +197,25 @@ namespace lms::av::transcoding return _childProcess->readSome(buffer, bufferSize); } + std::string_view Transcoder::getOutputMimeType() const + { + switch (_outputParams.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"; + } + + return "application/octet-stream"; // default, should not happen + } + bool Transcoder::finished() const { assert(_childProcess); @@ -220,4 +223,4 @@ namespace lms::av::transcoding return _childProcess->finished(); } -} // namespace lms::av::transcoding +} // namespace lms::av diff --git a/src/libs/av/impl/Transcoder.hpp b/src/libs/av/impl/Transcoder.hpp index 3b226dff..685d18b0 100644 --- a/src/libs/av/impl/Transcoder.hpp +++ b/src/libs/av/impl/Transcoder.hpp @@ -19,47 +19,37 @@ #pragma once -#include - -#include "av/TranscodingParameters.hpp" +#include "av/ITranscoder.hpp" namespace lms::core { class IChildProcess; } -namespace lms::av::transcoding +namespace lms::av { - class Transcoder + class Transcoder : public ITranscoder { public: Transcoder(const InputParameters& inputParameters, const OutputParameters& outputParameters); - ~Transcoder(); + ~Transcoder() override; Transcoder(const Transcoder&) = delete; Transcoder& operator=(const Transcoder&) = delete; - Transcoder(Transcoder&&) = delete; - Transcoder& operator=(Transcoder&&) = delete; - - // non blocking calls - using ReadCallback = std::function; - void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback); - std::size_t readSome(std::byte* buffer, std::size_t bufferSize); - - const std::string& getOutputMimeType() const { return _outputMimeType; } - const OutputParameters& getOutputParameters() const { return _outputParameters; } - - bool finished() const; private: - static void init(); + void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback) override; + std::size_t readSome(std::byte* buffer, std::size_t bufferSize) override; + std::string_view getOutputMimeType() const override; + const OutputParameters& getOutputParameters() const override { return _outputParams; } + + bool finished() const override; + static void init(); void start(); const std::size_t _debugId{}; - const InputParameters _inputParameters; - const OutputParameters _outputParameters; - std::string _outputMimeType; - + const InputParameters _inputParams; + const OutputParameters _outputParams; std::unique_ptr _childProcess; }; -} // namespace lms::av::transcoding \ No newline at end of file +} // namespace lms::av \ No newline at end of file diff --git a/src/libs/av/include/av/Types.hpp b/src/libs/av/include/av/Exception.hpp similarity index 100% rename from src/libs/av/include/av/Types.hpp rename to src/libs/av/include/av/Exception.hpp diff --git a/src/libs/av/include/av/IAudioFile.hpp b/src/libs/av/include/av/IAudioFile.hpp index 234f8df4..63087058 100644 --- a/src/libs/av/include/av/IAudioFile.hpp +++ b/src/libs/av/include/av/IAudioFile.hpp @@ -101,7 +101,4 @@ namespace lms::av }; std::unique_ptr parseAudioFile(const std::filesystem::path& p); - - std::string_view getMimeType(const std::filesystem::path& fileExtension); - } // namespace lms::av diff --git a/src/libs/av/include/av/ITranscoder.hpp b/src/libs/av/include/av/ITranscoder.hpp new file mode 100644 index 00000000..165858b0 --- /dev/null +++ b/src/libs/av/include/av/ITranscoder.hpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2025 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 +#include +#include +#include + +namespace lms::av +{ + struct InputParameters + { + std::filesystem::path file; // Path to the input file + std::chrono::milliseconds offset{}; // Offset in the input file to start transcoding from + std::optional streamIndex; // Index of the stream to be transcoded (select "best" audio stream if not set) + }; + + enum class OutputFormat + { + MP3, + OGG_OPUS, + MATROSKA_OPUS, + OGG_VORBIS, + WEBM_VORBIS, + }; + + struct OutputParameters + { + OutputFormat format; + std::size_t bitrate{ 128'000 }; + bool stripMetadata{ true }; + }; + + class ITranscoder + { + public: + virtual ~ITranscoder() = default; + + // non blocking calls + using ReadCallback = std::function; + virtual void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback callback) = 0; + virtual std::size_t readSome(std::byte* buffer, std::size_t bufferSize) = 0; + + virtual std::string_view getOutputMimeType() const = 0; + virtual const OutputParameters& getOutputParameters() const = 0; + + virtual bool finished() const = 0; + }; + + std::unique_ptr createTranscoder(const InputParameters& inputParameters, const OutputParameters& outputParameters); +} // namespace lms::av \ No newline at end of file diff --git a/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp b/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp deleted file mode 100644 index 553ab5ea..00000000 --- a/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp +++ /dev/null @@ -1,32 +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 . - */ - -#pragma once - -#include - -#include "core/IResourceHandler.hpp" - -namespace lms::av::transcoding -{ - struct InputParameters; - struct OutputParameters; - - std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength); -} // namespace lms::av::transcoding diff --git a/src/libs/core/CMakeLists.txt b/src/libs/core/CMakeLists.txt index 81f4edcc..8ce177b5 100644 --- a/src/libs/core/CMakeLists.txt +++ b/src/libs/core/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(lmscore STATIC impl/FileResourceHandler.cpp impl/IOContextRunner.cpp impl/Logger.cpp + impl/MimeTypes.cpp impl/NetAddress.cpp impl/PartialDateTime.cpp impl/Path.cpp diff --git a/src/libs/core/impl/FileResourceHandler.cpp b/src/libs/core/impl/FileResourceHandler.cpp index 130671dc..7db88efc 100644 --- a/src/libs/core/impl/FileResourceHandler.cpp +++ b/src/libs/core/impl/FileResourceHandler.cpp @@ -22,12 +22,13 @@ #include #include "core/ILogger.hpp" +#include "core/MimeTypes.hpp" -namespace lms +namespace lms::core { std::unique_ptr createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType) { - return std::make_unique(path, mimeType); + return std::make_unique(path, mimeType.empty() ? getMimeType(path.extension()) : mimeType); } FileResourceHandler::FileResourceHandler(const std::filesystem::path& path, std::string_view mimeType) @@ -134,4 +135,4 @@ namespace lms LMS_LOG(UTILS, DEBUG, "Job complete!"); return nullptr; } -} // namespace lms \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/FileResourceHandler.hpp b/src/libs/core/impl/FileResourceHandler.hpp index 6b79d4e5..7e541038 100644 --- a/src/libs/core/impl/FileResourceHandler.hpp +++ b/src/libs/core/impl/FileResourceHandler.hpp @@ -25,7 +25,7 @@ #include "core/IResourceHandler.hpp" -namespace lms +namespace lms::core { class FileResourceHandler final : public IResourceHandler { @@ -43,4 +43,4 @@ namespace lms ::uint64_t _beyondLastByte{}; ::uint64_t _offset{}; }; -} // namespace lms \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/MimeTypes.cpp b/src/libs/core/impl/MimeTypes.cpp new file mode 100644 index 00000000..337800ac --- /dev/null +++ b/src/libs/core/impl/MimeTypes.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "core/MimeTypes.hpp" + +#include + +#include "core/String.hpp" + +namespace lms::core +{ + std::string_view getMimeType(const std::filesystem::path& fileExtension) + { + static const std::unordered_map entries{ + // audio + { ".aac", "audio/aac" }, + { ".ac3", "audio/ac3" }, + { ".aif", "audio/x-aiff" }, + { ".aiff", "audio/x-aiff" }, + { ".alac", "audio/mp4" }, + { ".ape", "audio/x-monkeys-audio" }, + { ".dff", "audio/x-dsd-dff" }, + { ".dsdiff", "audio/x-dsd-diff" }, + { ".dsf", "audio/x-dsd" }, + { ".dsf", "audio/x-dsd-dsf" }, + { ".dts", "audio/vnd.dts" }, + { ".dtshd", "audio/vnd.dts.hd" }, + { ".eac3", "audio/eac3" }, + { ".flac", "audio/flac" }, + { ".m3u", "audio/x-mpegurl" }, + { ".m4a", "audio/mp4" }, + { ".m4b", "audio/mp4" }, + { ".mka", "audio/x-matroska" }, + { ".mka", "audio/x-matroska" }, + { ".mp3", "audio/mpeg" }, + { ".mpc", "audio/x-musepack" }, + { ".oga", "audio/ogg" }, + { ".ogg", "audio/ogg" }, + { ".opus", "audio/opus" }, + { ".pls", "audio/x-scpls" }, + { ".shn", "audio/x-shn" }, + { ".wav", "audio/x-wav" }, + { ".webm", "audio/webm" }, + { ".wma", "audio/x-ms-wma" }, + { ".wv", "audio/x-wavpack" }, + { ".wvp", "audio/x-wavpack" }, + + // image + { ".bmp", "image/bmp" }, + { ".gif", "image/gif" }, + { ".jpg", "image/jpeg" }, + { ".jpeg", "image/jpeg" }, + { ".png", "image/png" }, + { ".webp", "image/webp" }, + }; + + auto it{ entries.find(core::stringUtils::stringToLower(fileExtension.c_str())) }; + if (it == std::cend(entries)) + return "application/octet-stream"; + + return it->second; + } +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/FileResourceHandlerCreator.hpp b/src/libs/core/include/core/FileResourceHandlerCreator.hpp index 2ea0cb3d..6e9c755c 100644 --- a/src/libs/core/include/core/FileResourceHandlerCreator.hpp +++ b/src/libs/core/include/core/FileResourceHandlerCreator.hpp @@ -25,7 +25,7 @@ #include "core/IResourceHandler.hpp" -namespace lms +namespace lms::core { - std::unique_ptr createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType); + std::unique_ptr createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType = ""); } \ No newline at end of file diff --git a/src/libs/core/include/core/IResourceHandler.hpp b/src/libs/core/include/core/IResourceHandler.hpp index 49433b65..cdf04969 100644 --- a/src/libs/core/include/core/IResourceHandler.hpp +++ b/src/libs/core/include/core/IResourceHandler.hpp @@ -22,8 +22,7 @@ #include #include -// TODO, move elsewhere -namespace lms +namespace lms::core { // Helper class to serve a resource (must be saved as continuation data if not complete) class IResourceHandler @@ -34,4 +33,4 @@ namespace lms [[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0; virtual void abort() = 0; }; -} // namespace lms \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/av/include/av/RawResourceHandlerCreator.hpp b/src/libs/core/include/core/MimeTypes.hpp similarity index 77% rename from src/libs/av/include/av/RawResourceHandlerCreator.hpp rename to src/libs/core/include/core/MimeTypes.hpp index e2282de2..fde19596 100644 --- a/src/libs/av/include/av/RawResourceHandlerCreator.hpp +++ b/src/libs/core/include/core/MimeTypes.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Emeric Poupon + * Copyright (C) 2025 Emeric Poupon * * This file is part of LMS. * @@ -20,11 +20,9 @@ #pragma once #include -#include +#include -#include "core/IResourceHandler.hpp" - -namespace lms::av +namespace lms::core { - std::unique_ptr createRawResourceHandler(const std::filesystem::path& path); + std::string_view getMimeType(const std::filesystem::path& fileExtension); } \ No newline at end of file diff --git a/src/libs/metadata/impl/avformat/AvFormatImageReader.cpp b/src/libs/metadata/impl/avformat/AvFormatImageReader.cpp index 7ec95d1c..8a420534 100644 --- a/src/libs/metadata/impl/avformat/AvFormatImageReader.cpp +++ b/src/libs/metadata/impl/avformat/AvFormatImageReader.cpp @@ -19,8 +19,8 @@ #include "AvFormatImageReader.hpp" +#include "av/Exception.hpp" #include "av/IAudioFile.hpp" -#include "av/Types.hpp" #include "metadata/Exception.hpp" namespace lms::metadata::avformat diff --git a/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp b/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp index 04f50f62..1ac145a0 100644 --- a/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp +++ b/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp @@ -19,8 +19,8 @@ #include "AvFormatTagReader.hpp" +#include "av/Exception.hpp" #include "av/IAudioFile.hpp" -#include "av/Types.hpp" #include "core/ILogger.hpp" #include "core/String.hpp" #include "metadata/Exception.hpp" diff --git a/src/libs/services/CMakeLists.txt b/src/libs/services/CMakeLists.txt index 9cb1f7ce..02b6c6aa 100644 --- a/src/libs/services/CMakeLists.txt +++ b/src/libs/services/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(feedback) add_subdirectory(recommendation) add_subdirectory(scanner) add_subdirectory(scrobbling) +add_subdirectory(transcoding) diff --git a/src/libs/services/transcoding/CMakeLists.txt b/src/libs/services/transcoding/CMakeLists.txt new file mode 100644 index 00000000..2c0f5843 --- /dev/null +++ b/src/libs/services/transcoding/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(lmstranscoding STATIC + impl/TranscodingResourceHandler.cpp + impl/TranscodingService.cpp + ) + +target_include_directories(lmstranscoding INTERFACE + include + ) + +target_include_directories(lmstranscoding PRIVATE + include + impl + ) + +target_link_libraries(lmstranscoding PRIVATE + lmsav + ) diff --git a/src/libs/av/impl/TranscodingResourceHandler.cpp b/src/libs/services/transcoding/impl/TranscodingResourceHandler.cpp similarity index 66% rename from src/libs/av/impl/TranscodingResourceHandler.cpp rename to src/libs/services/transcoding/impl/TranscodingResourceHandler.cpp index 611c87c8..0fda5f9e 100644 --- a/src/libs/av/impl/TranscodingResourceHandler.cpp +++ b/src/libs/services/transcoding/impl/TranscodingResourceHandler.cpp @@ -18,9 +18,12 @@ */ #include "TranscodingResourceHandler.hpp" + +#include "av/Exception.hpp" +#include "av/ITranscoder.hpp" #include "core/ILogger.hpp" -namespace lms::av::transcoding +namespace lms::transcoding { namespace { @@ -29,9 +32,20 @@ namespace lms::av::transcoding const std::size_t estimatedContentLength{ outputParameters.bitrate / 8 * static_cast(std::chrono::duration_cast(inputParameters.duration).count()) / 1000 }; return estimatedContentLength; } + + av::InputParameters toAv(const InputParameters& in) + { + return { .file = in.file, .offset = in.offset, .streamIndex = in.streamIndex }; + } + + av::OutputParameters toAv(const OutputParameters& out) + { + return { .format = static_cast(out.format), .bitrate = out.bitrate, .stripMetadata = out.stripMetadata }; + } + } // namespace - std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) + std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) { return std::make_unique(inputParameters, outputParameters, estimateContentLength); } @@ -40,20 +54,36 @@ namespace lms::av::transcoding 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(TRANSCODING, DEBUG, "Estimated content length = " << *_estimatedContentLength); - else - LMS_LOG(TRANSCODING, DEBUG, "Not using estimated content length"); + try + { + _transcoder = av::createTranscoder(toAv(inputParameters), toAv(outputParameters)); + + if (_estimatedContentLength) + LMS_LOG(TRANSCODING, DEBUG, "Estimated content length = " << *_estimatedContentLength); + else + LMS_LOG(TRANSCODING, DEBUG, "Not using estimated content length"); + } + catch (av::Exception& e) + { + LMS_LOG(TRANSCODING, ERROR, "Failed to create transcoder: " << e.what()); + } } + TranscodingResourceHandler::~TranscodingResourceHandler() = default; + Wt::Http::ResponseContinuation* TranscodingResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response) { + if (!_transcoder) + { + response.setStatus(404); + return {}; + } + if (_estimatedContentLength) response.setContentLength(*_estimatedContentLength); - response.setMimeType(_transcoder.getOutputMimeType()); - LMS_LOG(TRANSCODING, DEBUG, "Transcoder finished = " << _transcoder.finished() << ", total served bytes = " << _totalServedByteCount << ", mime type = " << _transcoder.getOutputMimeType()); + response.setMimeType(std::string{ _transcoder->getOutputMimeType() }); + LMS_LOG(TRANSCODING, DEBUG, "Transcoder finished = " << _transcoder->finished() << ", total served bytes = " << _totalServedByteCount << ", mime type = " << _transcoder->getOutputMimeType()); if (_bytesReadyCount > 0) { @@ -64,11 +94,11 @@ namespace lms::av::transcoding _bytesReadyCount = 0; } - if (!_transcoder.finished()) + if (!_transcoder->finished()) { Wt::Http::ResponseContinuation* continuation{ response.createContinuation() }; continuation->waitForMoreData(); - _transcoder.asyncRead(_buffer.data(), _buffer.size(), [this, continuation](std::size_t nbBytesRead) { + _transcoder->asyncRead(_buffer.data(), _buffer.size(), [this, continuation](std::size_t nbBytesRead) { LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back"); assert(_bytesReadyCount == 0); @@ -96,4 +126,4 @@ namespace lms::av::transcoding return {}; } -} // namespace lms::av::transcoding +} // namespace lms::transcoding diff --git a/src/libs/av/impl/TranscodingResourceHandler.hpp b/src/libs/services/transcoding/impl/TranscodingResourceHandler.hpp similarity index 81% rename from src/libs/av/impl/TranscodingResourceHandler.hpp rename to src/libs/services/transcoding/impl/TranscodingResourceHandler.hpp index 6e5aa516..26465b1b 100644 --- a/src/libs/av/impl/TranscodingResourceHandler.hpp +++ b/src/libs/services/transcoding/impl/TranscodingResourceHandler.hpp @@ -20,20 +20,24 @@ #pragma once #include +#include #include -#include "av/TranscodingParameters.hpp" #include "core/IResourceHandler.hpp" +#include "services/transcoding/ITranscodingService.hpp" -#include "Transcoder.hpp" - -namespace lms::av::transcoding +namespace lms::av { - class TranscodingResourceHandler final : public IResourceHandler + class ITranscoder; +} + +namespace lms::transcoding +{ + class TranscodingResourceHandler final : public core::IResourceHandler { public: TranscodingResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength); - ~TranscodingResourceHandler() override = default; + ~TranscodingResourceHandler() override; TranscodingResourceHandler(const TranscodingResourceHandler&) = delete; TranscodingResourceHandler& operator=(const TranscodingResourceHandler&) = delete; @@ -47,6 +51,6 @@ namespace lms::av::transcoding std::array _buffer; std::size_t _bytesReadyCount{}; std::size_t _totalServedByteCount{}; - Transcoder _transcoder; + std::unique_ptr _transcoder; }; -} // namespace lms::av::transcoding +} // namespace lms::transcoding diff --git a/src/libs/services/transcoding/impl/TranscodingService.cpp b/src/libs/services/transcoding/impl/TranscodingService.cpp new file mode 100644 index 00000000..57a2f57e --- /dev/null +++ b/src/libs/services/transcoding/impl/TranscodingService.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "TranscodingService.hpp" + +#include "core/ILogger.hpp" + +#include "TranscodingResourceHandler.hpp" + +namespace lms::transcoding +{ + std::unique_ptr createTranscodingService(core::IChildProcessManager& childProcessManager) + { + return std::make_unique(childProcessManager); + } + + TranscodingService::TranscodingService(core::IChildProcessManager& childProcessManager) + : _childProcessManager(childProcessManager) + { + LMS_LOG(TRANSCODING, INFO, "Service started!"); + } + + TranscodingService::~TranscodingService() + { + LMS_LOG(TRANSCODING, INFO, "Service stopped!"); + } + + std::unique_ptr TranscodingService::createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) + { + return std::make_unique(inputParameters, outputParameters, estimateContentLength); + } +} // namespace lms::transcoding diff --git a/src/libs/services/transcoding/impl/TranscodingService.hpp b/src/libs/services/transcoding/impl/TranscodingService.hpp new file mode 100644 index 00000000..d3c55aee --- /dev/null +++ b/src/libs/services/transcoding/impl/TranscodingService.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025 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 "services/transcoding/ITranscodingService.hpp" + +namespace lms::transcoding +{ + class TranscodingService : public ITranscodingService + { + public: + explicit TranscodingService(core::IChildProcessManager& childProcessManager); + ~TranscodingService() override; + + TranscodingService(const TranscodingService&) = delete; + TranscodingService& operator=(const TranscodingService&) = delete; + + private: + std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) override; + + core::IChildProcessManager& _childProcessManager; + }; +} // namespace lms::transcoding diff --git a/src/libs/av/include/av/TranscodingParameters.hpp b/src/libs/services/transcoding/include/services/transcoding/ITranscodingService.hpp similarity index 50% rename from src/libs/av/include/av/TranscodingParameters.hpp rename to src/libs/services/transcoding/include/services/transcoding/ITranscodingService.hpp index a2f9516b..3c383f14 100644 --- a/src/libs/av/include/av/TranscodingParameters.hpp +++ b/src/libs/services/transcoding/include/services/transcoding/ITranscodingService.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2025 Emeric Poupon * * This file is part of LMS. * @@ -19,16 +19,24 @@ #pragma once -#include #include +#include #include -namespace lms::av::transcoding +namespace lms::core +{ + class IChildProcessManager; + class IResourceHandler; +} // namespace lms::core + +namespace lms::transcoding { struct InputParameters { - std::filesystem::path trackPath; - std::chrono::milliseconds duration; // used to estimate content length + std::filesystem::path file; // Path to the input file + std::chrono::milliseconds duration; // Offset in the input file to start transcoding from + std::chrono::milliseconds offset{}; // Offset in the input file to start transcoding from + std::optional streamIndex; // Index of the stream to be transcoded (select "best" audio stream if not set) }; enum class OutputFormat @@ -40,14 +48,20 @@ namespace lms::av::transcoding WEBM_VORBIS, }; - std::string_view toMimetype(OutputFormat format); - struct OutputParameters { OutputFormat format; std::size_t bitrate{ 128'000 }; - std::optional stream; // Id of the stream to be transcoded (auto detect by default) - std::chrono::milliseconds offset{ 0 }; bool stripMetadata{ true }; }; -} // namespace lms::av::transcoding + + class ITranscodingService + { + public: + virtual ~ITranscodingService() = default; + + virtual std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) = 0; + }; + + std::unique_ptr createTranscodingService(core::IChildProcessManager& childProcessManager); +} // namespace lms::transcoding diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt index 3149d14f..e1e9fe23 100644 --- a/src/libs/subsonic/CMakeLists.txt +++ b/src/libs/subsonic/CMakeLists.txt @@ -44,6 +44,7 @@ target_include_directories(lmssubsonic PRIVATE ) target_link_libraries(lmssubsonic PRIVATE + lmsartwork lmsauth lmsav lmsdatabase @@ -51,7 +52,7 @@ target_link_libraries(lmssubsonic PRIVATE lmsrecommendation lmsscanner lmsscrobbling - lmsartwork + lmstranscoding lmscore std::filesystem ) diff --git a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp index b3e764ae..d756ea1e 100644 --- a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp @@ -19,11 +19,9 @@ #include "MediaRetrieval.hpp" +#include "av/Exception.hpp" #include "av/IAudioFile.hpp" -#include "av/RawResourceHandlerCreator.hpp" -#include "av/TranscodingParameters.hpp" -#include "av/TranscodingResourceHandlerCreator.hpp" -#include "av/Types.hpp" +#include "core/FileResourceHandlerCreator.hpp" #include "core/ILogger.hpp" #include "core/IResourceHandler.hpp" #include "core/String.hpp" @@ -34,6 +32,7 @@ #include "database/TrackLyrics.hpp" #include "database/User.hpp" #include "services/artwork/IArtworkService.hpp" +#include "services/transcoding/ITranscodingService.hpp" #include "CoverArtId.hpp" #include "ParameterParsing.hpp" @@ -47,12 +46,12 @@ namespace lms::api::subsonic namespace { - std::optional subsonicStreamFormatToAvOutputFormat(std::string_view format) + std::optional subsonicStreamFormatToAvOutputFormat(std::string_view format) { - for (const auto& [str, avFormat] : std::initializer_list>{ - { "mp3", av::transcoding::OutputFormat::MP3 }, - { "opus", av::transcoding::OutputFormat::OGG_OPUS }, - { "vorbis", av::transcoding::OutputFormat::OGG_VORBIS }, + for (const auto& [str, avFormat] : std::initializer_list>{ + { "mp3", transcoding::OutputFormat::MP3 }, + { "opus", transcoding::OutputFormat::OGG_OPUS }, + { "vorbis", transcoding::OutputFormat::OGG_VORBIS }, }) { if (core::stringUtils::stringCaseInsensitiveEqual(str, format)) @@ -61,37 +60,37 @@ namespace lms::api::subsonic return std::nullopt; } - av::transcoding::OutputFormat userTranscodeFormatToAvFormat(db::TranscodingOutputFormat format) + transcoding::OutputFormat userTranscodeFormatToAvFormat(db::TranscodingOutputFormat format) { switch (format) { case db::TranscodingOutputFormat::MP3: - return av::transcoding::OutputFormat::MP3; + return transcoding::OutputFormat::MP3; case db::TranscodingOutputFormat::OGG_OPUS: - return av::transcoding::OutputFormat::OGG_OPUS; + return transcoding::OutputFormat::OGG_OPUS; case db::TranscodingOutputFormat::MATROSKA_OPUS: - return av::transcoding::OutputFormat::MATROSKA_OPUS; + return transcoding::OutputFormat::MATROSKA_OPUS; case db::TranscodingOutputFormat::OGG_VORBIS: - return av::transcoding::OutputFormat::OGG_VORBIS; + return transcoding::OutputFormat::OGG_VORBIS; case db::TranscodingOutputFormat::WEBM_VORBIS: - return av::transcoding::OutputFormat::WEBM_VORBIS; + return transcoding::OutputFormat::WEBM_VORBIS; } - return av::transcoding::OutputFormat::OGG_OPUS; + return transcoding::OutputFormat::OGG_OPUS; } - bool isCodecCompatibleWithOutputFormat(av::DecodingCodec codec, av::transcoding::OutputFormat outputFormat) + bool isCodecCompatibleWithOutputFormat(av::DecodingCodec codec, transcoding::OutputFormat outputFormat) { switch (outputFormat) { - case av::transcoding::OutputFormat::MP3: + case transcoding::OutputFormat::MP3: return codec == av::DecodingCodec::MP3; - case av::transcoding::OutputFormat::OGG_OPUS: - case av::transcoding::OutputFormat::MATROSKA_OPUS: + case transcoding::OutputFormat::OGG_OPUS: + case transcoding::OutputFormat::MATROSKA_OPUS: return codec == av::DecodingCodec::OPUS; - case av::transcoding::OutputFormat::OGG_VORBIS: - case av::transcoding::OutputFormat::WEBM_VORBIS: + case transcoding::OutputFormat::OGG_VORBIS: + case transcoding::OutputFormat::WEBM_VORBIS: return codec == av::DecodingCodec::VORBIS; } @@ -100,13 +99,14 @@ namespace lms::api::subsonic struct StreamParameters { - av::transcoding::InputParameters inputParameters; - std::optional outputParameters; + transcoding::InputParameters inputParameters; + std::optional outputParameters; bool estimateContentLength{}; }; - bool isOutputFormatCompatible(const std::filesystem::path& trackPath, av::transcoding::OutputFormat outputFormat) + bool isOutputFormatCompatible(const std::filesystem::path& trackPath, transcoding::OutputFormat outputFormat) { + // TODO: put this information in db during scan try { const auto audioFile{ av::parseAudioFile(trackPath) }; @@ -143,14 +143,15 @@ namespace lms::api::subsonic if (!track) throw RequestedDataNotFoundError{}; - parameters.inputParameters.trackPath = track->getAbsoluteFilePath(); + parameters.inputParameters.file = track->getAbsoluteFilePath(); parameters.inputParameters.duration = track->getDuration(); + parameters.inputParameters.offset = std::chrono::seconds{ timeOffset }; parameters.estimateContentLength = estimateContentLength; if (format == "raw") // raw => no transcoding return parameters; - std::optional requestedFormat{ subsonicStreamFormatToAvOutputFormat(format) }; + std::optional requestedFormat{ subsonicStreamFormatToAvOutputFormat(format) }; if (!requestedFormat) { if (context.user->getSubsonicEnableTranscodingByDefault()) @@ -185,10 +186,8 @@ namespace lms::api::subsonic if (maxBitRate) bitrate = std::min(bitrate, maxBitRate); - av::transcoding::OutputParameters& outputParameters{ parameters.outputParameters.emplace() }; - + transcoding::OutputParameters& outputParameters{ parameters.outputParameters.emplace() }; outputParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) - outputParameters.offset = std::chrono::seconds{ timeOffset }; outputParameters.format = *requestedFormat; outputParameters.bitrate = bitrate; @@ -266,7 +265,7 @@ namespace lms::api::subsonic void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response) { - std::shared_ptr resourceHandler; + std::shared_ptr resourceHandler; Wt::Http::ResponseContinuation* continuation{ request.continuation() }; if (!continuation) @@ -285,11 +284,11 @@ namespace lms::api::subsonic trackPath = track->getAbsoluteFilePath(); } - resourceHandler = av::createRawResourceHandler(trackPath); + resourceHandler = core::createFileResourceHandler(trackPath); } else { - resourceHandler = Wt::cpp17::any_cast>(continuation->data()); + resourceHandler = Wt::cpp17::any_cast>(continuation->data()); } continuation = resourceHandler->processRequest(request, response); @@ -299,7 +298,7 @@ namespace lms::api::subsonic void handleStream(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response) { - std::shared_ptr resourceHandler; + std::shared_ptr resourceHandler; try { @@ -308,13 +307,13 @@ namespace lms::api::subsonic { StreamParameters streamParameters{ getStreamParameters(context) }; if (streamParameters.outputParameters) - resourceHandler = av::transcoding::createResourceHandler(streamParameters.inputParameters, *streamParameters.outputParameters, streamParameters.estimateContentLength); + resourceHandler = core::Service::get()->createResourceHandler(streamParameters.inputParameters, *streamParameters.outputParameters, streamParameters.estimateContentLength); else - resourceHandler = av::createRawResourceHandler(streamParameters.inputParameters.trackPath); + resourceHandler = core::createFileResourceHandler(streamParameters.inputParameters.file); } else { - resourceHandler = Wt::cpp17::any_cast>(continuation->data()); + resourceHandler = Wt::cpp17::any_cast>(continuation->data()); } continuation = resourceHandler->processRequest(request, response); diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index fb6f37b3..830a11a5 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -23,6 +23,7 @@ #include "av/IAudioFile.hpp" #include "core/ITraceLogger.hpp" +#include "core/MimeTypes.hpp" #include "core/Service.hpp" #include "core/String.hpp" #include "database/Artist.hpp" @@ -107,7 +108,7 @@ namespace lms::api::subsonic { const std::string fileSuffix{ formatToSuffix(context.user->getSubsonicDefaultTranscodingOutputFormat()) }; trackResponse.setAttribute("transcodedSuffix", fileSuffix); - trackResponse.setAttribute("transcodedContentType", av::getMimeType(std::filesystem::path{ "." + fileSuffix })); + trackResponse.setAttribute("transcodedContentType", core::getMimeType(std::filesystem::path{ "." + fileSuffix })); } const Release::pointer release{ track->getRelease() }; @@ -156,7 +157,7 @@ namespace lms::api::subsonic trackResponse.setAttribute("bitRate", (track->getBitrate() / 1000)); trackResponse.setAttribute("type", "music"); trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getAddedTime())); - trackResponse.setAttribute("contentType", av::getMimeType(track->getAbsoluteFilePath().extension())); + trackResponse.setAttribute("contentType", core::getMimeType(track->getAbsoluteFilePath().extension())); if (const auto rating{ core::Service::get()->getRating(context.user->getId(), track->getId()) }) trackResponse.setAttribute("userRating", *rating); diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index b5027141..9db7d25a 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -73,6 +73,7 @@ target_link_libraries(lms PRIVATE lmsscrobbling lmsartwork lmssubsonic + lmstranscoding lmscore ) diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 39cacc87..fd9a7f12 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -44,6 +44,7 @@ #include "services/recommendation/IRecommendationService.hpp" #include "services/scanner/IScannerService.hpp" #include "services/scrobbling/IScrobblingService.hpp" +#include "services/transcoding/ITranscodingService.hpp" #include "subsonic/SubsonicResource.hpp" #include "ui/Auth.hpp" #include "ui/LmsApplication.hpp" @@ -373,6 +374,7 @@ namespace lms core::Service recommendationService{ recommendation::createRecommendationService(database) }; core::Service playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) }; core::Service scannerService{ scanner::createScannerService(database) }; + core::Service transcodingService{ transcoding::createTranscodingService(*childProcessManagerService.get()) }; scannerService->getEvents().scanComplete.connect([&] { // Flush cover cache even if no changes: diff --git a/src/lms/ui/resource/AudioFileResource.cpp b/src/lms/ui/resource/AudioFileResource.cpp index 2ab9d51d..1a90142a 100644 --- a/src/lms/ui/resource/AudioFileResource.cpp +++ b/src/lms/ui/resource/AudioFileResource.cpp @@ -21,7 +21,7 @@ #include -#include "av/RawResourceHandlerCreator.hpp" +#include "core/FileResourceHandlerCreator.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" #include "core/String.hpp" @@ -85,7 +85,7 @@ namespace lms::ui { LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleAudioFileRequest"); - std::shared_ptr fileResourceHandler; + std::shared_ptr fileResourceHandler; if (!request.continuation()) { @@ -93,11 +93,11 @@ namespace lms::ui if (!trackPath) return; - fileResourceHandler = av::createRawResourceHandler(*trackPath); + fileResourceHandler = core::createFileResourceHandler(*trackPath); } else { - fileResourceHandler = Wt::cpp17::any_cast>(request.continuation()->data()); + fileResourceHandler = Wt::cpp17::any_cast>(request.continuation()->data()); } auto* continuation{ fileResourceHandler->processRequest(request, response) }; diff --git a/src/lms/ui/resource/AudioTranscodingResource.cpp b/src/lms/ui/resource/AudioTranscodingResource.cpp index 37498a47..5cf7f8ee 100644 --- a/src/lms/ui/resource/AudioTranscodingResource.cpp +++ b/src/lms/ui/resource/AudioTranscodingResource.cpp @@ -23,14 +23,14 @@ #include -#include "av/TranscodingParameters.hpp" -#include "av/TranscodingResourceHandlerCreator.hpp" -#include "av/Types.hpp" #include "core/ILogger.hpp" +#include "core/IResourceHandler.hpp" +#include "core/Service.hpp" #include "core/String.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" +#include "services/transcoding/ITranscodingService.hpp" #include "LmsApplication.hpp" @@ -72,20 +72,20 @@ namespace lms::ui { namespace { - std::optional AudioFormatToAvFormat(db::TranscodingOutputFormat format) + std::optional AudioFormatToAvFormat(db::TranscodingOutputFormat format) { switch (format) { case db::TranscodingOutputFormat::MP3: - return av::transcoding::OutputFormat::MP3; + return transcoding::OutputFormat::MP3; case db::TranscodingOutputFormat::OGG_OPUS: - return av::transcoding::OutputFormat::OGG_OPUS; + return transcoding::OutputFormat::OGG_OPUS; case db::TranscodingOutputFormat::MATROSKA_OPUS: - return av::transcoding::OutputFormat::MATROSKA_OPUS; + return transcoding::OutputFormat::MATROSKA_OPUS; case db::TranscodingOutputFormat::OGG_VORBIS: - return av::transcoding::OutputFormat::OGG_VORBIS; + return transcoding::OutputFormat::OGG_VORBIS; case db::TranscodingOutputFormat::WEBM_VORBIS: - return av::transcoding::OutputFormat::WEBM_VORBIS; + return transcoding::OutputFormat::WEBM_VORBIS; } TRANSCODE_LOG(ERROR, "Cannot convert from audio format to AV format"); @@ -112,8 +112,8 @@ namespace lms::ui struct TranscodingParameters { - av::transcoding::InputParameters inputParameters; - av::transcoding::OutputParameters outputParameters; + transcoding::InputParameters inputParameters; + transcoding::OutputParameters outputParameters; }; std::optional readTranscodingParameters(const Wt::Http::Request& request) @@ -134,7 +134,7 @@ namespace lms::ui return std::nullopt; } - const std::optional avFormat{ AudioFormatToAvFormat(*format) }; + const std::optional avFormat{ AudioFormatToAvFormat(*format) }; if (!avFormat) return std::nullopt; @@ -152,14 +152,14 @@ namespace lms::ui return std::nullopt; } - parameters.inputParameters.trackPath = track->getAbsoluteFilePath(); + parameters.inputParameters.file = track->getAbsoluteFilePath(); parameters.inputParameters.duration = track->getDuration(); + parameters.inputParameters.offset = std::chrono::seconds{ offset }; } parameters.outputParameters.stripMetadata = true; parameters.outputParameters.format = *avFormat; parameters.outputParameters.bitrate = *bitrate; - parameters.outputParameters.offset = std::chrono::seconds{ offset }; return parameters; } @@ -177,31 +177,24 @@ namespace lms::ui void AudioTranscodingResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { - std::shared_ptr resourceHandler; + std::shared_ptr resourceHandler; - try + Wt::Http::ResponseContinuation* continuation{ request.continuation() }; + if (!continuation) { - Wt::Http::ResponseContinuation* continuation{ request.continuation() }; - if (!continuation) - { - if (const auto& parameters{ readTranscodingParameters(request) }) - resourceHandler = av::transcoding::createResourceHandler(parameters->inputParameters, parameters->outputParameters, false /* estimate content length */); - } - else - { - resourceHandler = Wt::cpp17::any_cast>(continuation->data()); - } - - if (resourceHandler) - { - continuation = resourceHandler->processRequest(request, response); - if (continuation) - continuation->setData(resourceHandler); - } + if (const auto& parameters{ readTranscodingParameters(request) }) + resourceHandler = core::Service::get()->createResourceHandler(parameters->inputParameters, parameters->outputParameters, false /* estimate content length */); } - catch (const av::Exception& e) + else { - TRANSCODE_LOG(ERROR, "Caught Av exception: " << e.what()); + resourceHandler = Wt::cpp17::any_cast>(continuation->data()); + } + + if (resourceHandler) + { + continuation = resourceHandler->processRequest(request, response); + if (continuation) + continuation->setData(resourceHandler); } }