Added a fallback to ffmpeg parser in case of audio properties are not properly decoded, ref #781
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user