diff --git a/src/libs/metadata/impl/Lyrics.cpp b/src/libs/metadata/impl/Lyrics.cpp index b7fe3e18..ee283d0e 100644 --- a/src/libs/metadata/impl/Lyrics.cpp +++ b/src/libs/metadata/impl/Lyrics.cpp @@ -34,50 +34,53 @@ namespace lms::metadata 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); - return std::string_view{ submatch.first, static_cast(submatch.length()) }; - } + if (line.empty()) + return false; - // Parse a single line with ID tags like [ar: Artist] and set the appropriate fields in the Lyrics object - bool parseIDTag(std::string_view line, Lyrics& lyrics) - { - static const std::regex idTagRegex{ R"(^\[([a-zA-Z_]+):(.+?)\])" }; - std::cmatch match; + if (line.front() != '[' || line.back() != ']') // consider lines are trimmed + return false; - 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]) }; - 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(tagValue) }) - lyrics.offset = std::chrono::milliseconds{ *value }; - } - // not interrested by other tags like 'duration', 'id', etc. - - return true; + 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(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 @@ -172,7 +175,7 @@ namespace lms::metadata if (currentState == State::None && trimmedLine.empty()) continue; - if (parseIDTag(trimmedLine, lyrics)) + if (parseTag(trimmedLine, lyrics)) continue; extractTimestamps(trimmedLine, timestamps); diff --git a/src/libs/metadata/test/Lyrics.cpp b/src/libs/metadata/test/Lyrics.cpp index ee859f88..795391eb 100644 --- a/src/libs/metadata/test/Lyrics.cpp +++ b/src/libs/metadata/test/Lyrics.cpp @@ -72,6 +72,34 @@ namespace lms::metadata::tests 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) { std::istringstream is{ R"([00:03.30]Ooh, ooh