/*
* Copyright (C) 2016 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 "Parser.hpp"
#include
#include "metadata/Exception.hpp"
#include "utils/ILogger.hpp"
#include "utils/String.hpp"
#include "AvFormatTagReader.hpp"
#include "TagLibTagReader.hpp"
#include "Utils.hpp"
namespace MetaData
{
namespace
{
void visitTagValues(const ITagReader& tagReader, std::string_view tagType, std::span tagDelimiters, ITagReader::TagValueVisitor visitor)
{
tagReader.visitTagValues(tagType, [&](std::string_view value)
{
auto visitTagIfNonEmpty{ [&](std::string_view tag)
{
tag = StringUtils::stringTrim(tag);
if (!tag.empty())
visitor(tag);
} };
if (!tagReader.hasMultiValuedTags())
{
for (std::string_view tagDelimiter : tagDelimiters)
{
if (value.find(tagDelimiter) != std::string_view::npos)
{
for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter))
visitTagIfNonEmpty(splitTag);
return;
}
}
}
visitTagIfNonEmpty(value);
});
}
template
std::vector getTagValuesFirstMatchAs(const ITagReader& tagReader, std::initializer_list tagTypes, std::span tagDelimiters)
{
std::vector res;
for (const TagType tagType : tagTypes)
{
auto addTagIfNonEmpty{ [&res](std::string_view tag)
{
tag = StringUtils::stringTrim(tag);
if (!tag.empty())
{
std::optional val{ StringUtils::readAs(tag) };
if (val)
res.emplace_back(std::move(*val));
}
} };
tagReader.visitTagValues(tagType, [&](std::string_view value)
{
if (!tagReader.hasMultiValuedTags())
{
for (std::string_view tagDelimiter : tagDelimiters)
{
if (value.find(tagDelimiter) != std::string_view::npos)
{
for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter))
addTagIfNonEmpty(splitTag);
return;
}
}
}
// no delimiter found, or no delimiter to be used
addTagIfNonEmpty(value);
});
if (!res.empty())
break;
}
return res;
}
template
std::optional getTagValueFirstMatchAs(const ITagReader& tagReader, std::initializer_list tagTypes)
{
std::optional res;
std::vector values{ getTagValuesFirstMatchAs(tagReader, tagTypes, {} /* don't expect multiple values here */) };
if (!values.empty())
res = std::move(values.front());
return res;
}
template
std::vector getTagValuesAs(const ITagReader& tagReader, TagType tagType, std::span tagDelimiters)
{
return getTagValuesFirstMatchAs(tagReader, { tagType }, tagDelimiters);
}
template
std::optional getTagValueAs(const ITagReader& tagReader, TagType tagType)
{
return getTagValueFirstMatchAs(tagReader, { tagType });
}
std::vector getArtists(const ITagReader& tagReader,
std::initializer_list artistTagNames,
std::initializer_list artistSortTagNames,
std::initializer_list artistMBIDTagNames,
std::span artistTagDelimiters
)
{
std::vector artistNames{ getTagValuesFirstMatchAs(tagReader, artistTagNames, artistTagDelimiters) };
if (artistNames.empty())
return {};
std::vector artistSortNames{ getTagValuesFirstMatchAs(tagReader, artistSortTagNames, artistTagDelimiters) };
std::vector artistMBIDs{ getTagValuesFirstMatchAs(tagReader, artistMBIDTagNames, artistTagDelimiters) };
std::vector artists;
artists.reserve(artistNames.size());
for (std::size_t i{}; i < artistNames.size(); ++i)
{
Artist& artist{ artists.emplace_back(std::move(artistNames[i])) };
if (artistNames.size() == artistSortNames.size())
artist.sortName = std::move(artistSortNames[i]);
if (artistNames.size() == artistMBIDs.size())
artist.mbid = std::move(artistMBIDs[i]);
}
return artists;
}
PerformerContainer getPerformerArtists(const ITagReader& tagReader)
{
PerformerContainer performers;
tagReader.visitPerformerTags([&](std::string_view role, std::string_view name)
{
// picard stores like this: (see https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#performer)
// We consider we may hit both styles for the same track
if (role.empty())
{
// "PERFORMER" "artist (role)"
Utils::PerformerArtist performer{ Utils::extractPerformerAndRole(name) };
StringUtils::capitalize(performer.role);
performers[performer.role].push_back(std::move(performer.artist));
}
else
{
// "PERFORMER:role", "artist" (MP3)
std::string roleCapitalized{ StringUtils::stringToLower(role) };
StringUtils::capitalize(roleCapitalized);
performers[roleCapitalized].push_back(Artist{ name });
}
});
return performers;
}
}
std::unique_ptr createParser(ParserBackend parserBackend, ParserReadStyle parserReadStyle)
{
return std::make_unique(parserBackend, parserReadStyle);
}
Parser::Parser(ParserBackend parserBackend, ParserReadStyle readStyle)
: _parserBackend{ parserBackend }
, _readStyle{ readStyle }
{
switch (_parserBackend)
{
case ParserBackend::TagLib:
LMS_LOG(METADATA, INFO, "Using TagLib parser with read style = " << Utils::readStyleToString(readStyle));
break;
case ParserBackend::AvFormat:
LMS_LOG(METADATA, INFO, "Using AvFormat parser");
break;
}
}
std::unique_ptr