Added a fallback to ffmpeg parser in case of audio properties are not properly decoded, ref #781

This commit is contained in:
emeric
2025-11-20 23:15:07 +01:00
parent 162d910904
commit d69827c2a2
35 changed files with 736 additions and 318 deletions
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<core::IConfig>::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
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <vector>
#include "audio/IAudioFileInfoParser.hpp"
namespace lms::scanner
{
struct AudioFileInfoParserSet
{
std::unique_ptr<audio::IAudioFileInfoParser> taglibParser;
std::unique_ptr<audio::IAudioFileInfoParser> ffmpegParser;
std::vector<std::filesystem::path> supportedExtensions;
audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle audioPropertiesReadStyle; // a bit hacky to have this here
};
AudioFileInfoParserSet createAudioFileInfoParserSet();
} // namespace lms::scanner
@@ -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<NoAudioTrackFoundError>(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<IOScanError>(getFilePath(), e.getErrorCode());
}
@@ -22,7 +22,6 @@
#include <vector>
#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
{
@@ -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<core::IConfig>::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<const std::filesystem::path> 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<IFileScanOperation> AudioFileScanner::createScanOperation(FileToScan&& fileToScan) const
{
return std::make_unique<AudioFileScanOperation>(std::move(fileToScan), _db, _settings, _trackMetadataParser, _parserOptions);
return std::make_unique<AudioFileScanOperation>(std::move(fileToScan), _db, _settings, _audioFileInfoParserSet, _trackMetadataParser);
}
} // namespace lms::scanner
@@ -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