Added a way to set custom tag delimiters for artists and for other fields, fixes #417
This commit is contained in:
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "ScanStepScanFiles.hpp"
|
||||
|
||||
#include "metadata/IParser.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Db.hpp"
|
||||
@@ -29,15 +28,17 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
#include "metadata/Exception.hpp"
|
||||
#include "metadata/IParser.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
|
||||
using namespace Database;
|
||||
|
||||
namespace Scanner
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
namespace
|
||||
{
|
||||
Artist::pointer createArtist(Session& session, const MetaData::Artist& artistInfo)
|
||||
@@ -179,25 +180,34 @@ namespace Scanner
|
||||
return Release::pointer{};
|
||||
}
|
||||
|
||||
std::vector<Cluster::pointer> getOrCreateClusters(Session& session, const MetaData::Tags& tags)
|
||||
std::vector<Cluster::pointer> getOrCreateClusters(Session& session, const MetaData::Track& track)
|
||||
{
|
||||
std::vector<Cluster::pointer> clusters;
|
||||
|
||||
for (const auto& [tag, values] : tags)
|
||||
auto getOrCreateClusters {[&](std::string tag, std::span<const std::string> values)
|
||||
{
|
||||
auto clusterType = ClusterType::find(session, tag);
|
||||
if (!clusterType)
|
||||
clusterType = session.create<ClusterType>(tag);
|
||||
|
||||
for (const auto& clusterName : values)
|
||||
for (const auto& value : values)
|
||||
{
|
||||
auto cluster = clusterType->getCluster(clusterName);
|
||||
auto cluster{ clusterType->getCluster(value) };
|
||||
if (!cluster)
|
||||
cluster = session.create<Cluster>(clusterType, clusterName);
|
||||
cluster = session.create<Cluster>(clusterType, value);
|
||||
|
||||
clusters.push_back(cluster);
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
// TODO: migrate these fields in dedicated tables in DB
|
||||
getOrCreateClusters("GENRE", track.genres);
|
||||
getOrCreateClusters("MOOD", track.genres);
|
||||
getOrCreateClusters("LANGUAGE", track.languages);
|
||||
getOrCreateClusters("GROUPING", track.groupings);
|
||||
|
||||
for (const auto& [tag, values] : track.userExtraTags)
|
||||
getOrCreateClusters(tag, values);
|
||||
|
||||
return clusters;
|
||||
}
|
||||
@@ -232,7 +242,7 @@ namespace Scanner
|
||||
, _scanContextRunner{ _scanContext, threadCount }
|
||||
{}
|
||||
|
||||
void ScanStepScanFiles::MetadataScanQueue::pushScanRequest(const std::filesystem::path path)
|
||||
void ScanStepScanFiles::MetadataScanQueue::pushScanRequest(const std::filesystem::path& path)
|
||||
{
|
||||
{
|
||||
std::scoped_lock lock{ _mutex };
|
||||
@@ -241,12 +251,21 @@ namespace Scanner
|
||||
|
||||
_scanContext.post([=, this]
|
||||
{
|
||||
std::optional<MetaData::Track> trackMetaData{ _metadataParser.parse(path) };
|
||||
std::unique_ptr<MetaData::Track> track;
|
||||
|
||||
try
|
||||
{
|
||||
track = _metadataParser.parse(path);
|
||||
}
|
||||
catch(const MetaData::Exception& e)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, INFO, "Failed to parse '" << path.string() << "'");
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock lock{ _mutex };
|
||||
|
||||
_scanResults.emplace_back(std::make_unique<MetaDataScanResult>(path, std::move(trackMetaData)));
|
||||
_scanResults.emplace_back(MetaDataScanResult{ std::move(path), std::move(track) });
|
||||
_ongoingScanCount -= 1;
|
||||
}
|
||||
_condVar.notify_all();
|
||||
@@ -259,7 +278,7 @@ namespace Scanner
|
||||
return _scanResults.size();
|
||||
}
|
||||
|
||||
size_t ScanStepScanFiles::MetadataScanQueue::popResults(std::vector<std::unique_ptr<MetaDataScanResult>>& results, std::size_t maxCount)
|
||||
size_t ScanStepScanFiles::MetadataScanQueue::popResults(std::vector<MetaDataScanResult>& results, std::size_t maxCount)
|
||||
{
|
||||
results.clear();
|
||||
results.reserve(maxCount);
|
||||
@@ -285,7 +304,7 @@ namespace Scanner
|
||||
|
||||
ScanStepScanFiles::ScanStepScanFiles(InitParams& initParams)
|
||||
: ScanStepBase{ initParams }
|
||||
, _metadataParser{ MetaData::createParser(MetaData::ParserType::TagLib, getParserReadStyle()) } // For now, always use TagLib
|
||||
, _metadataParser{ MetaData::createParser(MetaData::ParserBackend::TagLib, getParserReadStyle()) } // For now, always use TagLib
|
||||
, _metadataScanQueue{ *_metadataParser, getScanMetaDataThreadCount() }
|
||||
{
|
||||
LMS_LOG(DBUPDATER, INFO, "Using " << _metadataScanQueue.getThreadCount() << " thread(s) for scanning file metadata");
|
||||
@@ -300,9 +319,11 @@ namespace Scanner
|
||||
std::vector<std::string> tagsToParse{ _extraTagsToParse };
|
||||
tagsToParse.insert(std::end(tagsToParse), std::cbegin(_settings.extraTags), std::cend(_settings.extraTags));
|
||||
_metadataParser->setUserExtraTags(tagsToParse);
|
||||
_metadataParser->setArtistTagDelimiters(_settings.artistTagDelimiters);
|
||||
_metadataParser->setDefaultTagDelimiters(_settings.defaultTagDelimiters);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<MetaDataScanResult>> scanResults;
|
||||
std::vector<MetaDataScanResult> scanResults;
|
||||
context.currentStepStats.totalElems = context.stats.filesScanned;
|
||||
|
||||
for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries)
|
||||
@@ -400,21 +421,21 @@ namespace Scanner
|
||||
return true; // need to scan
|
||||
}
|
||||
|
||||
void ScanStepScanFiles::processMetaDataScanResults(ScanContext& context, std::span<const std::unique_ptr<MetaDataScanResult>> scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
void ScanStepScanFiles::processMetaDataScanResults(ScanContext& context, std::span<const MetaDataScanResult> scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
{
|
||||
Database::Session& dbSession{ _db.getTLSSession() };
|
||||
auto transaction{ dbSession.createWriteTransaction() };
|
||||
|
||||
for (const auto& scanResult : scanResults)
|
||||
for (const MetaDataScanResult& scanResult : scanResults)
|
||||
{
|
||||
if (_abortScan)
|
||||
return;
|
||||
|
||||
if (scanResult->trackMetaData)
|
||||
if (scanResult.trackMetaData)
|
||||
{
|
||||
context.stats.scans++;
|
||||
|
||||
processFileMetaData(context, scanResult->path, *scanResult->trackMetaData, libraryInfo);
|
||||
processFileMetaData(context, scanResult.path, *scanResult.trackMetaData, libraryInfo);
|
||||
|
||||
// optimize the database during scan (if we import a very large database, it may be too late to do it once at end)
|
||||
if ((context.stats.scans % 1'000) == 0)
|
||||
@@ -422,7 +443,7 @@ namespace Scanner
|
||||
}
|
||||
else
|
||||
{
|
||||
context.stats.errors.emplace_back(scanResult->path, ScanErrorType::CannotParseFile);
|
||||
context.stats.errors.emplace_back(scanResult.path, ScanErrorType::CannotParseFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,7 +604,7 @@ namespace Scanner
|
||||
track.modify()->setTotalTrack(trackMetadata.medium ? trackMetadata.medium->trackCount : std::nullopt);
|
||||
track.modify()->setReleaseReplayGain(trackMetadata.medium ? trackMetadata.medium->replayGain : std::nullopt);
|
||||
track.modify()->setDiscSubtitle(trackMetadata.medium ? trackMetadata.medium->name : "");
|
||||
track.modify()->setClusters(getOrCreateClusters(dbSession, trackMetadata.userExtraTags));
|
||||
track.modify()->setClusters(getOrCreateClusters(dbSession, trackMetadata));
|
||||
track.modify()->setLastWriteTime(lastWriteTime);
|
||||
track.modify()->setName(title);
|
||||
track.modify()->setDuration(trackMetadata.duration);
|
||||
|
||||
@@ -47,13 +47,13 @@ namespace Scanner
|
||||
struct MetaDataScanResult
|
||||
{
|
||||
std::filesystem::path path;
|
||||
std::optional<MetaData::Track> trackMetaData;
|
||||
std::unique_ptr<MetaData::Track> trackMetaData;
|
||||
};
|
||||
void processMetaDataScanResults(ScanContext& context, std::span<const std::unique_ptr<MetaDataScanResult>> scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo);
|
||||
void processMetaDataScanResults(ScanContext& context, std::span<const MetaDataScanResult> scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo);
|
||||
void processFileMetaData(ScanContext& context, const std::filesystem::path& file, const MetaData::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo);
|
||||
|
||||
std::unique_ptr<MetaData::IParser> _metadataParser;
|
||||
const std::vector<std::string> _extraTagsToParse{ "GENRE", "MOOD", "LANGUAGE", "ALBUMGROUPING" };
|
||||
const std::vector<std::string> _extraTagsToParse;
|
||||
|
||||
class MetadataScanQueue
|
||||
{
|
||||
@@ -62,10 +62,10 @@ namespace Scanner
|
||||
|
||||
std::size_t getThreadCount() const { return _scanContextRunner.getThreadCount(); }
|
||||
|
||||
void pushScanRequest(const std::filesystem::path path);
|
||||
void pushScanRequest(const std::filesystem::path& path);
|
||||
|
||||
std::size_t getResultsCount() const;
|
||||
size_t popResults(std::vector<std::unique_ptr<MetaDataScanResult>>& results, std::size_t maxCount);
|
||||
size_t popResults(std::vector<MetaDataScanResult>& results, std::size_t maxCount);
|
||||
|
||||
void wait(std::size_t maxScanRequestCount = 0); // wait until ongoing scan request count <= maxScanRequestCount
|
||||
|
||||
@@ -76,11 +76,11 @@ namespace Scanner
|
||||
|
||||
mutable std::mutex _mutex ;
|
||||
std::size_t _ongoingScanCount{};
|
||||
std::deque<std::unique_ptr<MetaDataScanResult>> _scanResults;
|
||||
std::deque<MetaDataScanResult> _scanResults;
|
||||
std::condition_variable _condVar;
|
||||
};
|
||||
MetadataScanQueue _metadataScanQueue;
|
||||
|
||||
std::deque<std::unique_ptr<MetaDataScanResult>> _metaDataScanResults;
|
||||
std::deque<MetaDataScanResult> _metaDataScanResults;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -383,6 +383,9 @@ namespace Scanner
|
||||
const auto& tags{ scanSettings->getExtraTagsToScan() };
|
||||
std::transform(std::cbegin(tags), std::cend(tags), std::back_inserter(newSettings.extraTags), [](std::string_view tag) { return std::string{ tag };});
|
||||
}
|
||||
|
||||
newSettings.artistTagDelimiters = scanSettings->getArtistTagDelimiters();
|
||||
newSettings.defaultTagDelimiters = scanSettings->getDefaultTagDelimiters();
|
||||
}
|
||||
|
||||
return newSettings;
|
||||
|
||||
@@ -30,31 +30,24 @@ namespace Scanner
|
||||
{
|
||||
struct ScannerSettings
|
||||
{
|
||||
std::size_t scanVersion{};
|
||||
Wt::WTime startTime;
|
||||
Database::ScanSettings::UpdatePeriod updatePeriod{ Database::ScanSettings::UpdatePeriod::Never };
|
||||
std::vector<std::filesystem::path> supportedExtensions;
|
||||
bool skipDuplicateMBID{};
|
||||
std::vector<std::string> extraTags;
|
||||
std::size_t scanVersion{};
|
||||
Wt::WTime startTime;
|
||||
Database::ScanSettings::UpdatePeriod updatePeriod{ Database::ScanSettings::UpdatePeriod::Never };
|
||||
std::vector<std::filesystem::path> supportedExtensions;
|
||||
bool skipDuplicateMBID{};
|
||||
std::vector<std::string> extraTags;
|
||||
std::vector<std::string> artistTagDelimiters;
|
||||
std::vector<std::string> defaultTagDelimiters;
|
||||
|
||||
struct MediaLibraryInfo
|
||||
{
|
||||
Database::MediaLibraryId id;
|
||||
std::filesystem::path rootDirectory;
|
||||
|
||||
bool operator==(const MediaLibraryInfo& other) const { return id == other.id && rootDirectory == other.rootDirectory; }
|
||||
bool operator<=>(const MediaLibraryInfo& other) const = default;
|
||||
};
|
||||
std::vector<MediaLibraryInfo> mediaLibraries;
|
||||
std::vector<MediaLibraryInfo> mediaLibraries;
|
||||
|
||||
bool operator==(const ScannerSettings& rhs) const
|
||||
{
|
||||
return scanVersion == rhs.scanVersion
|
||||
&& startTime == rhs.startTime
|
||||
&& updatePeriod == rhs.updatePeriod
|
||||
&& supportedExtensions == rhs.supportedExtensions
|
||||
&& mediaLibraries == rhs.mediaLibraries
|
||||
&& skipDuplicateMBID == rhs.skipDuplicateMBID
|
||||
&& extraTags == rhs.extraTags;
|
||||
}
|
||||
bool operator<=>(const ScannerSettings& rhs) const = default;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user