From 38efdfaf870c2aee960956895250b74b76ddf5ed Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 8 Dec 2024 11:53:31 +0100 Subject: [PATCH] Prefer first embedded image that contains the 'front' keyword --- src/libs/av/impl/AudioFile.cpp | 4 +- src/libs/av/include/av/IAudioFile.hpp | 4 +- src/libs/core/impl/String.cpp | 12 +++++ src/libs/core/include/core/String.hpp | 1 + src/libs/core/test/String.cpp | 12 +++++ .../services/artwork/impl/ArtworkService.cpp | 45 +++++++++++++++---- 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index bd0b6504..593ebbe4 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -281,9 +281,7 @@ namespace lms::av const AVPacket& pkt{ avstream->attached_pic }; - picture.data = reinterpret_cast(pkt.data); - picture.dataSize = pkt.size; - + picture.data = std::span{ reinterpret_cast(pkt.data), static_cast(pkt.size) }; func(picture, metadata); } } diff --git a/src/libs/av/include/av/IAudioFile.hpp b/src/libs/av/include/av/IAudioFile.hpp index 244fef58..4a57b0c2 100644 --- a/src/libs/av/include/av/IAudioFile.hpp +++ b/src/libs/av/include/av/IAudioFile.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -60,8 +61,7 @@ namespace lms::av struct Picture { std::string mimeType; - const std::byte* data{}; // valid as long as IAudioFile exists - std::size_t dataSize{}; + std::span data; // valid as long as IAudioFile exists }; struct ContainerInfo diff --git a/src/libs/core/impl/String.cpp b/src/libs/core/impl/String.cpp index 9a736474..5677b34c 100644 --- a/src/libs/core/impl/String.cpp +++ b/src/libs/core/impl/String.cpp @@ -321,6 +321,18 @@ namespace lms::core::stringUtils return true; } + std::string_view::size_type stringCaseInsensitiveContains(std::string_view str, std::string_view strtoFind) + { + if (str.empty() && strtoFind.empty()) + return true; // same as std + + const auto it{ std::search( + std::cbegin(str), std::cend(str), + std::cbegin(strtoFind), std::cend(strtoFind), + [](char chA, char chB) { return std::tolower(chA) == std::tolower(chB); }) }; + return (it != std::cend(str)); + } + void capitalize(std::string& str) { for (auto it{ std::begin(str) }; it != std::end(str); ++it) diff --git a/src/libs/core/include/core/String.hpp b/src/libs/core/include/core/String.hpp index 867e73e6..652ac009 100644 --- a/src/libs/core/include/core/String.hpp +++ b/src/libs/core/include/core/String.hpp @@ -62,6 +62,7 @@ namespace lms::core::stringUtils [[nodiscard]] std::string bufferToString(std::span data); [[nodiscard]] bool stringCaseInsensitiveEqual(std::string_view strA, std::string_view strB); + [[nodiscard]] std::string_view::size_type stringCaseInsensitiveContains(std::string_view str, std::string_view strtoFind); void capitalize(std::string& str); diff --git a/src/libs/core/test/String.cpp b/src/libs/core/test/String.cpp index 3d66bb99..7bae9e3e 100644 --- a/src/libs/core/test/String.cpp +++ b/src/libs/core/test/String.cpp @@ -322,4 +322,16 @@ namespace lms::core::stringUtils::tests EXPECT_FALSE(stringEndsWith("FooBar", "1FooBar")); EXPECT_FALSE(stringEndsWith("FooBar", "R")); } + + TEST(StringUtils, stringCaseInsensitiveContains) + { + EXPECT_TRUE(stringCaseInsensitiveContains("FooBar", "Bar")); + EXPECT_TRUE(stringCaseInsensitiveContains("FooBar", "bar")); + EXPECT_TRUE(stringCaseInsensitiveContains("FooBar", "Foo")); + EXPECT_TRUE(stringCaseInsensitiveContains("FooBar", "foo")); + EXPECT_FALSE(stringCaseInsensitiveContains("something", "foo")); + EXPECT_TRUE(stringCaseInsensitiveContains("FooBar", "")); + EXPECT_TRUE(stringCaseInsensitiveContains("", "")); + EXPECT_FALSE(stringCaseInsensitiveContains("", "Foo")); + } } // namespace lms::core::stringUtils::tests \ No newline at end of file diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index e9f7f01e..2612be66 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -19,12 +19,15 @@ #include "ArtworkService.hpp" +#include +#include #include #include "av/IAudioFile.hpp" #include "av/Types.hpp" #include "core/IConfig.hpp" #include "core/ILogger.hpp" +#include "core/String.hpp" #include "core/Utils.hpp" #include "database/Artist.hpp" #include "database/Db.hpp" @@ -68,21 +71,47 @@ namespace lms::cover std::unique_ptr ArtworkService::getFromAvMediaFile(const av::IAudioFile& input, std::optional width) const { + struct CandidatePicture + { + av::Picture picture; + bool isFront{}; + std::size_t index; + + // > means is better candidate + bool operator>(const CandidatePicture& other) const + { + if (!isFront && other.isFront) + return false; + if (isFront && !other.isFront) + return true; + + return index < other.index; + } + }; + + auto metadataHasFrontKeyword{ [](const av::IAudioFile::MetadataMap& metadata) { + return std::any_of(std::cbegin(metadata), std::cend(metadata), [](const auto& keyValue) { return core::stringUtils::stringCaseInsensitiveContains(keyValue.second, "front"); }); + } }; + + std::vector candidatePictures; + std::size_t pictureIndex{}; + input.visitAttachedPictures([&](const av::Picture& picture, const av::IAudioFile::MetadataMap& metadata) { + candidatePictures.emplace_back(picture, metadataHasFrontKeyword(metadata), pictureIndex++); + }); + std::stable_sort(std::begin(candidatePictures), std::end(candidatePictures), std::greater<>()); + std::unique_ptr image; - - input.visitAttachedPictures([&](const av::Picture& picture, const av::IAudioFile::MetadataMap& /* metadata */) { - if (image) - return; - + for (const CandidatePicture& candidatePicture : candidatePictures) + { try { if (!width) { - image = image::readImage(std::span{ picture.data, picture.dataSize }, picture.mimeType); + image = image::readImage(candidatePicture.picture.data, candidatePicture.picture.mimeType); } else { - auto rawImage{ image::decodeImage(std::span{ picture.data, picture.dataSize }) }; + auto rawImage{ image::decodeImage(candidatePicture.picture.data) }; rawImage->resize(*width); image = image::encodeToJPEG(*rawImage, _jpegQuality); } @@ -91,7 +120,7 @@ namespace lms::cover { LMS_LOG(COVER, ERROR, "Cannot read embedded cover: " << e.what()); } - }); + } return image; }