diff --git a/src/libs/audio/CMakeLists.txt b/src/libs/audio/CMakeLists.txt
index 48f071bb..bd8d1d4f 100644
--- a/src/libs/audio/CMakeLists.txt
+++ b/src/libs/audio/CMakeLists.txt
@@ -4,17 +4,19 @@ pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib)
add_library(lmsaudio STATIC
impl/ffmpeg/AudioFile.cpp
impl/ffmpeg/AudioFileInfo.cpp
+ impl/ffmpeg/AudioFileInfoParser.cpp
impl/ffmpeg/ImageReader.cpp
impl/ffmpeg/TagReader.cpp
impl/ffmpeg/Transcoder.cpp
impl/ffmpeg/Utils.cpp
impl/taglib/AudioFileInfo.cpp
+ impl/taglib/AudioFileInfoParser.cpp
impl/taglib/ImageReader.cpp
impl/taglib/TagReader.cpp
impl/taglib/Utils.cpp
impl/AudioTypes.cpp
impl/ImageReader.cpp
- impl/ParseAudioFileInfo.cpp
+ impl/AudioFileInfoParser.cpp
impl/TagReader.cpp
)
diff --git a/src/libs/audio/impl/AudioFileInfoParser.cpp b/src/libs/audio/impl/AudioFileInfoParser.cpp
new file mode 100644
index 00000000..069399d7
--- /dev/null
+++ b/src/libs/audio/impl/AudioFileInfoParser.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include
+
+#include "audio/IAudioFileInfoParser.hpp"
+
+#include "ffmpeg/AudioFileInfoParser.hpp"
+#include "taglib/AudioFileInfoParser.hpp"
+
+namespace lms::audio
+{
+ std::unique_ptr createAudioFileInfoParser(AudioFileInfoParserBackend backend)
+ {
+ switch (backend)
+ {
+ case AudioFileInfoParserBackend::TagLib:
+ return std::make_unique();
+ case AudioFileInfoParserBackend::FFmpeg:
+ return std::make_unique();
+ }
+
+ return nullptr;
+ }
+} // namespace lms::audio
\ No newline at end of file
diff --git a/src/libs/audio/impl/AudioTypes.cpp b/src/libs/audio/impl/AudioTypes.cpp
index d0a5930b..ad360bcb 100644
--- a/src/libs/audio/impl/AudioTypes.cpp
+++ b/src/libs/audio/impl/AudioTypes.cpp
@@ -62,12 +62,16 @@ namespace lms::audio
{
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 "E-AC3";
case CodecType::FLAC:
return "FLAC";
case CodecType::MP3:
diff --git a/src/libs/audio/impl/ParseAudioFileInfo.cpp b/src/libs/audio/impl/ParseAudioFileInfo.cpp
deleted file mode 100644
index 321d46a7..00000000
--- a/src/libs/audio/impl/ParseAudioFileInfo.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2015 Emeric Poupon
- *
- * This file is part of LMS.
- *
- * LMS is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LMS is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LMS. If not, see .
- */
-
-#include
-
-#include "audio/IAudioFileInfo.hpp"
-
-#include "ffmpeg/AudioFileInfo.hpp"
-#include "ffmpeg/Utils.hpp"
-#include "taglib/AudioFileInfo.hpp"
-#include "taglib/Utils.hpp"
-
-namespace lms::audio
-{
- std::unique_ptr parseAudioFile(const std::filesystem::path& p, const ParserOptions& parserOptions)
- {
- switch (parserOptions.parser)
- {
- case ParserOptions::Parser::TagLib:
- return std::make_unique(p, parserOptions.readStyle, parserOptions.enableExtraDebugLogs);
- case ParserOptions::Parser::FFmpeg:
- return std::make_unique(p, parserOptions.enableExtraDebugLogs);
- }
-
- return {};
- }
-
- std::span getSupportedExtensions(ParserOptions::Parser parser)
- {
- switch (parser)
- {
- case ParserOptions::Parser::TagLib:
- return taglib::utils::getSupportedExtensions();
- case ParserOptions::Parser::FFmpeg:
- return ffmpeg::utils::getSupportedExtensions();
- }
-
- return {};
- }
-
-} // namespace lms::audio
\ No newline at end of file
diff --git a/src/libs/audio/impl/ffmpeg/AudioFile.cpp b/src/libs/audio/impl/ffmpeg/AudioFile.cpp
index ad1861a7..7f679b99 100644
--- a/src/libs/audio/impl/ffmpeg/AudioFile.cpp
+++ b/src/libs/audio/impl/ffmpeg/AudioFile.cpp
@@ -20,21 +20,22 @@
#include "AudioFile.hpp"
#include
+#include
#include
extern "C"
{
-#define __STDC_CONSTANT_MACROS
#include
#include
#include
+#include
}
#include "core/ILogger.hpp"
#include "core/String.hpp"
#include "audio/AudioTypes.hpp"
-#include "audio/IAudioFileInfo.hpp"
+#include "audio/Exception.hpp"
namespace lms::audio::ffmpeg
{
@@ -50,25 +51,23 @@ namespace lms::audio::ffmpeg
return "Unknown error";
}
- class AudioFileException : public AudioFileParsingException
+ class AvException : public Exception
{
public:
- AudioFileException(int avError)
- : AudioFileParsingException{ averror_to_string(avError) }
+ AvException(int avError)
+ : Exception{ averror_to_string(avError) }
{
}
};
- void getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& res)
+ void extractMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& res)
{
if (!dictionnary)
return;
- AVDictionaryEntry* tag = NULL;
- while ((tag = ::av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
- {
+ const AVDictionaryEntry* tag{ NULL };
+ while ((tag = av_dict_iterate(dictionnary, tag)))
res[core::stringUtils::stringToUpper(tag->key)] = tag->value;
- }
}
std::optional avdemuxerToContainerType(std::string_view name)
@@ -107,58 +106,112 @@ namespace lms::audio::ffmpeg
{
switch (codec)
{
- case AV_CODEC_ID_MP3:
- return CodecType::MP3;
case AV_CODEC_ID_AAC:
return CodecType::AAC;
- case AV_CODEC_ID_VORBIS:
- return CodecType::Vorbis;
- case AV_CODEC_ID_WMAV1:
- return CodecType::WMA1;
- case AV_CODEC_ID_WMAV2:
- return CodecType::WMA2;
- case AV_CODEC_ID_WMAPRO:
- return CodecType::WMA9Pro;
- case AV_CODEC_ID_WMALOSSLESS:
- return CodecType::WMA9Lossless;
- case AV_CODEC_ID_FLAC:
- return CodecType::FLAC;
+ case AV_CODEC_ID_AC3:
+ return CodecType::AC3;
case AV_CODEC_ID_ALAC:
return CodecType::ALAC;
- case AV_CODEC_ID_WAVPACK:
- return CodecType::WavPack;
- case AV_CODEC_ID_MUSEPACK7:
- return CodecType::MPC7;
- case AV_CODEC_ID_MUSEPACK8:
- return CodecType::MPC8;
case AV_CODEC_ID_APE:
return CodecType::APE;
- case AV_CODEC_ID_MP4ALS:
- return CodecType::MP4ALS;
- case AV_CODEC_ID_OPUS:
- return CodecType::Opus;
- case AV_CODEC_ID_SHORTEN:
- return CodecType::Shorten;
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;
+ case AV_CODEC_ID_EAC3:
+ return CodecType::EAC3;
+ case AV_CODEC_ID_FLAC:
+ return CodecType::FLAC;
+ case AV_CODEC_ID_MP3:
+ return CodecType::MP3;
+ case AV_CODEC_ID_MP4ALS:
+ return CodecType::MP4ALS;
+ case AV_CODEC_ID_MUSEPACK7:
+ return CodecType::MPC7;
+ case AV_CODEC_ID_MUSEPACK8:
+ return CodecType::MPC8;
+ case AV_CODEC_ID_OPUS:
+ return CodecType::Opus;
+ case AV_CODEC_ID_SHORTEN:
+ return CodecType::Shorten;
+ case AV_CODEC_ID_VORBIS:
+ return CodecType::Vorbis;
+ case AV_CODEC_ID_WAVPACK:
+ return CodecType::WavPack;
+ case AV_CODEC_ID_WMALOSSLESS:
+ return CodecType::WMA9Lossless;
+ case AV_CODEC_ID_WMAPRO:
+ return CodecType::WMA9Pro;
+ case AV_CODEC_ID_WMAV1:
+ return CodecType::WMA1;
+ case AV_CODEC_ID_WMAV2:
+ return CodecType::WMA2;
default:
return std::nullopt;
}
}
+
+ core::LiteralString avLogLevelToStr(int level)
+ {
+ switch (level)
+ {
+ case AV_LOG_TRACE:
+ return "trace";
+ case AV_LOG_DEBUG:
+ return "debug";
+ case AV_LOG_VERBOSE:
+ return "verbose";
+ case AV_LOG_INFO:
+ return "info";
+ case AV_LOG_WARNING:
+ return "warning";
+ case AV_LOG_ERROR:
+ return "error";
+ case AV_LOG_FATAL:
+ return "fatal";
+ case AV_LOG_PANIC:
+ return "panic";
+ default:
+ return "unknown";
+ }
+ }
+
+ void avLogCallback(void*, int level, const char* fmt, va_list vl)
+ {
+ if (!core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG))
+ return;
+
+ if (level > AV_LOG_WARNING)
+ return;
+
+ std::array buffer{ 0 };
+ std::vsnprintf(buffer.data(), buffer.size(), fmt, vl);
+
+ LMS_LOG(AUDIO, DEBUG, "ffmpeg [" << avLogLevelToStr(level) << "] " << buffer.data());
+ }
+
+ class AvInitializer
+ {
+ public:
+ AvInitializer()
+ {
+ ::av_log_set_callback(avLogCallback);
+ }
+ };
} // namespace
AudioFile::AudioFile(const std::filesystem::path& p)
: _p{ p }
{
+ static AvInitializer init;
+
int error{ avformat_open_input(&_context, _p.c_str(), nullptr, nullptr) };
if (error < 0)
{
LMS_LOG(AUDIO, ERROR, "Cannot open " << _p << ": " << averror_to_string(error));
- throw AudioFileException{ error };
+ throw AvException{ error };
}
error = avformat_find_stream_info(_context, nullptr);
@@ -166,7 +219,7 @@ namespace lms::audio::ffmpeg
{
LMS_LOG(AUDIO, ERROR, "Cannot find stream information on " << _p << ": " << averror_to_string(error));
avformat_close_input(&_context);
- throw AudioFileException{ error };
+ throw AvException{ error };
}
}
@@ -193,11 +246,11 @@ namespace lms::audio::ffmpeg
return info;
}
- AudioFile::MetadataMap AudioFile::getMetaData() const
+ AudioFile::MetadataMap AudioFile::extractMetaData() const
{
MetadataMap res;
- getMetaDataFromDictionnary(_context->metadata, res);
+ extractMetaDataFromDictionnary(_context->metadata, res);
// HACK for OGG files
// If we did not find tags, search metadata in streams
@@ -205,7 +258,7 @@ namespace lms::audio::ffmpeg
{
for (std::size_t i{}; i < _context->nb_streams; ++i)
{
- getMetaDataFromDictionnary(_context->streams[i]->metadata, res);
+ extractMetaDataFromDictionnary(_context->streams[i]->metadata, res);
if (!res.empty())
break;
@@ -266,7 +319,7 @@ namespace lms::audio::ffmpeg
return false;
}
- void AudioFile::visitAttachedPictures(std::function func) const
+ void AudioFile::visitAttachedPictures(std::function func) const
{
static const std::unordered_map codecMimeMap{
{ AV_CODEC_ID_BMP, "image/bmp" },
@@ -286,14 +339,14 @@ namespace lms::audio::ffmpeg
if (avstream->codecpar == nullptr)
{
- LMS_LOG(AUDIO, ERROR, "Skipping stream " << i << " since no codecpar is set");
+ LMS_LOG(AUDIO, WARNING, "Skipping stream " << i << " since no codecpar is set");
continue;
}
MetadataMap metadata;
- getMetaDataFromDictionnary(avstream->metadata, metadata);
+ extractMetaDataFromDictionnary(avstream->metadata, metadata);
- Picture picture;
+ PictureView picture;
auto itMime = codecMimeMap.find(avstream->codecpar->codec_id);
if (itMime != codecMimeMap.end())
@@ -303,10 +356,10 @@ namespace lms::audio::ffmpeg
else
{
picture.mimeType = "application/octet-stream";
- LMS_LOG(AUDIO, ERROR, "CODEC ID " << avstream->codecpar->codec_id << " not handled in mime type conversion");
+ LMS_LOG(AUDIO, WARNING, "AVCodecID" << avstream->codecpar->codec_id << " (" << ::avcodec_get_name(avstream->codecpar->codec_id) << ") not handled in mime type conversion");
}
- const AVPacket& pkt{ avstream->attached_pic };
+ const ::AVPacket& pkt{ avstream->attached_pic };
picture.data = std::span{ reinterpret_cast(pkt.data), static_cast(pkt.size) };
func(picture, metadata);
@@ -325,7 +378,7 @@ namespace lms::audio::ffmpeg
if (!avstream->codecpar)
{
- LMS_LOG(AUDIO, ERROR, "Skipping stream " << streamIndex << " since no codecpar is set");
+ LMS_LOG(AUDIO, WARNING, "Skipping stream " << streamIndex << " since no codecpar is set");
return res;
}
diff --git a/src/libs/audio/impl/ffmpeg/AudioFile.hpp b/src/libs/audio/impl/ffmpeg/AudioFile.hpp
index 2af7fb1b..64c4303d 100644
--- a/src/libs/audio/impl/ffmpeg/AudioFile.hpp
+++ b/src/libs/audio/impl/ffmpeg/AudioFile.hpp
@@ -36,7 +36,7 @@ extern "C"
namespace lms::audio::ffmpeg
{
- struct Picture
+ struct PictureView
{
std::string mimeType;
std::span data; // valid as long as IAudioFile exists
@@ -75,12 +75,12 @@ namespace lms::audio::ffmpeg
const std::filesystem::path& getPath() const;
ContainerInfo getContainerInfo() const;
- MetadataMap getMetaData() const;
+ MetadataMap extractMetaData() const;
std::vector getStreamInfo() const;
std::optional getBestStreamInfo() const;
std::optional getBestStreamIndex() const;
bool hasAttachedPictures() const;
- void visitAttachedPictures(std::function func) const;
+ void visitAttachedPictures(std::function func) const;
private:
std::optional getStreamInfo(std::size_t streamIndex) const;
diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp b/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp
index fe0793f9..f1ebbcc6 100644
--- a/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp
+++ b/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp
@@ -19,6 +19,8 @@
#include "AudioFileInfo.hpp"
+#include "core/ILogger.hpp"
+
#include "audio/AudioTypes.hpp"
#include "audio/IAudioFileInfo.hpp"
@@ -30,65 +32,88 @@ namespace lms::audio::ffmpeg
{
namespace
{
- AudioProperties computeAudioProperties(const AudioFile& audioFile)
+ std::optional computeAudioProperties(const AudioFile& audioFile)
{
- AudioProperties audioProperties;
+ std::optional audioProperties;
const auto containerInfo{ audioFile.getContainerInfo() };
const auto bestStreamInfo{ audioFile.getBestStreamInfo() };
if (!bestStreamInfo)
- throw AudioFileParsingException{ "Cannot find best audio stream" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot find best audio stream in " << audioFile.getPath());
+ return audioProperties;
+ }
if (!containerInfo.container)
- throw AudioFileParsingException{ "Unhandled container type '" + containerInfo.containerName + "'" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Unhandled container '" << containerInfo.containerName << "' in " << audioFile.getPath());
+ return audioProperties;
+ }
if (!bestStreamInfo->codec)
- throw AudioFileParsingException{ "Unhandled codec type '" + bestStreamInfo->codecName + "'" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Unhandled codec '" << bestStreamInfo->codecName << "' in " << audioFile.getPath());
+ return audioProperties;
+ }
if (!bestStreamInfo->bitrate || *bestStreamInfo->bitrate == 0)
- throw AudioFileParsingException{ "Cannot determine bitrate" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine bitrate in " << audioFile.getPath());
+ return audioProperties;
+ }
if (!bestStreamInfo->channelCount || *bestStreamInfo->channelCount == 0)
- throw AudioFileParsingException{ "Cannot determine channel count" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine channel count in " << audioFile.getPath());
+ return audioProperties;
+ }
if (!bestStreamInfo->sampleRate || *bestStreamInfo->sampleRate == 0)
- throw AudioFileParsingException{ "Cannot determine sample rate" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine sample rate in " << audioFile.getPath());
+ return audioProperties;
+ }
- audioProperties.container = *containerInfo.container;
- audioProperties.duration = containerInfo.duration;
- audioProperties.codec = *bestStreamInfo->codec;
- audioProperties.bitrate = *bestStreamInfo->bitrate;
- audioProperties.channelCount = *bestStreamInfo->channelCount;
- audioProperties.sampleRate = *bestStreamInfo->sampleRate;
- audioProperties.bitsPerSample = bestStreamInfo->bitsPerSample;
+ audioProperties.emplace();
+
+ audioProperties->container = *containerInfo.container;
+ audioProperties->duration = containerInfo.duration;
+ audioProperties->codec = *bestStreamInfo->codec;
+ audioProperties->bitrate = *bestStreamInfo->bitrate;
+ audioProperties->channelCount = *bestStreamInfo->channelCount;
+ audioProperties->sampleRate = *bestStreamInfo->sampleRate;
+ audioProperties->bitsPerSample = bestStreamInfo->bitsPerSample;
return audioProperties;
}
} // namespace
- AudioFileInfo::AudioFileInfo(const std::filesystem::path& filePath, bool enableExtraDebugLogs)
+ AudioFileInfo::AudioFileInfo(const std::filesystem::path& filePath, const AudioFileInfoParseOptions& parseOptions)
: _audioFile{ std::make_unique(filePath) }
- , _audioProperties{ std::make_unique(computeAudioProperties(*_audioFile)) }
- , _tagReader{ std::make_unique(*_audioFile, enableExtraDebugLogs) }
- , _imageReader{ std::make_unique(*_audioFile) }
+ , _audioProperties{ computeAudioProperties(*_audioFile) }
{
+ if (parseOptions.readTags)
+ _tagReader = std::make_unique(*_audioFile, parseOptions.enableExtraDebugLogs);
+
+ if (parseOptions.readImages)
+ _imageReader = std::make_unique(*_audioFile);
}
AudioFileInfo::~AudioFileInfo() = default;
- const AudioProperties& AudioFileInfo::getAudioProperties() const
+ const AudioProperties* AudioFileInfo::getAudioProperties() const
{
- return *_audioProperties;
+ return _audioProperties.has_value() ? &_audioProperties.value() : nullptr;
}
- const IImageReader& AudioFileInfo::getImageReader() const
+ const IImageReader* AudioFileInfo::getImageReader() const
{
- return *_imageReader;
+ return _imageReader.get();
}
- const ITagReader& AudioFileInfo::getTagReader() const
+ const ITagReader* AudioFileInfo::getTagReader() const
{
- return *_tagReader;
+ return _tagReader.get();
}
} // namespace lms::audio::ffmpeg
diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp b/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp
index e8c806a7..8d89a766 100644
--- a/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp
+++ b/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp
@@ -20,9 +20,11 @@
#pragma once
#include
+#include
#include "audio/AudioTypes.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
namespace lms::audio::ffmpeg
{
@@ -33,18 +35,19 @@ namespace lms::audio::ffmpeg
class AudioFileInfo final : public IAudioFileInfo
{
public:
- AudioFileInfo(const std::filesystem::path& filePath, bool enableExtraDebugLogs);
- ~AudioFileInfo();
+ AudioFileInfo(const std::filesystem::path& filePath, const AudioFileInfoParseOptions& parseOptions);
+ ~AudioFileInfo() override;
+
AudioFileInfo(const AudioFileInfo&) = delete;
AudioFileInfo& operator=(const AudioFileInfo&) = delete;
private:
- const AudioProperties& getAudioProperties() const override;
- const IImageReader& getImageReader() const override;
- const ITagReader& getTagReader() const override;
+ const AudioProperties* getAudioProperties() const override;
+ const IImageReader* getImageReader() const override;
+ const ITagReader* getTagReader() const override;
std::unique_ptr _audioFile;
- std::unique_ptr _audioProperties;
+ const std::optional _audioProperties;
std::unique_ptr _tagReader;
std::unique_ptr _imageReader;
};
diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfoParser.cpp b/src/libs/audio/impl/ffmpeg/AudioFileInfoParser.cpp
new file mode 100644
index 00000000..9af6d1f8
--- /dev/null
+++ b/src/libs/audio/impl/ffmpeg/AudioFileInfoParser.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include "AudioFileInfoParser.hpp"
+
+#include
+
+#include "AudioFileInfo.hpp"
+#include "Utils.hpp"
+
+namespace lms::audio::ffmpeg
+{
+ std::unique_ptr AudioFileInfoParser::parse(const std::filesystem::path& p, const AudioFileInfoParseOptions& parseOptions) const
+ {
+ return std::make_unique(p, parseOptions);
+ }
+
+ std::span AudioFileInfoParser::getSupportedExtensions() const
+ {
+ return utils::getSupportedExtensions();
+ }
+} // namespace lms::audio::ffmpeg
\ No newline at end of file
diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfoParser.hpp b/src/libs/audio/impl/ffmpeg/AudioFileInfoParser.hpp
new file mode 100644
index 00000000..0c3a1e98
--- /dev/null
+++ b/src/libs/audio/impl/ffmpeg/AudioFileInfoParser.hpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#pragma once
+
+#include "audio/IAudioFileInfoParser.hpp"
+
+namespace lms::audio::ffmpeg
+{
+ class AudioFileInfoParser : public IAudioFileInfoParser
+ {
+ private:
+ std::unique_ptr parse(const std::filesystem::path& p, const AudioFileInfoParseOptions& parseOptions = AudioFileInfoParseOptions{}) const override;
+ std::span getSupportedExtensions() const override;
+ };
+} // namespace lms::audio::ffmpeg
\ No newline at end of file
diff --git a/src/libs/audio/impl/ffmpeg/ImageReader.cpp b/src/libs/audio/impl/ffmpeg/ImageReader.cpp
index ad935d1e..8014ea93 100644
--- a/src/libs/audio/impl/ffmpeg/ImageReader.cpp
+++ b/src/libs/audio/impl/ffmpeg/ImageReader.cpp
@@ -40,7 +40,7 @@ namespace lms::audio::ffmpeg
return std::any_of(std::cbegin(metadata), std::cend(metadata), [&](const auto& keyValue) { return core::stringUtils::stringCaseInsensitiveContains(keyValue.second, keyword); });
} };
- _audioFile.visitAttachedPictures([&](const Picture& picture, const AudioFile::MetadataMap& metaData) {
+ _audioFile.visitAttachedPictures([&](const PictureView& picture, const AudioFile::MetadataMap& metaData) {
Image image;
image.data = picture.data;
image.mimeType = picture.mimeType;
@@ -52,5 +52,4 @@ namespace lms::audio::ffmpeg
visitor(image);
});
}
-
} // namespace lms::audio::ffmpeg
diff --git a/src/libs/audio/impl/ffmpeg/TagReader.cpp b/src/libs/audio/impl/ffmpeg/TagReader.cpp
index fa5d9e4f..8c3eccda 100644
--- a/src/libs/audio/impl/ffmpeg/TagReader.cpp
+++ b/src/libs/audio/impl/ffmpeg/TagReader.cpp
@@ -145,7 +145,7 @@ namespace lms::audio::ffmpeg
TagReader::TagReader(const AudioFile& audioFile, bool enableExtraDebugLogs)
: _audioFile{ audioFile }
- , _metaDataMap{ audioFile.getMetaData() }
+ , _metaDataMap{ audioFile.extractMetaData() }
{
if (enableExtraDebugLogs && core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG))
{
diff --git a/src/libs/audio/impl/taglib/AudioFileInfo.cpp b/src/libs/audio/impl/taglib/AudioFileInfo.cpp
index 6cc81dea..02b41ed5 100644
--- a/src/libs/audio/impl/taglib/AudioFileInfo.cpp
+++ b/src/libs/audio/impl/taglib/AudioFileInfo.cpp
@@ -43,8 +43,11 @@
#include
#endif
+#include "core/ILogger.hpp"
+
#include "audio/AudioTypes.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "ImageReader.hpp"
#include "TagReader.hpp"
@@ -54,31 +57,48 @@ namespace lms::audio::taglib
{
namespace
{
- AudioProperties computeAudioProperties(const ::TagLib::File& file)
+ std::optional computeAudioProperties(const ::TagLib::File& file, const std::filesystem::path& filePath)
{
assert(file.audioProperties());
AudioProperties audioProperties;
{
- const ::TagLib::AudioProperties& properties{ *file.audioProperties() };
+ const ::TagLib::AudioProperties* properties{ file.audioProperties() };
+ if (!properties)
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine audio properties in " << filePath);
+ return std::nullopt;
+ }
// Common properties
- audioProperties.bitrate = static_cast(properties.bitrate() * 1000);
+ audioProperties.bitrate = static_cast(properties->bitrate() * 1000);
if (audioProperties.bitrate == 0)
- throw AudioFileParsingException{ "Cannot determine bitrate" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine bitrate in " << filePath);
+ return std::nullopt;
+ }
- audioProperties.channelCount = static_cast(properties.channels());
+ audioProperties.channelCount = static_cast(properties->channels());
if (audioProperties.channelCount == 0)
- throw AudioFileParsingException{ "Cannot determine channel count" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine channel count in " << filePath);
+ return std::nullopt;
+ }
- audioProperties.duration = std::chrono::milliseconds{ properties.lengthInMilliseconds() };
+ audioProperties.duration = std::chrono::milliseconds{ properties->lengthInMilliseconds() };
if (audioProperties.duration == decltype(audioProperties.duration)::zero())
- throw AudioFileParsingException{ "Cannot determine duration" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine duration in " << filePath);
+ return std::nullopt;
+ }
- audioProperties.sampleRate = static_cast(properties.sampleRate());
+ audioProperties.sampleRate = static_cast(properties->sampleRate());
if (audioProperties.sampleRate == 0)
- throw AudioFileParsingException{ "Cannot determine sample rate" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Cannot determine sample rate in " << filePath);
+ return std::nullopt;
+ }
}
// Guess container from the file type
@@ -107,7 +127,8 @@ namespace lms::audio::taglib
audioProperties.codec = CodecType::WMA9Pro;
break;
case ::TagLib::ASF::Properties::Codec::Unknown:
- throw AudioFileParsingException{ "Unhandled ASF codec" };
+ LMS_LOG(AUDIO, DEBUG, "Unhandled ASF codec in " << filePath);
+ return std::nullopt;
}
audioProperties.bitsPerSample = asfFile->audioProperties()->bitsPerSample();
@@ -138,7 +159,8 @@ namespace lms::audio::taglib
audioProperties.codec = CodecType::ALAC;
break;
case ::TagLib::MP4::Properties::Codec::Unknown:
- throw AudioFileParsingException{ "Unhandled MP4 codec" };
+ LMS_LOG(AUDIO, DEBUG, "Unhandled MP4 codec in " << filePath);
+ return std::nullopt;
}
audioProperties.bitsPerSample = mp4File->audioProperties()->bitsPerSample();
@@ -156,7 +178,8 @@ namespace lms::audio::taglib
audioProperties.codec = CodecType::MPC8;
break;
default:
- throw AudioFileParsingException{ "Unhandled MPC codec" };
+ LMS_LOG(AUDIO, DEBUG, "Unhandled MPC codec version " << mpcFile->audioProperties()->mpcVersion() << " in " << filePath);
+ return std::nullopt;
}
}
else if (const auto* mpegFile{ dynamic_cast(&file) })
@@ -172,7 +195,10 @@ namespace lms::audio::taglib
audioProperties.codec = CodecType::AAC;
#endif
else
- throw AudioFileParsingException{ "Unhandled MPEG codec" };
+ {
+ LMS_LOG(AUDIO, DEBUG, "Unhandled MPEG codec in " << filePath);
+ return std::nullopt;
+ }
}
else if (dynamic_cast(&file))
{
@@ -218,7 +244,8 @@ namespace lms::audio::taglib
}
else
{
- throw AudioFileParsingException{ "Unhandled file type" };
+ LMS_LOG(AUDIO, DEBUG, "Unhandled file type in " << filePath);
+ return std::nullopt;
}
if (audioProperties.bitsPerSample && *audioProperties.bitsPerSample == 0)
@@ -228,30 +255,32 @@ namespace lms::audio::taglib
}
} // namespace
- AudioFileInfo::AudioFileInfo(const std::filesystem::path& filePath, ParserOptions::AudioPropertiesReadStyle readStyle, bool enableExtraDebugLogs)
+ AudioFileInfo::AudioFileInfo(const std::filesystem::path& filePath, const AudioFileInfoParseOptions& parseOptions)
: _filePath{ filePath }
- , _file{ utils::parseFile(filePath, readStyle) }
- , _audioProperties{ std::make_unique(computeAudioProperties(*_file)) }
- , _tagReader{ std::make_unique(*_file, enableExtraDebugLogs) }
- , _imageReader{ std::make_unique(*_file) }
+ , _file{ utils::parseFile(filePath, parseOptions.audioPropertiesReadStyle) }
+ , _audioProperties{ computeAudioProperties(*_file, filePath) }
{
+ if (parseOptions.readTags)
+ _tagReader = std::make_unique(*_file, parseOptions.enableExtraDebugLogs);
+
+ if (parseOptions.readImages)
+ _imageReader = std::make_unique(*_file);
}
AudioFileInfo::~AudioFileInfo() = default;
- const AudioProperties& AudioFileInfo::getAudioProperties() const
+ const AudioProperties* AudioFileInfo::getAudioProperties() const
{
- return *_audioProperties;
+ return _audioProperties.has_value() ? &_audioProperties.value() : nullptr;
}
- const IImageReader& AudioFileInfo::getImageReader() const
+ const IImageReader* AudioFileInfo::getImageReader() const
{
- return *_imageReader;
+ return _imageReader.get();
}
- const ITagReader& AudioFileInfo::getTagReader() const
+ const ITagReader* AudioFileInfo::getTagReader() const
{
- return *_tagReader;
+ return _tagReader.get();
}
-
} // namespace lms::audio::taglib
diff --git a/src/libs/audio/impl/taglib/AudioFileInfo.hpp b/src/libs/audio/impl/taglib/AudioFileInfo.hpp
index 4cd85452..4dca6f08 100644
--- a/src/libs/audio/impl/taglib/AudioFileInfo.hpp
+++ b/src/libs/audio/impl/taglib/AudioFileInfo.hpp
@@ -19,8 +19,12 @@
#pragma once
+#include
+#include
+
#include "audio/AudioTypes.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "audio/IImageReader.hpp"
#include "audio/ITagReader.hpp"
@@ -37,20 +41,20 @@ namespace lms::audio::taglib
class AudioFileInfo final : public IAudioFileInfo
{
public:
- AudioFileInfo(const std::filesystem::path& filePath, ParserOptions::AudioPropertiesReadStyle readStyle, bool enableExtraDebugLogs);
+ AudioFileInfo(const std::filesystem::path& filePath, const AudioFileInfoParseOptions& parseOptions);
~AudioFileInfo() override;
AudioFileInfo(const AudioFileInfo&) = delete;
AudioFileInfo& operator=(const AudioFileInfo&) = delete;
private:
- const AudioProperties& getAudioProperties() const override;
- const IImageReader& getImageReader() const override;
- const ITagReader& getTagReader() const override;
+ const AudioProperties* getAudioProperties() const override;
+ const IImageReader* getImageReader() const override;
+ const ITagReader* getTagReader() const override;
const std::filesystem::path _filePath;
std::unique_ptr<::TagLib::File> _file;
- std::unique_ptr _audioProperties;
+ std::optional _audioProperties;
std::unique_ptr _tagReader;
std::unique_ptr _imageReader;
};
diff --git a/src/libs/audio/impl/taglib/AudioFileInfoParser.cpp b/src/libs/audio/impl/taglib/AudioFileInfoParser.cpp
new file mode 100644
index 00000000..bc87c66e
--- /dev/null
+++ b/src/libs/audio/impl/taglib/AudioFileInfoParser.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include "AudioFileInfoParser.hpp"
+
+#include
+
+#include "AudioFileInfo.hpp"
+#include "Utils.hpp"
+
+namespace lms::audio::taglib
+{
+ std::unique_ptr AudioFileInfoParser::parse(const std::filesystem::path& p, const AudioFileInfoParseOptions& parseOptions) const
+ {
+ return std::make_unique(p, parseOptions);
+ }
+
+ std::span AudioFileInfoParser::getSupportedExtensions() const
+ {
+ return utils::getSupportedExtensions();
+ }
+} // namespace lms::audio::taglib
\ No newline at end of file
diff --git a/src/libs/audio/impl/taglib/AudioFileInfoParser.hpp b/src/libs/audio/impl/taglib/AudioFileInfoParser.hpp
new file mode 100644
index 00000000..c6e30cc4
--- /dev/null
+++ b/src/libs/audio/impl/taglib/AudioFileInfoParser.hpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#pragma once
+
+#include "audio/IAudioFileInfoParser.hpp"
+
+namespace lms::audio::taglib
+{
+ class AudioFileInfoParser : public IAudioFileInfoParser
+ {
+ private:
+ std::unique_ptr parse(const std::filesystem::path& p, const AudioFileInfoParseOptions& parseOptions = AudioFileInfoParseOptions{}) const override;
+ std::span getSupportedExtensions() const override;
+ };
+} // namespace lms::audio::taglib
\ No newline at end of file
diff --git a/src/libs/audio/impl/taglib/Utils.cpp b/src/libs/audio/impl/taglib/Utils.cpp
index aebdaf05..121a2a6a 100644
--- a/src/libs/audio/impl/taglib/Utils.cpp
+++ b/src/libs/audio/impl/taglib/Utils.cpp
@@ -47,7 +47,7 @@
#include "core/ITraceLogger.hpp"
#include "core/String.hpp"
-#include "audio/IAudioFileInfo.hpp"
+#include "audio/Exception.hpp"
namespace lms::audio::taglib::utils
{
@@ -89,15 +89,15 @@ namespace lms::audio::taglib::utils
return std::span{ supportedExtensions };
}
- TagLib::AudioProperties::ReadStyle readStyleToTagLibReadStyle(ParserOptions::ParserOptions::AudioPropertiesReadStyle readStyle)
+ TagLib::AudioProperties::ReadStyle readStyleToTagLibReadStyle(AudioFileInfoParseOptions::AudioPropertiesReadStyle readStyle)
{
switch (readStyle)
{
- case ParserOptions::AudioPropertiesReadStyle::Fast:
+ case AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast:
return TagLib::AudioProperties::ReadStyle::Fast;
- case ParserOptions::AudioPropertiesReadStyle::Average:
+ case AudioFileInfoParseOptions::AudioPropertiesReadStyle::Average:
return TagLib::AudioProperties::ReadStyle::Average;
- case ParserOptions::AudioPropertiesReadStyle::Accurate:
+ case AudioFileInfoParseOptions::AudioPropertiesReadStyle::Accurate:
return TagLib::AudioProperties::ReadStyle::Accurate;
}
@@ -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 IOException{ "fopen failed", ec };
+ throw IOFileException{ "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 IOException{ "fileno failed", ec };
+ throw IOFileException{ "fileno failed", ec };
}
return TagLib::FileStream{ fd, true };
@@ -236,7 +236,7 @@ namespace lms::audio::taglib::utils
return file;
}
- std::unique_ptr parseFile(const std::filesystem::path& p, ParserOptions::AudioPropertiesReadStyle readStyle)
+ std::unique_ptr parseFile(const std::filesystem::path& p, AudioFileInfoParseOptions::AudioPropertiesReadStyle readStyle)
{
LMS_SCOPED_TRACE_DETAILED("MetaData", "TagLibParseFile");
@@ -252,10 +252,7 @@ namespace lms::audio::taglib::utils
}
if (!file)
- throw AudioFileParsingException{ "Parsing failed" };
-
- if (!file->audioProperties())
- throw AudioFileParsingException{ "No audio properties" };
+ throw Exception{ "Parsing failed" };
return file;
}
diff --git a/src/libs/audio/impl/taglib/Utils.hpp b/src/libs/audio/impl/taglib/Utils.hpp
index f79e96bf..fa614508 100644
--- a/src/libs/audio/impl/taglib/Utils.hpp
+++ b/src/libs/audio/impl/taglib/Utils.hpp
@@ -25,10 +25,10 @@
#include
-#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
namespace lms::audio::taglib::utils
{
std::span getSupportedExtensions();
- std::unique_ptr<::TagLib::File> parseFile(const std::filesystem::path& p, ParserOptions::AudioPropertiesReadStyle readStyle);
+ std::unique_ptr<::TagLib::File> parseFile(const std::filesystem::path& p, AudioFileInfoParseOptions::AudioPropertiesReadStyle readStyle);
} // namespace lms::audio::taglib::utils
\ No newline at end of file
diff --git a/src/libs/audio/include/audio/AudioTypes.hpp b/src/libs/audio/include/audio/AudioTypes.hpp
index 84478949..952f9c14 100644
--- a/src/libs/audio/include/audio/AudioTypes.hpp
+++ b/src/libs/audio/include/audio/AudioTypes.hpp
@@ -48,9 +48,11 @@ namespace lms::audio
enum class CodecType
{
AAC,
+ AC3,
ALAC, // Apple Lossless Audio Codec (ALAC)
APE, // Monkey's Audio
DSD, // DSD
+ EAC3,
FLAC, // Flac
MP3,
MP4ALS, // MPEG-4 Audio Lossless Coding
diff --git a/src/libs/audio/include/audio/Exception.hpp b/src/libs/audio/include/audio/Exception.hpp
index 13f13437..6c3c9e52 100644
--- a/src/libs/audio/include/audio/Exception.hpp
+++ b/src/libs/audio/include/audio/Exception.hpp
@@ -19,6 +19,8 @@
#pragma once
+#include
+
#include "core/Exception.hpp"
namespace lms::audio
@@ -28,4 +30,20 @@ namespace lms::audio
public:
using LmsException::LmsException;
};
+
+ class IOFileException : public Exception
+ {
+ public:
+ IOFileException(std::string_view message, std::error_code err)
+ : Exception{ std::string{ message } + ": " + err.message() }
+ , _err{ err }
+ {
+ }
+
+ std::error_code getErrorCode() const { return _err; }
+
+ private:
+ std::error_code _err;
+ };
+
} // namespace lms::audio
diff --git a/src/libs/audio/include/audio/IAudioFileInfo.hpp b/src/libs/audio/include/audio/IAudioFileInfo.hpp
index b417e78b..68639161 100644
--- a/src/libs/audio/include/audio/IAudioFileInfo.hpp
+++ b/src/libs/audio/include/audio/IAudioFileInfo.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Emeric Poupon
+ * Copyright (C) 2025 Emeric Poupon
*
* This file is part of LMS.
*
@@ -19,11 +19,6 @@
#pragma once
-#include
-#include
-
-#include "audio/Exception.hpp"
-
namespace lms::audio
{
class AudioProperties;
@@ -35,52 +30,8 @@ namespace lms::audio
public:
virtual ~IAudioFileInfo() = default;
- virtual const AudioProperties& getAudioProperties() const = 0;
- virtual const IImageReader& getImageReader() const = 0;
- virtual const ITagReader& getTagReader() const = 0;
+ virtual const AudioProperties* getAudioProperties() const = 0; // may be null even if requested, due to backend limitation
+ virtual const IImageReader* getImageReader() const = 0;
+ virtual const ITagReader* getTagReader() const = 0;
};
-
- class AudioFileParsingException : public Exception
- {
- public:
- using Exception::Exception;
- };
-
- class IOException : public Exception
- {
- public:
- IOException(std::string_view message, std::error_code err)
- : Exception{ std::string{ message } + ": " + err.message() }
- , _err{ err }
- {
- }
-
- std::error_code getErrorCode() const { return _err; }
-
- private:
- std::error_code _err;
- };
-
- struct ParserOptions
- {
- enum class Parser
- {
- TagLib,
- FFmpeg,
- };
-
- enum class AudioPropertiesReadStyle
- {
- Fast,
- Average,
- Accurate,
- };
-
- Parser parser{ Parser::TagLib };
- AudioPropertiesReadStyle readStyle{ AudioPropertiesReadStyle::Average };
- bool enableExtraDebugLogs{};
- };
- std::unique_ptr parseAudioFile(const std::filesystem::path& p, const ParserOptions& parserOptions = ParserOptions{});
-
- std::span getSupportedExtensions(ParserOptions::ParserOptions::Parser parser);
} // namespace lms::audio
diff --git a/src/libs/audio/include/audio/IAudioFileInfoParser.hpp b/src/libs/audio/include/audio/IAudioFileInfoParser.hpp
new file mode 100644
index 00000000..45350209
--- /dev/null
+++ b/src/libs/audio/include/audio/IAudioFileInfoParser.hpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+namespace lms::audio
+{
+ class IAudioFileInfo;
+
+ struct AudioFileInfoParseOptions
+ {
+ enum class AudioPropertiesReadStyle
+ {
+ Fast, // fastest
+ Average, // normal
+ Accurate, // full scan
+ };
+
+ AudioPropertiesReadStyle audioPropertiesReadStyle{ AudioPropertiesReadStyle::Average };
+ bool readTags{ true };
+ bool readImages{ true };
+
+ bool enableExtraDebugLogs{};
+ };
+
+ class IAudioFileInfoParser
+ {
+ public:
+ virtual ~IAudioFileInfoParser() = default;
+
+ virtual std::unique_ptr parse(const std::filesystem::path& p, const AudioFileInfoParseOptions& parseOptions = AudioFileInfoParseOptions{}) const = 0;
+ virtual std::span getSupportedExtensions() const = 0;
+ };
+
+ enum class AudioFileInfoParserBackend
+ {
+ TagLib,
+ FFmpeg,
+ };
+ static inline constexpr AudioFileInfoParserBackend defaultAudioFileInfoParserBackend{ AudioFileInfoParserBackend::TagLib };
+ std::unique_ptr createAudioFileInfoParser(AudioFileInfoParserBackend backend = defaultAudioFileInfoParserBackend);
+} // namespace lms::audio
diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp
index 7395ed20..8dda3d27 100644
--- a/src/libs/services/artwork/impl/ArtworkService.cpp
+++ b/src/libs/services/artwork/impl/ArtworkService.cpp
@@ -24,7 +24,9 @@
#include "core/IConfig.hpp"
#include "core/ILogger.hpp"
+#include "audio/Exception.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "audio/IImageReader.hpp"
#include "database/IDb.hpp"
#include "database/Session.hpp"
@@ -53,6 +55,7 @@ namespace lms::artwork
const std::filesystem::path& defaultArtistImageSvgPath)
: _db{ db }
, _cache{ core::Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
+ , _audioFileInfoParser{ audio::createAudioFileInfoParser() }
{
setJpegQuality(core::Service::get()->getULong("cover-jpeg-quality", 75));
@@ -108,11 +111,14 @@ namespace lms::artwork
{
std::size_t currentIndex{};
- audio::ParserOptions options;
- options.readStyle = audio::ParserOptions::AudioPropertiesReadStyle::Fast; // only for images
+ audio::AudioFileInfoParseOptions options;
+ options.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only for images
+ options.readTags = false;
+ options.readImages = true;
- auto audioFile{ audio::parseAudioFile(p) };
- audioFile->getImageReader().visitImages([&](const audio::Image& parsedImage) {
+ const auto audioFileInfo{ _audioFileInfoParser->parse(p, options) };
+ assert(audioFileInfo->getImageReader());
+ audioFileInfo->getImageReader()->visitImages([&](const audio::Image& parsedImage) {
if (currentIndex++ != index)
return;
diff --git a/src/libs/services/artwork/impl/ArtworkService.hpp b/src/libs/services/artwork/impl/ArtworkService.hpp
index 53bd1455..e698cc35 100644
--- a/src/libs/services/artwork/impl/ArtworkService.hpp
+++ b/src/libs/services/artwork/impl/ArtworkService.hpp
@@ -28,6 +28,11 @@
#include "ImageCache.hpp"
+namespace lms::audio
+{
+ class IAudioFileInfoParser;
+}
+
namespace lms::db
{
class Session;
@@ -65,6 +70,7 @@ namespace lms::artwork
ImageCache _cache;
std::shared_ptr _defaultReleaseCover;
std::shared_ptr _defaultArtistImage;
+ std::unique_ptr _audioFileInfoParser;
static inline const std::vector _fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; // TODO parametrize
unsigned _jpegQuality;
diff --git a/src/libs/services/scanner/CMakeLists.txt b/src/libs/services/scanner/CMakeLists.txt
index 055371cc..69ef7c3e 100644
--- a/src/libs/services/scanner/CMakeLists.txt
+++ b/src/libs/services/scanner/CMakeLists.txt
@@ -2,6 +2,7 @@ add_library(lmsscanner STATIC
impl/helpers/ArtistHelpers.cpp
impl/scanners/artistinfo/ArtistInfoParser.cpp
impl/scanners/artistinfo/ArtistInfoFileScanner.cpp
+ impl/scanners/audiofile/AudioFileInfoParserSet.cpp
impl/scanners/audiofile/AudioFileScanOperation.cpp
impl/scanners/audiofile/AudioFileScanner.cpp
impl/scanners/audiofile/TrackMetadataParser.cpp
diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileInfoParserSet.cpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileInfoParserSet.cpp
new file mode 100644
index 00000000..738a04dc
--- /dev/null
+++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileInfoParserSet.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include "audio/IAudioFileInfoParser.hpp"
+#include "core/Exception.hpp"
+#include "core/IConfig.hpp"
+#include "core/Service.hpp"
+
+#include "scanners/audiofile/AudioFileInfoParserSet.hpp"
+
+namespace lms::scanner
+{
+ namespace
+ {
+ audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle getParserReadStyle()
+ {
+ std::string_view readStyle{ core::Service::get()->getString("scanner-parser-read-style", "average") };
+
+ if (readStyle == "fast")
+ return audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast;
+ if (readStyle == "average")
+ return audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Average;
+ if (readStyle == "accurate")
+ return audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Accurate;
+
+ throw core::LmsException{ "Invalid value for 'scanner-parser-read-style'" };
+ }
+ } // namespace
+
+ AudioFileInfoParserSet createAudioFileInfoParserSet()
+ {
+ AudioFileInfoParserSet parserSet;
+
+ parserSet.taglibParser = audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::TagLib);
+ parserSet.ffmpegParser = audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg);
+
+ const auto extensions{ parserSet.taglibParser->getSupportedExtensions() };
+ parserSet.supportedExtensions.assign(std::cbegin(extensions), std::cend(extensions));
+
+ parserSet.audioPropertiesReadStyle = getParserReadStyle();
+
+ return parserSet;
+ }
+} // namespace lms::scanner
diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileInfoParserSet.hpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileInfoParserSet.hpp
new file mode 100644
index 00000000..f097d548
--- /dev/null
+++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileInfoParserSet.hpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "audio/IAudioFileInfoParser.hpp"
+
+namespace lms::scanner
+{
+ struct AudioFileInfoParserSet
+ {
+ std::unique_ptr taglibParser;
+ std::unique_ptr ffmpegParser;
+ std::vector supportedExtensions;
+
+ audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle audioPropertiesReadStyle; // a bit hacky to have this here
+ };
+
+ AudioFileInfoParserSet createAudioFileInfoParserSet();
+} // namespace lms::scanner
diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp
index 814b9242..502d14ab 100644
--- a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp
+++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp
@@ -25,7 +25,9 @@
#include "core/Path.hpp"
#include "core/XxHash3.hpp"
+#include "audio/Exception.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "image/Exception.hpp"
#include "image/Image.hpp"
@@ -52,6 +54,7 @@
#include "helpers/ArtistHelpers.hpp"
#include "scanners/IFileScanOperation.hpp"
#include "scanners/Utils.hpp"
+#include "scanners/audiofile/AudioFileInfoParserSet.hpp"
#include "scanners/audiofile/TrackMetadataParser.hpp"
namespace lms::scanner
@@ -488,10 +491,10 @@ namespace lms::scanner
}
} // namespace
- AudioFileScanOperation::AudioFileScanOperation(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings, const TrackMetadataParser& metadataParser, const audio::ParserOptions& parserOptions)
+ AudioFileScanOperation::AudioFileScanOperation(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings, const AudioFileInfoParserSet& audioFileInfoParserSet, const TrackMetadataParser& metadataParser)
: FileScanOperationBase{ std::move(fileToScan), db, settings }
+ , _audioFileInfoParserSet{ audioFileInfoParserSet }
, _metadataParser{ metadataParser }
- , _parserOptions{ parserOptions }
{
}
@@ -501,18 +504,42 @@ namespace lms::scanner
{
try
{
- auto audioFileInfo{ audio::parseAudioFile(getFilePath(), _parserOptions) };
+ audio::AudioFileInfoParseOptions options;
+ options.audioPropertiesReadStyle = _audioFileInfoParserSet.audioPropertiesReadStyle;
+ options.readImages = true;
+ options.readTags = true;
+
+ const auto audioFileInfo{ _audioFileInfoParserSet.taglibParser->parse(getFilePath(), options) };
_file.emplace();
- _file->audioProperties = audioFileInfo->getAudioProperties();
- _file->track = _metadataParser.parseTrackMetaData(audioFileInfo->getTagReader());
+ // Fallback on ffmpeg in case no audio properties are found by taglib
+ if (!audioFileInfo->getAudioProperties())
+ {
+ LMS_LOG(DBUPDATER, DEBUG, "Cannot parse audio properties in " << getFilePath() << " using TagLib, switching to ffmpeg");
+
+ options.readTags = false;
+ options.readImages = false;
+ const auto ffmpegAudioFileInfo{ _audioFileInfoParserSet.ffmpegParser->parse(getFilePath(), options) };
+ if (!ffmpegAudioFileInfo->getAudioProperties())
+ {
+ addError(getFilePath());
+ return;
+ }
+ _file->audioProperties = *ffmpegAudioFileInfo->getAudioProperties();
+ }
+ else
+ {
+ _file->audioProperties = *audioFileInfo->getAudioProperties();
+ }
+
+ _file->track = _metadataParser.parseTrackMetaData(*audioFileInfo->getTagReader());
// We fill missing artist mbids with mbids found on other artist roles
fillMissingMbids(_file->track);
std::size_t index{};
- audioFileInfo->getImageReader().visitImages([&](const audio::Image& image) {
+ audioFileInfo->getImageReader()->visitImages([&](const audio::Image& image) {
try
{
image::ImageProperties properties{ image::probeImage(image.data) };
@@ -539,7 +566,7 @@ namespace lms::scanner
index++;
});
}
- catch (const audio::IOException& e)
+ catch (const audio::IOFileException& e)
{
addError(getFilePath(), e.getErrorCode());
}
diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp
index 7d5e00ff..0cb62b57 100644
--- a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp
+++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp
@@ -22,7 +22,6 @@
#include
#include "audio/AudioTypes.hpp"
-#include "audio/IAudioFileInfo.hpp"
#include "audio/IImageReader.hpp"
#include "image/Types.hpp"
@@ -39,6 +38,7 @@ namespace lms::db
namespace lms::scanner
{
+ class AudioFileInfoParserSet;
class TrackMetadataParser;
struct ImageInfo
@@ -55,7 +55,7 @@ namespace lms::scanner
class AudioFileScanOperation : public FileScanOperationBase
{
public:
- AudioFileScanOperation(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings, const TrackMetadataParser& metadataParser, const audio::ParserOptions& parserOptions);
+ AudioFileScanOperation(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings, const AudioFileInfoParserSet& audioFileInfoParserSet, const TrackMetadataParser& metadataParser);
~AudioFileScanOperation() override;
AudioFileScanOperation(const AudioFileScanOperation&) = delete;
AudioFileScanOperation& operator=(const AudioFileScanOperation&) = delete;
@@ -65,8 +65,8 @@ namespace lms::scanner
void scan() override;
OperationResult processResult() override;
+ const AudioFileInfoParserSet& _audioFileInfoParserSet;
const TrackMetadataParser& _metadataParser;
- const audio::ParserOptions& _parserOptions;
struct AudioFileInfo
{
diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.cpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.cpp
index f38d65af..4d796301 100644
--- a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.cpp
+++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.cpp
@@ -19,11 +19,6 @@
#include "AudioFileScanner.hpp"
-#include "core/IConfig.hpp"
-#include "core/Service.hpp"
-
-#include "audio/IAudioFileInfo.hpp"
-
#include "database/IDb.hpp"
#include "database/Session.hpp"
#include "database/objects/MediaLibrary.hpp"
@@ -31,6 +26,7 @@
#include "ScannerSettings.hpp"
#include "scanners/Utils.hpp"
+#include "scanners/audiofile/AudioFileInfoParserSet.hpp"
#include "scanners/audiofile/AudioFileScanOperation.hpp"
#include "scanners/audiofile/TrackMetadataParser.hpp"
@@ -38,20 +34,6 @@ namespace lms::scanner
{
namespace
{
- audio::ParserOptions::AudioPropertiesReadStyle getParserReadStyle()
- {
- std::string_view readStyle{ core::Service::get()->getString("scanner-parser-read-style", "average") };
-
- if (readStyle == "fast")
- return audio::ParserOptions::AudioPropertiesReadStyle::Fast;
- if (readStyle == "average")
- return audio::ParserOptions::AudioPropertiesReadStyle::Average;
- if (readStyle == "accurate")
- return audio::ParserOptions::AudioPropertiesReadStyle::Accurate;
-
- throw core::LmsException{ "Invalid value for 'scanner-parser-read-style'" };
- }
-
TrackMetadataParser::Parameters createTrackMetadataParserParameters(const ScannerSettings& settings)
{
TrackMetadataParser::Parameters params;
@@ -62,23 +44,13 @@ namespace lms::scanner
return params;
}
-
- audio::ParserOptions createAudioFileParserOptions()
- {
- audio::ParserOptions options;
- options.readStyle = getParserReadStyle();
- options.parser = audio::ParserOptions::Parser::TagLib; // For now, always use TagLib
-
- return options;
- }
-
} // namespace
AudioFileScanner::AudioFileScanner(db::IDb& db, const ScannerSettings& settings)
: _db{ db }
, _settings{ settings }
, _trackMetadataParser{ createTrackMetadataParserParameters(settings) }
- , _parserOptions{ createAudioFileParserOptions() }
+ , _audioFileInfoParserSet{ createAudioFileInfoParserSet() }
{
}
@@ -96,7 +68,7 @@ namespace lms::scanner
std::span AudioFileScanner::getSupportedExtensions() const
{
- return audio::getSupportedExtensions(_parserOptions.parser);
+ return _audioFileInfoParserSet.supportedExtensions;
}
bool AudioFileScanner::needsScan(const FileToScan& file) const
@@ -112,6 +84,6 @@ namespace lms::scanner
std::unique_ptr AudioFileScanner::createScanOperation(FileToScan&& fileToScan) const
{
- return std::make_unique(std::move(fileToScan), _db, _settings, _trackMetadataParser, _parserOptions);
+ return std::make_unique(std::move(fileToScan), _db, _settings, _audioFileInfoParserSet, _trackMetadataParser);
}
} // namespace lms::scanner
diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.hpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.hpp
index 5a8b5c32..a7e3527b 100644
--- a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.hpp
+++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanner.hpp
@@ -19,23 +19,14 @@
#pragma once
-#include "audio/IAudioFileInfo.hpp"
-
#include "scanners/IFileScanner.hpp"
+#include "scanners/audiofile/AudioFileInfoParserSet.hpp"
#include "scanners/audiofile/TrackMetadataParser.hpp"
-namespace lms
+namespace lms::db
{
- namespace db
- {
- class IDb;
- }
-
- namespace metadata
- {
- class IAudioFileParser;
- }
-} // namespace lms
+ class IDb;
+}
namespace lms::scanner
{
@@ -59,6 +50,6 @@ namespace lms::scanner
db::IDb& _db;
const ScannerSettings& _settings;
const TrackMetadataParser _trackMetadataParser;
- const audio::ParserOptions _parserOptions;
+ const AudioFileInfoParserSet _audioFileInfoParserSet;
};
} // namespace lms::scanner
\ No newline at end of file
diff --git a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp
index 8254c229..13c84402 100644
--- a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp
+++ b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp
@@ -30,6 +30,7 @@
#include "audio/AudioTypes.hpp"
#include "audio/Exception.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "database/Session.hpp"
#include "database/objects/PodcastEpisode.hpp"
@@ -118,13 +119,21 @@ namespace lms::api::subsonic
// TODO: put this information in db during scan
try
{
- const auto audioFile{ audio::parseAudioFile(trackPath) };
+ const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
- return isCodecCompatibleWithOutputFormat(audioFile->getAudioProperties().codec, outputFormat);
+ audio::AudioFileInfoParseOptions parseOptions;
+ parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only coded needed
+ parseOptions.readImages = false;
+ parseOptions.readTags = false;
+ const auto audioFile{ parser->parse(trackPath, parseOptions) };
+
+ if (!audioFile->getAudioProperties())
+ throw RequestedDataNotFoundError{};
+
+ return isCodecCompatibleWithOutputFormat(audioFile->getAudioProperties()->codec, outputFormat);
}
catch (const audio::Exception& e)
{
- // TODO 404?
throw RequestedDataNotFoundError{};
}
}
diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp
index 63c451d6..cf001f96 100644
--- a/src/lms/ui/explore/ReleaseView.cpp
+++ b/src/lms/ui/explore/ReleaseView.cpp
@@ -30,7 +30,9 @@
#include "core/String.hpp"
#include "audio/AudioTypes.hpp"
+#include "audio/Exception.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "database/Session.hpp"
#include "database/Types.hpp"
#include "database/objects/Artist.hpp"
@@ -145,10 +147,18 @@ namespace lms::ui
{
try
{
- if (const auto audioFile{ audio::parseAudioFile(track->getAbsoluteFilePath()) })
+ const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
+
+ audio::AudioFileInfoParseOptions parseOptions;
+ parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only coded needed
+ parseOptions.readImages = false;
+ parseOptions.readTags = false;
+ const auto audioFile{ parser->parse(track->getAbsoluteFilePath(), parseOptions) };
+
+ if (audioFile->getAudioProperties())
{
releaseInfo->setCondition("if-has-codec", true);
- releaseInfo->bindString("codec", audio::codecTypeToString(audioFile->getAudioProperties().codec).c_str(), Wt::TextFormat::Plain);
+ releaseInfo->bindString("codec", audio::codecTypeToString(audioFile->getAudioProperties()->codec).c_str(), Wt::TextFormat::Plain);
break;
}
}
diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp
index 8721745b..3591fe46 100644
--- a/src/lms/ui/explore/TrackListHelpers.cpp
+++ b/src/lms/ui/explore/TrackListHelpers.cpp
@@ -26,7 +26,9 @@
#include "core/Service.hpp"
#include "audio/AudioTypes.hpp"
+#include "audio/Exception.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "database/Session.hpp"
#include "database/Types.hpp"
#include "database/objects/Artist.hpp"
@@ -131,10 +133,18 @@ namespace lms::ui::TrackListHelpers
try
{
- if (const auto audioFile{ audio::parseAudioFile(track->getAbsoluteFilePath()) })
+ const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
+
+ audio::AudioFileInfoParseOptions parseOptions;
+ parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only coded needed
+ parseOptions.readImages = false;
+ parseOptions.readTags = false;
+ const auto audioFile{ parser->parse(track->getAbsoluteFilePath(), parseOptions) };
+
+ if (audioFile->getAudioProperties())
{
trackInfo->setCondition("if-has-codec", true);
- trackInfo->bindString("codec", audio::codecTypeToString(audioFile->getAudioProperties().codec).c_str(), Wt::TextFormat::Plain);
+ trackInfo->bindString("codec", audio::codecTypeToString(audioFile->getAudioProperties()->codec).c_str(), Wt::TextFormat::Plain);
}
}
catch (const audio::Exception& e)
diff --git a/src/tools/audioinfo/LmsAudioInfo.cpp b/src/tools/audioinfo/LmsAudioInfo.cpp
index 2a05ad15..d7df8362 100644
--- a/src/tools/audioinfo/LmsAudioInfo.cpp
+++ b/src/tools/audioinfo/LmsAudioInfo.cpp
@@ -31,6 +31,7 @@
#include "audio/AudioTypes.hpp"
#include "audio/Exception.hpp"
#include "audio/IAudioFileInfo.hpp"
+#include "audio/IAudioFileInfoParser.hpp"
#include "audio/IImageReader.hpp"
#include "audio/ITagReader.hpp"
@@ -133,11 +134,21 @@ namespace lms::audio
void displayInfo(const IAudioFileInfo& fileInfo)
{
- std::cout << "Audio properties:\n"
- << fileInfo.getAudioProperties() << std::endl;
+ if (fileInfo.getAudioProperties())
+ {
+ std::cout << "Audio properties:\n"
+ << *fileInfo.getAudioProperties() << "\n";
+ }
+ else
+ {
+ std::cout << "Failed to parse audio properties!\n\n";
+ }
- displayImages(fileInfo.getImageReader());
- displayTags(fileInfo.getTagReader());
+ if (fileInfo.getImageReader())
+ displayImages(*fileInfo.getImageReader());
+
+ if (fileInfo.getTagReader())
+ displayTags(*fileInfo.getTagReader());
std::cout << "\n";
}
@@ -200,15 +211,16 @@ int main(int argc, char* argv[])
return EXIT_FAILURE;
}
- audio::ParserOptions parserOptions;
- parserOptions.enableExtraDebugLogs = true;
+ audio::AudioFileInfoParserBackend parserBackend{ audio::defaultAudioFileInfoParserBackend };
if (core::stringUtils::stringCaseInsensitiveEqual(vm["parser"].as(), "taglib"))
- parserOptions.parser = audio::ParserOptions::Parser::TagLib;
+ parserBackend = audio::AudioFileInfoParserBackend::TagLib;
else if (core::stringUtils::stringCaseInsensitiveEqual(vm["parser"].as(), "ffmpeg"))
- parserOptions.parser = audio::ParserOptions::Parser::FFmpeg;
+ parserBackend = audio::AudioFileInfoParserBackend::FFmpeg;
else
throw program_options::validation_error{ program_options::validation_error::invalid_option_value, "parser" };
+ const auto parser{ audio::createAudioFileInfoParser(parserBackend) };
+
const auto& inputFiles{ vm["file"].as>() };
// log to stdout
core::Service logger{ core::logging::createLogger(core::logging::Severity::DEBUG) };
@@ -219,8 +231,11 @@ int main(int argc, char* argv[])
try
{
+ audio::AudioFileInfoParseOptions parseOptions;
+ parseOptions.enableExtraDebugLogs = true;
+
std::cout << "Parsing file " << file << ":\n";
- const auto audioFileInfo{ audio::parseAudioFile(file, parserOptions) };
+ const auto audioFileInfo{ parser->parse(file, parseOptions) };
displayInfo(*audioFileInfo);
}