From b45513a61110032ed6fcee3d603071ad60f848b5 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 2 Mar 2024 23:05:20 +0100 Subject: [PATCH] Added album sort name in subsonic responses, fixes #419 --- src/libs/database/impl/Migration.cpp | 20 +++++++++++++++++++ src/libs/database/impl/Migration.hpp | 6 ++++-- .../database/include/database/Release.hpp | 6 +++++- src/libs/metadata/impl/AvFormatTagReader.cpp | 2 +- src/libs/metadata/impl/Parser.cpp | 1 + .../metadata/include/metadata/IParser.hpp | 1 + src/libs/metadata/test/Parser.cpp | 2 ++ .../scanner/impl/ScanStepScanFiles.cpp | 2 ++ .../impl/listenbrainz/ListensSynchronizer.cpp | 2 +- src/libs/subsonic/impl/Utils.cpp | 2 +- src/libs/subsonic/impl/Utils.hpp | 3 ++- src/libs/subsonic/impl/responses/Album.cpp | 2 ++ src/lms/ui/MediaPlayer.cpp | 2 +- src/lms/ui/Utils.cpp | 5 +++-- src/lms/ui/explore/ReleaseView.cpp | 4 ++-- src/tools/metadata/LmsMetadata.cpp | 10 ++++++---- .../recommendation/LmsRecommendation.cpp | 4 ++-- 17 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 7ac08ba5..9b006d58 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -30,6 +30,15 @@ namespace Database { + namespace + { + static constexpr Version LMS_DATABASE_VERSION{ 53 }; + } + + VersionInfo::VersionInfo() + : _version{ LMS_DATABASE_VERSION } + {} + VersionInfo::pointer VersionInfo::getOrCreate(Session& session) { session.checkWriteTransaction(); @@ -401,6 +410,16 @@ SELECT session.getDboSession().execute("ALTER TABLE scan_settings ADD default_tag_delimiters TEXT NOT NULL DEFAULT ''"); } + void migrateFromV52(Session& session) + { + // Add sort name for releases + session.getDboSession().execute("ALTER TABLE release ADD sort_name TEXT NOT NULL DEFAULT ''"); + + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession().execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } + + void doDbMigration(Session& session) { static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; @@ -430,6 +449,7 @@ SELECT {49, migrateFromV49}, {50, migrateFromV50}, {51, migrateFromV51}, + {52, migrateFromV52}, }; { diff --git a/src/libs/database/impl/Migration.hpp b/src/libs/database/impl/Migration.hpp index 0d2d0d80..559c7f29 100644 --- a/src/libs/database/impl/Migration.hpp +++ b/src/libs/database/impl/Migration.hpp @@ -26,12 +26,14 @@ namespace Database class Session; using Version = std::size_t; - static constexpr Version LMS_DATABASE_VERSION{ 52 }; + class VersionInfo { public: using pointer = Wt::Dbo::ptr; + VersionInfo(); + static VersionInfo::pointer getOrCreate(Session& session); static VersionInfo::pointer get(Session& session); @@ -45,7 +47,7 @@ namespace Database } private: - int _version{ LMS_DATABASE_VERSION }; + int _version; }; namespace Migration diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index 40c9d80a..e0cea3d0 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -143,7 +143,8 @@ namespace Database std::size_t getMeanBitrate() const; // Accessors - const std::string& getName() const { return _name; } + std::string_view getName() const { return _name; } + std::string_view getSortName() const { return _sortName; } std::optional getMBID() const { return UUID::fromString(_MBID); } std::optional getTotalDisc() const { return _totalDisc; } std::size_t getDiscCount() const; // may not be total disc (if incomplete for example) @@ -157,6 +158,7 @@ namespace Database // Setters void setName(std::string_view name) { _name = name; } + void setSortName(std::string_view sortName) { _sortName = sortName; } void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } void setTotalDisc(std::optional totalDisc) { _totalDisc = totalDisc; } void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; } @@ -173,6 +175,7 @@ namespace Database void persist(Action& a) { Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _sortName, "sort_name"); Wt::Dbo::field(a, _MBID, "mbid"); Wt::Dbo::field(a, _totalDisc, "total_disc"); Wt::Dbo::field(a, _artistDisplayName, "artist_display_name"); @@ -191,6 +194,7 @@ namespace Database static constexpr std::size_t _maxNameLength{ 256 }; std::string _name; + std::string _sortName; std::string _MBID; std::optional _totalDisc{}; std::string _artistDisplayName; diff --git a/src/libs/metadata/impl/AvFormatTagReader.cpp b/src/libs/metadata/impl/AvFormatTagReader.cpp index 85091942..0a81fec3 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.cpp +++ b/src/libs/metadata/impl/AvFormatTagReader.cpp @@ -42,7 +42,7 @@ namespace MetaData { TagType::AlbumArtistSortOrder, { "ALBUMARTISTSORT", "TSO2" } }, { TagType::AlbumArtists, { "ALBUMARTISTS" } }, { TagType::AlbumArtistsSortOrder, { "ALBUMARTISTSSORT" } }, - { TagType::AlbumSortOrder, { "ALBUMSORT" } }, + { TagType::AlbumSortOrder, { "ALBUMSORT", "ALBUM-SORT" } }, { TagType::Arranger, { "ARRANGER" } }, { TagType::Artist, { "ARTIST" } }, { TagType::ArtistSortOrder, { "ARTISTSORT", "ARTIST-SORT", "WM/ARTISTSORTORDER" } }, diff --git a/src/libs/metadata/impl/Parser.cpp b/src/libs/metadata/impl/Parser.cpp index 698d3ef4..5ae90fd1 100644 --- a/src/libs/metadata/impl/Parser.cpp +++ b/src/libs/metadata/impl/Parser.cpp @@ -372,6 +372,7 @@ namespace MetaData release.emplace(); release->name = std::move(*releaseName); + release->sortName = getTagValueAs(tagReader, TagType::AlbumSortOrder).value_or(""); release->artistDisplayName = getTagValueAs(tagReader, TagType::AlbumArtist).value_or(""); // TODO try to join albumartists if present release->mbid = getTagValueAs(tagReader, TagType::MusicBrainzReleaseID); release->artists = getArtists(tagReader, { TagType::AlbumArtists, TagType::AlbumArtist }, { TagType::AlbumArtistsSortOrder, TagType::AlbumArtistSortOrder }, { TagType::MusicBrainzReleaseArtistID }, _artistTagDelimiters); diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index 6dfc6e10..412ad948 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -55,6 +55,7 @@ namespace MetaData { std::optional mbid; std::string name; + std::string sortName; std::string artistDisplayName; std::vector artists; std::optional mediumCount; diff --git a/src/libs/metadata/test/Parser.cpp b/src/libs/metadata/test/Parser.cpp index 4fbcced0..2e645639 100644 --- a/src/libs/metadata/test/Parser.cpp +++ b/src/libs/metadata/test/Parser.cpp @@ -33,6 +33,7 @@ namespace MetaData { { TagType::AcoustID, { "e987a441-e134-4960-8019-274eddacc418" } }, { TagType::Album, { "MyAlbum" } }, + { TagType::AlbumSortOrder, { "MyAlbumSortName" } }, { TagType::Artist, { "MyArtist1 & MyArtist2" } }, { TagType::Artists, { "MyArtist1", "MyArtist2" } }, { TagType::ArtistSortOrder, { "MyArtist1SortName", "MyArtist2SortName" } }, @@ -195,6 +196,7 @@ namespace MetaData EXPECT_EQ(track->medium->release->mbid.value(), UUID::fromString("3fa39992-b786-4585-a70e-85d5cc15ef69")); EXPECT_EQ(track->medium->release->mediumCount, 3); EXPECT_EQ(track->medium->release->name, "MyAlbum"); + EXPECT_EQ(track->medium->release->sortName, "MyAlbumSortName"); { std::vector expectedReleaseTypes{ "Album", "Compilation" }; EXPECT_EQ(track->medium->release->releaseTypes, expectedReleaseTypes); diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index 843b8792..c975bd51 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -129,6 +129,8 @@ namespace Scanner { if (release->getName() != releaseInfo.name) release.modify()->setName(releaseInfo.name); + if (release->getSortName() != releaseInfo.sortName) + release.modify()->setSortName(releaseInfo.sortName); if (release->getTotalDisc() != releaseInfo.mediumCount) release.modify()->setTotalDisc(releaseInfo.mediumCount); if (release->getArtistDisplayName() != releaseInfo.artistDisplayName) diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp index 95e851c5..ea04df81 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp @@ -97,7 +97,7 @@ namespace Scrobbling::ListenBrainz trackMetadata["artist_name"] = Wt::Json::Value{ std::string{ track->getArtistDisplayName() } }; trackMetadata["track_name"] = Wt::Json::Value{ track->getName() }; if (track->getRelease()) - trackMetadata["release_name"] = Wt::Json::Value{ track->getRelease()->getName() }; + trackMetadata["release_name"] = Wt::Json::Value{ std::string {track->getRelease()->getName()} }; Wt::Json::Object payload; payload["track_metadata"] = std::move(trackMetadata); diff --git a/src/libs/subsonic/impl/Utils.cpp b/src/libs/subsonic/impl/Utils.cpp index ec7a3b6f..2bef51be 100644 --- a/src/libs/subsonic/impl/Utils.cpp +++ b/src/libs/subsonic/impl/Utils.cpp @@ -33,7 +33,7 @@ namespace API::Subsonic::Utils throw NotImplementedGenericError{}; } - std::string makeNameFilesystemCompatible(const std::string& name) + std::string makeNameFilesystemCompatible(std::string_view name) { return StringUtils::replaceInString(name, "/", "_"); } diff --git a/src/libs/subsonic/impl/Utils.hpp b/src/libs/subsonic/impl/Utils.hpp index d94f11b8..0d85bce4 100644 --- a/src/libs/subsonic/impl/Utils.hpp +++ b/src/libs/subsonic/impl/Utils.hpp @@ -20,9 +20,10 @@ #pragma once #include +#include namespace API::Subsonic::Utils { void checkSetPasswordImplemented(); - std::string makeNameFilesystemCompatible(const std::string& name); + std::string makeNameFilesystemCompatible(std::string_view name); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index f570c959..b263acc4 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -106,6 +106,8 @@ namespace API::Subsonic return albumNode; // OpenSubsonic specific fields (must always be set) + albumNode.setAttribute("sortName", release->getSortName()); + if (!id3) albumNode.setAttribute("mediaType", "album"); diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index aeeca1a1..b1644471 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -289,7 +289,7 @@ namespace UserInterface if (track->getRelease()) { _release->setTextFormat(Wt::TextFormat::Plain); - _release->setText(Wt::WString::fromUTF8(track->getRelease()->getName())); + _release->setText(Wt::WString::fromUTF8(std::string{ track->getRelease()->getName() })); _release->setLink(Utils::createReleaseLink(track->getRelease())); } else diff --git a/src/lms/ui/Utils.cpp b/src/lms/ui/Utils.cpp index eb7111a1..b4f02e61 100644 --- a/src/lms/ui/Utils.cpp +++ b/src/lms/ui/Utils.cpp @@ -255,9 +255,10 @@ namespace UserInterface::Utils if (setText) { + std::string releaseName{ release->getName() }; res->setTextFormat(Wt::TextFormat::Plain); - res->setText(Wt::WString::fromUTF8(release->getName())); - res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain); + res->setText(Wt::WString::fromUTF8(releaseName)); + res->setToolTip(Wt::WString::fromUTF8(releaseName), Wt::TextFormat::Plain); } return res; diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 82dea623..0d83012c 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -238,14 +238,14 @@ namespace UserInterface if (!release) throw ReleaseNotFoundException{}; - LmsApp->setTitle(release->getName()); + LmsApp->setTitle(std::string{ release->getName() }); _releaseId = *releaseId; refreshCopyright(release); refreshLinks(release); refreshSimilarReleases(similarReleasesIds); - bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain); + bindString("name", Wt::WString::fromUTF8(std::string{ release->getName() }), Wt::TextFormat::Plain); Wt::WString year{ ReleaseHelpers::buildReleaseYearString(release->getYear(), release->getOriginalYear()) }; if (!year.empty()) diff --git a/src/tools/metadata/LmsMetadata.cpp b/src/tools/metadata/LmsMetadata.cpp index b9b467c8..bee33735 100644 --- a/src/tools/metadata/LmsMetadata.cpp +++ b/src/tools/metadata/LmsMetadata.cpp @@ -50,7 +50,10 @@ namespace os << release.name; if (release.mbid) - os << " (" << release.mbid->getAsString() << ")" << std::endl; + os << " (" << release.mbid->getAsString() << ")"; + if (!release.sortName.empty()) + os << " '" << release.sortName << "'"; + os << std::endl; if (release.mediumCount) std::cout << "\tMediumCount: " << *release.mediumCount << std::endl; @@ -61,9 +64,8 @@ namespace for (const MetaData::Artist& artist : release.artists) std::cout << "\tRelease artist: " << artist << std::endl; - std::cout << "Release types:" << std::endl; for (std::string_view releaseType : release.releaseTypes) - std::cout << "\t" << releaseType << std::endl; + std::cout << "\tRelease type: " << releaseType << std::endl; return os; } @@ -87,7 +89,7 @@ namespace std::cout << "\tReplay gain: " << *medium.replayGain << std::endl; if (medium.release) - std::cout << "Release: " << *medium.release << std::endl; + std::cout << "Release: " << *medium.release; return os; } diff --git a/src/tools/recommendation/LmsRecommendation.cpp b/src/tools/recommendation/LmsRecommendation.cpp index 2daeb002..c34a419c 100644 --- a/src/tools/recommendation/LmsRecommendation.cpp +++ b/src/tools/recommendation/LmsRecommendation.cpp @@ -57,7 +57,7 @@ static void dumpTracksRecommendation(Session session, Recommendation::IRecommend res += track->getName(); if (track->getRelease()) - res += " [" + track->getRelease()->getName() + "]"; + res += " [" + std::string{ track->getRelease()->getName() } + "]"; for (auto artist : track->getArtists({ TrackArtistLinkType::Artist })) res += " - " + artist->getName(); for (auto cluster : track->getClusters()) @@ -88,7 +88,7 @@ static void dumpReleasesRecommendation(Session session, Recommendation::IRecomme auto transaction{ session.createReadTransaction() }; Release::pointer release{ Release::find(session, releaseId) }; - return release->getName(); + return std::string{ release->getName() }; }; std::cout << "Processing release '" << releaseToString(releaseId) << "'" << std::endl;