Added some logic to handle multi artists in tags involving the artist whitelist, fixes #669

This commit is contained in:
emeric
2025-05-04 22:28:04 +02:00
parent 83dcac39c5
commit aa45cea988
7 changed files with 427 additions and 93 deletions
+6
View File
@@ -1,6 +1,12 @@
add_executable(bench-metadata
LyricsBench.cpp
Metadata.cpp
)
target_include_directories(bench-metadata PRIVATE
../impl
../test
)
target_link_libraries(bench-metadata PRIVATE
-2
View File
@@ -90,5 +90,3 @@ namespace lms::metadata::benchmarks
BENCHMARK(BM_Lyrics);
} // namespace lms::metadata::benchmarks
BENCHMARK_MAIN();
+149
View File
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2024 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 <benchmark/benchmark.h>
#include "AudioFileParser.hpp"
#include "TestTagReader.hpp"
#include "core/String.hpp"
#include "metadata/Types.hpp"
namespace lms::metadata::benchmarks
{
class TestAudioFileParser : public AudioFileParser
{
public:
using AudioFileParser::AudioFileParser;
using AudioFileParser::parseMetaData;
};
static void BM_Metadata_parse(benchmark::State& state)
{
AudioFileParserParameters params;
params.userExtraTags = { "MY_AWESOME_TAG_A", "MY_AWESOME_TAG_B", "MY_AWESOME_MISSING_TAG" };
std::unique_ptr<ITagReader> testTags{ tests::createDefaultPopulatedTestTagReader() };
for (auto _ : state)
{
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(*testTags) };
}
}
static void BM_Metadata_parseArtists(benchmark::State& state)
{
const tests::TestTagReader testTags{
{
{ TagType::Artist, { "AC/DC; MyArtist" } },
}
};
AudioFileParserParameters params;
for (auto _ : state)
{
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
}
}
static void BM_Metadata_parseArtists_WithWhitelist(benchmark::State& state)
{
const tests::TestTagReader testTags{
{
{ TagType::Artist, { "AC/DC; MyArtist" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/", ";" };
// The list itself is not important, the idea is to have some volume
params.artistsToNotSplit = { "AC/DC",
"+/-",
R"(A/N【eɪ-ɛn)",
"Akron/Family",
"AM/FM",
"Ashes/Dust",
"B/B/S/",
"BLCK/MRKT/RGNS",
"Body/Gate/Head",
"Body/Head",
"Born/Dead",
"Burger/Ink",
"case/lang/veirs",
"Chicago / London Underground",
"Dakota/Dakota",
"Dark/Light",
"Decades/Failures",
"The Denison/Kimball Trio",
"D-W/L-SS",
"F/i",
"Friend / Enemy",
"GZA/Genius",
"I/O",
"I/O3",
"In/Humanity",
"Love/Lust",
"Mirror/Dash",
"Model/Actress",
"N/N",
"Neither/Neither World",
"P1/E",
"Sick/Tired",
"t/e/u/",
"tide/edit",
"V/Vm",
"White/Lichens",
"White/Light",
"Yamantaka // Sonic Titan" };
for (auto _ : state)
{
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
}
}
static void BM_Metadata_parseArtists_WithoutWhitelist(benchmark::State& state)
{
const tests::TestTagReader testTags{
{
{ TagType::Artist, { "AC/DC; MyArtist" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/", ";" };
for (auto _ : state)
{
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
}
}
BENCHMARK(BM_Metadata_parse);
BENCHMARK(BM_Metadata_parseArtists);
BENCHMARK(BM_Metadata_parseArtists_WithWhitelist);
BENCHMARK(BM_Metadata_parseArtists_WithoutWhitelist);
} // namespace lms::metadata::benchmarks
BENCHMARK_MAIN();
+52 -11
View File
@@ -20,6 +20,8 @@
#include "AudioFileParser.hpp"
#include <span>
#include <string>
#include <string_view>
#include "core/ILogger.hpp"
#include "core/PartialDateTime.hpp"
@@ -64,7 +66,6 @@ namespace lms::metadata
template<typename T>
void addTagIfNonEmpty(std::vector<T>& res, std::string_view tag)
{
tag = core::stringUtils::stringTrim(tag);
if (tag.empty())
return;
@@ -81,22 +82,62 @@ namespace lms::metadata
{
tagReader.visitTagValues(tagType, [&](std::string_view value) {
value = core::stringUtils::stringTrim(value);
if (!whitelist || !whitelist->contains(value))
{
for (std::string_view tagDelimiter : tagDelimiters)
{
if (value.find(tagDelimiter) != std::string_view::npos)
{
for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiters))
addTagIfNonEmpty(res, splitTag);
// short path: no custom delimiter
if (tagDelimiters.empty())
{
addTagIfNonEmpty(res, value);
return;
}
// Algo:
// 1. replace whitelist entries by placeholders
// 2. apply delimiters
// 3. replace whitelist entries back
constexpr std::string_view substitutionPrefix{ "__LMS_ENTRY__" };
std::unordered_map<std::string, std::string_view> substitutionMap;
std::string strToSplit{ value };
if (whitelist)
{
std::size_t counter{};
for (std::string_view whiteListEntry : *whitelist)
{
whiteListEntry = core::stringUtils::stringTrim(whiteListEntry);
const std::string::size_type pos{ strToSplit.find(whiteListEntry) };
if (pos == std::string::npos)
continue;
std::string substitutionStr{ std::string{ substitutionPrefix } + std::to_string(counter++) };
strToSplit.replace(pos, whiteListEntry.size(), substitutionStr);
substitutionMap.emplace(std::move(substitutionStr), whiteListEntry);
}
}
// no delimiter found, or no delimiter to be used
addTagIfNonEmpty(res, value);
for (std::string_view strSplit : core::stringUtils::splitString(strToSplit, tagDelimiters))
{
std::string str{ core::stringUtils::stringTrim(strSplit) };
while (true)
{
std::string::size_type prefixPos{ str.find(substitutionPrefix) };
if (prefixPos == std::string::npos)
break;
std::string::size_type counterEnd{ prefixPos + substitutionPrefix.size() };
while (std::isdigit(str[counterEnd]))
counterEnd++;
std::string substitutionStr{ str.substr(prefixPos, counterEnd - prefixPos) };
auto it{ substitutionMap.find(substitutionStr) };
if (it != std::cend(substitutionMap))
str.replace(prefixPos, counterEnd - prefixPos, it->second);
}
addTagIfNonEmpty(res, str);
}
});
if (!res.empty())
+7 -12
View File
@@ -22,10 +22,10 @@
#include <chrono>
#include <map>
#include <optional>
#include <set>
#include <span>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "core/PartialDateTime.hpp"
@@ -214,22 +214,17 @@ namespace lms::metadata
Accurate,
};
struct WhiteListHash : std::hash<std::string>, std::hash<std::string_view>
struct SortByLengthDesc
{
using is_transparent = void;
[[nodiscard]] size_t operator()(std::string_view str) const
bool operator()(const std::string& a, const std::string& b) const
{
return std::hash<std::string_view>{}(str);
}
[[nodiscard]] size_t operator()(const std::string& str) const
{
return std::hash<std::string>{}(str);
if (a.length() != b.length())
return a.length() > b.length();
return a < b; // Break ties using lexicographical order
}
};
using WhiteList = std::unordered_set<std::string, WhiteListHash, std::equal_to<>>;
using WhiteList = std::set<std::string, SortByLengthDesc>;
struct AudioFileParserParameters
{
ParserBackend backend{ ParserBackend::TagLib };
+144 -62
View File
@@ -25,9 +25,8 @@
#include "AudioFileParser.hpp"
#include "TestTagReader.hpp"
#include "metadata/Types.hpp"
namespace lms::metadata
namespace lms::metadata::tests
{
class TestAudioFileParser : public AudioFileParser
{
@@ -42,68 +41,13 @@ namespace lms::metadata
params.userExtraTags = { "MY_AWESOME_TAG_A", "MY_AWESOME_TAG_B", "MY_AWESOME_MISSING_TAG" };
TestAudioFileParser parser{ params };
TestTagReader testTags{
{
{ TagType::AcoustID, { "e987a441-e134-4960-8019-274eddacc418" } },
{ TagType::Advisory, { "2" } },
{ TagType::Album, { "MyAlbum" } },
{ TagType::AlbumSortOrder, { "MyAlbumSortName" } },
{ TagType::Artist, { "MyArtist1 & MyArtist2" } },
{ TagType::Artists, { "MyArtist1", "MyArtist2" } },
{ TagType::ArtistSortOrder, { "MyArtist1SortName", "MyArtist2SortName" } },
{ TagType::AlbumArtist, { "MyAlbumArtist1 & MyAlbumArtist2" } },
{ TagType::AlbumArtists, { "MyAlbumArtist1", "MyAlbumArtist2" } },
{ TagType::AlbumArtistsSortOrder, { "MyAlbumArtist1SortName", "MyAlbumArtist2SortName" } },
{ TagType::AlbumComment, { "MyAlbumComment" } },
{ TagType::Barcode, { "MyBarcode" } },
{ TagType::Comment, { "Comment1", "Comment2" } },
{ TagType::Compilation, { "1" } },
{ TagType::Composer, { "MyComposer1", "MyComposer2" } },
{ TagType::ComposerSortOrder, { "MyComposerSortOrder1", "MyComposerSortOrder2" } },
{ TagType::Conductor, { "MyConductor1", "MyConductor2" } },
{ TagType::Copyright, { "MyCopyright" } },
{ TagType::CopyrightURL, { "MyCopyrightURL" } },
{ TagType::Date, { "2020/03/04" } },
{ TagType::DiscNumber, { "2" } },
{ TagType::DiscSubtitle, { "MySubtitle" } },
{ TagType::Genre, { "Genre1", "Genre2" } },
{ TagType::Grouping, { "Grouping1", "Grouping2" } },
{ TagType::Media, { "CD" } },
{ TagType::Mixer, { "MyMixer1", "MyMixer2" } },
{ TagType::Mood, { "Mood1", "Mood2" } },
{ TagType::MusicBrainzArtistID, { "9d2e0c8c-8c5e-4372-a061-590955eaeaae", "5e2cf87f-c8d7-4504-8a86-954dc0840229" } },
{ TagType::MusicBrainzTrackID, { "0afb190a-6735-46df-a16d-199f48206e4a" } },
{ TagType::MusicBrainzReleaseArtistID, { "6fbf097c-1487-43e8-874b-50dd074398a7", "5ed3d6b3-2aed-4a03-828c-3c4d4f7406e1" } },
{ TagType::MusicBrainzReleaseID, { "3fa39992-b786-4585-a70e-85d5cc15ef69" } },
{ TagType::MusicBrainzReleaseGroupID, { "5b1a5a44-8420-4426-9b86-d25dc8d04838" } },
{ TagType::MusicBrainzRecordingID, { "bd3fc666-89de-4ac8-93f6-2dbf028ad8d5" } },
{ TagType::Producer, { "MyProducer1", "MyProducer2" } },
{ TagType::Remixer, { "MyRemixer1", "MyRemixer2" } },
{ TagType::RecordLabel, { "Label1", "Label2" } },
{ TagType::ReleaseCountry, { "MyCountry1", "MyCountry2" } },
{ TagType::Language, { "Language1", "Language2" } },
{ TagType::Lyricist, { "MyLyricist1", "MyLyricist2" } },
{ TagType::OriginalReleaseDate, { "2019/02/03" } },
{ TagType::ReleaseType, { "Album", "Compilation" } },
{ TagType::ReplayGainTrackGain, { "-0.33" } },
{ TagType::ReplayGainAlbumGain, { "-0.5" } },
{ TagType::TrackTitle, { "MyTitle" } },
{ TagType::TrackNumber, { "7" } },
{ TagType::TotalTracks, { "12" } },
{ TagType::TotalDiscs, { "3" } },
}
};
testTags.setExtraUserTags({ { "MY_AWESOME_TAG_A", { "MyTagValue1ForTagA", "MyTagValue2ForTagA" } },
{ "MY_AWESOME_TAG_B", { "MyTagValue1ForTagB", "MyTagValue2ForTagB" } } });
testTags.setPerformersTags({ { "RoleA", { "MyPerformer1ForRoleA", "MyPerformer2ForRoleA" } },
{ "RoleB", { "MyPerformer1ForRoleB", "MyPerformer2ForRoleB" } } });
testTags.setLyricsTags({ { "eng", "[00:00.00]First line\n[00:01.00]Second line" } });
std::unique_ptr<ITagReader> testTags{ createDefaultPopulatedTestTagReader() };
const std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
const std::unique_ptr<Track> track{ parser.parseMetaData(*testTags) };
// Audio properties
{
const AudioProperties& audioProperties{ testTags.getAudioProperties() };
const AudioProperties& audioProperties{ testTags->getAudioProperties() };
EXPECT_EQ(track->audioProperties.bitrate, audioProperties.bitrate);
EXPECT_EQ(track->audioProperties.bitsPerSample, audioProperties.bitsPerSample);
EXPECT_EQ(track->audioProperties.channelCount, audioProperties.channelCount);
@@ -328,7 +272,7 @@ namespace lms::metadata
EXPECT_EQ(track->medium->release->artistDisplayName, "AC/DC");
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_multi)
TEST(AudioFileParser, customArtistDelimiters_whitelist_multi_artists)
{
const TestTagReader testTags{
{
@@ -339,6 +283,26 @@ namespace lms::metadata
AudioFileParserParameters params;
params.artistTagDelimiters = { "/" };
params.artistsToNotSplit = { " AC/DC " };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 2);
EXPECT_EQ(track->artists[0].name, "AC/DC");
EXPECT_EQ(track->artists[1].name, "MyArtist");
EXPECT_EQ(track->artistDisplayName, "AC/DC, MyArtist"); // Reconstructed since this use case is not handled
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_multi_separators_first)
{
const TestTagReader testTags{
{
{ TagType::Artist, { "AC/DC;MyArtist" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/", ";" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
@@ -349,6 +313,124 @@ namespace lms::metadata
EXPECT_EQ(track->artistDisplayName, "AC/DC, MyArtist"); // Reconstructed since this use case is not handled
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_multi_separators_middle)
{
const TestTagReader testTags{
{
{ TagType::Artist, { " MyArtist1; AC/DC ; MyArtist2 " } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/", ";" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 3);
EXPECT_EQ(track->artists[0].name, "MyArtist1");
EXPECT_EQ(track->artists[1].name, "AC/DC");
EXPECT_EQ(track->artists[2].name, "MyArtist2");
EXPECT_EQ(track->artistDisplayName, "MyArtist1, AC/DC, MyArtist2"); // Reconstructed since this use case is not handled
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_multi_separators_last)
{
const TestTagReader testTags{
{
{ TagType::Artist, { " AC/DC; MyArtist" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { ";", "/" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 2);
EXPECT_EQ(track->artists[0].name, "AC/DC");
EXPECT_EQ(track->artists[1].name, "MyArtist");
EXPECT_EQ(track->artistDisplayName, "AC/DC, MyArtist"); // Reconstructed since this use case is not handled
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_longest_first)
{
const TestTagReader testTags{
{
{ TagType::Artist, { " AC/DC; MyArtist" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { ";", "/" };
params.artistsToNotSplit = { "AC", "DC", "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 2);
EXPECT_EQ(track->artists[0].name, "AC/DC");
EXPECT_EQ(track->artists[1].name, "MyArtist");
EXPECT_EQ(track->artistDisplayName, "AC/DC, MyArtist"); // Reconstructed since this use case is not handled
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_partial_begin)
{
const TestTagReader testTags{
{
{ TagType::Artist, { " AC/DC; MyArtist" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 1);
EXPECT_EQ(track->artists[0].name, "AC/DC; MyArtist");
EXPECT_EQ(track->artistDisplayName, "AC/DC; MyArtist");
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_partial_middle)
{
const TestTagReader testTags{
{
{ TagType::Artist, { " MyArtist1; AC/DC ; MyArtist2" } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 1);
EXPECT_EQ(track->artists[0].name, "MyArtist1; AC/DC ; MyArtist2");
EXPECT_EQ(track->artistDisplayName, "MyArtist1; AC/DC ; MyArtist2");
}
TEST(AudioFileParser, customArtistDelimiters_whitelist_partial_end)
{
const TestTagReader testTags{
{
{ TagType::Artist, { " MyArtist; AC/DC " } },
}
};
AudioFileParserParameters params;
params.artistTagDelimiters = { "/" };
params.artistsToNotSplit = { "AC/DC" };
TestAudioFileParser parser{ params };
std::unique_ptr<Track> track{ parser.parseMetaData(testTags) };
ASSERT_EQ(track->artists.size(), 1);
EXPECT_EQ(track->artists[0].name, "MyArtist; AC/DC");
EXPECT_EQ(track->artistDisplayName, "MyArtist; AC/DC");
}
TEST(AudioFileParser, customDelimiters_foundInArtist)
{
const TestTagReader testTags{
@@ -740,4 +822,4 @@ namespace lms::metadata
doTest("2020/01", core::PartialDateTime{ 2020, 1 });
doTest("2020", core::PartialDateTime{ 2020 });
}
} // namespace lms::metadata
} // namespace lms::metadata::tests
+67 -4
View File
@@ -18,13 +18,12 @@
*/
#include <map>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include "ITagReader.hpp"
namespace lms::metadata
namespace lms::metadata::tests
{
class TestTagReader : public ITagReader
{
@@ -45,6 +44,9 @@ namespace lms::metadata
: _tags{ std::move(tags) }
{
}
~TestTagReader() override = default;
TestTagReader(const TestTagReader&) = delete;
TestTagReader& operator=(const TestTagReader&) = delete;
void setPerformersTags(Performers&& performers)
{
@@ -103,4 +105,65 @@ namespace lms::metadata
ExtraUserTags _extraUserTags;
LyricsTags _lyricsTags;
};
} // namespace lms::metadata
inline std::unique_ptr<ITagReader> createDefaultPopulatedTestTagReader()
{
std::unique_ptr<TestTagReader> testTags{ std::make_unique<TestTagReader>(
TestTagReader::Tags{
{ TagType::AcoustID, { "e987a441-e134-4960-8019-274eddacc418" } },
{ TagType::Advisory, { "2" } },
{ TagType::Album, { "MyAlbum" } },
{ TagType::AlbumSortOrder, { "MyAlbumSortName" } },
{ TagType::Artist, { "MyArtist1 & MyArtist2" } },
{ TagType::Artists, { "MyArtist1", "MyArtist2" } },
{ TagType::ArtistSortOrder, { "MyArtist1SortName", "MyArtist2SortName" } },
{ TagType::AlbumArtist, { "MyAlbumArtist1 & MyAlbumArtist2" } },
{ TagType::AlbumArtists, { "MyAlbumArtist1", "MyAlbumArtist2" } },
{ TagType::AlbumArtistsSortOrder, { "MyAlbumArtist1SortName", "MyAlbumArtist2SortName" } },
{ TagType::AlbumComment, { "MyAlbumComment" } },
{ TagType::Barcode, { "MyBarcode" } },
{ TagType::Comment, { "Comment1", "Comment2" } },
{ TagType::Compilation, { "1" } },
{ TagType::Composer, { "MyComposer1", "MyComposer2" } },
{ TagType::ComposerSortOrder, { "MyComposerSortOrder1", "MyComposerSortOrder2" } },
{ TagType::Conductor, { "MyConductor1", "MyConductor2" } },
{ TagType::Copyright, { "MyCopyright" } },
{ TagType::CopyrightURL, { "MyCopyrightURL" } },
{ TagType::Date, { "2020/03/04" } },
{ TagType::DiscNumber, { "2" } },
{ TagType::DiscSubtitle, { "MySubtitle" } },
{ TagType::Genre, { "Genre1", "Genre2" } },
{ TagType::Grouping, { "Grouping1", "Grouping2" } },
{ TagType::Media, { "CD" } },
{ TagType::Mixer, { "MyMixer1", "MyMixer2" } },
{ TagType::Mood, { "Mood1", "Mood2" } },
{ TagType::MusicBrainzArtistID, { "9d2e0c8c-8c5e-4372-a061-590955eaeaae", "5e2cf87f-c8d7-4504-8a86-954dc0840229" } },
{ TagType::MusicBrainzTrackID, { "0afb190a-6735-46df-a16d-199f48206e4a" } },
{ TagType::MusicBrainzReleaseArtistID, { "6fbf097c-1487-43e8-874b-50dd074398a7", "5ed3d6b3-2aed-4a03-828c-3c4d4f7406e1" } },
{ TagType::MusicBrainzReleaseID, { "3fa39992-b786-4585-a70e-85d5cc15ef69" } },
{ TagType::MusicBrainzReleaseGroupID, { "5b1a5a44-8420-4426-9b86-d25dc8d04838" } },
{ TagType::MusicBrainzRecordingID, { "bd3fc666-89de-4ac8-93f6-2dbf028ad8d5" } },
{ TagType::Producer, { "MyProducer1", "MyProducer2" } },
{ TagType::Remixer, { "MyRemixer1", "MyRemixer2" } },
{ TagType::RecordLabel, { "Label1", "Label2" } },
{ TagType::ReleaseCountry, { "MyCountry1", "MyCountry2" } },
{ TagType::Language, { "Language1", "Language2" } },
{ TagType::Lyricist, { "MyLyricist1", "MyLyricist2" } },
{ TagType::OriginalReleaseDate, { "2019/02/03" } },
{ TagType::ReleaseType, { "Album", "Compilation" } },
{ TagType::ReplayGainTrackGain, { "-0.33" } },
{ TagType::ReplayGainAlbumGain, { "-0.5" } },
{ TagType::TrackTitle, { "MyTitle" } },
{ TagType::TrackNumber, { "7" } },
{ TagType::TotalTracks, { "12" } },
{ TagType::TotalDiscs, { "3" } },
}) };
testTags->setExtraUserTags({ { "MY_AWESOME_TAG_A", { "MyTagValue1ForTagA", "MyTagValue2ForTagA" } },
{ "MY_AWESOME_TAG_B", { "MyTagValue1ForTagB", "MyTagValue2ForTagB" } } });
testTags->setPerformersTags({ { "RoleA", { "MyPerformer1ForRoleA", "MyPerformer2ForRoleA" } },
{ "RoleB", { "MyPerformer1ForRoleB", "MyPerformer2ForRoleB" } } });
testTags->setLyricsTags({ { "eng", "[00:00.00]First line\n[00:01.00]Second line" } });
return testTags;
}
} // namespace lms::metadata::tests