Factorized some container/codec handling code + removed the no longer needed transcode output formats mka and webm
This commit is contained in:
@@ -14,8 +14,6 @@ add_library(lmsaudio STATIC
|
||||
impl/taglib/ImageReader.cpp
|
||||
impl/taglib/TagReader.cpp
|
||||
impl/taglib/Utils.cpp
|
||||
impl/AudioTypes.cpp
|
||||
impl/ImageReader.cpp
|
||||
impl/AudioFileInfoParser.cpp
|
||||
impl/TagReader.cpp
|
||||
)
|
||||
|
||||
@@ -35,7 +35,6 @@ extern "C"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/Exception.hpp"
|
||||
|
||||
namespace lms::audio::ffmpeg
|
||||
@@ -71,83 +70,83 @@ namespace lms::audio::ffmpeg
|
||||
res[core::stringUtils::stringToUpper(tag->key)] = tag->value;
|
||||
}
|
||||
|
||||
std::optional<ContainerType> avdemuxerToContainerType(std::string_view name)
|
||||
std::optional<core::media::ContainerType> avdemuxerToContainerType(std::string_view name)
|
||||
{
|
||||
if (name == "aiff")
|
||||
return ContainerType::AIFF;
|
||||
return core::media::ContainerType::AIFF;
|
||||
if (name == "ape")
|
||||
return ContainerType::APE;
|
||||
return core::media::ContainerType::APE;
|
||||
if (name.starts_with("asf"))
|
||||
return ContainerType::ASF;
|
||||
return core::media::ContainerType::ASF;
|
||||
if (name == "dsf")
|
||||
return ContainerType::DSF;
|
||||
return core::media::ContainerType::DSF;
|
||||
if (name == "flac")
|
||||
return ContainerType::FLAC;
|
||||
return core::media::ContainerType::FLAC;
|
||||
if (name.find("mp4") != std::string_view::npos)
|
||||
return ContainerType::MP4;
|
||||
return core::media::ContainerType::MP4;
|
||||
if (name.starts_with("mpc"))
|
||||
return ContainerType::MPC;
|
||||
return core::media::ContainerType::MPC;
|
||||
if (name == "mp3")
|
||||
return ContainerType::MPEG;
|
||||
return core::media::ContainerType::MPEG;
|
||||
if (name == "ogg")
|
||||
return ContainerType::Ogg;
|
||||
return core::media::ContainerType::Ogg;
|
||||
if (name == "shn")
|
||||
return ContainerType::Shorten;
|
||||
return core::media::ContainerType::Shorten;
|
||||
if (name == "tta")
|
||||
return ContainerType::TrueAudio;
|
||||
return core::media::ContainerType::TrueAudio;
|
||||
if (name == "wav")
|
||||
return ContainerType::WAV;
|
||||
return core::media::ContainerType::WAV;
|
||||
if (name == "wv")
|
||||
return ContainerType::WavPack;
|
||||
return core::media::ContainerType::WavPack;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<CodecType> avcodecToCodecType(AVCodecID codec)
|
||||
std::optional<core::media::CodecType> avcodecToCodecType(AVCodecID codec)
|
||||
{
|
||||
switch (codec)
|
||||
{
|
||||
case AV_CODEC_ID_AAC:
|
||||
return CodecType::AAC;
|
||||
return core::media::CodecType::AAC;
|
||||
case AV_CODEC_ID_AC3:
|
||||
return CodecType::AC3;
|
||||
return core::media::CodecType::AC3;
|
||||
case AV_CODEC_ID_ALAC:
|
||||
return CodecType::ALAC;
|
||||
return core::media::CodecType::ALAC;
|
||||
case AV_CODEC_ID_APE:
|
||||
return CodecType::APE;
|
||||
return core::media::CodecType::APE;
|
||||
case AV_CODEC_ID_DSD_LSBF:
|
||||
case AV_CODEC_ID_DSD_LSBF_PLANAR:
|
||||
case AV_CODEC_ID_DSD_MSBF:
|
||||
case AV_CODEC_ID_DSD_MSBF_PLANAR:
|
||||
return CodecType::DSD;
|
||||
return core::media::CodecType::DSD;
|
||||
case AV_CODEC_ID_EAC3:
|
||||
return CodecType::EAC3;
|
||||
return core::media::CodecType::EAC3;
|
||||
case AV_CODEC_ID_FLAC:
|
||||
return CodecType::FLAC;
|
||||
return core::media::CodecType::FLAC;
|
||||
case AV_CODEC_ID_MP3:
|
||||
return CodecType::MP3;
|
||||
return core::media::CodecType::MP3;
|
||||
case AV_CODEC_ID_MP4ALS:
|
||||
return CodecType::MP4ALS;
|
||||
return core::media::CodecType::MP4ALS;
|
||||
case AV_CODEC_ID_MUSEPACK7:
|
||||
return CodecType::MPC7;
|
||||
return core::media::CodecType::MPC7;
|
||||
case AV_CODEC_ID_MUSEPACK8:
|
||||
return CodecType::MPC8;
|
||||
return core::media::CodecType::MPC8;
|
||||
case AV_CODEC_ID_OPUS:
|
||||
return CodecType::Opus;
|
||||
return core::media::CodecType::Opus;
|
||||
case AV_CODEC_ID_SHORTEN:
|
||||
return CodecType::Shorten;
|
||||
return core::media::CodecType::Shorten;
|
||||
case AV_CODEC_ID_VORBIS:
|
||||
return CodecType::Vorbis;
|
||||
return core::media::CodecType::Vorbis;
|
||||
case AV_CODEC_ID_WAVPACK:
|
||||
return CodecType::WavPack;
|
||||
return core::media::CodecType::WavPack;
|
||||
case AV_CODEC_ID_WMALOSSLESS:
|
||||
return CodecType::WMA9Lossless;
|
||||
return core::media::CodecType::WMA9Lossless;
|
||||
case AV_CODEC_ID_WMAPRO:
|
||||
return CodecType::WMA9Pro;
|
||||
return core::media::CodecType::WMA9Pro;
|
||||
case AV_CODEC_ID_WMAV1:
|
||||
return CodecType::WMA1;
|
||||
return core::media::CodecType::WMA1;
|
||||
case AV_CODEC_ID_WMAV2:
|
||||
return CodecType::WMA2;
|
||||
return core::media::CodecType::WMA2;
|
||||
|
||||
default:
|
||||
return std::nullopt;
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "core/media/CodecType.hpp"
|
||||
#include "core/media/ContainerType.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@@ -44,7 +45,7 @@ namespace lms::audio::ffmpeg
|
||||
|
||||
struct ContainerInfo
|
||||
{
|
||||
std::optional<ContainerType> container;
|
||||
std::optional<core::media::ContainerType> container;
|
||||
std::string containerName;
|
||||
|
||||
std::optional<std::size_t> bitrate;
|
||||
@@ -54,7 +55,7 @@ namespace lms::audio::ffmpeg
|
||||
struct StreamInfo
|
||||
{
|
||||
size_t index{};
|
||||
std::optional<CodecType> codec;
|
||||
std::optional<core::media::CodecType> codec;
|
||||
std::string codecName;
|
||||
|
||||
std::optional<size_t> bitrate;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
|
||||
#include "AudioFile.hpp"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace lms::audio::ffmpeg
|
||||
image.data = picture.data;
|
||||
image.mimeType = picture.mimeType;
|
||||
if (metaDataHasKeyword(metaData, "front"))
|
||||
image.type = Image::Type::FrontCover;
|
||||
image.type = core::media::ImageType::FrontCover;
|
||||
else if (metaDataHasKeyword(metaData, "back"))
|
||||
image.type = Image::Type::BackCover;
|
||||
image.type = core::media::ImageType::BackCover;
|
||||
|
||||
visitor(image);
|
||||
});
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
|
||||
#include "Transcoder.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
|
||||
#include "core/IChildProcessManager.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/media/MimeType.hpp"
|
||||
|
||||
#include "audio/Exception.hpp"
|
||||
#include "audio/TranscodeTypes.hpp"
|
||||
@@ -42,18 +43,29 @@ namespace lms::audio::ffmpeg
|
||||
{
|
||||
#define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message)
|
||||
|
||||
static std::atomic<size_t> globalId{};
|
||||
static std::filesystem::path ffmpegPath;
|
||||
|
||||
void Transcoder::init()
|
||||
namespace
|
||||
{
|
||||
ffmpegPath = core::Service<core::IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
|
||||
if (!std::filesystem::exists(ffmpegPath))
|
||||
throw Exception{ "File '" + ffmpegPath.string() + "' does not exist!" };
|
||||
}
|
||||
class FFmpegPath
|
||||
{
|
||||
public:
|
||||
FFmpegPath()
|
||||
{
|
||||
path = core::Service<core::IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
|
||||
if (!std::filesystem::exists(path))
|
||||
throw Exception{ "File '" + path.string() + "' does not exist!" };
|
||||
}
|
||||
|
||||
const std::filesystem::path& get() const { return path; }
|
||||
|
||||
private:
|
||||
std::filesystem::path path;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::atomic<std::size_t> Transcoder::_nextDebugId;
|
||||
|
||||
Transcoder::Transcoder(const TranscodeParameters& parameters)
|
||||
: _debugId{ globalId++ }
|
||||
: _debugId{ _nextDebugId++ }
|
||||
, _inputParams{ parameters.inputParameters }
|
||||
, _outputParams{ parameters.outputParameters }
|
||||
{
|
||||
@@ -64,8 +76,7 @@ namespace lms::audio::ffmpeg
|
||||
|
||||
void Transcoder::start()
|
||||
{
|
||||
if (ffmpegPath.empty())
|
||||
init();
|
||||
static FFmpegPath ffmpegPath;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -76,15 +87,16 @@ namespace lms::audio::ffmpeg
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& e)
|
||||
{
|
||||
// TODO store/raise e.code()
|
||||
throw Exception{ "File error '" + _inputParams.filePath.string() + "': " + e.what() };
|
||||
throw IOFileException{ _inputParams.filePath, "Failed to test file existence", e.code() };
|
||||
}
|
||||
|
||||
LOG(INFO, "Transcoding file " << _inputParams.filePath);
|
||||
|
||||
std::vector<std::string> args;
|
||||
|
||||
args.emplace_back(ffmpegPath.string());
|
||||
args.emplace_back(ffmpegPath.get().string());
|
||||
|
||||
// TODO some codecs have restrictions, take them into account (channel count, sample rate, etc.)
|
||||
|
||||
// Make sure:
|
||||
// - we do not produce anything in the stderr output
|
||||
@@ -114,7 +126,7 @@ namespace lms::audio::ffmpeg
|
||||
args.emplace_back("-1");
|
||||
}
|
||||
|
||||
// Skip video flows (including covers)
|
||||
// Skip video flows (including covers!)
|
||||
args.emplace_back("-vn");
|
||||
|
||||
// Output bitrates
|
||||
@@ -124,63 +136,68 @@ namespace lms::audio::ffmpeg
|
||||
args.emplace_back(std::to_string(*_outputParams.bitrate));
|
||||
}
|
||||
|
||||
// Codecs and formats
|
||||
if (_outputParams.format)
|
||||
{
|
||||
switch (*_outputParams.format)
|
||||
args.emplace_back("-f");
|
||||
|
||||
switch (_outputParams.format->container)
|
||||
{
|
||||
case OutputFormat::MP3:
|
||||
args.emplace_back("-f");
|
||||
case core::media::ContainerType::FLAC:
|
||||
args.emplace_back("flac");
|
||||
break;
|
||||
case core::media::ContainerType::Ogg:
|
||||
args.emplace_back("ogg");
|
||||
break;
|
||||
case core::media::ContainerType::MPEG:
|
||||
args.emplace_back("mp3");
|
||||
break;
|
||||
|
||||
case OutputFormat::OGG_OPUS:
|
||||
args.emplace_back("-acodec");
|
||||
default:
|
||||
throw Exception{ "Unsupported container type " + std::string{ core::media::containerTypeToString(_outputParams.format->container).str() } };
|
||||
}
|
||||
|
||||
args.emplace_back("-acodec");
|
||||
|
||||
switch (_outputParams.format->codec)
|
||||
{
|
||||
case core::media::CodecType::MP3:
|
||||
args.emplace_back("libmp3lame");
|
||||
break;
|
||||
|
||||
case core::media::CodecType::Opus:
|
||||
args.emplace_back("libopus");
|
||||
args.emplace_back("-f");
|
||||
args.emplace_back("ogg");
|
||||
break;
|
||||
|
||||
case OutputFormat::MATROSKA_OPUS:
|
||||
args.emplace_back("-acodec");
|
||||
args.emplace_back("libopus");
|
||||
args.emplace_back("-f");
|
||||
args.emplace_back("matroska");
|
||||
break;
|
||||
|
||||
case OutputFormat::OGG_VORBIS:
|
||||
args.emplace_back("-acodec");
|
||||
case core::media::CodecType::Vorbis:
|
||||
args.emplace_back("libvorbis");
|
||||
args.emplace_back("-f");
|
||||
args.emplace_back("ogg");
|
||||
break;
|
||||
|
||||
case OutputFormat::WEBM_VORBIS:
|
||||
args.emplace_back("-acodec");
|
||||
args.emplace_back("libvorbis");
|
||||
args.emplace_back("-f");
|
||||
args.emplace_back("webm");
|
||||
case core::media::CodecType::FLAC:
|
||||
args.emplace_back("flac");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Exception{ "Unhandled format (" + std::to_string(static_cast<int>(*_outputParams.format)) + ")" };
|
||||
throw Exception{ "Unhandled codec type " + std::string{ codecTypeToString(_outputParams.format->codec).str() } };
|
||||
}
|
||||
}
|
||||
|
||||
args.emplace_back("pipe:1");
|
||||
|
||||
LOG(DEBUG, "Dumping args (" << args.size() << ")");
|
||||
for (const std::string& arg : args)
|
||||
LOG(DEBUG, "Arg = '" << arg << "'");
|
||||
if (core::Service<core::logging::ILogger>::get()->isSeverityActive(core::logging::Severity::DEBUG))
|
||||
{
|
||||
LOG(DEBUG, "Dumping args (" << args.size() << ")");
|
||||
for (const std::string& arg : args)
|
||||
LOG(DEBUG, "Arg = '" << arg << "'");
|
||||
}
|
||||
|
||||
// Caution: stdin must have been closed before
|
||||
try
|
||||
{
|
||||
_childProcess = core::Service<core::IChildProcessManager>::get()->spawnChildProcess(ffmpegPath, args);
|
||||
_childProcess = core::Service<core::IChildProcessManager>::get()->spawnChildProcess(ffmpegPath.get(), args);
|
||||
}
|
||||
catch (core::ChildProcessException& exception)
|
||||
{
|
||||
throw Exception{ "Cannot execute '" + ffmpegPath.string() + "': " + exception.what() };
|
||||
throw Exception{ "Cannot execute '" + ffmpegPath.get().string() + "': " + exception.what() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,25 +219,10 @@ namespace lms::audio::ffmpeg
|
||||
|
||||
std::string_view Transcoder::getOutputMimeType() const
|
||||
{
|
||||
// TODO: use input mime type
|
||||
if (_outputParams.format)
|
||||
{
|
||||
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 core::media::getMimeType(_outputParams.format->container, _outputParams.format->codec).str();
|
||||
|
||||
return "application/octet-stream"; // default, should not happen
|
||||
return core::media::getMimeType(_inputParams.audioProperties.container, _inputParams.audioProperties.codec).str();
|
||||
}
|
||||
|
||||
bool Transcoder::finished() const
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "audio/ITranscoder.hpp"
|
||||
|
||||
namespace lms::core
|
||||
@@ -47,7 +49,8 @@ namespace lms::audio::ffmpeg
|
||||
static void init();
|
||||
void start();
|
||||
|
||||
const std::size_t _debugId{};
|
||||
static std::atomic<std::size_t> _nextDebugId;
|
||||
const std::size_t _debugId;
|
||||
const TranscodeInputParameters _inputParams;
|
||||
const TranscodeOutputParameters _outputParams;
|
||||
std::unique_ptr<core::IChildProcess> _childProcess;
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
|
||||
@@ -104,27 +104,27 @@ namespace lms::audio::taglib
|
||||
// Guess container from the file type
|
||||
if (const auto* apeFile{ dynamic_cast<const ::TagLib::APE::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::APE;
|
||||
audioProperties.codec = CodecType::APE; // TODO version?
|
||||
audioProperties.container = core::media::ContainerType::APE;
|
||||
audioProperties.codec = core::media::CodecType::APE; // TODO version?
|
||||
audioProperties.bitsPerSample = apeFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
else if (const auto* asfFile{ dynamic_cast<const ::TagLib::ASF::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::ASF;
|
||||
audioProperties.container = core::media::ContainerType::ASF;
|
||||
|
||||
switch (asfFile->audioProperties()->codec())
|
||||
{
|
||||
case ::TagLib::ASF::Properties::Codec::WMA1:
|
||||
audioProperties.codec = CodecType::WMA1;
|
||||
audioProperties.codec = core::media::CodecType::WMA1;
|
||||
break;
|
||||
case ::TagLib::ASF::Properties::Codec::WMA2:
|
||||
audioProperties.codec = CodecType::WMA2;
|
||||
audioProperties.codec = core::media::CodecType::WMA2;
|
||||
break;
|
||||
case ::TagLib::ASF::Properties::Codec::WMA9Lossless:
|
||||
audioProperties.codec = CodecType::WMA9Lossless;
|
||||
audioProperties.codec = core::media::CodecType::WMA9Lossless;
|
||||
break;
|
||||
case ::TagLib::ASF::Properties::Codec::WMA9Pro:
|
||||
audioProperties.codec = CodecType::WMA9Pro;
|
||||
audioProperties.codec = core::media::CodecType::WMA9Pro;
|
||||
break;
|
||||
case ::TagLib::ASF::Properties::Codec::Unknown:
|
||||
LMS_LOG(AUDIO, DEBUG, "Unhandled ASF codec in " << filePath);
|
||||
@@ -136,27 +136,27 @@ namespace lms::audio::taglib
|
||||
#if LMS_TAGLIB_HAS_DSF
|
||||
else if (const auto* dsfFile{ dynamic_cast<const ::TagLib::DSF::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::DSF;
|
||||
audioProperties.codec = CodecType::DSD;
|
||||
audioProperties.container = core::media::ContainerType::DSF;
|
||||
audioProperties.codec = core::media::CodecType::DSD;
|
||||
audioProperties.bitsPerSample = dsfFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
#endif // LMS_TAGLIB_HAS_DSF
|
||||
else if (const auto* flacFile{ dynamic_cast<const ::TagLib::FLAC::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::FLAC;
|
||||
audioProperties.codec = CodecType::FLAC;
|
||||
audioProperties.container = core::media::ContainerType::FLAC;
|
||||
audioProperties.codec = core::media::CodecType::FLAC;
|
||||
audioProperties.bitsPerSample = flacFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
else if (const auto* mp4File{ dynamic_cast<const ::TagLib::MP4::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::MP4;
|
||||
audioProperties.container = core::media::ContainerType::MP4;
|
||||
switch (mp4File->audioProperties()->codec())
|
||||
{
|
||||
case ::TagLib::MP4::Properties::Codec::AAC:
|
||||
audioProperties.codec = CodecType::AAC;
|
||||
audioProperties.codec = core::media::CodecType::AAC;
|
||||
break;
|
||||
case ::TagLib::MP4::Properties::Codec::ALAC:
|
||||
audioProperties.codec = CodecType::ALAC;
|
||||
audioProperties.codec = core::media::CodecType::ALAC;
|
||||
break;
|
||||
case ::TagLib::MP4::Properties::Codec::Unknown:
|
||||
LMS_LOG(AUDIO, DEBUG, "Unhandled MP4 codec in " << filePath);
|
||||
@@ -167,15 +167,15 @@ namespace lms::audio::taglib
|
||||
}
|
||||
else if (const auto* mpcFile{ dynamic_cast<const ::TagLib::MPC::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::MPC;
|
||||
audioProperties.container = core::media::ContainerType::MPC;
|
||||
|
||||
switch (mpcFile->audioProperties()->mpcVersion())
|
||||
{
|
||||
case 7:
|
||||
audioProperties.codec = CodecType::MPC7;
|
||||
audioProperties.codec = core::media::CodecType::MPC7;
|
||||
break;
|
||||
case 8:
|
||||
audioProperties.codec = CodecType::MPC8;
|
||||
audioProperties.codec = core::media::CodecType::MPC8;
|
||||
break;
|
||||
default:
|
||||
LMS_LOG(AUDIO, DEBUG, "Unhandled MPC codec version " << mpcFile->audioProperties()->mpcVersion() << " in " << filePath);
|
||||
@@ -186,13 +186,13 @@ namespace lms::audio::taglib
|
||||
{
|
||||
const auto& properties{ *mpegFile->audioProperties() };
|
||||
|
||||
audioProperties.container = ContainerType::MPEG;
|
||||
audioProperties.container = core::media::ContainerType::MPEG;
|
||||
if ((properties.version() == TagLib::MPEG::Header::Version::Version1 || properties.version() == TagLib::MPEG::Header::Version::Version2 || properties.version() == TagLib::MPEG::Header::Version::Version2_5)
|
||||
&& mpegFile->audioProperties()->layer() == 3)
|
||||
audioProperties.codec = CodecType::MP3; // could be MPEG-1 layer 3 or MPEG-2(.5) layer 3
|
||||
audioProperties.codec = core::media::CodecType::MP3; // could be MPEG-1 layer 3 or MPEG-2(.5) layer 3
|
||||
#if LMS_TAGLIB_HAS_ADTS
|
||||
else if (mpegFile->audioProperties()->isADTS()) // likely AAC
|
||||
audioProperties.codec = CodecType::AAC;
|
||||
audioProperties.codec = core::media::CodecType::AAC;
|
||||
#endif
|
||||
else
|
||||
{
|
||||
@@ -202,44 +202,44 @@ namespace lms::audio::taglib
|
||||
}
|
||||
else if (dynamic_cast<const ::TagLib::Ogg::Opus::File*>(&file))
|
||||
{
|
||||
audioProperties.container = ContainerType::Ogg;
|
||||
audioProperties.codec = CodecType::Opus;
|
||||
audioProperties.container = core::media::ContainerType::Ogg;
|
||||
audioProperties.codec = core::media::CodecType::Opus;
|
||||
}
|
||||
else if (dynamic_cast<const ::TagLib::Ogg::Vorbis::File*>(&file))
|
||||
{
|
||||
audioProperties.container = ContainerType::Ogg;
|
||||
audioProperties.codec = CodecType::Vorbis;
|
||||
audioProperties.container = core::media::ContainerType::Ogg;
|
||||
audioProperties.codec = core::media::CodecType::Vorbis;
|
||||
}
|
||||
else if (const auto* aiffFile{ dynamic_cast<const ::TagLib::RIFF::AIFF::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::AIFF;
|
||||
audioProperties.codec = CodecType::PCM;
|
||||
audioProperties.container = core::media::ContainerType::AIFF;
|
||||
audioProperties.codec = core::media::CodecType::PCM;
|
||||
audioProperties.bitsPerSample = aiffFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
else if (const auto* wavFile{ dynamic_cast<const ::TagLib::RIFF::WAV::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::WAV;
|
||||
audioProperties.codec = CodecType::PCM;
|
||||
audioProperties.container = core::media::ContainerType::WAV;
|
||||
audioProperties.codec = core::media::CodecType::PCM;
|
||||
audioProperties.bitsPerSample = wavFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
#if LMS_TAGLIB_HAS_SHORTEN
|
||||
else if (const auto* shortenFile{ dynamic_cast<const ::TagLib::Shorten::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::Shorten;
|
||||
audioProperties.codec = CodecType::Shorten;
|
||||
audioProperties.container = core::media::ContainerType::Shorten;
|
||||
audioProperties.codec = core::media::CodecType::Shorten;
|
||||
audioProperties.bitsPerSample = shortenFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
#endif // LMS_TAGLIB_HAS_SHORTEN
|
||||
else if (const auto* trueAudioFile{ dynamic_cast<const ::TagLib::TrueAudio::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::TrueAudio;
|
||||
audioProperties.codec = CodecType::TrueAudio;
|
||||
audioProperties.container = core::media::ContainerType::TrueAudio;
|
||||
audioProperties.codec = core::media::CodecType::TrueAudio;
|
||||
audioProperties.bitsPerSample = trueAudioFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
else if (const auto* wavPackFile{ dynamic_cast<const ::TagLib::WavPack::File*>(&file) })
|
||||
{
|
||||
audioProperties.container = ContainerType::WavPack;
|
||||
audioProperties.codec = CodecType::WavPack;
|
||||
audioProperties.container = core::media::ContainerType::WavPack;
|
||||
audioProperties.codec = core::media::CodecType::WavPack;
|
||||
audioProperties.bitsPerSample = wavPackFile->audioProperties()->bitsPerSample();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
#include "audio/IImageReader.hpp"
|
||||
|
||||
@@ -44,157 +44,157 @@ namespace lms::audio::taglib
|
||||
{
|
||||
namespace
|
||||
{
|
||||
Image::Type imageTypeFromfromID3v2(TagLib::ID3v2::AttachedPictureFrame::Type type)
|
||||
core::media::ImageType imageTypeFromfromID3v2(TagLib::ID3v2::AttachedPictureFrame::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Other:
|
||||
return Image::Type::Other;
|
||||
return core::media::ImageType::Other;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::FileIcon:
|
||||
return Image::Type::FileIcon;
|
||||
return core::media::ImageType::FileIcon;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::OtherFileIcon:
|
||||
return Image::Type::OtherFileIcon;
|
||||
return core::media::ImageType::OtherFileIcon;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::FrontCover:
|
||||
return Image::Type::FrontCover;
|
||||
return core::media::ImageType::FrontCover;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::BackCover:
|
||||
return Image::Type::BackCover;
|
||||
return core::media::ImageType::BackCover;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::LeafletPage:
|
||||
return Image::Type::LeafletPage;
|
||||
return core::media::ImageType::LeafletPage;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Media:
|
||||
return Image::Type::Media;
|
||||
return core::media::ImageType::Media;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::LeadArtist:
|
||||
return Image::Type::LeadArtist;
|
||||
return core::media::ImageType::LeadArtist;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Artist:
|
||||
return Image::Type::Artist;
|
||||
return core::media::ImageType::Artist;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Conductor:
|
||||
return Image::Type::Conductor;
|
||||
return core::media::ImageType::Conductor;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Band:
|
||||
return Image::Type::Band;
|
||||
return core::media::ImageType::Band;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Composer:
|
||||
return Image::Type::Composer;
|
||||
return core::media::ImageType::Composer;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Lyricist:
|
||||
return Image::Type::Lyricist;
|
||||
return core::media::ImageType::Lyricist;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::RecordingLocation:
|
||||
return Image::Type::RecordingLocation;
|
||||
return core::media::ImageType::RecordingLocation;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::DuringRecording:
|
||||
return Image::Type::DuringRecording;
|
||||
return core::media::ImageType::DuringRecording;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::DuringPerformance:
|
||||
return Image::Type::DuringPerformance;
|
||||
return core::media::ImageType::DuringPerformance;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::MovieScreenCapture:
|
||||
return Image::Type::MovieScreenCapture;
|
||||
return core::media::ImageType::MovieScreenCapture;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::ColouredFish:
|
||||
return Image::Type::ColouredFish;
|
||||
return core::media::ImageType::ColouredFish;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::Illustration:
|
||||
return Image::Type::Illustration;
|
||||
return core::media::ImageType::Illustration;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::BandLogo:
|
||||
return Image::Type::BandLogo;
|
||||
return core::media::ImageType::BandLogo;
|
||||
case ::TagLib::ID3v2::AttachedPictureFrame::Type::PublisherLogo:
|
||||
return Image::Type::PublisherLogo;
|
||||
return core::media::ImageType::PublisherLogo;
|
||||
}
|
||||
|
||||
return Image::Type::Unknown;
|
||||
return core::media::ImageType::Unknown;
|
||||
}
|
||||
|
||||
Image::Type imageTypeFromfromASF(TagLib::ASF::Picture::Type type)
|
||||
core::media::ImageType imageTypeFromfromASF(TagLib::ASF::Picture::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ::TagLib::ASF::Picture::Type::Other:
|
||||
return Image::Type::Other;
|
||||
return core::media::ImageType::Other;
|
||||
case ::TagLib::ASF::Picture::Type::FileIcon:
|
||||
return Image::Type::FileIcon;
|
||||
return core::media::ImageType::FileIcon;
|
||||
case ::TagLib::ASF::Picture::Type::OtherFileIcon:
|
||||
return Image::Type::OtherFileIcon;
|
||||
return core::media::ImageType::OtherFileIcon;
|
||||
case ::TagLib::ASF::Picture::Type::FrontCover:
|
||||
return Image::Type::FrontCover;
|
||||
return core::media::ImageType::FrontCover;
|
||||
case ::TagLib::ASF::Picture::Type::BackCover:
|
||||
return Image::Type::BackCover;
|
||||
return core::media::ImageType::BackCover;
|
||||
case ::TagLib::ASF::Picture::Type::LeafletPage:
|
||||
return Image::Type::LeafletPage;
|
||||
return core::media::ImageType::LeafletPage;
|
||||
case ::TagLib::ASF::Picture::Type::Media:
|
||||
return Image::Type::Media;
|
||||
return core::media::ImageType::Media;
|
||||
case ::TagLib::ASF::Picture::Type::LeadArtist:
|
||||
return Image::Type::LeadArtist;
|
||||
return core::media::ImageType::LeadArtist;
|
||||
case ::TagLib::ASF::Picture::Type::Artist:
|
||||
return Image::Type::Artist;
|
||||
return core::media::ImageType::Artist;
|
||||
case ::TagLib::ASF::Picture::Type::Conductor:
|
||||
return Image::Type::Conductor;
|
||||
return core::media::ImageType::Conductor;
|
||||
case ::TagLib::ASF::Picture::Type::Band:
|
||||
return Image::Type::Band;
|
||||
return core::media::ImageType::Band;
|
||||
case ::TagLib::ASF::Picture::Type::Composer:
|
||||
return Image::Type::Composer;
|
||||
return core::media::ImageType::Composer;
|
||||
case ::TagLib::ASF::Picture::Type::Lyricist:
|
||||
return Image::Type::Lyricist;
|
||||
return core::media::ImageType::Lyricist;
|
||||
case ::TagLib::ASF::Picture::Type::RecordingLocation:
|
||||
return Image::Type::RecordingLocation;
|
||||
return core::media::ImageType::RecordingLocation;
|
||||
case ::TagLib::ASF::Picture::Type::DuringRecording:
|
||||
return Image::Type::DuringRecording;
|
||||
return core::media::ImageType::DuringRecording;
|
||||
case ::TagLib::ASF::Picture::Type::DuringPerformance:
|
||||
return Image::Type::DuringPerformance;
|
||||
return core::media::ImageType::DuringPerformance;
|
||||
case ::TagLib::ASF::Picture::Type::MovieScreenCapture:
|
||||
return Image::Type::MovieScreenCapture;
|
||||
return core::media::ImageType::MovieScreenCapture;
|
||||
case ::TagLib::ASF::Picture::Type::ColouredFish:
|
||||
return Image::Type::ColouredFish;
|
||||
return core::media::ImageType::ColouredFish;
|
||||
case ::TagLib::ASF::Picture::Type::Illustration:
|
||||
return Image::Type::Illustration;
|
||||
return core::media::ImageType::Illustration;
|
||||
case ::TagLib::ASF::Picture::Type::BandLogo:
|
||||
return Image::Type::BandLogo;
|
||||
return core::media::ImageType::BandLogo;
|
||||
case ::TagLib::ASF::Picture::Type::PublisherLogo:
|
||||
return Image::Type::PublisherLogo;
|
||||
return core::media::ImageType::PublisherLogo;
|
||||
}
|
||||
|
||||
return Image::Type::Unknown;
|
||||
return core::media::ImageType::Unknown;
|
||||
}
|
||||
|
||||
Image::Type imageTypeFromfromFLAC(TagLib::FLAC::Picture::Type type)
|
||||
core::media::ImageType imageTypeFromfromFLAC(TagLib::FLAC::Picture::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ::TagLib::FLAC::Picture::Type::Other:
|
||||
return Image::Type::Other;
|
||||
return core::media::ImageType::Other;
|
||||
case ::TagLib::FLAC::Picture::Type::FileIcon:
|
||||
return Image::Type::FileIcon;
|
||||
return core::media::ImageType::FileIcon;
|
||||
case ::TagLib::FLAC::Picture::Type::OtherFileIcon:
|
||||
return Image::Type::OtherFileIcon;
|
||||
return core::media::ImageType::OtherFileIcon;
|
||||
case ::TagLib::FLAC::Picture::Type::FrontCover:
|
||||
return Image::Type::FrontCover;
|
||||
return core::media::ImageType::FrontCover;
|
||||
case ::TagLib::FLAC::Picture::Type::BackCover:
|
||||
return Image::Type::BackCover;
|
||||
return core::media::ImageType::BackCover;
|
||||
case ::TagLib::FLAC::Picture::Type::LeafletPage:
|
||||
return Image::Type::LeafletPage;
|
||||
return core::media::ImageType::LeafletPage;
|
||||
case ::TagLib::FLAC::Picture::Type::Media:
|
||||
return Image::Type::Media;
|
||||
return core::media::ImageType::Media;
|
||||
case ::TagLib::FLAC::Picture::Type::LeadArtist:
|
||||
return Image::Type::LeadArtist;
|
||||
return core::media::ImageType::LeadArtist;
|
||||
case ::TagLib::FLAC::Picture::Type::Artist:
|
||||
return Image::Type::Artist;
|
||||
return core::media::ImageType::Artist;
|
||||
case ::TagLib::FLAC::Picture::Type::Conductor:
|
||||
return Image::Type::Conductor;
|
||||
return core::media::ImageType::Conductor;
|
||||
case ::TagLib::FLAC::Picture::Type::Band:
|
||||
return Image::Type::Band;
|
||||
return core::media::ImageType::Band;
|
||||
case ::TagLib::FLAC::Picture::Type::Composer:
|
||||
return Image::Type::Composer;
|
||||
return core::media::ImageType::Composer;
|
||||
case ::TagLib::FLAC::Picture::Type::Lyricist:
|
||||
return Image::Type::Lyricist;
|
||||
return core::media::ImageType::Lyricist;
|
||||
case ::TagLib::FLAC::Picture::Type::RecordingLocation:
|
||||
return Image::Type::RecordingLocation;
|
||||
return core::media::ImageType::RecordingLocation;
|
||||
case ::TagLib::FLAC::Picture::Type::DuringRecording:
|
||||
return Image::Type::DuringRecording;
|
||||
return core::media::ImageType::DuringRecording;
|
||||
case ::TagLib::FLAC::Picture::Type::DuringPerformance:
|
||||
return Image::Type::DuringPerformance;
|
||||
return core::media::ImageType::DuringPerformance;
|
||||
case ::TagLib::FLAC::Picture::Type::MovieScreenCapture:
|
||||
return Image::Type::MovieScreenCapture;
|
||||
return core::media::ImageType::MovieScreenCapture;
|
||||
case ::TagLib::FLAC::Picture::Type::ColouredFish:
|
||||
return Image::Type::ColouredFish;
|
||||
return core::media::ImageType::ColouredFish;
|
||||
case ::TagLib::FLAC::Picture::Type::Illustration:
|
||||
return Image::Type::Illustration;
|
||||
return core::media::ImageType::Illustration;
|
||||
case ::TagLib::FLAC::Picture::Type::BandLogo:
|
||||
return Image::Type::BandLogo;
|
||||
return core::media::ImageType::BandLogo;
|
||||
case ::TagLib::FLAC::Picture::Type::PublisherLogo:
|
||||
return Image::Type::PublisherLogo;
|
||||
return core::media::ImageType::PublisherLogo;
|
||||
}
|
||||
|
||||
return Image::Type::Unknown;
|
||||
return core::media::ImageType::Unknown;
|
||||
}
|
||||
|
||||
const char* mp4ImageFormatToMimeType(TagLib::MP4::CoverArt::Format format)
|
||||
@@ -217,14 +217,14 @@ namespace lms::audio::taglib
|
||||
}
|
||||
|
||||
#if LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES
|
||||
Image::Type imageTypeFromAPEPictureType(std::string_view pictureType)
|
||||
core::media::ImageType imageTypeFromAPEPictureType(std::string_view pictureType)
|
||||
{
|
||||
if (core::stringUtils::stringCaseInsensitiveContains(pictureType, "front"))
|
||||
return Image::Type::FrontCover;
|
||||
return core::media::ImageType::FrontCover;
|
||||
if (core::stringUtils::stringCaseInsensitiveContains(pictureType, "back"))
|
||||
return Image::Type::BackCover;
|
||||
return core::media::ImageType::BackCover;
|
||||
|
||||
return Image::Type::Unknown;
|
||||
return core::media::ImageType::Unknown;
|
||||
}
|
||||
#endif // LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES
|
||||
|
||||
@@ -294,7 +294,7 @@ namespace lms::audio::taglib
|
||||
image.data = pictureData;
|
||||
|
||||
// By convention, consider the first cover art as the front cover
|
||||
image.type = firstCover ? Image::Type::FrontCover : Image::Type::Unknown;
|
||||
image.type = firstCover ? core::media::ImageType::FrontCover : core::media::ImageType::Unknown;
|
||||
firstCover = false;
|
||||
|
||||
visitor(image);
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace lms::audio::taglib::utils
|
||||
{
|
||||
const std::error_code ec{ errno, std::generic_category() };
|
||||
LMS_LOG(METADATA, DEBUG, "fopen failed for " << p << ": " << ec.message());
|
||||
throw IOFileException{ "fopen failed", ec };
|
||||
throw IOFileException{ p, "fopen failed", ec };
|
||||
}
|
||||
|
||||
int fd{ ::fileno(file) };
|
||||
@@ -119,7 +119,7 @@ namespace lms::audio::taglib::utils
|
||||
{
|
||||
const std::error_code ec{ errno, std::generic_category() };
|
||||
LMS_LOG(METADATA, DEBUG, "fileno failed for " << p << ": " << ec.message());
|
||||
throw IOFileException{ "fileno failed", ec };
|
||||
throw IOFileException{ p, "fileno failed", ec };
|
||||
}
|
||||
|
||||
return TagLib::FileStream{ fd, true };
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "core/media/CodecType.hpp"
|
||||
#include "core/media/ContainerType.hpp"
|
||||
|
||||
namespace lms::audio
|
||||
{
|
||||
struct AudioProperties
|
||||
{
|
||||
core::media::ContainerType container;
|
||||
core::media::CodecType codec;
|
||||
std::chrono::milliseconds duration;
|
||||
unsigned bitrate;
|
||||
unsigned channelCount;
|
||||
unsigned sampleRate;
|
||||
std::optional<unsigned> bitsPerSample;
|
||||
};
|
||||
} // namespace lms::audio
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
|
||||
#include "core/Exception.hpp"
|
||||
@@ -34,15 +35,18 @@ namespace lms::audio
|
||||
class IOFileException : public Exception
|
||||
{
|
||||
public:
|
||||
IOFileException(std::string_view message, std::error_code err)
|
||||
: Exception{ std::string{ message } + ": " + err.message() }
|
||||
IOFileException(const std::filesystem::path& path, std::string_view message, std::error_code err)
|
||||
: Exception{ "File '" + path.string() + "': " + std::string{ message } + ": " + err.message() }
|
||||
, _path{ path }
|
||||
, _err{ err }
|
||||
{
|
||||
}
|
||||
|
||||
const std::filesystem::path& getPath() const { return _path; }
|
||||
std::error_code getErrorCode() const { return _err; }
|
||||
|
||||
private:
|
||||
std::filesystem::path _path;
|
||||
std::error_code _err;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,69 +23,18 @@
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "core/LiteralString.hpp"
|
||||
#include "core/media/ImageType.hpp"
|
||||
|
||||
namespace lms::audio
|
||||
{
|
||||
struct Image
|
||||
{
|
||||
// See TagLib types (based on ID3v2 APIC types)
|
||||
enum class Type
|
||||
{
|
||||
// No information
|
||||
Unknown,
|
||||
// A type not enumerated below
|
||||
Other,
|
||||
// 32x32 PNG image that should be used as the file icon
|
||||
FileIcon,
|
||||
// File icon of a different size or format
|
||||
OtherFileIcon,
|
||||
// Front cover image of the album
|
||||
FrontCover,
|
||||
// Back cover image of the album
|
||||
BackCover,
|
||||
// Inside leaflet page of the album
|
||||
LeafletPage,
|
||||
// Image from the album itself
|
||||
Media,
|
||||
// Picture of the lead artist or soloist
|
||||
LeadArtist,
|
||||
// Picture of the artist or performer
|
||||
Artist,
|
||||
// Picture of the conductor
|
||||
Conductor,
|
||||
// Picture of the band or orchestra
|
||||
Band,
|
||||
// Picture of the composer
|
||||
Composer,
|
||||
// Picture of the lyricist or text writer
|
||||
Lyricist,
|
||||
// Picture of the recording location or studio
|
||||
RecordingLocation,
|
||||
// Picture of the artists during recording
|
||||
DuringRecording,
|
||||
// Picture of the artists during performance
|
||||
DuringPerformance,
|
||||
// Picture from a movie or video related to the track
|
||||
MovieScreenCapture,
|
||||
// Picture of a large, coloured fish
|
||||
ColouredFish,
|
||||
// Illustration related to the track
|
||||
Illustration,
|
||||
// Logo of the band or performer
|
||||
BandLogo,
|
||||
// Logo of the publisher (record company)
|
||||
PublisherLogo
|
||||
};
|
||||
|
||||
Type type{ Type::Unknown };
|
||||
core::media::ImageType type{ core::media::ImageType::Unknown };
|
||||
std::string mimeType{ "application/octet-stream" };
|
||||
std::string description;
|
||||
std::span<const std::byte> data;
|
||||
};
|
||||
|
||||
core::LiteralString imageTypeToString(Image::Type type);
|
||||
|
||||
class IImageReader
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -23,28 +23,30 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "core/media/CodecType.hpp"
|
||||
#include "core/media/ContainerType.hpp"
|
||||
|
||||
#include "audio/AudioProperties.hpp"
|
||||
|
||||
namespace lms::audio
|
||||
{
|
||||
// TODO deprecate?
|
||||
enum class OutputFormat
|
||||
{
|
||||
MP3,
|
||||
OGG_OPUS,
|
||||
MATROSKA_OPUS,
|
||||
OGG_VORBIS,
|
||||
WEBM_VORBIS,
|
||||
};
|
||||
|
||||
struct TranscodeInputParameters
|
||||
{
|
||||
std::filesystem::path filePath;
|
||||
std::chrono::milliseconds duration{}; // Duration of the audio file
|
||||
std::chrono::milliseconds offset{}; // Offset in the audio file to start transcoding from
|
||||
AudioProperties audioProperties; // properties of the audio file
|
||||
std::chrono::milliseconds offset{}; // Offset in the audio file to start transcoding from
|
||||
};
|
||||
|
||||
struct TranscodeOutputFormat
|
||||
{
|
||||
core::media::ContainerType container;
|
||||
core::media::CodecType codec;
|
||||
};
|
||||
|
||||
struct TranscodeOutputParameters
|
||||
{
|
||||
std::optional<OutputFormat> format;
|
||||
// Not setting a value here means to use the same value as the input
|
||||
std::optional<TranscodeOutputFormat> format;
|
||||
std::optional<unsigned> bitrate;
|
||||
std::optional<unsigned> bitsPerSample;
|
||||
std::optional<unsigned> channelCount;
|
||||
|
||||
@@ -13,6 +13,10 @@ configure_file(
|
||||
add_library(lmscore STATIC
|
||||
impl/http/Client.cpp
|
||||
impl/http/SendQueue.cpp
|
||||
impl/media/CodecType.cpp
|
||||
impl/media/ContainerType.cpp
|
||||
impl/media/ImageType.cpp
|
||||
impl/media/MimeType.cpp
|
||||
impl/ArchiveZipper.cpp
|
||||
impl/ChildProcess.cpp
|
||||
impl/ChildProcessManager.cpp
|
||||
|
||||
@@ -17,45 +17,10 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "core/media/CodecType.hpp"
|
||||
|
||||
namespace lms::audio
|
||||
namespace lms::core::media
|
||||
{
|
||||
core::LiteralString containerTypeToString(ContainerType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ContainerType::AIFF:
|
||||
return "AIFF";
|
||||
case ContainerType::APE:
|
||||
return "APE";
|
||||
case ContainerType::ASF:
|
||||
return "ASF";
|
||||
case ContainerType::DSF:
|
||||
return "DSF";
|
||||
case ContainerType::FLAC:
|
||||
return "FLAC";
|
||||
case ContainerType::MP4:
|
||||
return "MP4";
|
||||
case ContainerType::MPC:
|
||||
return "MPC";
|
||||
case ContainerType::MPEG:
|
||||
return "MPEG";
|
||||
case ContainerType::Ogg:
|
||||
return "Ogg";
|
||||
case ContainerType::Shorten:
|
||||
return "Shorten";
|
||||
case ContainerType::TrueAudio:
|
||||
return "TrueAudio";
|
||||
case ContainerType::WAV:
|
||||
return "WAV";
|
||||
case ContainerType::WavPack:
|
||||
return "WavPack";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
core::LiteralString codecTypeToString(CodecType type)
|
||||
{
|
||||
switch (type)
|
||||
@@ -104,6 +69,6 @@ namespace lms::audio
|
||||
return "WMA9Lossless";
|
||||
}
|
||||
|
||||
return "";
|
||||
return "Unknown";
|
||||
}
|
||||
} // namespace lms::audio
|
||||
} // namespace lms::core::media
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/media/ContainerType.hpp"
|
||||
|
||||
namespace lms::core::media
|
||||
{
|
||||
core::LiteralString containerTypeToString(ContainerType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ContainerType::AIFF:
|
||||
return "AIFF";
|
||||
case ContainerType::APE:
|
||||
return "APE";
|
||||
case ContainerType::ASF:
|
||||
return "ASF";
|
||||
case ContainerType::DSF:
|
||||
return "DSF";
|
||||
case ContainerType::FLAC:
|
||||
return "FLAC";
|
||||
case ContainerType::MP4:
|
||||
return "MP4";
|
||||
case ContainerType::MPC:
|
||||
return "MPC";
|
||||
case ContainerType::MPEG:
|
||||
return "MPEG";
|
||||
case ContainerType::Ogg:
|
||||
return "Ogg";
|
||||
case ContainerType::Shorten:
|
||||
return "Shorten";
|
||||
case ContainerType::TrueAudio:
|
||||
return "TrueAudio";
|
||||
case ContainerType::WAV:
|
||||
return "WAV";
|
||||
case ContainerType::WavPack:
|
||||
return "WavPack";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
} // namespace lms::core::media
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Emeric Poupon
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
@@ -17,61 +17,60 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "audio/IImageReader.hpp"
|
||||
#include "core/media/ImageType.hpp"
|
||||
|
||||
namespace lms::audio
|
||||
namespace lms::core::media
|
||||
{
|
||||
core::LiteralString imageTypeToString(Image::Type type)
|
||||
core::LiteralString imageTypeToString(ImageType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Image::Type::Other:
|
||||
case ImageType::Other:
|
||||
return "Other";
|
||||
case Image::Type::FileIcon:
|
||||
case ImageType::FileIcon:
|
||||
return "FileIcon";
|
||||
case Image::Type::OtherFileIcon:
|
||||
case ImageType::OtherFileIcon:
|
||||
return "OtherFileIcon";
|
||||
case Image::Type::FrontCover:
|
||||
case ImageType::FrontCover:
|
||||
return "FrontCover";
|
||||
case Image::Type::BackCover:
|
||||
case ImageType::BackCover:
|
||||
return "BackCover";
|
||||
case Image::Type::LeafletPage:
|
||||
case ImageType::LeafletPage:
|
||||
return "LeafletPage";
|
||||
case Image::Type::Media:
|
||||
case ImageType::Media:
|
||||
return "Media";
|
||||
case Image::Type::LeadArtist:
|
||||
case ImageType::LeadArtist:
|
||||
return "LeadArtist";
|
||||
case Image::Type::Artist:
|
||||
case ImageType::Artist:
|
||||
return "Artist";
|
||||
case Image::Type::Conductor:
|
||||
case ImageType::Conductor:
|
||||
return "Conductor";
|
||||
case Image::Type::Band:
|
||||
case ImageType::Band:
|
||||
return "Band";
|
||||
case Image::Type::Composer:
|
||||
case ImageType::Composer:
|
||||
return "Composer";
|
||||
case Image::Type::Lyricist:
|
||||
case ImageType::Lyricist:
|
||||
return "Lyricist";
|
||||
case Image::Type::RecordingLocation:
|
||||
case ImageType::RecordingLocation:
|
||||
return "RecordingLocation";
|
||||
case Image::Type::DuringRecording:
|
||||
case ImageType::DuringRecording:
|
||||
return "DuringRecording";
|
||||
case Image::Type::DuringPerformance:
|
||||
case ImageType::DuringPerformance:
|
||||
return "DuringPerformance";
|
||||
case Image::Type::MovieScreenCapture:
|
||||
case ImageType::MovieScreenCapture:
|
||||
return "MovieScreenCapture";
|
||||
case Image::Type::ColouredFish:
|
||||
case ImageType::ColouredFish:
|
||||
return "ColouredFish";
|
||||
case Image::Type::Illustration:
|
||||
case ImageType::Illustration:
|
||||
return "Illustration";
|
||||
case Image::Type::BandLogo:
|
||||
case ImageType::BandLogo:
|
||||
return "BandLogo";
|
||||
case Image::Type::PublisherLogo:
|
||||
case ImageType::PublisherLogo:
|
||||
return "PublisherLogo";
|
||||
case Image::Type::Unknown:
|
||||
case ImageType::Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
} // namespace lms::audio
|
||||
} // namespace lms::core::media
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/media/MimeType.hpp"
|
||||
|
||||
namespace lms::core::media
|
||||
{
|
||||
core::LiteralString getMimeType(ContainerType container, CodecType codec)
|
||||
{
|
||||
switch (container)
|
||||
{
|
||||
case ContainerType::AIFF:
|
||||
return "audio/x-aiff";
|
||||
case ContainerType::APE:
|
||||
return "audio/x-monkeys-audio";
|
||||
case ContainerType::ASF:
|
||||
return "audio/x-ms-wma";
|
||||
case ContainerType::DSF:
|
||||
return "audio/x-dsd-dsf";
|
||||
case ContainerType::FLAC:
|
||||
return "audio/flac";
|
||||
case ContainerType::MP4:
|
||||
switch (codec)
|
||||
{
|
||||
case CodecType::AAC:
|
||||
return "audio/mp4; codecs=\"mp4a.40.2\"";
|
||||
case CodecType::ALAC:
|
||||
return "audio/mp4; codecs=\"alac\"";
|
||||
case CodecType::MP4ALS:
|
||||
return "audio/mp4; codecs=\"mp4als\"";
|
||||
default:
|
||||
return "audio/mp4";
|
||||
}
|
||||
|
||||
case ContainerType::MPC:
|
||||
return "audio/x-musepack";
|
||||
case ContainerType::MPEG:
|
||||
return "audio/mpeg";
|
||||
case ContainerType::Ogg:
|
||||
switch (codec)
|
||||
{
|
||||
case CodecType::Opus:
|
||||
return "audio/opus";
|
||||
case CodecType::Vorbis:
|
||||
return "audio/ogg; codecs=\"vorbis\"";
|
||||
case CodecType::FLAC:
|
||||
return "audio/ogg; codecs=\"flac\"";
|
||||
default:
|
||||
return "audio/ogg";
|
||||
}
|
||||
|
||||
case ContainerType::Shorten:
|
||||
return "audio/x-shn";
|
||||
case ContainerType::TrueAudio:
|
||||
return "audio/x-tta";
|
||||
case ContainerType::WAV:
|
||||
return "audio/wav";
|
||||
case ContainerType::WavPack:
|
||||
return "audio/x-wavpack";
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
} // namespace lms::core::media
|
||||
+2
-35
@@ -19,32 +19,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "core/LiteralString.hpp"
|
||||
|
||||
namespace lms::audio
|
||||
namespace lms::core::media
|
||||
{
|
||||
enum class ContainerType
|
||||
{
|
||||
AIFF,
|
||||
APE, // Monkey's Audio
|
||||
ASF, // Advanced Systems Format
|
||||
DSF,
|
||||
FLAC,
|
||||
MP4,
|
||||
MPC, // Musepack
|
||||
MPEG,
|
||||
Ogg,
|
||||
Shorten,
|
||||
TrueAudio,
|
||||
WAV,
|
||||
WavPack,
|
||||
};
|
||||
|
||||
core::LiteralString containerTypeToString(ContainerType type);
|
||||
|
||||
enum class CodecType
|
||||
{
|
||||
AAC,
|
||||
@@ -71,15 +49,4 @@ namespace lms::audio
|
||||
};
|
||||
|
||||
core::LiteralString codecTypeToString(CodecType type);
|
||||
|
||||
struct AudioProperties
|
||||
{
|
||||
ContainerType container;
|
||||
CodecType codec;
|
||||
std::chrono::milliseconds duration;
|
||||
unsigned bitrate;
|
||||
unsigned channelCount;
|
||||
unsigned sampleRate;
|
||||
std::optional<unsigned> bitsPerSample;
|
||||
};
|
||||
} // namespace lms::audio
|
||||
} // namespace lms::core::media
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/LiteralString.hpp"
|
||||
|
||||
namespace lms::core::media
|
||||
{
|
||||
enum class ContainerType
|
||||
{
|
||||
AIFF,
|
||||
APE, // Monkey's Audio
|
||||
ASF, // Advanced Systems Format
|
||||
DSF,
|
||||
FLAC,
|
||||
MP4,
|
||||
MPC, // Musepack
|
||||
MPEG,
|
||||
Ogg,
|
||||
Shorten,
|
||||
TrueAudio,
|
||||
WAV,
|
||||
WavPack,
|
||||
};
|
||||
|
||||
core::LiteralString containerTypeToString(ContainerType type);
|
||||
} // namespace lms::core::media
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/LiteralString.hpp"
|
||||
|
||||
namespace lms::core::media
|
||||
{
|
||||
// Based on ID3v2 APIC types
|
||||
enum class ImageType
|
||||
{
|
||||
// No information
|
||||
Unknown,
|
||||
// A type not enumerated below
|
||||
Other,
|
||||
// 32x32 PNG image that should be used as the file icon
|
||||
FileIcon,
|
||||
// File icon of a different size or format
|
||||
OtherFileIcon,
|
||||
// Front cover image of the album
|
||||
FrontCover,
|
||||
// Back cover image of the album
|
||||
BackCover,
|
||||
// Inside leaflet page of the album
|
||||
LeafletPage,
|
||||
// Image from the album itself
|
||||
Media,
|
||||
// Picture of the lead artist or soloist
|
||||
LeadArtist,
|
||||
// Picture of the artist or performer
|
||||
Artist,
|
||||
// Picture of the conductor
|
||||
Conductor,
|
||||
// Picture of the band or orchestra
|
||||
Band,
|
||||
// Picture of the composer
|
||||
Composer,
|
||||
// Picture of the lyricist or text writer
|
||||
Lyricist,
|
||||
// Picture of the recording location or studio
|
||||
RecordingLocation,
|
||||
// Picture of the artists during recording
|
||||
DuringRecording,
|
||||
// Picture of the artists during performance
|
||||
DuringPerformance,
|
||||
// Picture from a movie or video related to the track
|
||||
MovieScreenCapture,
|
||||
// Picture of a large, coloured fish
|
||||
ColouredFish,
|
||||
// Illustration related to the track
|
||||
Illustration,
|
||||
// Logo of the band or performer
|
||||
BandLogo,
|
||||
// Logo of the publisher (record company)
|
||||
PublisherLogo
|
||||
};
|
||||
|
||||
core::LiteralString imageTypeToString(ImageType type);
|
||||
} // namespace lms::core::media
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/LiteralString.hpp"
|
||||
#include "core/media/CodecType.hpp"
|
||||
#include "core/media/ContainerType.hpp"
|
||||
|
||||
namespace lms::core::media
|
||||
{
|
||||
core::LiteralString getMimeType(ContainerType container, CodecType codec);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
add_library(lmsdatabase STATIC
|
||||
impl/objects/detail/Types.cpp
|
||||
impl/objects/Artist.cpp
|
||||
impl/objects/ArtistInfo.cpp
|
||||
impl/objects/Artwork.cpp
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "database/objects/Podcast.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "detail/Types.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
|
||||
@@ -98,6 +99,16 @@ namespace lms::db
|
||||
utils::forEachQueryRangeResult(query, params.range, func);
|
||||
}
|
||||
|
||||
std::optional<core::media::ContainerType> PodcastEpisode::getContainer() const
|
||||
{
|
||||
return detail::getMediaContainerType(_container);
|
||||
}
|
||||
|
||||
std::optional<core::media::CodecType> PodcastEpisode::getCodec() const
|
||||
{
|
||||
return detail::getMediaCodecType(_codec);
|
||||
}
|
||||
|
||||
ObjectPtr<Artwork> PodcastEpisode::getArtwork() const
|
||||
{
|
||||
return _artwork;
|
||||
@@ -108,6 +119,16 @@ namespace lms::db
|
||||
return _artwork.id();
|
||||
}
|
||||
|
||||
void PodcastEpisode::setContainer(core::media::ContainerType container)
|
||||
{
|
||||
_container = detail::getDbContainerType(container);
|
||||
}
|
||||
|
||||
void PodcastEpisode::setCodec(core::media::CodecType codec)
|
||||
{
|
||||
_codec = detail::getDbCodecType(codec);
|
||||
}
|
||||
|
||||
void PodcastEpisode::setArtwork(ObjectPtr<Artwork> artwork)
|
||||
{
|
||||
_artwork = getDboPtr(artwork);
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "detail/Types.hpp"
|
||||
#include "traits/EnumSetTraits.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PartialDateTimeTraits.hpp"
|
||||
@@ -679,12 +680,20 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session()->query<int>("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t").where("release_id = ?").bind(getId()).where("bitrate > 0"));
|
||||
}
|
||||
|
||||
std::vector<CodecType> Release::getCodecs() const
|
||||
std::vector<core::media::CodecType> Release::getCodecs() const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
// Get the codec ordered by frequency
|
||||
return utils::fetchQueryResults(session()->query<CodecType>("SELECT t.codec FROM track t").where("release_id = ?").bind(getId()).groupBy("t.codec").orderBy("COUNT(t.id) DESC"));
|
||||
auto query{ session()->query<detail::CodecType>("SELECT t.codec FROM track t").where("release_id = ?").bind(getId()).groupBy("t.codec").orderBy("COUNT(t.id) DESC") };
|
||||
|
||||
std::vector<core::media::CodecType> res;
|
||||
utils::forEachQueryResult(query, [&](detail::CodecType codec) {
|
||||
if (const auto mediaCodecType{ detail::getMediaCodecType(codec) })
|
||||
res.push_back(*mediaCodecType);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
@@ -41,6 +42,7 @@
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "objects/detail/Types.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PartialDateTimeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
@@ -523,6 +525,16 @@ namespace lms::db
|
||||
_absoluteFilePath = filePath;
|
||||
}
|
||||
|
||||
void Track::setContainer(core::media::ContainerType container)
|
||||
{
|
||||
_container = detail::getDbContainerType(container);
|
||||
}
|
||||
|
||||
void Track::setCodec(core::media::CodecType codec)
|
||||
{
|
||||
_codec = detail::getDbCodecType(codec);
|
||||
}
|
||||
|
||||
void Track::setName(std::string_view name)
|
||||
{
|
||||
_name = std::string{ name, 0, _maxNameLength };
|
||||
@@ -603,6 +615,16 @@ namespace lms::db
|
||||
_preferredMediaArtwork = getDboPtr(artwork);
|
||||
}
|
||||
|
||||
std::optional<core::media::ContainerType> Track::getContainer() const
|
||||
{
|
||||
return detail::getMediaContainerType(_container);
|
||||
}
|
||||
|
||||
std::optional<core::media::CodecType> Track::getCodec() const
|
||||
{
|
||||
return detail::getMediaCodecType(_codec);
|
||||
}
|
||||
|
||||
std::optional<int> Track::getYear() const
|
||||
{
|
||||
return _date.getYear();
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "detail/Types.hpp"
|
||||
#include "objects/detail/Types.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/ImageHashTypeTraits.hpp"
|
||||
|
||||
@@ -83,7 +85,7 @@ namespace lms::db
|
||||
}
|
||||
|
||||
if (params.imageType.has_value())
|
||||
query.where("t_e_i_l.type = ?").bind(params.imageType.value());
|
||||
query.where("t_e_i_l.type = ?").bind(detail::getDbImageType(params.imageType.value()));
|
||||
}
|
||||
|
||||
switch (params.sortMethod)
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "detail/Types.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/ImageHashTypeTraits.hpp"
|
||||
|
||||
@@ -77,4 +77,14 @@ namespace lms::db
|
||||
{
|
||||
return _image;
|
||||
}
|
||||
|
||||
core::media::ImageType TrackEmbeddedImageLink::getType() const
|
||||
{
|
||||
return detail::getMediaImageType(_type);
|
||||
}
|
||||
|
||||
void TrackEmbeddedImageLink::setType(core::media::ImageType type)
|
||||
{
|
||||
_type = detail::getDbImageType(type);
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -72,96 +72,4 @@ namespace lms::db
|
||||
{
|
||||
return allowedAudioBitrates.find(bitrate) != std::cend(allowedAudioBitrates);
|
||||
}
|
||||
|
||||
core::LiteralString containerTypeToString(ContainerType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ContainerType::Unknown:
|
||||
break;
|
||||
|
||||
case ContainerType::AIFF:
|
||||
return "AIFF";
|
||||
case ContainerType::APE:
|
||||
return "APE";
|
||||
case ContainerType::ASF:
|
||||
return "ASF";
|
||||
case ContainerType::DSF:
|
||||
return "DSF";
|
||||
case ContainerType::FLAC:
|
||||
return "FLAC";
|
||||
case ContainerType::MP4:
|
||||
return "MP4";
|
||||
case ContainerType::MPC:
|
||||
return "MPC";
|
||||
case ContainerType::MPEG:
|
||||
return "MPEG";
|
||||
case ContainerType::Ogg:
|
||||
return "Ogg";
|
||||
case ContainerType::Shorten:
|
||||
return "Shorten";
|
||||
case ContainerType::TrueAudio:
|
||||
return "TrueAudio";
|
||||
case ContainerType::WAV:
|
||||
return "WAV";
|
||||
case ContainerType::WavPack:
|
||||
return "WavPack";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
core::LiteralString codecTypeToString(CodecType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CodecType::Unknown:
|
||||
break;
|
||||
|
||||
case CodecType::AAC:
|
||||
return "AAC";
|
||||
case CodecType::AC3:
|
||||
return "AC3";
|
||||
case CodecType::ALAC:
|
||||
return "ALAC";
|
||||
case CodecType::APE:
|
||||
return "APE";
|
||||
case CodecType::DSD:
|
||||
return "DSD";
|
||||
case CodecType::EAC3:
|
||||
return "EAC3";
|
||||
case CodecType::FLAC:
|
||||
return "FLAC";
|
||||
case CodecType::MP3:
|
||||
return "MP3";
|
||||
case CodecType::MP4ALS:
|
||||
return "MP4ALS";
|
||||
case CodecType::MPC7:
|
||||
return "MPC7";
|
||||
case CodecType::MPC8:
|
||||
return "MPC8";
|
||||
case CodecType::Opus:
|
||||
return "Opus";
|
||||
case CodecType::PCM:
|
||||
return "PCM";
|
||||
case CodecType::Shorten:
|
||||
return "Shorten";
|
||||
case CodecType::TrueAudio:
|
||||
return "TrueAudio";
|
||||
case CodecType::Vorbis:
|
||||
return "Vorbis";
|
||||
case CodecType::WavPack:
|
||||
return "WavPack";
|
||||
case CodecType::WMA1:
|
||||
return "WMA1";
|
||||
case CodecType::WMA2:
|
||||
return "WMA2";
|
||||
case CodecType::WMA9Pro:
|
||||
return "WMA9Pro";
|
||||
case CodecType::WMA9Lossless:
|
||||
return "WMA9Lossless";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
namespace lms::db::detail
|
||||
{
|
||||
std::optional<core::media::ContainerType> getMediaContainerType(db::detail::ContainerType container)
|
||||
{
|
||||
switch (container)
|
||||
{
|
||||
case ContainerType::AIFF:
|
||||
return core::media::ContainerType::AIFF;
|
||||
case ContainerType::APE:
|
||||
return core::media::ContainerType::APE;
|
||||
case ContainerType::ASF:
|
||||
return core::media::ContainerType::ASF;
|
||||
case ContainerType::DSF:
|
||||
return core::media::ContainerType::DSF;
|
||||
case ContainerType::FLAC:
|
||||
return core::media::ContainerType::FLAC;
|
||||
case ContainerType::MP4:
|
||||
return core::media::ContainerType::MP4;
|
||||
case ContainerType::MPC:
|
||||
return core::media::ContainerType::MPC;
|
||||
case ContainerType::MPEG:
|
||||
return core::media::ContainerType::MPEG;
|
||||
case ContainerType::Ogg:
|
||||
return core::media::ContainerType::Ogg;
|
||||
case ContainerType::Shorten:
|
||||
return core::media::ContainerType::Shorten;
|
||||
case ContainerType::TrueAudio:
|
||||
return core::media::ContainerType::TrueAudio;
|
||||
case ContainerType::WAV:
|
||||
return core::media::ContainerType::WAV;
|
||||
case ContainerType::WavPack:
|
||||
return core::media::ContainerType::WavPack;
|
||||
|
||||
case ContainerType::Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
db::detail::ContainerType getDbContainerType(core::media::ContainerType container)
|
||||
{
|
||||
switch (container)
|
||||
{
|
||||
case core::media::ContainerType::AIFF:
|
||||
return ContainerType::AIFF;
|
||||
case core::media::ContainerType::APE:
|
||||
return ContainerType::APE;
|
||||
case core::media::ContainerType::ASF:
|
||||
return ContainerType::ASF;
|
||||
case core::media::ContainerType::DSF:
|
||||
return ContainerType::DSF;
|
||||
case core::media::ContainerType::FLAC:
|
||||
return ContainerType::FLAC;
|
||||
case core::media::ContainerType::MP4:
|
||||
return ContainerType::MP4;
|
||||
case core::media::ContainerType::MPC:
|
||||
return ContainerType::MPC;
|
||||
case core::media::ContainerType::MPEG:
|
||||
return ContainerType::MPEG;
|
||||
case core::media::ContainerType::Ogg:
|
||||
return ContainerType::Ogg;
|
||||
case core::media::ContainerType::Shorten:
|
||||
return ContainerType::Shorten;
|
||||
case core::media::ContainerType::TrueAudio:
|
||||
return ContainerType::TrueAudio;
|
||||
case core::media::ContainerType::WAV:
|
||||
return ContainerType::WAV;
|
||||
case core::media::ContainerType::WavPack:
|
||||
return ContainerType::WavPack;
|
||||
}
|
||||
|
||||
return ContainerType::Unknown;
|
||||
}
|
||||
|
||||
std::optional<core::media::CodecType> getMediaCodecType(db::detail::CodecType codec)
|
||||
{
|
||||
switch (codec)
|
||||
{
|
||||
case CodecType::AAC:
|
||||
return core::media::CodecType::AAC;
|
||||
case CodecType::AC3:
|
||||
return core::media::CodecType::AC3;
|
||||
case CodecType::ALAC:
|
||||
return core::media::CodecType::ALAC;
|
||||
case CodecType::APE:
|
||||
return core::media::CodecType::APE;
|
||||
case CodecType::DSD:
|
||||
return core::media::CodecType::DSD;
|
||||
case CodecType::EAC3:
|
||||
return core::media::CodecType::EAC3;
|
||||
case CodecType::FLAC:
|
||||
return core::media::CodecType::FLAC;
|
||||
case CodecType::MP3:
|
||||
return core::media::CodecType::MP3;
|
||||
case CodecType::MP4ALS:
|
||||
return core::media::CodecType::MP4ALS;
|
||||
case CodecType::MPC7:
|
||||
return core::media::CodecType::MPC7;
|
||||
case CodecType::MPC8:
|
||||
return core::media::CodecType::MPC8;
|
||||
case CodecType::Opus:
|
||||
return core::media::CodecType::Opus;
|
||||
case CodecType::PCM:
|
||||
return core::media::CodecType::PCM;
|
||||
case CodecType::Shorten:
|
||||
return core::media::CodecType::Shorten;
|
||||
case CodecType::TrueAudio:
|
||||
return core::media::CodecType::TrueAudio;
|
||||
case CodecType::Vorbis:
|
||||
return core::media::CodecType::Vorbis;
|
||||
case CodecType::WavPack:
|
||||
return core::media::CodecType::WavPack;
|
||||
case CodecType::WMA1:
|
||||
return core::media::CodecType::WMA1;
|
||||
case CodecType::WMA2:
|
||||
return core::media::CodecType::WMA2;
|
||||
case CodecType::WMA9Pro:
|
||||
return core::media::CodecType::WMA9Pro;
|
||||
case CodecType::WMA9Lossless:
|
||||
return core::media::CodecType::WMA9Lossless;
|
||||
|
||||
case CodecType::Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
db::detail::CodecType getDbCodecType(core::media::CodecType codec)
|
||||
{
|
||||
switch (codec)
|
||||
{
|
||||
case core::media::CodecType::AAC:
|
||||
return CodecType::AAC;
|
||||
case core::media::CodecType::AC3:
|
||||
return CodecType::AC3;
|
||||
case core::media::CodecType::ALAC:
|
||||
return CodecType::ALAC;
|
||||
case core::media::CodecType::APE:
|
||||
return CodecType::APE;
|
||||
case core::media::CodecType::DSD:
|
||||
return CodecType::DSD;
|
||||
case core::media::CodecType::EAC3:
|
||||
return CodecType::EAC3;
|
||||
case core::media::CodecType::FLAC:
|
||||
return CodecType::FLAC;
|
||||
case core::media::CodecType::MP3:
|
||||
return CodecType::MP3;
|
||||
case core::media::CodecType::MP4ALS:
|
||||
return CodecType::MP4ALS;
|
||||
case core::media::CodecType::MPC7:
|
||||
return CodecType::MPC7;
|
||||
case core::media::CodecType::MPC8:
|
||||
return CodecType::MPC8;
|
||||
case core::media::CodecType::Opus:
|
||||
return CodecType::Opus;
|
||||
case core::media::CodecType::PCM:
|
||||
return CodecType::PCM;
|
||||
case core::media::CodecType::Shorten:
|
||||
return CodecType::Shorten;
|
||||
case core::media::CodecType::TrueAudio:
|
||||
return CodecType::TrueAudio;
|
||||
case core::media::CodecType::Vorbis:
|
||||
return CodecType::Vorbis;
|
||||
case core::media::CodecType::WavPack:
|
||||
return CodecType::WavPack;
|
||||
case core::media::CodecType::WMA1:
|
||||
return CodecType::WMA1;
|
||||
case core::media::CodecType::WMA2:
|
||||
return CodecType::WMA2;
|
||||
case core::media::CodecType::WMA9Pro:
|
||||
return CodecType::WMA9Pro;
|
||||
case core::media::CodecType::WMA9Lossless:
|
||||
return CodecType::WMA9Lossless;
|
||||
}
|
||||
|
||||
return CodecType::Unknown;
|
||||
}
|
||||
|
||||
core::media::ImageType getMediaImageType(db::detail::ImageType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case db::detail::ImageType::Unknown:
|
||||
return core::media::ImageType::Unknown;
|
||||
case db::detail::ImageType::Other:
|
||||
return core::media::ImageType::Other;
|
||||
case db::detail::ImageType::FileIcon:
|
||||
return core::media::ImageType::FileIcon;
|
||||
case db::detail::ImageType::OtherFileIcon:
|
||||
return core::media::ImageType::OtherFileIcon;
|
||||
case db::detail::ImageType::FrontCover:
|
||||
return core::media::ImageType::FrontCover;
|
||||
case db::detail::ImageType::BackCover:
|
||||
return core::media::ImageType::BackCover;
|
||||
case db::detail::ImageType::LeafletPage:
|
||||
return core::media::ImageType::LeafletPage;
|
||||
case db::detail::ImageType::Media:
|
||||
return core::media::ImageType::Media;
|
||||
case db::detail::ImageType::LeadArtist:
|
||||
return core::media::ImageType::LeadArtist;
|
||||
case db::detail::ImageType::Artist:
|
||||
return core::media::ImageType::Artist;
|
||||
case db::detail::ImageType::Conductor:
|
||||
return core::media::ImageType::Conductor;
|
||||
case db::detail::ImageType::Band:
|
||||
return core::media::ImageType::Band;
|
||||
case db::detail::ImageType::Composer:
|
||||
return core::media::ImageType::Composer;
|
||||
case db::detail::ImageType::Lyricist:
|
||||
return core::media::ImageType::Lyricist;
|
||||
case db::detail::ImageType::RecordingLocation:
|
||||
return core::media::ImageType::RecordingLocation;
|
||||
case db::detail::ImageType::DuringRecording:
|
||||
return core::media::ImageType::DuringRecording;
|
||||
case db::detail::ImageType::DuringPerformance:
|
||||
return core::media::ImageType::DuringPerformance;
|
||||
case db::detail::ImageType::MovieScreenCapture:
|
||||
return core::media::ImageType::MovieScreenCapture;
|
||||
case db::detail::ImageType::ColouredFish:
|
||||
return core::media::ImageType::ColouredFish;
|
||||
case db::detail::ImageType::Illustration:
|
||||
return core::media::ImageType::Illustration;
|
||||
case db::detail::ImageType::BandLogo:
|
||||
return core::media::ImageType::BandLogo;
|
||||
case db::detail::ImageType::PublisherLogo:
|
||||
return core::media::ImageType::PublisherLogo;
|
||||
}
|
||||
|
||||
return core::media::ImageType::Unknown;
|
||||
}
|
||||
|
||||
db::detail::ImageType getDbImageType(core::media::ImageType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case core::media::ImageType::Unknown:
|
||||
return db::detail::ImageType::Unknown;
|
||||
case core::media::ImageType::Other:
|
||||
return db::detail::ImageType::Other;
|
||||
case core::media::ImageType::FileIcon:
|
||||
return db::detail::ImageType::FileIcon;
|
||||
case core::media::ImageType::OtherFileIcon:
|
||||
return db::detail::ImageType::OtherFileIcon;
|
||||
case core::media::ImageType::FrontCover:
|
||||
return db::detail::ImageType::FrontCover;
|
||||
case core::media::ImageType::BackCover:
|
||||
return db::detail::ImageType::BackCover;
|
||||
case core::media::ImageType::LeafletPage:
|
||||
return db::detail::ImageType::LeafletPage;
|
||||
case core::media::ImageType::Media:
|
||||
return db::detail::ImageType::Media;
|
||||
case core::media::ImageType::LeadArtist:
|
||||
return db::detail::ImageType::LeadArtist;
|
||||
case core::media::ImageType::Artist:
|
||||
return db::detail::ImageType::Artist;
|
||||
case core::media::ImageType::Conductor:
|
||||
return db::detail::ImageType::Conductor;
|
||||
case core::media::ImageType::Band:
|
||||
return db::detail::ImageType::Band;
|
||||
case core::media::ImageType::Composer:
|
||||
return db::detail::ImageType::Composer;
|
||||
case core::media::ImageType::Lyricist:
|
||||
return db::detail::ImageType::Lyricist;
|
||||
case core::media::ImageType::RecordingLocation:
|
||||
return db::detail::ImageType::RecordingLocation;
|
||||
case core::media::ImageType::DuringRecording:
|
||||
return db::detail::ImageType::DuringRecording;
|
||||
case core::media::ImageType::DuringPerformance:
|
||||
return db::detail::ImageType::DuringPerformance;
|
||||
case core::media::ImageType::MovieScreenCapture:
|
||||
return db::detail::ImageType::MovieScreenCapture;
|
||||
case core::media::ImageType::ColouredFish:
|
||||
return db::detail::ImageType::ColouredFish;
|
||||
case core::media::ImageType::Illustration:
|
||||
return db::detail::ImageType::Illustration;
|
||||
case core::media::ImageType::BandLogo:
|
||||
return db::detail::ImageType::BandLogo;
|
||||
case core::media::ImageType::PublisherLogo:
|
||||
return db::detail::ImageType::PublisherLogo;
|
||||
}
|
||||
|
||||
return db::detail::ImageType::Unknown;
|
||||
}
|
||||
} // namespace lms::db::detail
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "core/media/CodecType.hpp"
|
||||
#include "core/media/ContainerType.hpp"
|
||||
#include "core/media/ImageType.hpp"
|
||||
|
||||
#include "database/objects/detail/Types.hpp"
|
||||
|
||||
namespace lms::db::detail
|
||||
{
|
||||
std::optional<core::media::ContainerType> getMediaContainerType(db::detail::ContainerType container);
|
||||
db::detail::ContainerType getDbContainerType(core::media::ContainerType container);
|
||||
|
||||
std::optional<core::media::CodecType> getMediaCodecType(db::detail::CodecType codec);
|
||||
db::detail::CodecType getDbCodecType(core::media::CodecType codec);
|
||||
|
||||
core::media::ImageType getMediaImageType(db::detail::ImageType type);
|
||||
db::detail::ImageType getDbImageType(core::media::ImageType type);
|
||||
} // namespace lms::db::detail
|
||||
@@ -29,12 +29,16 @@
|
||||
#include <Wt/Dbo/collection.h>
|
||||
#include <Wt/WDateTime.h>
|
||||
|
||||
#include "core/media/CodecType.hpp"
|
||||
#include "core/media/ContainerType.hpp"
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/ArtworkId.hpp"
|
||||
#include "database/objects/PodcastEpisodeId.hpp"
|
||||
#include "database/objects/PodcastId.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
#include "database/objects/detail/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -93,8 +97,8 @@ namespace lms::db
|
||||
|
||||
// Audio properties
|
||||
std::chrono::milliseconds getDuration() const { return _duration; }
|
||||
ContainerType getContainer() const { return _container; }
|
||||
CodecType getCodec() const { return _codec; }
|
||||
std::optional<core::media::ContainerType> getContainer() const;
|
||||
std::optional<core::media::CodecType> getCodec() const;
|
||||
std::size_t getBitrate() const { return _bitrate; }
|
||||
std::size_t getChannelCount() const { return _channelCount; }
|
||||
std::size_t getSampleRate() const { return _sampleRate; }
|
||||
@@ -124,8 +128,8 @@ namespace lms::db
|
||||
|
||||
// Audio properties
|
||||
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
|
||||
void setContainer(ContainerType container) { _container = container; }
|
||||
void setCodec(CodecType codec) { _codec = codec; }
|
||||
void setContainer(core::media::ContainerType container);
|
||||
void setCodec(core::media::CodecType codec);
|
||||
void setBitrate(std::size_t bitrate) { _bitrate = bitrate; }
|
||||
void setChannelCount(std::size_t channelCount) { _channelCount = channelCount; }
|
||||
void setSampleRate(std::size_t sampleRate) { _sampleRate = sampleRate; }
|
||||
@@ -189,8 +193,8 @@ namespace lms::db
|
||||
|
||||
// Audio properties
|
||||
std::chrono::duration<int, std::milli> _duration{ 0 };
|
||||
ContainerType _container{ ContainerType::Unknown };
|
||||
CodecType _codec{ CodecType::Unknown };
|
||||
detail::ContainerType _container{ detail::ContainerType::Unknown };
|
||||
detail::CodecType _codec{ detail::CodecType::Unknown };
|
||||
int _bitrate{}; // in bps
|
||||
int _channelCount{};
|
||||
int _sampleRate{};
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "core/EnumSet.hpp"
|
||||
#include "core/PartialDateTime.hpp"
|
||||
#include "core/UUID.hpp"
|
||||
#include "core/media/CodecType.hpp"
|
||||
|
||||
#include "database/IdRange.hpp"
|
||||
#include "database/Object.hpp"
|
||||
@@ -279,7 +280,7 @@ namespace lms::db
|
||||
std::optional<std::string> getCopyright() const;
|
||||
std::optional<std::string> getCopyrightURL() const;
|
||||
std::size_t getMeanBitrate() const;
|
||||
std::vector<CodecType> getCodecs() const;
|
||||
std::vector<core::media::CodecType> getCodecs() const;
|
||||
|
||||
// Accessors
|
||||
std::string_view getName() const { return _name; }
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#include "core/EnumSet.hpp"
|
||||
#include "core/PartialDateTime.hpp"
|
||||
#include "core/UUID.hpp"
|
||||
#include "core/media/CodecType.hpp"
|
||||
#include "core/media/ContainerType.hpp"
|
||||
|
||||
#include "database/IdRange.hpp"
|
||||
#include "database/Object.hpp"
|
||||
@@ -51,6 +53,7 @@
|
||||
#include "database/objects/TrackListId.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
#include "database/objects/UserId.hpp"
|
||||
#include "database/objects/detail/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -229,8 +232,8 @@ namespace lms::db
|
||||
|
||||
// Audio properties
|
||||
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
|
||||
void setContainer(ContainerType container) { _container = container; }
|
||||
void setCodec(CodecType codec) { _codec = codec; }
|
||||
void setContainer(core::media::ContainerType container);
|
||||
void setCodec(core::media::CodecType codec);
|
||||
void setBitrate(std::size_t bitrate) { _bitrate = bitrate; }
|
||||
void setChannelCount(std::size_t channelCount) { _channelCount = channelCount; }
|
||||
void setSampleRate(std::size_t sampleRate) { _sampleRate = sampleRate; }
|
||||
@@ -275,8 +278,8 @@ namespace lms::db
|
||||
|
||||
// Audio properties
|
||||
std::chrono::milliseconds getDuration() const { return _duration; }
|
||||
ContainerType getContainer() const { return _container; }
|
||||
CodecType getCodec() const { return _codec; }
|
||||
std::optional<core::media::ContainerType> getContainer() const;
|
||||
std::optional<core::media::CodecType> getCodec() const;
|
||||
std::size_t getBitrate() const { return _bitrate; }
|
||||
std::size_t getChannelCount() const { return _channelCount; }
|
||||
std::size_t getSampleRate() const { return _sampleRate; }
|
||||
@@ -380,8 +383,8 @@ namespace lms::db
|
||||
|
||||
// Audio properties
|
||||
std::chrono::duration<int, std::milli> _duration{};
|
||||
ContainerType _container{ ContainerType::Unknown };
|
||||
CodecType _codec{ CodecType::Unknown };
|
||||
detail::ContainerType _container{ detail::ContainerType::Unknown };
|
||||
detail::CodecType _codec{ detail::CodecType::Unknown };
|
||||
int _bitrate{}; // in bps
|
||||
int _channelCount{};
|
||||
int _sampleRate{};
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <Wt/Dbo/Field.h>
|
||||
#include <Wt/Dbo/collection.h>
|
||||
|
||||
#include "core/media/ImageType.hpp"
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/MediumId.hpp"
|
||||
@@ -53,7 +55,7 @@ namespace lms::db
|
||||
ReleaseId release;
|
||||
MediumId medium;
|
||||
TrackListId trackList;
|
||||
std::optional<ImageType> imageType;
|
||||
std::optional<core::media::ImageType> imageType;
|
||||
TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None };
|
||||
|
||||
FindParameters& setRange(std::optional<Range> _range)
|
||||
@@ -81,7 +83,7 @@ namespace lms::db
|
||||
trackList = _trackList;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setImageType(std::optional<ImageType> _imageType)
|
||||
FindParameters& setImageType(std::optional<core::media::ImageType> _imageType)
|
||||
{
|
||||
imageType = _imageType;
|
||||
return *this;
|
||||
|
||||
@@ -24,10 +24,13 @@
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
|
||||
#include "core/media/ImageType.hpp"
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageId.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLinkId.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
#include "database/objects/detail/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -49,12 +52,12 @@ namespace lms::db
|
||||
ObjectPtr<Track> getTrack() const;
|
||||
ObjectPtr<TrackEmbeddedImage> getImage() const;
|
||||
std::size_t getIndex() const { return _index; }
|
||||
ImageType getType() const { return _type; }
|
||||
core::media::ImageType getType() const;
|
||||
std::string_view getDescription() const { return _description; }
|
||||
|
||||
// setters
|
||||
void setIndex(std::size_t index) { _index = static_cast<int>(index); }
|
||||
void setType(ImageType type) { _type = type; }
|
||||
void setType(core::media::ImageType type);
|
||||
void setDescription(std::string_view description) { _description = description; }
|
||||
|
||||
template<class Action>
|
||||
@@ -75,7 +78,7 @@ namespace lms::db
|
||||
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image);
|
||||
|
||||
int _index{}; // index within the track
|
||||
ImageType _type{ ImageType::Unknown };
|
||||
detail::ImageType _type{ detail::ImageType::Unknown };
|
||||
std::string _description;
|
||||
|
||||
Wt::Dbo::ptr<Track> _track;
|
||||
|
||||
@@ -155,32 +155,6 @@ namespace lms::db
|
||||
EmbeddedFirst,
|
||||
};
|
||||
|
||||
enum class ImageType
|
||||
{
|
||||
Unknown = 0,
|
||||
Other = 1,
|
||||
FileIcon = 2,
|
||||
OtherFileIcon = 3,
|
||||
FrontCover = 4,
|
||||
BackCover = 5,
|
||||
LeafletPage = 6,
|
||||
Media = 7,
|
||||
LeadArtist = 8,
|
||||
Artist = 9,
|
||||
Conductor = 10,
|
||||
Band = 11,
|
||||
Composer = 12,
|
||||
Lyricist = 13,
|
||||
RecordingLocation = 14,
|
||||
DuringRecording = 15,
|
||||
DuringPerformance = 16,
|
||||
MovieScreenCapture = 17,
|
||||
ColouredFish = 18,
|
||||
Illustration = 19,
|
||||
BandLogo = 20,
|
||||
PublisherLogo = 21
|
||||
};
|
||||
|
||||
enum class TrackArtistLinkType
|
||||
{
|
||||
Artist = 0, // regular track artist
|
||||
@@ -204,8 +178,8 @@ namespace lms::db
|
||||
MP3 = 1,
|
||||
OGG_OPUS = 2,
|
||||
OGG_VORBIS = 3,
|
||||
WEBM_VORBIS = 4,
|
||||
MATROSKA_OPUS = 5,
|
||||
// WEBM_VORBIS = 4,
|
||||
// MATROSKA_OPUS = 5,
|
||||
};
|
||||
|
||||
using Bitrate = std::uint32_t;
|
||||
@@ -266,54 +240,4 @@ namespace lms::db
|
||||
Clean = 2,
|
||||
Explicit = 3,
|
||||
};
|
||||
|
||||
enum class ContainerType
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
AIFF = 1,
|
||||
APE = 2,
|
||||
ASF = 3,
|
||||
DSF = 4,
|
||||
FLAC = 5,
|
||||
MP4 = 6,
|
||||
MPC = 7,
|
||||
MPEG = 8,
|
||||
Ogg = 9,
|
||||
Shorten = 10,
|
||||
TrueAudio = 11,
|
||||
WAV = 12,
|
||||
WavPack = 13,
|
||||
};
|
||||
|
||||
core::LiteralString containerTypeToString(ContainerType type);
|
||||
|
||||
enum class CodecType
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
AAC = 1,
|
||||
AC3 = 2,
|
||||
ALAC = 3,
|
||||
APE = 4,
|
||||
DSD = 5,
|
||||
EAC3 = 6,
|
||||
FLAC = 7,
|
||||
MP3 = 8,
|
||||
MP4ALS = 9,
|
||||
MPC7 = 10,
|
||||
MPC8 = 11,
|
||||
Opus = 12,
|
||||
PCM = 13,
|
||||
Shorten = 14,
|
||||
TrueAudio = 15,
|
||||
Vorbis = 16,
|
||||
WavPack = 17,
|
||||
WMA1 = 18,
|
||||
WMA2 = 19,
|
||||
WMA9Pro = 20,
|
||||
WMA9Lossless = 21,
|
||||
};
|
||||
|
||||
core::LiteralString codecTypeToString(CodecType type);
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace lms::db::detail
|
||||
{
|
||||
// For enums that have explicit values, never change enum values as they are stored in database.
|
||||
// => only add new values or remove unused values, do not recycle values.
|
||||
enum class ImageType
|
||||
{
|
||||
Unknown = 0,
|
||||
Other = 1,
|
||||
FileIcon = 2,
|
||||
OtherFileIcon = 3,
|
||||
FrontCover = 4,
|
||||
BackCover = 5,
|
||||
LeafletPage = 6,
|
||||
Media = 7,
|
||||
LeadArtist = 8,
|
||||
Artist = 9,
|
||||
Conductor = 10,
|
||||
Band = 11,
|
||||
Composer = 12,
|
||||
Lyricist = 13,
|
||||
RecordingLocation = 14,
|
||||
DuringRecording = 15,
|
||||
DuringPerformance = 16,
|
||||
MovieScreenCapture = 17,
|
||||
ColouredFish = 18,
|
||||
Illustration = 19,
|
||||
BandLogo = 20,
|
||||
PublisherLogo = 21
|
||||
};
|
||||
|
||||
enum class ContainerType
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
AIFF = 1,
|
||||
APE = 2,
|
||||
ASF = 3,
|
||||
DSF = 4,
|
||||
FLAC = 5,
|
||||
MP4 = 6,
|
||||
MPC = 7,
|
||||
MPEG = 8,
|
||||
Ogg = 9,
|
||||
Shorten = 10,
|
||||
TrueAudio = 11,
|
||||
WAV = 12,
|
||||
WavPack = 13,
|
||||
};
|
||||
|
||||
enum class CodecType
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
AAC = 1,
|
||||
AC3 = 2,
|
||||
ALAC = 3,
|
||||
APE = 4,
|
||||
DSD = 5,
|
||||
EAC3 = 6,
|
||||
FLAC = 7,
|
||||
MP3 = 8,
|
||||
MP4ALS = 9,
|
||||
MPC7 = 10,
|
||||
MPC8 = 11,
|
||||
Opus = 12,
|
||||
PCM = 13,
|
||||
Shorten = 14,
|
||||
TrueAudio = 15,
|
||||
Vorbis = 16,
|
||||
WavPack = 17,
|
||||
WMA1 = 18,
|
||||
WMA2 = 19,
|
||||
WMA9Pro = 20,
|
||||
WMA9Lossless = 21,
|
||||
};
|
||||
} // namespace lms::db::detail
|
||||
@@ -1229,22 +1229,22 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track1.get().modify()->setCodec(CodecType::FLAC);
|
||||
track1.get().modify()->setCodec(core::media::CodecType::FLAC);
|
||||
track1.get().modify()->setRelease(release1.get());
|
||||
|
||||
track2.get().modify()->setCodec(CodecType::MP3);
|
||||
track2.get().modify()->setCodec(core::media::CodecType::MP3);
|
||||
track2.get().modify()->setRelease(release1.get());
|
||||
|
||||
track3.get().modify()->setCodec(CodecType::MP3);
|
||||
track3.get().modify()->setCodec(core::media::CodecType::MP3);
|
||||
track3.get().modify()->setRelease(release1.get());
|
||||
|
||||
track4.get().modify()->setCodec(CodecType::AAC);
|
||||
track4.get().modify()->setCodec(core::media::CodecType::AAC);
|
||||
track4.get().modify()->setRelease(release1.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(release1->getCodecs(), (std::vector<CodecType>{ CodecType::MP3, CodecType::FLAC, CodecType::AAC }));
|
||||
EXPECT_EQ(release1->getCodecs(), (std::vector<core::media::CodecType>{ core::media::CodecType::MP3, core::media::CodecType::FLAC, core::media::CodecType::AAC }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
link.get().modify()->setType(ImageType::FrontCover);
|
||||
link.get().modify()->setType(core::media::ImageType::FrontCover);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -173,6 +173,30 @@ namespace lms::db::tests
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(track.getId());
|
||||
params.setImageType(core::media::ImageType::FrontCover);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(track.getId());
|
||||
params.setImageType(core::media::ImageType::BackCover);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_sorts)
|
||||
@@ -200,18 +224,18 @@ namespace lms::db::tests
|
||||
track1.get().modify()->setMedium(medium1.get());
|
||||
track1.get().modify()->setTrackNumber(2);
|
||||
|
||||
link1.get().modify()->setType(ImageType::FrontCover);
|
||||
link1.get().modify()->setType(core::media::ImageType::FrontCover);
|
||||
image1.get().modify()->setSize(750);
|
||||
link2.get().modify()->setType(ImageType::Media);
|
||||
link2.get().modify()->setType(core::media::ImageType::Media);
|
||||
image2.get().modify()->setSize(1000);
|
||||
link3.get().modify()->setType(ImageType::Media);
|
||||
link3.get().modify()->setType(core::media::ImageType::Media);
|
||||
image3.get().modify()->setSize(2000);
|
||||
|
||||
track2.get().modify()->setRelease(release.get());
|
||||
track2.get().modify()->setMedium(medium2.get());
|
||||
track2.get().modify()->setTrackNumber(1);
|
||||
|
||||
link4.get().modify()->setType(ImageType::Media);
|
||||
link4.get().modify()->setType(core::media::ImageType::Media);
|
||||
image4.get().modify()->setSize(1500);
|
||||
}
|
||||
|
||||
@@ -220,7 +244,7 @@ namespace lms::db::tests
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release.getId());
|
||||
params.setImageType(ImageType::Media);
|
||||
params.setImageType(core::media::ImageType::Media);
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::SizeDesc);
|
||||
|
||||
std::vector<TrackEmbeddedImageId> visitedIds;
|
||||
@@ -236,7 +260,7 @@ namespace lms::db::tests
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setMedium(medium1.getId());
|
||||
params.setImageType(ImageType::Media);
|
||||
params.setImageType(core::media::ImageType::Media);
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc);
|
||||
|
||||
std::vector<TrackEmbeddedImageId> visitedIds;
|
||||
@@ -251,7 +275,7 @@ namespace lms::db::tests
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release.getId());
|
||||
params.setImageType(ImageType::Media);
|
||||
params.setImageType(core::media::ImageType::Media);
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc);
|
||||
|
||||
std::vector<TrackEmbeddedImageId> visitedIds;
|
||||
@@ -267,7 +291,7 @@ namespace lms::db::tests
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release.getId());
|
||||
params.setImageType(ImageType::BackCover);
|
||||
params.setImageType(core::media::ImageType::BackCover);
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc);
|
||||
|
||||
std::vector<TrackEmbeddedImageId> visitedIds;
|
||||
@@ -383,7 +407,7 @@ namespace lms::db::tests
|
||||
const TrackEmbeddedImageLink::pointer link{ TrackEmbeddedImageLink::find(session, imageLink.getId()) };
|
||||
ASSERT_NE(link, TrackEmbeddedImageLink::pointer{});
|
||||
EXPECT_EQ(link->getIndex(), 0);
|
||||
EXPECT_EQ(link->getType(), ImageType::Unknown);
|
||||
EXPECT_EQ(link->getType(), core::media::ImageType::Unknown);
|
||||
EXPECT_EQ(link->getDescription(), "");
|
||||
EXPECT_EQ(link->getTrack(), track.get());
|
||||
EXPECT_EQ(link->getImage(), image.get());
|
||||
@@ -395,7 +419,7 @@ namespace lms::db::tests
|
||||
TrackEmbeddedImageLink::pointer link{ TrackEmbeddedImageLink::find(session, imageLink.getId()) };
|
||||
ASSERT_NE(link, TrackEmbeddedImage::pointer{});
|
||||
link.modify()->setIndex(2);
|
||||
link.modify()->setType(ImageType::FrontCover);
|
||||
link.modify()->setType(core::media::ImageType::FrontCover);
|
||||
link.modify()->setDescription("MyDesc");
|
||||
}
|
||||
|
||||
@@ -405,7 +429,7 @@ namespace lms::db::tests
|
||||
const TrackEmbeddedImageLink::pointer img{ TrackEmbeddedImageLink::find(session, imageLink.getId()) };
|
||||
ASSERT_NE(img, TrackEmbeddedImage::pointer{});
|
||||
EXPECT_EQ(img->getIndex(), 2);
|
||||
EXPECT_EQ(img->getType(), ImageType::FrontCover);
|
||||
EXPECT_EQ(img->getType(), core::media::ImageType::FrontCover);
|
||||
EXPECT_EQ(img->getDescription(), "MyDesc");
|
||||
}
|
||||
|
||||
@@ -415,7 +439,7 @@ namespace lms::db::tests
|
||||
bool visited{};
|
||||
TrackEmbeddedImageLink::find(session, image->getId(), [&](const TrackEmbeddedImageLink::pointer& link) {
|
||||
EXPECT_EQ(link->getIndex(), 2);
|
||||
EXPECT_EQ(link->getType(), ImageType::FrontCover);
|
||||
EXPECT_EQ(link->getType(), core::media::ImageType::FrontCover);
|
||||
EXPECT_EQ(link->getDescription(), "MyDesc");
|
||||
EXPECT_EQ(link->getTrack(), track.get());
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "core/Service.hpp"
|
||||
#include "core/http/IClient.hpp"
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/Exception.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
@@ -42,92 +42,6 @@ namespace lms::podcast
|
||||
{
|
||||
namespace
|
||||
{
|
||||
db::ContainerType audioContainerToDbContainer(audio::ContainerType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case audio::ContainerType::AIFF:
|
||||
return db::ContainerType::AIFF;
|
||||
case audio::ContainerType::APE:
|
||||
return db::ContainerType::APE;
|
||||
case audio::ContainerType::ASF:
|
||||
return db::ContainerType::ASF;
|
||||
case audio::ContainerType::DSF:
|
||||
return db::ContainerType::DSF;
|
||||
case audio::ContainerType::FLAC:
|
||||
return db::ContainerType::FLAC;
|
||||
case audio::ContainerType::MP4:
|
||||
return db::ContainerType::MP4;
|
||||
case audio::ContainerType::MPC:
|
||||
return db::ContainerType::MPC;
|
||||
case audio::ContainerType::MPEG:
|
||||
return db::ContainerType::MPEG;
|
||||
case audio::ContainerType::Shorten:
|
||||
return db::ContainerType::Shorten;
|
||||
case audio::ContainerType::Ogg:
|
||||
return db::ContainerType::Ogg;
|
||||
case audio::ContainerType::TrueAudio:
|
||||
return db::ContainerType::TrueAudio;
|
||||
case audio::ContainerType::WAV:
|
||||
return db::ContainerType::WAV;
|
||||
case audio::ContainerType::WavPack:
|
||||
return db::ContainerType::WavPack;
|
||||
}
|
||||
|
||||
return db::ContainerType::Unknown;
|
||||
}
|
||||
|
||||
db::CodecType audioCodecToDbCodec(audio::CodecType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case audio::CodecType::AAC:
|
||||
return db::CodecType::AAC;
|
||||
case audio::CodecType::AC3:
|
||||
return db::CodecType::AC3;
|
||||
case audio::CodecType::ALAC:
|
||||
return db::CodecType::ALAC;
|
||||
case audio::CodecType::APE:
|
||||
return db::CodecType::APE;
|
||||
case audio::CodecType::DSD:
|
||||
return db::CodecType::DSD;
|
||||
case audio::CodecType::EAC3:
|
||||
return db::CodecType::EAC3;
|
||||
case audio::CodecType::FLAC:
|
||||
return db::CodecType::FLAC;
|
||||
case audio::CodecType::MP3:
|
||||
return db::CodecType::MP3;
|
||||
case audio::CodecType::MP4ALS:
|
||||
return db::CodecType::MP4ALS;
|
||||
case audio::CodecType::MPC7:
|
||||
return db::CodecType::MPC7;
|
||||
case audio::CodecType::MPC8:
|
||||
return db::CodecType::MPC8;
|
||||
case audio::CodecType::Opus:
|
||||
return db::CodecType::Opus;
|
||||
case audio::CodecType::PCM:
|
||||
return db::CodecType::PCM;
|
||||
case audio::CodecType::Shorten:
|
||||
return db::CodecType::Shorten;
|
||||
case audio::CodecType::TrueAudio:
|
||||
return db::CodecType::TrueAudio;
|
||||
case audio::CodecType::Vorbis:
|
||||
return db::CodecType::Vorbis;
|
||||
case audio::CodecType::WavPack:
|
||||
return db::CodecType::WavPack;
|
||||
case audio::CodecType::WMA1:
|
||||
return db::CodecType::WMA1;
|
||||
case audio::CodecType::WMA2:
|
||||
return db::CodecType::WMA2;
|
||||
case audio::CodecType::WMA9Pro:
|
||||
return db::CodecType::WMA9Pro;
|
||||
case audio::CodecType::WMA9Lossless:
|
||||
return db::CodecType::WMA9Lossless;
|
||||
}
|
||||
|
||||
return db::CodecType::Unknown;
|
||||
}
|
||||
|
||||
void updateEpisode(db::Session& session, db::PodcastEpisodeId episodeId, const std::filesystem::path& relativeFilePath, const audio::AudioProperties& audioProperties)
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
@@ -139,8 +53,8 @@ namespace lms::podcast
|
||||
dbEpisode.modify()->setAudioRelativeFilePath(relativeFilePath);
|
||||
|
||||
dbEpisode.modify()->setDuration(audioProperties.duration);
|
||||
dbEpisode.modify()->setContainer(audioContainerToDbContainer(audioProperties.container));
|
||||
dbEpisode.modify()->setCodec(audioCodecToDbCodec(audioProperties.codec));
|
||||
dbEpisode.modify()->setContainer(audioProperties.container);
|
||||
dbEpisode.modify()->setCodec(audioProperties.codec);
|
||||
dbEpisode.modify()->setBitrate(audioProperties.bitrate);
|
||||
dbEpisode.modify()->setChannelCount(audioProperties.channelCount);
|
||||
dbEpisode.modify()->setSampleRate(audioProperties.sampleRate);
|
||||
|
||||
@@ -28,12 +28,9 @@
|
||||
#include "audio/Exception.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
#include "image/Exception.hpp"
|
||||
#include "image/Image.hpp"
|
||||
|
||||
#include "audio/IImageReader.hpp"
|
||||
#include "database/IDb.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
@@ -47,6 +44,8 @@
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackFeatures.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "image/Exception.hpp"
|
||||
#include "image/Image.hpp"
|
||||
|
||||
#include "services/scanner/ScanErrors.hpp"
|
||||
|
||||
@@ -296,145 +295,6 @@ namespace lms::scanner
|
||||
return dbLyrics;
|
||||
}
|
||||
|
||||
db::ImageType audioImageTypeToDbImageType(audio::Image::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case audio::Image::Type::Unknown:
|
||||
return db::ImageType::Unknown;
|
||||
case audio::Image::Type::Other:
|
||||
return db::ImageType::Other;
|
||||
case audio::Image::Type::FileIcon:
|
||||
return db::ImageType::FileIcon;
|
||||
case audio::Image::Type::OtherFileIcon:
|
||||
return db::ImageType::OtherFileIcon;
|
||||
case audio::Image::Type::FrontCover:
|
||||
return db::ImageType::FrontCover;
|
||||
case audio::Image::Type::BackCover:
|
||||
return db::ImageType::BackCover;
|
||||
case audio::Image::Type::LeafletPage:
|
||||
return db::ImageType::LeafletPage;
|
||||
case audio::Image::Type::Media:
|
||||
return db::ImageType::Media;
|
||||
case audio::Image::Type::LeadArtist:
|
||||
return db::ImageType::LeadArtist;
|
||||
case audio::Image::Type::Artist:
|
||||
return db::ImageType::Artist;
|
||||
case audio::Image::Type::Conductor:
|
||||
return db::ImageType::Conductor;
|
||||
case audio::Image::Type::Band:
|
||||
return db::ImageType::Band;
|
||||
case audio::Image::Type::Composer:
|
||||
return db::ImageType::Composer;
|
||||
case audio::Image::Type::Lyricist:
|
||||
return db::ImageType::Lyricist;
|
||||
case audio::Image::Type::RecordingLocation:
|
||||
return db::ImageType::RecordingLocation;
|
||||
case audio::Image::Type::DuringRecording:
|
||||
return db::ImageType::DuringRecording;
|
||||
case audio::Image::Type::DuringPerformance:
|
||||
return db::ImageType::DuringPerformance;
|
||||
case audio::Image::Type::MovieScreenCapture:
|
||||
return db::ImageType::MovieScreenCapture;
|
||||
case audio::Image::Type::ColouredFish:
|
||||
return db::ImageType::ColouredFish;
|
||||
case audio::Image::Type::Illustration:
|
||||
return db::ImageType::Illustration;
|
||||
case audio::Image::Type::BandLogo:
|
||||
return db::ImageType::BandLogo;
|
||||
case audio::Image::Type::PublisherLogo:
|
||||
return db::ImageType::PublisherLogo;
|
||||
}
|
||||
|
||||
return db::ImageType::Unknown;
|
||||
}
|
||||
|
||||
db::ContainerType audioContainerToDbContainer(audio::ContainerType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case audio::ContainerType::AIFF:
|
||||
return db::ContainerType::AIFF;
|
||||
case audio::ContainerType::APE:
|
||||
return db::ContainerType::APE;
|
||||
case audio::ContainerType::ASF:
|
||||
return db::ContainerType::ASF;
|
||||
case audio::ContainerType::DSF:
|
||||
return db::ContainerType::DSF;
|
||||
case audio::ContainerType::FLAC:
|
||||
return db::ContainerType::FLAC;
|
||||
case audio::ContainerType::MP4:
|
||||
return db::ContainerType::MP4;
|
||||
case audio::ContainerType::MPC:
|
||||
return db::ContainerType::MPC;
|
||||
case audio::ContainerType::MPEG:
|
||||
return db::ContainerType::MPEG;
|
||||
case audio::ContainerType::Shorten:
|
||||
return db::ContainerType::Shorten;
|
||||
case audio::ContainerType::Ogg:
|
||||
return db::ContainerType::Ogg;
|
||||
case audio::ContainerType::TrueAudio:
|
||||
return db::ContainerType::TrueAudio;
|
||||
case audio::ContainerType::WAV:
|
||||
return db::ContainerType::WAV;
|
||||
case audio::ContainerType::WavPack:
|
||||
return db::ContainerType::WavPack;
|
||||
}
|
||||
|
||||
return db::ContainerType::Unknown;
|
||||
}
|
||||
|
||||
db::CodecType audioCodecToDbCodec(audio::CodecType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case audio::CodecType::AAC:
|
||||
return db::CodecType::AAC;
|
||||
case audio::CodecType::AC3:
|
||||
return db::CodecType::AC3;
|
||||
case audio::CodecType::ALAC:
|
||||
return db::CodecType::ALAC;
|
||||
case audio::CodecType::APE:
|
||||
return db::CodecType::APE;
|
||||
case audio::CodecType::DSD:
|
||||
return db::CodecType::DSD;
|
||||
case audio::CodecType::EAC3:
|
||||
return db::CodecType::EAC3;
|
||||
case audio::CodecType::FLAC:
|
||||
return db::CodecType::FLAC;
|
||||
case audio::CodecType::MP3:
|
||||
return db::CodecType::MP3;
|
||||
case audio::CodecType::MP4ALS:
|
||||
return db::CodecType::MP4ALS;
|
||||
case audio::CodecType::MPC7:
|
||||
return db::CodecType::MPC7;
|
||||
case audio::CodecType::MPC8:
|
||||
return db::CodecType::MPC8;
|
||||
case audio::CodecType::Opus:
|
||||
return db::CodecType::Opus;
|
||||
case audio::CodecType::PCM:
|
||||
return db::CodecType::PCM;
|
||||
case audio::CodecType::Shorten:
|
||||
return db::CodecType::Shorten;
|
||||
case audio::CodecType::TrueAudio:
|
||||
return db::CodecType::TrueAudio;
|
||||
case audio::CodecType::Vorbis:
|
||||
return db::CodecType::Vorbis;
|
||||
case audio::CodecType::WavPack:
|
||||
return db::CodecType::WavPack;
|
||||
case audio::CodecType::WMA1:
|
||||
return db::CodecType::WMA1;
|
||||
case audio::CodecType::WMA2:
|
||||
return db::CodecType::WMA2;
|
||||
case audio::CodecType::WMA9Pro:
|
||||
return db::CodecType::WMA9Pro;
|
||||
case audio::CodecType::WMA9Lossless:
|
||||
return db::CodecType::WMA9Lossless;
|
||||
}
|
||||
|
||||
return db::CodecType::Unknown;
|
||||
}
|
||||
|
||||
db::TrackEmbeddedImage::pointer getOrCreateTrackEmbeddedImage(db::Session& session, const ImageInfo& imageInfo)
|
||||
{
|
||||
db::TrackEmbeddedImage::pointer image{ db::TrackEmbeddedImage::find(session, imageInfo.size, db::ImageHashType{ imageInfo.hash }) };
|
||||
@@ -458,7 +318,7 @@ namespace lms::scanner
|
||||
const db::TrackEmbeddedImage::pointer image{ getOrCreateTrackEmbeddedImage(session, imageInfo) };
|
||||
db::TrackEmbeddedImageLink::pointer imageLink{ session.create<db::TrackEmbeddedImageLink>(dbTrack, image) };
|
||||
imageLink.modify()->setIndex(imageInfo.index);
|
||||
imageLink.modify()->setType(audioImageTypeToDbImageType(imageInfo.type));
|
||||
imageLink.modify()->setType(imageInfo.type);
|
||||
imageLink.modify()->setDescription(imageInfo.description);
|
||||
|
||||
return imageLink;
|
||||
@@ -654,7 +514,7 @@ namespace lms::scanner
|
||||
}
|
||||
catch (const audio::IOFileException& e)
|
||||
{
|
||||
addError<IOScanError>(getFilePath(), e.getErrorCode());
|
||||
addError<IOScanError>(e.getPath(), e.getErrorCode());
|
||||
}
|
||||
catch (const audio::Exception& e)
|
||||
{
|
||||
@@ -782,8 +642,8 @@ namespace lms::scanner
|
||||
|
||||
// Audio properties
|
||||
track.modify()->setDuration(_file->audioProperties.duration);
|
||||
track.modify()->setContainer(audioContainerToDbContainer(_file->audioProperties.container));
|
||||
track.modify()->setCodec(audioCodecToDbCodec(_file->audioProperties.codec));
|
||||
track.modify()->setContainer(_file->audioProperties.container);
|
||||
track.modify()->setCodec(_file->audioProperties.codec);
|
||||
track.modify()->setBitrate(_file->audioProperties.bitrate);
|
||||
track.modify()->setChannelCount(_file->audioProperties.channelCount);
|
||||
track.modify()->setSampleRate(_file->audioProperties.sampleRate);
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/IImageReader.hpp"
|
||||
#include "core/media/ImageType.hpp"
|
||||
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "image/Types.hpp"
|
||||
|
||||
#include "scanners/FileScanOperationBase.hpp"
|
||||
@@ -44,7 +45,7 @@ namespace lms::scanner
|
||||
struct ImageInfo
|
||||
{
|
||||
std::size_t index;
|
||||
audio::Image::Type type{ audio::Image::Type::Unknown };
|
||||
core::media::ImageType type{ core::media::ImageType::Unknown };
|
||||
std::uint64_t hash{};
|
||||
std::size_t size{};
|
||||
image::ImageProperties properties;
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace lms::scanner
|
||||
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setMedium(medium->getId());
|
||||
params.setImageType(db::ImageType::Media);
|
||||
params.setImageType(core::media::ImageType::Media);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc);
|
||||
|
||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& foundImage) {
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace lms::scanner
|
||||
{
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release->getId());
|
||||
params.setImageType(db::ImageType::FrontCover);
|
||||
params.setImageType(core::media::ImageType::FrontCover);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc);
|
||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
if (!artwork)
|
||||
@@ -155,7 +155,7 @@ namespace lms::scanner
|
||||
{
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release->getId());
|
||||
params.setImageType(db::ImageType::Media);
|
||||
params.setImageType(core::media::ImageType::Media);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc);
|
||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
if (!artwork)
|
||||
@@ -167,7 +167,7 @@ namespace lms::scanner
|
||||
{
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release->getId());
|
||||
params.setImageType(db::ImageType::Other);
|
||||
params.setImageType(core::media::ImageType::Other);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc);
|
||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
if (!artwork)
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace lms::scanner
|
||||
{
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(track->getId());
|
||||
params.setImageType(db::ImageType::FrontCover);
|
||||
params.setImageType(core::media::ImageType::FrontCover);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc);
|
||||
|
||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
@@ -94,7 +94,7 @@ namespace lms::scanner
|
||||
{
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(track->getId());
|
||||
params.setImageType(db::ImageType::Media);
|
||||
params.setImageType(core::media::ImageType::Media);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc);
|
||||
|
||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
|
||||
@@ -19,11 +19,7 @@
|
||||
|
||||
#include "TranscodeService.hpp"
|
||||
|
||||
#include "audio/ITranscoder.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/UUID.hpp"
|
||||
#include "database/IDb.hpp"
|
||||
#include "database/Session.hpp"
|
||||
|
||||
#include "TranscodeResourceHandler.hpp"
|
||||
|
||||
@@ -61,10 +57,10 @@ namespace lms::transcoding
|
||||
|
||||
if (estimateContentLength)
|
||||
{
|
||||
if (parameters.inputParameters.offset < parameters.inputParameters.duration)
|
||||
estimatedContentLength = doEstimateContentLength(*parameters.outputParameters.bitrate, parameters.inputParameters.duration - parameters.inputParameters.offset);
|
||||
if (parameters.inputParameters.offset < parameters.inputParameters.audioProperties.duration)
|
||||
estimatedContentLength = doEstimateContentLength(*parameters.outputParameters.bitrate, parameters.inputParameters.audioProperties.duration - parameters.inputParameters.offset);
|
||||
else
|
||||
LMS_LOG(TRANSCODING, WARNING, "Offset " << parameters.inputParameters.offset << " is greater than audio file duration " << parameters.inputParameters.duration << ": not estimating content length");
|
||||
LMS_LOG(TRANSCODING, WARNING, "Offset " << parameters.inputParameters.offset << " is greater than audio file duration " << parameters.inputParameters.audioProperties.duration << ": not estimating content length");
|
||||
}
|
||||
|
||||
return std::make_unique<transcoding::ResourceHandler>(parameters, estimatedContentLength);
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "core/media/MimeType.hpp"
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/Exception.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
@@ -37,6 +38,7 @@
|
||||
#include "database/objects/PodcastEpisodeId.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
|
||||
#include "services/artwork/IArtworkService.hpp"
|
||||
@@ -54,84 +56,73 @@ namespace lms::api::subsonic
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::optional<audio::OutputFormat> subsonicStreamFormatToTranscodingOutputFormat(std::string_view format)
|
||||
struct OutputFormat
|
||||
{
|
||||
for (const auto& [str, avFormat] : std::initializer_list<std::pair<std::string_view, audio::OutputFormat>>{
|
||||
{ "mp3", audio::OutputFormat::MP3 },
|
||||
{ "opus", audio::OutputFormat::OGG_OPUS },
|
||||
{ "vorbis", audio::OutputFormat::OGG_VORBIS },
|
||||
})
|
||||
{
|
||||
if (core::stringUtils::stringCaseInsensitiveEqual(str, format))
|
||||
return avFormat;
|
||||
}
|
||||
return std::nullopt;
|
||||
std::string name;
|
||||
core::media::ContainerType container;
|
||||
core::media::CodecType codec;
|
||||
};
|
||||
constexpr std::array outputFormats{
|
||||
OutputFormat{ "mp3", core::media::ContainerType::MPEG, core::media::CodecType::MP3 },
|
||||
OutputFormat{ "opus", core::media::ContainerType::Ogg, core::media::CodecType::Opus },
|
||||
OutputFormat{ "vorbis", core::media::ContainerType::Ogg, core::media::CodecType::Vorbis },
|
||||
};
|
||||
|
||||
std::optional<OutputFormat> getOutputFormatByName(std::string_view format)
|
||||
{
|
||||
std::optional<OutputFormat> res;
|
||||
|
||||
const auto itFormat{ std::find_if(outputFormats.begin(), outputFormats.end(), [&format, &res](const OutputFormat& outputFormat) {
|
||||
if (core::stringUtils::stringCaseInsensitiveEqual(format, outputFormat.name))
|
||||
{
|
||||
res = outputFormat;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}) };
|
||||
if (itFormat != outputFormats.end())
|
||||
res = *itFormat;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
audio::OutputFormat userTranscodeFormatToTranscodingFormat(db::TranscodingOutputFormat format)
|
||||
std::optional<OutputFormat> userTranscodeFormatToOutputFormat(db::TranscodingOutputFormat format)
|
||||
{
|
||||
std::optional<OutputFormat> res;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case db::TranscodingOutputFormat::MP3:
|
||||
return audio::OutputFormat::MP3;
|
||||
res = getOutputFormatByName("mp3");
|
||||
break;
|
||||
case db::TranscodingOutputFormat::OGG_OPUS:
|
||||
return audio::OutputFormat::OGG_OPUS;
|
||||
case db::TranscodingOutputFormat::MATROSKA_OPUS:
|
||||
return audio::OutputFormat::MATROSKA_OPUS;
|
||||
res = getOutputFormatByName("opus");
|
||||
break;
|
||||
case db::TranscodingOutputFormat::OGG_VORBIS:
|
||||
return audio::OutputFormat::OGG_VORBIS;
|
||||
case db::TranscodingOutputFormat::WEBM_VORBIS:
|
||||
return audio::OutputFormat::WEBM_VORBIS;
|
||||
res = getOutputFormatByName("vorbis");
|
||||
break;
|
||||
}
|
||||
|
||||
return audio::OutputFormat::OGG_OPUS;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool isCodecCompatibleWithOutputFormat(audio::CodecType codec, audio::OutputFormat outputFormat)
|
||||
audio::AudioProperties getAudioProperties(const std::filesystem::path& trackPath)
|
||||
{
|
||||
switch (outputFormat)
|
||||
{
|
||||
case audio::OutputFormat::MP3:
|
||||
return codec == audio::CodecType::MP3;
|
||||
|
||||
case audio::OutputFormat::OGG_OPUS:
|
||||
case audio::OutputFormat::MATROSKA_OPUS:
|
||||
return codec == audio::CodecType::Opus;
|
||||
|
||||
case audio::OutputFormat::OGG_VORBIS:
|
||||
case audio::OutputFormat::WEBM_VORBIS:
|
||||
return codec == audio::CodecType::Vorbis;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct StreamParameters
|
||||
{
|
||||
std::filesystem::path filePath;
|
||||
std::string fileMimeType; // set if known
|
||||
std::optional<audio::TranscodeParameters> transcodeParameters;
|
||||
bool estimateContentLength{};
|
||||
};
|
||||
|
||||
bool isOutputFormatCompatible(const std::filesystem::path& trackPath, audio::OutputFormat outputFormat)
|
||||
{
|
||||
// TODO: put this information in db during scan
|
||||
// It is in base only for tracks, not yet for podcasts
|
||||
try
|
||||
{
|
||||
const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
|
||||
|
||||
audio::AudioFileInfoParseOptions parseOptions;
|
||||
parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only coded needed
|
||||
parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Average;
|
||||
parseOptions.readImages = false;
|
||||
parseOptions.readTags = false;
|
||||
const auto audioFile{ parser->parse(trackPath, parseOptions) };
|
||||
|
||||
if (!audioFile->getAudioProperties())
|
||||
const audio::AudioProperties* properties{ audioFile->getAudioProperties() };
|
||||
if (!properties)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
return isCodecCompatibleWithOutputFormat(audioFile->getAudioProperties()->codec, outputFormat);
|
||||
return *properties;
|
||||
}
|
||||
catch (const audio::Exception& e)
|
||||
{
|
||||
@@ -143,9 +134,7 @@ namespace lms::api::subsonic
|
||||
struct AudioFileInfo
|
||||
{
|
||||
std::filesystem::path path;
|
||||
std::chrono::milliseconds duration{};
|
||||
std::size_t bitrate{};
|
||||
std::string mimeType; // set if known
|
||||
audio::AudioProperties audioProperties;
|
||||
};
|
||||
|
||||
AudioFileInfo getAudioFileInfo(db::Session& session, AudioFileId audioFileId)
|
||||
@@ -161,8 +150,20 @@ namespace lms::api::subsonic
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
res.path = track->getAbsoluteFilePath();
|
||||
res.duration = track->getDuration();
|
||||
res.bitrate = track->getBitrate();
|
||||
if (track->getContainer() && track->getCodec())
|
||||
{
|
||||
res.audioProperties.container = *track->getContainer();
|
||||
res.audioProperties.codec = *track->getCodec();
|
||||
res.audioProperties.duration = track->getDuration();
|
||||
res.audioProperties.bitrate = track->getBitrate();
|
||||
res.audioProperties.channelCount = track->getChannelCount();
|
||||
res.audioProperties.sampleRate = track->getSampleRate();
|
||||
res.audioProperties.bitsPerSample = track->getBitsPerSample();
|
||||
}
|
||||
else
|
||||
{
|
||||
res.audioProperties = getAudioProperties(res.path);
|
||||
}
|
||||
}
|
||||
else if (const db::PodcastEpisodeId * episodeId{ std::get_if<db::PodcastEpisodeId>(&audioFileId) })
|
||||
{
|
||||
@@ -173,14 +174,33 @@ namespace lms::api::subsonic
|
||||
std::filesystem::path podcastCachePath{ core::Service<podcast::IPodcastService>::get()->getCachePath() };
|
||||
|
||||
res.path = podcastCachePath / episode->getAudioRelativeFilePath();
|
||||
res.duration = episode->getDuration();
|
||||
res.bitrate = episode->getEnclosureLength() / std::chrono::duration_cast<std::chrono::seconds>(episode->getDuration()).count() * 8;
|
||||
res.mimeType = episode->getEnclosureContentType();
|
||||
if (episode->getContainer() && episode->getCodec())
|
||||
{
|
||||
res.audioProperties.container = *episode->getContainer();
|
||||
res.audioProperties.codec = *episode->getCodec();
|
||||
res.audioProperties.duration = episode->getDuration();
|
||||
res.audioProperties.bitrate = episode->getBitrate();
|
||||
res.audioProperties.channelCount = episode->getChannelCount();
|
||||
res.audioProperties.sampleRate = episode->getSampleRate();
|
||||
res.audioProperties.bitsPerSample = episode->getBitsPerSample();
|
||||
}
|
||||
else
|
||||
{
|
||||
res.audioProperties = getAudioProperties(res.path);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
struct StreamParameters
|
||||
{
|
||||
std::filesystem::path filePath;
|
||||
audio::AudioProperties audioProperties;
|
||||
std::optional<audio::TranscodeParameters> transcodeParameters;
|
||||
bool estimateContentLength{};
|
||||
};
|
||||
|
||||
StreamParameters getStreamParameters(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
@@ -201,32 +221,31 @@ namespace lms::api::subsonic
|
||||
|
||||
StreamParameters parameters;
|
||||
parameters.filePath = audioFileInfo.path;
|
||||
parameters.fileMimeType = audioFileInfo.mimeType;
|
||||
parameters.estimateContentLength = estimateContentLength;
|
||||
|
||||
if (format == "raw") // raw => no transcoding
|
||||
return parameters; // TODO: what if offset is not 0?
|
||||
|
||||
std::optional<audio::OutputFormat> requestedFormat{ subsonicStreamFormatToTranscodingOutputFormat(format) };
|
||||
std::optional<OutputFormat> requestedFormat{ getOutputFormatByName(format) };
|
||||
if (!requestedFormat)
|
||||
{
|
||||
if (context.getUser()->getSubsonicEnableTranscodingByDefault())
|
||||
requestedFormat = userTranscodeFormatToTranscodingFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
|
||||
requestedFormat = userTranscodeFormatToOutputFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
|
||||
}
|
||||
|
||||
if (!requestedFormat && (maxBitRate == 0 || audioFileInfo.bitrate <= maxBitRate))
|
||||
if (!requestedFormat && (maxBitRate == 0 || audioFileInfo.audioProperties.bitrate <= maxBitRate))
|
||||
{
|
||||
LMS_LOG(API_SUBSONIC, DEBUG, "File's bitrate is compatible with parameters => no transcoding");
|
||||
return parameters; // no transcoding needed
|
||||
}
|
||||
|
||||
// scan the file to check if its format is compatible with the actual requested format
|
||||
// Check if the input file is compatible with the actual requested format
|
||||
// same codec => apply max bitrate
|
||||
// otherwise => apply default bitrate (because we can't really compare bitrates between formats) + max bitrate)
|
||||
std::size_t bitrate{};
|
||||
if (requestedFormat && isOutputFormatCompatible(audioFileInfo.path, *requestedFormat))
|
||||
if (requestedFormat && requestedFormat->container == audioFileInfo.audioProperties.container && requestedFormat->codec == audioFileInfo.audioProperties.codec)
|
||||
{
|
||||
if (maxBitRate == 0 || audioFileInfo.bitrate <= maxBitRate)
|
||||
if (maxBitRate == 0 || audioFileInfo.audioProperties.bitrate <= maxBitRate)
|
||||
{
|
||||
LMS_LOG(API_SUBSONIC, DEBUG, "File's bitrate and format are compatible with parameters => no transcoding");
|
||||
return parameters; // no transcoding needed
|
||||
@@ -235,22 +254,24 @@ namespace lms::api::subsonic
|
||||
}
|
||||
|
||||
// Need to transcode here
|
||||
if (!requestedFormat)
|
||||
requestedFormat = userTranscodeFormatToTranscodingFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
|
||||
if (!bitrate)
|
||||
bitrate = context.getUser()->getSubsonicDefaultTranscodingOutputBitrate();
|
||||
if (maxBitRate)
|
||||
bitrate = std::min<std::size_t>(bitrate, maxBitRate);
|
||||
|
||||
audio::TranscodeParameters& transcodeParameters{ parameters.transcodeParameters.emplace() };
|
||||
|
||||
if (!requestedFormat) // no format provided => use user's default
|
||||
requestedFormat = userTranscodeFormatToOutputFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat());
|
||||
if (!bitrate) // no bitrate provided => use user's default
|
||||
bitrate = context.getUser()->getSubsonicDefaultTranscodingOutputBitrate();
|
||||
if (maxBitRate) // but still honor provided maxBitRate, if any
|
||||
bitrate = std::min<std::size_t>(bitrate, maxBitRate);
|
||||
|
||||
transcodeParameters.inputParameters.filePath = audioFileInfo.path;
|
||||
transcodeParameters.inputParameters.duration = audioFileInfo.duration;
|
||||
transcodeParameters.inputParameters.audioProperties = audioFileInfo.audioProperties;
|
||||
transcodeParameters.inputParameters.offset = std::chrono::seconds{ timeOffset };
|
||||
;
|
||||
|
||||
transcodeParameters.outputParameters.bitrate = bitrate;
|
||||
transcodeParameters.outputParameters.format = *requestedFormat;
|
||||
transcodeParameters.outputParameters.format.emplace();
|
||||
transcodeParameters.outputParameters.format->container = requestedFormat->container;
|
||||
transcodeParameters.outputParameters.format->codec = requestedFormat->codec;
|
||||
|
||||
transcodeParameters.outputParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.)
|
||||
|
||||
return parameters;
|
||||
@@ -371,7 +392,7 @@ namespace lms::api::subsonic
|
||||
if (streamParameters.transcodeParameters)
|
||||
resourceHandler = core::Service<transcoding::ITranscodeService>::get()->createTranscodeResourceHandler(*streamParameters.transcodeParameters, streamParameters.estimateContentLength);
|
||||
else
|
||||
resourceHandler = core::createFileResourceHandler(streamParameters.filePath, streamParameters.fileMimeType);
|
||||
resourceHandler = core::createFileResourceHandler(streamParameters.filePath, core::media::getMimeType(streamParameters.audioProperties.container, streamParameters.audioProperties.codec).str());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -51,31 +51,25 @@
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
using namespace db;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string_view formatToSuffix(TranscodingOutputFormat format)
|
||||
std::string_view formatToSuffix(db::TranscodingOutputFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TranscodingOutputFormat::MP3:
|
||||
case db::TranscodingOutputFormat::MP3:
|
||||
return "mp3";
|
||||
case TranscodingOutputFormat::OGG_OPUS:
|
||||
case db::TranscodingOutputFormat::OGG_OPUS:
|
||||
return "opus";
|
||||
case TranscodingOutputFormat::MATROSKA_OPUS:
|
||||
return "mka";
|
||||
case TranscodingOutputFormat::OGG_VORBIS:
|
||||
case db::TranscodingOutputFormat::OGG_VORBIS:
|
||||
return "ogg";
|
||||
case TranscodingOutputFormat::WEBM_VORBIS:
|
||||
return "webm";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Response::Node createSongNode(RequestContext& context, const Track::pointer& track, bool id3)
|
||||
Response::Node createSongNode(RequestContext& context, const db::Track::pointer& track, bool id3)
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateSong");
|
||||
|
||||
@@ -136,7 +130,7 @@ namespace lms::api::subsonic
|
||||
trackResponse.setAttribute("coverArt", idToString(coverArtId));
|
||||
}
|
||||
|
||||
const std::vector<Artist::pointer>& artists{ track->getArtists({ TrackArtistLinkType::Artist }) };
|
||||
const std::vector<db::Artist::pointer>& artists{ track->getArtists({ db::TrackArtistLinkType::Artist }) };
|
||||
if (!artists.empty())
|
||||
{
|
||||
if (!track->getArtistDisplayName().empty())
|
||||
@@ -148,7 +142,7 @@ namespace lms::api::subsonic
|
||||
trackResponse.setAttribute("artistId", idToString(artists.front()->getId()));
|
||||
}
|
||||
|
||||
const Release::pointer release{ track->getRelease() };
|
||||
const db::Release::pointer release{ track->getRelease() };
|
||||
if (release)
|
||||
{
|
||||
trackResponse.setAttribute("album", release->getName());
|
||||
@@ -167,13 +161,13 @@ namespace lms::api::subsonic
|
||||
trackResponse.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
|
||||
|
||||
// Report the first GENRE for this track
|
||||
std::vector<Cluster::pointer> genres;
|
||||
std::vector<db::Cluster::pointer> genres;
|
||||
{
|
||||
Cluster::FindParameters params;
|
||||
db::Cluster::FindParameters params;
|
||||
params.setTrack(track->getId());
|
||||
params.setClusterTypeName("GENRE");
|
||||
|
||||
genres = Cluster::find(context.getDbSession(), params).results;
|
||||
genres = db::Cluster::find(context.getDbSession(), params).results;
|
||||
if (!genres.empty())
|
||||
trackResponse.setAttribute("genre", genres.front()->getName());
|
||||
}
|
||||
@@ -204,13 +198,13 @@ namespace lms::api::subsonic
|
||||
trackResponse.createEmptyArrayChild("artists");
|
||||
trackResponse.createEmptyArrayChild("contributors");
|
||||
|
||||
TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) {
|
||||
db::TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const db::TrackArtistLink::pointer& link, const db::Artist::pointer& artist) {
|
||||
switch (link->getType())
|
||||
{
|
||||
case TrackArtistLinkType::Artist:
|
||||
case db::TrackArtistLinkType::Artist:
|
||||
trackResponse.addArrayChild("artists", createArtistNode(artist));
|
||||
break;
|
||||
case TrackArtistLinkType::ReleaseArtist:
|
||||
case db::TrackArtistLinkType::ReleaseArtist:
|
||||
trackResponse.addArrayChild("albumartists", createArtistNode(artist));
|
||||
break;
|
||||
default:
|
||||
@@ -226,11 +220,11 @@ namespace lms::api::subsonic
|
||||
auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) {
|
||||
trackResponse.createEmptyArrayValue(field);
|
||||
|
||||
Cluster::FindParameters params;
|
||||
db::Cluster::FindParameters params;
|
||||
params.setTrack(track->getId());
|
||||
params.setClusterTypeName(clusterTypeName);
|
||||
|
||||
for (const auto& cluster : Cluster::find(context.getDbSession(), params).results)
|
||||
for (const auto& cluster : db::Cluster::find(context.getDbSession(), params).results)
|
||||
trackResponse.addArrayValue(field, cluster->getName());
|
||||
} };
|
||||
|
||||
|
||||
@@ -101,9 +101,7 @@ namespace lms::ui
|
||||
{
|
||||
case MediaPlayer::Format::MP3:
|
||||
case MediaPlayer::Format::OGG_OPUS:
|
||||
case MediaPlayer::Format::MATROSKA_OPUS:
|
||||
case MediaPlayer::Format::OGG_VORBIS:
|
||||
case MediaPlayer::Format::WEBM_VORBIS:
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
@@ -478,9 +478,7 @@ namespace lms::ui
|
||||
_transcodingOutputFormatModel = std::make_shared<ValueStringModel<db::TranscodingOutputFormat>>();
|
||||
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.mp3"), db::TranscodingOutputFormat::MP3);
|
||||
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.ogg_opus"), db::TranscodingOutputFormat::OGG_OPUS);
|
||||
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.matroska_opus"), db::TranscodingOutputFormat::MATROSKA_OPUS);
|
||||
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.ogg_vorbis"), db::TranscodingOutputFormat::OGG_VORBIS);
|
||||
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.webm_vorbis"), db::TranscodingOutputFormat::WEBM_VORBIS);
|
||||
|
||||
_replayGainModeModel = std::make_shared<ReplayGainModeModel>();
|
||||
_replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.none"), MediaPlayer::Settings::ReplayGain::Mode::None);
|
||||
|
||||
@@ -142,11 +142,11 @@ namespace lms::ui
|
||||
// Codecs
|
||||
{
|
||||
std::string codecStr;
|
||||
for (db::CodecType codec : release->getCodecs())
|
||||
for (core::media::CodecType codec : release->getCodecs())
|
||||
{
|
||||
if (!codecStr.empty())
|
||||
codecStr += " · ";
|
||||
codecStr += db::codecTypeToString(codec).str();
|
||||
codecStr += core::media::codecTypeToString(codec).str();
|
||||
}
|
||||
|
||||
if (!codecStr.empty())
|
||||
|
||||
@@ -127,8 +127,11 @@ namespace lms::ui::TrackListHelpers
|
||||
}
|
||||
}
|
||||
|
||||
trackInfo->setCondition("if-has-codec", true);
|
||||
trackInfo->bindString("codec", db::codecTypeToString(track->getCodec()).c_str(), Wt::TextFormat::Plain);
|
||||
if (const auto codec{ track->getCodec() })
|
||||
{
|
||||
trackInfo->setCondition("if-has-codec", true);
|
||||
trackInfo->bindString("codec", core::media::codecTypeToString(*codec).c_str(), Wt::TextFormat::Plain);
|
||||
}
|
||||
|
||||
trackInfo->bindString("duration", utils::durationToString(track->getDuration()));
|
||||
if (track->getBitrate())
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
#include "core/IResourceHandler.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/Exception.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
@@ -51,14 +56,8 @@ namespace lms::core::stringUtils
|
||||
switch (static_cast<db::TranscodingOutputFormat>(*encodedFormat))
|
||||
{
|
||||
case db::TranscodingOutputFormat::MP3:
|
||||
[[fallthrough]];
|
||||
case db::TranscodingOutputFormat::OGG_OPUS:
|
||||
[[fallthrough]];
|
||||
case db::TranscodingOutputFormat::MATROSKA_OPUS:
|
||||
[[fallthrough]];
|
||||
case db::TranscodingOutputFormat::OGG_VORBIS:
|
||||
[[fallthrough]];
|
||||
case db::TranscodingOutputFormat::WEBM_VORBIS:
|
||||
return format;
|
||||
}
|
||||
|
||||
@@ -72,20 +71,64 @@ namespace lms::ui
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::optional<audio::OutputFormat> AudioFormatToTranscodingFormat(db::TranscodingOutputFormat format)
|
||||
std::optional<audio::AudioProperties> getAudioProperties(const std::filesystem::path& trackPath)
|
||||
{
|
||||
std::optional<audio::AudioProperties> res;
|
||||
|
||||
try
|
||||
{
|
||||
const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
|
||||
|
||||
audio::AudioFileInfoParseOptions parseOptions;
|
||||
parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Average;
|
||||
parseOptions.readImages = false;
|
||||
parseOptions.readTags = false;
|
||||
const auto audioFile{ parser->parse(trackPath, parseOptions) };
|
||||
|
||||
if (const audio::AudioProperties * properties{ audioFile->getAudioProperties() })
|
||||
res = *properties;
|
||||
}
|
||||
catch (const audio::Exception& e)
|
||||
{
|
||||
LMS_LOG(UI, DEBUG, "Cannot parse audio properties in " << trackPath << ": " << e.what());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<audio::AudioProperties> getAudioProperties(const db::Track::pointer& track)
|
||||
{
|
||||
std::optional<audio::AudioProperties> res;
|
||||
|
||||
if (track->getContainer() && track->getCodec())
|
||||
{
|
||||
res.emplace();
|
||||
res->container = *track->getContainer();
|
||||
res->codec = *track->getCodec();
|
||||
res->duration = track->getDuration();
|
||||
res->bitrate = track->getBitrate();
|
||||
res->channelCount = track->getChannelCount();
|
||||
res->sampleRate = track->getSampleRate();
|
||||
res->bitsPerSample = track->getBitsPerSample();
|
||||
}
|
||||
else
|
||||
{
|
||||
res = getAudioProperties(track->getAbsoluteFilePath());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<audio::TranscodeOutputFormat> AudioFormatToTranscodingFormat(db::TranscodingOutputFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case db::TranscodingOutputFormat::MP3:
|
||||
return audio::OutputFormat::MP3;
|
||||
return audio::TranscodeOutputFormat{ core::media::ContainerType::MPEG, core::media::CodecType::MP3 };
|
||||
case db::TranscodingOutputFormat::OGG_OPUS:
|
||||
return audio::OutputFormat::OGG_OPUS;
|
||||
case db::TranscodingOutputFormat::MATROSKA_OPUS:
|
||||
return audio::OutputFormat::MATROSKA_OPUS;
|
||||
return audio::TranscodeOutputFormat{ core::media::ContainerType::Ogg, core::media::CodecType::Opus };
|
||||
case db::TranscodingOutputFormat::OGG_VORBIS:
|
||||
return audio::OutputFormat::OGG_VORBIS;
|
||||
case db::TranscodingOutputFormat::WEBM_VORBIS:
|
||||
return audio::OutputFormat::WEBM_VORBIS;
|
||||
return audio::TranscodeOutputFormat{ core::media::ContainerType::Ogg, core::media::CodecType::Vorbis };
|
||||
}
|
||||
|
||||
TRANSCODE_LOG(ERROR, "Cannot convert from db audio format to transcoding format");
|
||||
@@ -128,8 +171,8 @@ namespace lms::ui
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::optional<audio::OutputFormat> avFormat{ AudioFormatToTranscodingFormat(*format) };
|
||||
if (!avFormat)
|
||||
const std::optional<audio::TranscodeOutputFormat> outputFormat{ AudioFormatToTranscodingFormat(*format) };
|
||||
if (!outputFormat)
|
||||
return std::nullopt;
|
||||
|
||||
// optional parameter
|
||||
@@ -145,12 +188,16 @@ namespace lms::ui
|
||||
return std::nullopt;
|
||||
|
||||
parameters.inputParameters.filePath = track->getAbsoluteFilePath();
|
||||
parameters.inputParameters.duration = track->getDuration();
|
||||
const auto audioProperties{ getAudioProperties(track) };
|
||||
if (!audioProperties)
|
||||
return std::nullopt;
|
||||
|
||||
parameters.inputParameters.audioProperties = *audioProperties;
|
||||
}
|
||||
|
||||
parameters.inputParameters.offset = std::chrono::seconds{ offset };
|
||||
parameters.outputParameters.stripMetadata = true;
|
||||
parameters.outputParameters.format = *avFormat;
|
||||
parameters.outputParameters.format = *outputFormat;
|
||||
parameters.outputParameters.bitrate = *bitrate;
|
||||
|
||||
return parameters;
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "core/media/MimeType.hpp"
|
||||
|
||||
#include "audio/AudioTypes.hpp"
|
||||
#include "audio/AudioProperties.hpp"
|
||||
#include "audio/Exception.hpp"
|
||||
#include "audio/IAudioFileInfo.hpp"
|
||||
#include "audio/IAudioFileInfoParser.hpp"
|
||||
@@ -39,14 +40,15 @@ namespace lms::audio
|
||||
{
|
||||
std::ostream& operator<<(std::ostream& os, const AudioProperties& audioProperties)
|
||||
{
|
||||
os << "\tDuration: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast<std::chrono::duration<float>>(audioProperties.duration) << std::endl;
|
||||
os << "\tContainer: " << containerTypeToString(audioProperties.container) << std::endl;
|
||||
os << "\tCodec: " << codecTypeToString(audioProperties.codec) << std::endl;
|
||||
os << "\tBitrate: " << audioProperties.bitrate << " bps" << std::endl;
|
||||
os << "\tDuration: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast<std::chrono::duration<float>>(audioProperties.duration) << '\n';
|
||||
os << "\tContainer: " << core::media::containerTypeToString(audioProperties.container) << '\n';
|
||||
os << "\tCodec: " << core::media::codecTypeToString(audioProperties.codec) << '\n';
|
||||
os << "\tMimeType: " << core::media::getMimeType(audioProperties.container, audioProperties.codec) << '\n';
|
||||
os << "\tBitrate: " << audioProperties.bitrate << " bps" << '\n';
|
||||
if (audioProperties.bitsPerSample)
|
||||
os << "\tBitsPerSample: " << *audioProperties.bitsPerSample << std::endl;
|
||||
os << "\tChannelCount: " << audioProperties.channelCount << std::endl;
|
||||
os << "\tSampleRate: " << audioProperties.sampleRate << std::endl;
|
||||
os << "\tBitsPerSample: " << *audioProperties.bitsPerSample << '\n';
|
||||
os << "\tChannelCount: " << audioProperties.channelCount << '\n';
|
||||
os << "\tSampleRate: " << audioProperties.sampleRate << '\n';
|
||||
|
||||
return os;
|
||||
}
|
||||
@@ -115,7 +117,7 @@ namespace lms::audio
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Image& image)
|
||||
{
|
||||
os << "\ttype = " << imageTypeToString(image.type) << std::endl;
|
||||
os << "\ttype = " << core::media::imageTypeToString(image.type) << std::endl;
|
||||
if (!image.description.empty())
|
||||
os << "\tdesc: " << image.description << std::endl;
|
||||
os << "\tmimeType: " << image.mimeType << std::endl;
|
||||
|
||||
Reference in New Issue
Block a user