From c73ff7dc033ed70b46b6ff1cad173c5c8f87ff09 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 18 Jan 2025 16:51:31 +0100 Subject: [PATCH] Use ENCODINGTIME to set the added time, if present, otherwise use the last modified time of the file (only during the first import), fixes #595 --- src/libs/core/impl/String.cpp | 6 +++++ src/libs/core/include/core/String.hpp | 2 ++ src/libs/core/test/String.cpp | 11 ++++++-- src/libs/metadata/impl/AvFormatTagReader.cpp | 1 + src/libs/metadata/impl/ITagReader.hpp | 1 + src/libs/metadata/impl/Parser.cpp | 8 ++++++ src/libs/metadata/impl/TagLibTagReader.cpp | 1 + src/libs/metadata/include/metadata/Types.hpp | 2 ++ src/libs/metadata/test/Parser.cpp | 25 ++++++++++++++++++- .../impl/scanners/AudioFileScanner.cpp | 5 +++- src/tools/metadata/LmsMetadata.cpp | 11 +++++--- 11 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/libs/core/impl/String.cpp b/src/libs/core/impl/String.cpp index 5677b34c..3be9817b 100644 --- a/src/libs/core/impl/String.cpp +++ b/src/libs/core/impl/String.cpp @@ -476,6 +476,12 @@ namespace lms::core::stringUtils return date.toString("yyyy-MM-dd").toUTF8(); } + Wt::WDateTime fromISO8601String(std::string_view dateTime) + { + // assume UTC + return Wt::WDateTime::fromString(Wt::WString{ std::string{ dateTime } }, "yyyy-MM-ddThh:mm:ss.zzz"); + } + std::string formatTimestamp(std::chrono::milliseconds timestamp) { using namespace std::chrono; diff --git a/src/libs/core/include/core/String.hpp b/src/libs/core/include/core/String.hpp index 652ac009..31542808 100644 --- a/src/libs/core/include/core/String.hpp +++ b/src/libs/core/include/core/String.hpp @@ -116,6 +116,8 @@ namespace lms::core::stringUtils [[nodiscard]] std::string toISO8601String(const Wt::WDateTime& dateTime); [[nodiscard]] std::string toISO8601String(const Wt::WDate& date); + [[nodiscard]] Wt::WDateTime fromISO8601String(std::string_view dateTime); + // to "[minutes:seconds.milliseconds]" std::string formatTimestamp(std::chrono::milliseconds timestamp); } // namespace lms::core::stringUtils \ No newline at end of file diff --git a/src/libs/core/test/String.cpp b/src/libs/core/test/String.cpp index 7bae9e3e..a1fa628e 100644 --- a/src/libs/core/test/String.cpp +++ b/src/libs/core/test/String.cpp @@ -299,18 +299,25 @@ namespace lms::core::stringUtils::tests } } - TEST(Stringutils, date) + TEST(Stringutils, DateToString) { const Wt::WDate date{ 2020, 01, 03 }; EXPECT_EQ(toISO8601String(date), "2020-01-03"); } - TEST(Stringutils, dateTime) + TEST(Stringutils, DateTimeToString) { const Wt::WDateTime dateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } }; EXPECT_EQ(toISO8601String(dateTime), "2020-01-03T09:08:11.075"); } + TEST(Stringutils, DateTimeFromString) + { + EXPECT_EQ(fromISO8601String("2020-01-03T09:08:11.075"), (Wt::WDateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } })); + EXPECT_EQ(fromISO8601String("2020-01-03"), Wt::WDateTime{}); + EXPECT_EQ(fromISO8601String(""), Wt::WDateTime{}); + } + TEST(StringUtils, stringEndsWith) { EXPECT_TRUE(stringEndsWith("FooBar", "Bar")); diff --git a/src/libs/metadata/impl/AvFormatTagReader.cpp b/src/libs/metadata/impl/AvFormatTagReader.cpp index a7a330a7..14a0da76 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.cpp +++ b/src/libs/metadata/impl/AvFormatTagReader.cpp @@ -64,6 +64,7 @@ namespace lms::metadata { TagType::DiscNumber, { "TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET" } }, { TagType::DiscSubtitle, { "TSST", "DISCSUBTITLE", "SETSUBTITLE" } }, { TagType::EncodedBy, { "ENCODEDBY" } }, + { TagType::EncodingTime, { "ENCODINGTIME", "TDEN" } }, { TagType::Engineer, { "ENGINEER" } }, { TagType::GaplessPlayback, { "GAPLESSPLAYBACK" } }, { TagType::Genre, { "GENRE" } }, diff --git a/src/libs/metadata/impl/ITagReader.hpp b/src/libs/metadata/impl/ITagReader.hpp index 55c37e8c..ce120b94 100644 --- a/src/libs/metadata/impl/ITagReader.hpp +++ b/src/libs/metadata/impl/ITagReader.hpp @@ -65,6 +65,7 @@ namespace lms::metadata DiscSubtitle, EncodedBy, EncoderSettings, + EncodingTime, Engineer, GaplessPlayback, Genre, diff --git a/src/libs/metadata/impl/Parser.cpp b/src/libs/metadata/impl/Parser.cpp index 153652ea..4e8e0948 100644 --- a/src/libs/metadata/impl/Parser.cpp +++ b/src/libs/metadata/impl/Parser.cpp @@ -390,6 +390,14 @@ namespace lms::metadata } track.advisory = getAdvisory(tagReader); + + if (const auto encodingTime{ getTagValueAs(tagReader, TagType::EncodingTime) }) + { + if (auto dateTime{ core::stringUtils::fromISO8601String(*encodingTime) }; dateTime.isValid()) + track.encodingTime = dateTime; + else if (const Wt::WDate date{ utils::parseDate(*encodingTime) }; date.isValid()) + track.encodingTime = Wt::WDateTime{ date }; + } track.lyrics = getLyrics(tagReader); // no custom delimiter on lyrics track.comments = getTagValuesAs(tagReader, TagType::Comment, {} /* no custom delimiter on comments */); track.copyright = getTagValueAs(tagReader, TagType::Copyright).value_or(""); diff --git a/src/libs/metadata/impl/TagLibTagReader.cpp b/src/libs/metadata/impl/TagLibTagReader.cpp index 0acf72b1..18b6e582 100644 --- a/src/libs/metadata/impl/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/TagLibTagReader.cpp @@ -96,6 +96,7 @@ namespace lms::metadata { TagType::DiscSubtitle, { "DISCSUBTITLE", "SETSUBTITLE" } }, { TagType::EncodedBy, { "ENCODEDBY" } }, { TagType::Engineer, { "ENGINEER" } }, + { TagType::EncodingTime, { "ENCODINGTIME" } }, { TagType::GaplessPlayback, { "GAPLESSPLAYBACK" } }, { TagType::Genre, { "GENRE" } }, { TagType::Grouping, { "GROUPING", "ALBUMGROUPING" } }, diff --git a/src/libs/metadata/include/metadata/Types.hpp b/src/libs/metadata/include/metadata/Types.hpp index 6c044210..6843cac0 100644 --- a/src/libs/metadata/include/metadata/Types.hpp +++ b/src/libs/metadata/include/metadata/Types.hpp @@ -27,6 +27,7 @@ #include #include +#include #include "core/UUID.hpp" @@ -124,6 +125,7 @@ namespace lms::metadata std::optional originalYear{}; Wt::WDate originalDate; std::optional advisory; + Wt::WDateTime encodingTime; bool hasCover{}; std::optional acoustID; std::string copyright; diff --git a/src/libs/metadata/test/Parser.cpp b/src/libs/metadata/test/Parser.cpp index 6ee0d2af..e9df1e70 100644 --- a/src/libs/metadata/test/Parser.cpp +++ b/src/libs/metadata/test/Parser.cpp @@ -17,11 +17,12 @@ * along with LMS. If not, see . */ -#include #include #include +#include + #include "Parser.hpp" #include "TestTagReader.hpp" @@ -611,4 +612,26 @@ namespace lms::metadata doTest("", std::nullopt); doTest("3", std::nullopt); } + + TEST(Parser, encodingTime) + { + auto doTest = [](std::string_view value, Wt::WDateTime expectedValue) { + const TestTagReader testTags{ + { + { TagType::EncodingTime, { value } }, + } + }; + + Parser parser; + std::unique_ptr track{ Parser{}.parse(testTags) }; + + ASSERT_EQ(track->encodingTime, expectedValue) << "Value = '" << value << "'"; + }; + + doTest("", Wt::WDateTime{}); + doTest("foo", Wt::WDateTime{}); + doTest("2020-01-03T09:08:11.075", Wt::WDateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } }); + doTest("2020-01-03", Wt::WDateTime{ Wt::WDate{ 2020, 01, 03 } }); + doTest("2020/01/03", Wt::WDateTime{ Wt::WDate{ 2020, 01, 03 } }); + } } // namespace lms::metadata diff --git a/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp b/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp index 7d6c7b69..b275f0f0 100644 --- a/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp +++ b/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp @@ -479,6 +479,7 @@ namespace lms::scanner { track = dbSession.create(); track.modify()->setAbsoluteFilePath(_file); + track.modify()->setAddedTime(fileInfo->lastWriteTime); // may be erased by encodingTime added = true; } @@ -496,6 +497,9 @@ namespace lms::scanner track.modify()->setFileSize(fileInfo->fileSize); track.modify()->setLastWriteTime(fileInfo->lastWriteTime); + if (_parsedTrack->encodingTime.isValid()) + track.modify()->setAddedTime(_parsedTrack->encodingTime); + db::MediaLibrary::pointer mediaLibrary{ db::MediaLibrary::find(dbSession, _mediaLibrary.id) }; // may be null if settings are updated in // => next scan will correct this track.modify()->setMediaLibrary(mediaLibrary); db::Directory::pointer directory{ utils::getOrCreateDirectory(dbSession, _file.parent_path(), mediaLibrary) }; @@ -548,7 +552,6 @@ namespace lms::scanner track.modify()->setDiscSubtitle(_parsedTrack->medium ? _parsedTrack->medium->name : ""); track.modify()->setClusters(getOrCreateClusters(dbSession, *_parsedTrack)); track.modify()->setName(title); - track.modify()->setAddedTime(Wt::WDateTime::currentDateTime()); track.modify()->setTrackNumber(_parsedTrack->position); track.modify()->setDiscNumber(_parsedTrack->medium ? _parsedTrack->medium->position : std::nullopt); track.modify()->setDate(_parsedTrack->date); diff --git a/src/tools/metadata/LmsMetadata.cpp b/src/tools/metadata/LmsMetadata.cpp index 72a7a387..b6bb6168 100644 --- a/src/tools/metadata/LmsMetadata.cpp +++ b/src/tools/metadata/LmsMetadata.cpp @@ -202,9 +202,6 @@ namespace lms::metadata for (const Artist& artist : track->remixerArtists) std::cout << "Remixer: " << artist << std::endl; - if (track->medium) - std::cout << "Medium: " << *track->medium; - std::cout << "Title: " << track->title << std::endl; if (track->mbid) @@ -271,6 +268,12 @@ namespace lms::metadata if (track->advisory) std::cout << "Advisory: " << *track->advisory << std::endl; + if (track->encodingTime.isValid()) + std::cout << "Encoding time: " << core::stringUtils::toISO8601String(track->encodingTime) << std::endl; + + if (track->medium) + std::cout << "Medium: " << *track->medium; + std::cout << std::endl; } } // namespace lms::metadata @@ -445,4 +448,4 @@ int main(int argc, char* argv[]) } return EXIT_SUCCESS; -} +} \ No newline at end of file