Improve album and title disc, remastered, etc matching

Don't partial remove things like "(Mono / Remastered)".

Fixes #1387
This commit is contained in:
Jonas Kvinge 2024-03-02 19:48:19 +01:00
parent b365131363
commit 7f4c61b15a
14 changed files with 96 additions and 42 deletions

View File

@ -1681,7 +1681,7 @@ QString CollectionModel::PrettyYearAlbum(const int year, const QString &album) {
QString CollectionModel::PrettyAlbumDisc(const QString &album, const int disc) {
if (disc <= 0 || album.contains(Song::kAlbumRemoveDisc)) return TextOrUnknown(album);
if (disc <= 0 || Song::AlbumContainsDisc(album)) return TextOrUnknown(album);
else return TextOrUnknown(album) + " - (Disc " + QString::number(disc) + ")";
}
@ -1693,7 +1693,7 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
if (year <= 0) str = TextOrUnknown(album);
else str = QString::number(year) + " - " + TextOrUnknown(album);
if (!album.contains(Song::kAlbumRemoveDisc) && disc > 0) str += " - (Disc " + QString::number(disc) + ")";
if (!Song::AlbumContainsDisc(album) && disc > 0) str += " - (Disc " + QString::number(disc) + ")";
return str;

View File

@ -167,9 +167,20 @@ const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", ");
const QString Song::kFtsBindSpec = Utilities::Prepend(":", Song::kFtsColumns).join(", ");
const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).join(", ");
const QRegularExpression Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|([0-9]{1,4}) *Remaster|Explicit) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Remastered Version|([0-9]{1,4}) *Remaster) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const Song::RegularExpressionList Song::kAlbumDisc = Song::RegularExpressionList()
<< QRegularExpression("\\s+-*\\s*(Disc|CD)\\s*([0-9]{1,2})$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\(\\s*(Disc|CD)\\s*([0-9]{1,2})\\)$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\[\\s*(Disc|CD)\\s*([0-9]{1,2})\\]$", QRegularExpression::CaseInsensitiveOption);
const Song::RegularExpressionList Song::kAlbumMisc = Song::RegularExpressionList()
<< QRegularExpression("\\s+-*\\s*(Remastered|([0-9]{1,4})\\s*Remaster|Explicit)\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\(\\s*(Remastered|([0-9]{1,4})\\s*Remaster|Explicit)\\s*\\)\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\[\\s*(Remastered|([0-9]{1,4})\\s*Remaster|Explicit)\\s*\\]\\s*$", QRegularExpression::CaseInsensitiveOption);
const Song::RegularExpressionList Song::kTitleMisc = Song::RegularExpressionList()
<< QRegularExpression("\\s+-*\\s*(Remastered|Remastered Version|([0-9]{1,4})\\s*Remaster)\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\(\\s*(Remastered|Remastered Version|([0-9]{1,4})\\s*Remaster)\\s*\\)\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\[\\s*(Remastered|Remastered Version|([0-9]{1,4})\\s*Remaster)\\s*\\]\\s*$", QRegularExpression::CaseInsensitiveOption);
const QStringList Song::kArticles = QStringList() << "the " << "a " << "an ";
@ -1843,3 +1854,53 @@ size_t HashSimilar(const Song &song) {
// Should compare the same fields as function IsSimilar
return qHash(song.title().toLower()) ^ qHash(song.artist().toLower()) ^ qHash(song.album().toLower());
}
bool Song::ContainsRegexList(const QString &str, const RegularExpressionList &regex_list) {
for (const QRegularExpression &regex : regex_list) {
if (str.contains(regex)) return true;
}
return false;
}
QString Song::StripRegexList(QString str, const RegularExpressionList &regex_list) {
for (const QRegularExpression &regex : regex_list) {
str = str.remove(regex);
}
return str;
}
bool Song::AlbumContainsDisc(const QString &album) {
return ContainsRegexList(album, kAlbumDisc);
}
QString Song::AlbumRemoveDisc(const QString &album) {
return StripRegexList(album, kAlbumDisc);
}
QString Song::AlbumRemoveMisc(const QString &album) {
return StripRegexList(album, kAlbumMisc);
}
QString Song::AlbumRemoveDiscMisc(const QString &album) {
return StripRegexList(album, RegularExpressionList() << kAlbumDisc << kAlbumMisc);
}
QString Song::TitleRemoveMisc(const QString &title) {
return StripRegexList(title, kTitleMisc);
}

View File

@ -123,9 +123,10 @@ class Song {
static const QString kFtsBindSpec;
static const QString kFtsUpdateSpec;
static const QRegularExpression kAlbumRemoveDisc;
static const QRegularExpression kAlbumRemoveMisc;
static const QRegularExpression kTitleRemoveMisc;
using RegularExpressionList = QList<QRegularExpression>;
static const RegularExpressionList kAlbumDisc;
static const RegularExpressionList kAlbumMisc;
static const RegularExpressionList kTitleMisc;
static const QStringList kArticles;
@ -443,6 +444,14 @@ class Song {
// It is more efficient to use IsOnSameAlbum, but this function can be used when you need to hash the key to do fast lookups.
QString AlbumKey() const;
static bool ContainsRegexList(const QString &str, const RegularExpressionList &regex_list);
static QString StripRegexList(QString str, const RegularExpressionList &regex_list);
static bool AlbumContainsDisc(const QString &album);
static QString AlbumRemoveDisc(const QString &album);
static QString AlbumRemoveMisc(const QString &album);
static QString AlbumRemoveDiscMisc(const QString &album);
static QString TitleRemoveMisc(const QString &title);
private:
struct Private;

View File

@ -333,7 +333,7 @@ AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return AlbumCoverImageResult();
QString album = song->effective_album();
album = album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
album = Song::AlbumRemoveDiscMisc(album);
// Get something sensible to stick in the search box
return cover_searcher_->Exec(song->effective_albumartist(), album);

View File

@ -66,9 +66,7 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
CoverSearchRequest request;
request.id = ++next_id_;
request.artist = artist;
request.album = album;
request.album = request.album.remove(Song::kAlbumRemoveDisc);
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.album = Song::AlbumRemoveDiscMisc(album);
request.title = title;
request.search = false;
request.batch = batch;
@ -83,9 +81,7 @@ quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString
CoverSearchRequest request;
request.id = ++next_id_;
request.artist = artist;
request.album = album;
request.album = request.album.remove(Song::kAlbumRemoveDisc);
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.album = Song::AlbumRemoveDiscMisc(album);
request.title = title;
request.search = true;
request.batch = false;

View File

@ -265,12 +265,9 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
}
QString album = obj_album["title"].toString();
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.album = Song::AlbumRemoveDiscMisc(album);
bool have_cover = false;
QList<QPair<QString, QSize>> cover_sizes = QList<QPair<QString, QSize>>() << qMakePair(QString("cover_xl"), QSize(1000, 1000))

View File

@ -262,12 +262,9 @@ void QobuzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
}
QUrl cover_url(obj_image["large"].toString());
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.album = Song::AlbumRemoveDiscMisc(album);
cover_result.image_url = cover_url;
cover_result.image_size = QSize(600, 600);
results << cover_result;

View File

@ -241,15 +241,11 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
continue;
}
QString album = obj_album["title"].toString();
QString cover = obj_album["cover"].toString();
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
cover = cover.replace("-", "/");
QString cover = obj_album["cover"].toString().replace("-", "/");
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.album = Song::AlbumRemoveDiscMisc(album);
cover_result.number = ++i;
QList<QPair<QString, QSize>> cover_sizes = QList<QPair<QString, QSize>>() << qMakePair(QString("1280x1280"), QSize(1280, 1280))

View File

@ -53,10 +53,8 @@ quint64 LyricsFetcher::Search(const QString &effective_albumartist, const QStrin
LyricsSearchRequest search_request;
search_request.albumartist = effective_albumartist;
search_request.artist = artist;
search_request.album = album;
search_request.album.remove(Song::kAlbumRemoveMisc);
search_request.title = title;
search_request.title.remove(Song::kTitleRemoveMisc);
search_request.album = Song::AlbumRemoveDiscMisc(album);
search_request.title = Song::TitleRemoveMisc(title);
Request request;
request.id = ++next_id_;

View File

@ -1189,7 +1189,7 @@ void QobuzRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti
url.setScheme(url_handler_->scheme());
url.setPath(song_id);
title.remove(Song::kTitleRemoveMisc);
title = Song::TitleRemoveMisc(title);
//qLog(Debug) << "id" << song_id << "track" << track << "title" << title << "album" << album << "album artist" << album_artist << cover_url << streamable << url;

View File

@ -50,14 +50,14 @@ bool ScrobblerService::ExtractJsonObj(const QByteArray &data, QJsonObject &json_
}
QString ScrobblerService::StripAlbum(QString album) const {
QString ScrobblerService::StripAlbum(const QString &album) const {
return album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
return Song::AlbumRemoveDisc(album);
}
QString ScrobblerService::StripTitle(QString title) const {
QString ScrobblerService::StripTitle(const QString &title) const {
return title.remove(Song::kTitleRemoveMisc);
return Song::TitleRemoveMisc(title);
}

View File

@ -61,8 +61,8 @@ class ScrobblerService : public QObject {
bool ExtractJsonObj(const QByteArray &data, QJsonObject &json_obj, QString &error_description);
QString StripAlbum(QString album) const;
QString StripTitle(QString title) const;
QString StripAlbum(const QString &album) const;
QString StripTitle(const QString &title) const;
public slots:
virtual void Submit() = 0;

View File

@ -1129,7 +1129,7 @@ void TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti
}
}
title.remove(Song::kTitleRemoveMisc);
title = Song::TitleRemoveMisc(title);
//qLog(Debug) << "id" << song_id << "track" << track << "disc" << disc << "title" << title << "album" << album << "album artist" << album_artist << "artist" << artist << cover << allow_streaming << url;

View File

@ -152,7 +152,7 @@ QString CoverUtils::CoverFilenameFromSource(const Song::Source source, const QUr
QString CoverUtils::CoverFilenameFromVariable(const CoverOptions &options, const QString &artist, QString album, const QString &extension) {
album = album.remove(Song::kAlbumRemoveDisc);
album = Song::AlbumRemoveDisc(album);
QString filename(options.cover_pattern);
filename.replace("%albumartist", artist);