Subsonic API: fixed missing mimetype for raw files

This commit is contained in:
emeric
2023-11-07 23:51:04 +01:00
parent 4bc5aee986
commit 504dfa86a9
8 changed files with 163 additions and 100 deletions
+1
View File
@@ -1,6 +1,7 @@
add_library(lmsav SHARED add_library(lmsav SHARED
impl/AudioFile.cpp impl/AudioFile.cpp
impl/RawResourceHandlerCreator.cpp
impl/Transcoder.cpp impl/Transcoder.cpp
impl/TranscodeResourceHandler.cpp impl/TranscodeResourceHandler.cpp
impl/Types.cpp impl/Types.cpp
@@ -0,0 +1,38 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "av/RawResourceHandlerCreator.hpp"
#include "av/IAudioFile.hpp"
#include "utils/FileResourceHandlerCreator.hpp"
namespace Av
{
std::unique_ptr<IResourceHandler> createRawResourceHandler(const std::filesystem::path& path)
{
std::string mimeType;
const auto guessedAudioFormat{ Av::guessAudioFileFormat(path) };
if (guessedAudioFormat)
mimeType = guessedAudioFormat->mimeType;
else
mimeType = "application/octet-stream";
return createFileResourceHandler(path, mimeType);
}
}
@@ -0,0 +1,30 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <filesystem>
#include <memory>
#include "utils/IResourceHandler.hpp"
namespace Av
{
std::unique_ptr<IResourceHandler> createRawResourceHandler(const std::filesystem::path& path);
}
@@ -20,6 +20,7 @@
#include "MediaRetrieval.hpp" #include "MediaRetrieval.hpp"
#include "av/IAudioFile.hpp" #include "av/IAudioFile.hpp"
#include "av/RawResourceHandlerCreator.hpp"
#include "av/TranscodeParameters.hpp" #include "av/TranscodeParameters.hpp"
#include "av/TranscodeResourceHandlerCreator.hpp" #include "av/TranscodeResourceHandlerCreator.hpp"
#include "av/Types.hpp" #include "av/Types.hpp"
@@ -160,7 +161,7 @@ namespace API::Subsonic
trackPath = track->getPath(); trackPath = track->getPath();
} }
resourceHandler = createFileResourceHandler(trackPath); resourceHandler = Av::createRawResourceHandler(trackPath);
} }
else else
{ {
@@ -185,7 +186,7 @@ namespace API::Subsonic
if (streamParameters.transcodeParameters) if (streamParameters.transcodeParameters)
resourceHandler = Av::createTranscodeResourceHandler(streamParameters.inputFileParameters, *streamParameters.transcodeParameters, streamParameters.estimateContentLength); resourceHandler = Av::createTranscodeResourceHandler(streamParameters.inputFileParameters, *streamParameters.transcodeParameters, streamParameters.estimateContentLength);
else else
resourceHandler = createFileResourceHandler(streamParameters.inputFileParameters.trackPath); resourceHandler = Av::createRawResourceHandler(streamParameters.inputFileParameters.trackPath);
} }
else else
{ {
+75 -77
View File
@@ -24,108 +24,106 @@
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
std::unique_ptr<IResourceHandler> std::unique_ptr<IResourceHandler>
createFileResourceHandler(const std::filesystem::path& path) createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType)
{ {
return std::make_unique<FileResourceHandler>(path); return std::make_unique<FileResourceHandler>(path, mimeType);
} }
FileResourceHandler::FileResourceHandler(const std::filesystem::path& path, std::string_view mimeType)
FileResourceHandler::FileResourceHandler(const std::filesystem::path& path) : _path{ path }
: _path {path} , _mimeType{ mimeType }
{ {
} }
Wt::Http::ResponseContinuation* Wt::Http::ResponseContinuation*
FileResourceHandler::processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) FileResourceHandler::processRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{ {
::uint64_t startByte {_offset}; ::uint64_t startByte{ _offset };
std::ifstream ifs {_path.string().c_str(), std::ios::in | std::ios::binary}; std::ifstream ifs{ _path.string().c_str(), std::ios::in | std::ios::binary };
if (startByte == 0) if (startByte == 0)
{ {
if (!ifs) if (!ifs)
{ {
LMS_LOG(UTILS, ERROR) << "Cannot open file stream for '" << _path.string() << "'"; LMS_LOG(UTILS, ERROR) << "Cannot open file stream for '" << _path.string() << "'";
response.setStatus(404); response.setStatus(404);
return {}; return {};
} }
else
{
response.setStatus(200);
}
ifs.seekg(0, std::ios::end); ifs.seekg(0, std::ios::end);
const ::uint64_t fileSize {static_cast<::uint64_t>(ifs.tellg())}; const ::uint64_t fileSize{ static_cast<::uint64_t>(ifs.tellg()) };
ifs.seekg(0, std::ios::beg); ifs.seekg(0, std::ios::beg);
LMS_LOG(UTILS, DEBUG) << "File '" << _path.string() << "', fileSize = " << fileSize; LMS_LOG(UTILS, DEBUG) << "File '" << _path.string() << "', fileSize = " << fileSize;
const Wt::Http::Request::ByteRangeSpecifier ranges {request.getRanges(fileSize)}; const Wt::Http::Request::ByteRangeSpecifier ranges{ request.getRanges(fileSize) };
if (!ranges.isSatisfiable()) if (!ranges.isSatisfiable())
{ {
std::ostringstream contentRange; std::ostringstream contentRange;
contentRange << "bytes */" << fileSize; contentRange << "bytes */" << fileSize;
response.setStatus(416); // Requested range not satisfiable response.setStatus(416); // Requested range not satisfiable
response.addHeader("Content-Range", contentRange.str()); response.addHeader("Content-Range", contentRange.str());
LMS_LOG(UTILS, DEBUG) << "Range not satisfiable"; LMS_LOG(UTILS, DEBUG) << "Range not satisfiable";
return {}; return {};
} }
if (ranges.size() == 1) if (ranges.size() == 1)
{ {
LMS_LOG(UTILS, DEBUG) << "Range requested = " << ranges[0].firstByte() << "/" << ranges[0].lastByte(); LMS_LOG(UTILS, DEBUG) << "Range requested = " << ranges[0].firstByte() << "/" << ranges[0].lastByte();
response.setStatus(206); response.setStatus(206);
startByte = ranges[0].firstByte(); startByte = ranges[0].firstByte();
_beyondLastByte = ranges[0].lastByte() + 1; _beyondLastByte = ranges[0].lastByte() + 1;
std::ostringstream contentRange; std::ostringstream contentRange;
contentRange << "bytes " << startByte << "-" contentRange << "bytes " << startByte << "-"
<< _beyondLastByte - 1 << "/" << fileSize; << _beyondLastByte - 1 << "/" << fileSize;
response.addHeader("Content-Range", contentRange.str()); response.addHeader("Content-Range", contentRange.str());
response.setContentLength(_beyondLastByte - startByte); response.setContentLength(_beyondLastByte - startByte);
} }
else else
{ {
LMS_LOG(UTILS, DEBUG) << "No range requested"; LMS_LOG(UTILS, DEBUG) << "No range requested";
_beyondLastByte = fileSize; response.setStatus(200);
response.setContentLength(_beyondLastByte); _beyondLastByte = fileSize;
} response.setContentLength(_beyondLastByte);
} }
else if (!ifs)
{
LMS_LOG(UTILS, ERROR) << "Cannot reopen file stream for '" << _path.string() << "'";
return {};
}
ifs.seekg(static_cast<std::istream::pos_type>(startByte)); LMS_LOG(UTILS, DEBUG) << "Mimetype set to '" << _mimeType << "'";
response.setMimeType(_mimeType);
}
else if (!ifs)
{
LMS_LOG(UTILS, ERROR) << "Cannot reopen file stream for '" << _path.string() << "'";
return {};
}
std::vector<char> buf; ifs.seekg(static_cast<std::istream::pos_type>(startByte));
buf.resize(_chunkSize);
::uint64_t restSize = _beyondLastByte - startByte; std::vector<char> buf;
::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size(); buf.resize(_chunkSize);
ifs.read(&buf[0], pieceSize); ::uint64_t restSize = _beyondLastByte - startByte;
const ::uint64_t actualPieceSize {static_cast<::uint64_t>(ifs.gcount())}; ::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size();
response.out().write(&buf[0], actualPieceSize);
LMS_LOG(UTILS, DEBUG) << "Written " << actualPieceSize << " bytes"; ifs.read(&buf[0], pieceSize);
const ::uint64_t actualPieceSize{ static_cast<::uint64_t>(ifs.gcount()) };
response.out().write(&buf[0], actualPieceSize);
LMS_LOG(UTILS, DEBUG) << "Progress: " << actualPieceSize << "/" << restSize; LMS_LOG(UTILS, DEBUG) << "Written " << actualPieceSize << " bytes";
if (ifs.good() && actualPieceSize < restSize)
{
_offset = startByte + actualPieceSize;
LMS_LOG(UTILS, DEBUG) << "Job not complete! Next chunk offset = " << _offset;
return response.createContinuation(); LMS_LOG(UTILS, DEBUG) << "Progress: " << actualPieceSize << "/" << restSize;
} if (ifs.good() && actualPieceSize < restSize)
{
_offset = startByte + actualPieceSize;
LMS_LOG(UTILS, DEBUG) << "Job not complete! Next chunk offset = " << _offset;
LMS_LOG(UTILS, DEBUG) << "Job complete!"; return response.createContinuation();
return nullptr; }
LMS_LOG(UTILS, DEBUG) << "Job complete!";
return nullptr;
} }
+12 -9
View File
@@ -20,21 +20,24 @@
#pragma once #pragma once
#include <filesystem> #include <filesystem>
#include <string>
#include <string_view>
#include "utils/IResourceHandler.hpp" #include "utils/IResourceHandler.hpp"
class FileResourceHandler final : public IResourceHandler class FileResourceHandler final : public IResourceHandler
{ {
public: public:
FileResourceHandler(const std::filesystem::path& filePath); FileResourceHandler(const std::filesystem::path& filePath, std::string_view mimeType);
private: private:
Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
void abort() override {}; void abort() override {};
static constexpr std::size_t _chunkSize {65536}; static constexpr std::size_t _chunkSize{ 65536 };
std::filesystem::path _path; std::filesystem::path _path;
::uint64_t _beyondLastByte {}; std::string _mimeType;
::uint64_t _offset {}; ::uint64_t _beyondLastByte{};
::uint64_t _offset{};
}; };
@@ -21,8 +21,9 @@
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <string_view>
#include "utils/IResourceHandler.hpp" #include "utils/IResourceHandler.hpp"
std::unique_ptr<IResourceHandler> createFileResourceHandler(const std::filesystem::path& path); std::unique_ptr<IResourceHandler> createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType);
+2 -11
View File
@@ -23,9 +23,9 @@
#include <Wt/Http/Response.h> #include <Wt/Http/Response.h>
#include "av/IAudioFile.hpp" #include "av/IAudioFile.hpp"
#include "av/RawResourceHandlerCreator.hpp"
#include "services/database/Session.hpp" #include "services/database/Session.hpp"
#include "services/database/Track.hpp" #include "services/database/Track.hpp"
#include "utils/FileResourceHandlerCreator.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/String.hpp" #include "utils/String.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
@@ -94,16 +94,7 @@ AudioFileResource::handleRequest(const Wt::Http::Request& request,
if (!trackPath) if (!trackPath)
return; return;
fileResourceHandler = createFileResourceHandler(*trackPath); fileResourceHandler = Av::createRawResourceHandler(*trackPath);
const auto guessedAudioFormat {Av::guessAudioFileFormat(*trackPath)};
if (guessedAudioFormat)
{
LOG(DEBUG) << "Set mime type to " << guessedAudioFormat->mimeType;
response.setMimeType(guessedAudioFormat->mimeType);
}
else
response.setMimeType("application/octet-stream");
} }
else else
{ {