Fixed lyrics parsing on very long tags
This commit is contained in:
@@ -34,50 +34,53 @@ namespace lms::metadata
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::string_view getSubmatchString(const std::csub_match& submatch)
|
// Parse a single line with a tag like [ar: Artist] and set the appropriate fields in the Lyrics object
|
||||||
|
bool parseTag(std::string_view line, Lyrics& lyrics)
|
||||||
{
|
{
|
||||||
assert(submatch.matched);
|
if (line.empty())
|
||||||
return std::string_view{ submatch.first, static_cast<std::string_view::size_type>(submatch.length()) };
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// Parse a single line with ID tags like [ar: Artist] and set the appropriate fields in the Lyrics object
|
if (line.front() != '[' || line.back() != ']') // consider lines are trimmed
|
||||||
bool parseIDTag(std::string_view line, Lyrics& lyrics)
|
return false;
|
||||||
{
|
|
||||||
static const std::regex idTagRegex{ R"(^\[([a-zA-Z_]+):(.+?)\])" };
|
|
||||||
std::cmatch match;
|
|
||||||
|
|
||||||
if (std::regex_search(line.data(), line.data() + line.size(), match, idTagRegex))
|
const auto separator{ line.find(':') };
|
||||||
|
if (separator == std::string_view::npos)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const std::string_view tagType{ core::stringUtils::stringTrim(line.substr(1, separator - 1)) };
|
||||||
|
const std::string_view tagValue{ core::stringUtils::stringTrim(line.substr(separator + 1, line.size() - separator - 2)) };
|
||||||
|
|
||||||
|
if (tagType.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// check for timestamps
|
||||||
|
if (std::any_of(tagType.begin(), tagType.end(), [](char c) { return std::isdigit(c); }))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (tagType == "ar")
|
||||||
{
|
{
|
||||||
std::string_view tagType{ getSubmatchString(match[1]) };
|
lyrics.displayArtist = tagValue;
|
||||||
std::string_view tagValue{ core::stringUtils::stringTrim(getSubmatchString(match[2])) };
|
|
||||||
|
|
||||||
if (tagType == "ar")
|
|
||||||
{
|
|
||||||
lyrics.displayArtist = tagValue;
|
|
||||||
}
|
|
||||||
else if (tagType == "al")
|
|
||||||
{
|
|
||||||
lyrics.displayAlbum = tagValue;
|
|
||||||
}
|
|
||||||
else if (tagType == "ti")
|
|
||||||
{
|
|
||||||
lyrics.displayTitle = tagValue;
|
|
||||||
}
|
|
||||||
else if (tagType == "la")
|
|
||||||
{
|
|
||||||
lyrics.language = tagValue;
|
|
||||||
}
|
|
||||||
else if (tagType == "offset")
|
|
||||||
{
|
|
||||||
if (const auto value{ core::stringUtils::readAs<int>(tagValue) })
|
|
||||||
lyrics.offset = std::chrono::milliseconds{ *value };
|
|
||||||
}
|
|
||||||
// not interrested by other tags like 'duration', 'id', etc.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
else if (tagType == "al")
|
||||||
|
{
|
||||||
|
lyrics.displayAlbum = tagValue;
|
||||||
|
}
|
||||||
|
else if (tagType == "ti")
|
||||||
|
{
|
||||||
|
lyrics.displayTitle = tagValue;
|
||||||
|
}
|
||||||
|
else if (tagType == "la")
|
||||||
|
{
|
||||||
|
lyrics.language = tagValue;
|
||||||
|
}
|
||||||
|
else if (tagType == "offset")
|
||||||
|
{
|
||||||
|
if (const auto value{ core::stringUtils::readAs<int>(tagValue) })
|
||||||
|
lyrics.offset = std::chrono::milliseconds{ *value };
|
||||||
|
}
|
||||||
|
// not interrested by other tags like 'duration', 'id', etc.
|
||||||
|
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse timestamps from a line and return the associated times in milliseconds
|
// Parse timestamps from a line and return the associated times in milliseconds
|
||||||
@@ -172,7 +175,7 @@ namespace lms::metadata
|
|||||||
if (currentState == State::None && trimmedLine.empty())
|
if (currentState == State::None && trimmedLine.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (parseIDTag(trimmedLine, lyrics))
|
if (parseTag(trimmedLine, lyrics))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
extractTimestamps(trimmedLine, timestamps);
|
extractTimestamps(trimmedLine, timestamps);
|
||||||
|
|||||||
@@ -72,6 +72,34 @@ namespace lms::metadata::tests
|
|||||||
EXPECT_EQ(lyrics.synchronizedLines.find(9s + 160ms)->second, "I, I just woke up from a dream");
|
EXPECT_EQ(lyrics.synchronizedLines.find(9s + 160ms)->second, "I, I just woke up from a dream");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Lyrics, tagsWithSpaces)
|
||||||
|
{
|
||||||
|
std::istringstream is{ R"([al: dqsxdkbu ]
|
||||||
|
[00:09.16]I, I just woke up from a dream)" };
|
||||||
|
|
||||||
|
const Lyrics lyrics{ parseLyrics(is) };
|
||||||
|
|
||||||
|
EXPECT_EQ(lyrics.unsynchronizedLines.size(), 0);
|
||||||
|
ASSERT_EQ(lyrics.synchronizedLines.size(), 1);
|
||||||
|
EXPECT_EQ(lyrics.displayAlbum, "dqsxdkbu");
|
||||||
|
ASSERT_TRUE(lyrics.synchronizedLines.contains(9s + 160ms));
|
||||||
|
EXPECT_EQ(lyrics.synchronizedLines.find(9s + 160ms)->second, "I, I just woke up from a dream");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Lyrics, tagIDsWithSpaces)
|
||||||
|
{
|
||||||
|
std::istringstream is{ R"([ al : dqsxdkbu ]
|
||||||
|
[00:09.16]I, I just woke up from a dream)" };
|
||||||
|
|
||||||
|
const Lyrics lyrics{ parseLyrics(is) };
|
||||||
|
|
||||||
|
EXPECT_EQ(lyrics.unsynchronizedLines.size(), 0);
|
||||||
|
ASSERT_EQ(lyrics.synchronizedLines.size(), 1);
|
||||||
|
EXPECT_EQ(lyrics.displayAlbum, "dqsxdkbu");
|
||||||
|
ASSERT_TRUE(lyrics.synchronizedLines.contains(9s + 160ms));
|
||||||
|
EXPECT_EQ(lyrics.synchronizedLines.find(9s + 160ms)->second, "I, I just woke up from a dream");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Lyrics, tagAtTheEndOfLyrics)
|
TEST(Lyrics, tagAtTheEndOfLyrics)
|
||||||
{
|
{
|
||||||
std::istringstream is{ R"([00:03.30]Ooh, ooh
|
std::istringstream is{ R"([00:03.30]Ooh, ooh
|
||||||
|
|||||||
Reference in New Issue
Block a user