From e1fbe9ae5465c99b05f8c1cd2114f3d5c9ceec3e Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Thu, 4 Apr 2024 22:22:02 +0200 Subject: [PATCH] Resolve song from collection using track with Cue in XSPF Fixes #1181 --- src/collection/collectionbackend.cpp | 30 ++++++++++++++++++++++++++++ src/collection/collectionbackend.h | 2 ++ src/playlistparsers/asxiniparser.cpp | 2 +- src/playlistparsers/asxparser.cpp | 2 +- src/playlistparsers/cueparser.cpp | 2 +- src/playlistparsers/m3uparser.cpp | 2 +- src/playlistparsers/parserbase.cpp | 18 ++++++++++++----- src/playlistparsers/parserbase.h | 4 ++-- src/playlistparsers/plsparser.cpp | 2 +- src/playlistparsers/wplparser.cpp | 2 +- src/playlistparsers/xspfparser.cpp | 11 ++++++---- 11 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp index 6f05b32c..e02d1003 100644 --- a/src/collection/collectionbackend.cpp +++ b/src/collection/collectionbackend.cpp @@ -1210,6 +1210,36 @@ Song CollectionBackend::GetSongByUrl(const QUrl &url, const qint64 beginning) { } +Song CollectionBackend::GetSongByUrlAndTrack(const QUrl &url, const int track) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + SqlQuery q(db); + q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND track = :track AND unavailable = 0").arg(Song::kColumnSpec, songs_table_)); + + q.BindValue(":url1", url); + q.BindValue(":url2", url.toString()); + q.BindValue(":url3", url.toString(QUrl::FullyEncoded)); + q.BindValue(":url4", url.toEncoded()); + q.BindValue(":track", track); + + if (!q.Exec()) { + db_->ReportErrors(q); + return Song(); + } + + if (!q.next()) { + return Song(); + } + + Song song(source_); + song.InitFromQuery(q, true); + + return song; + +} + SongList CollectionBackend::GetSongsByUrl(const QUrl &url, const bool unavailable) { QMutexLocker l(db_->Mutex()); diff --git a/src/collection/collectionbackend.h b/src/collection/collectionbackend.h index 03f2c2b9..72f9aab5 100644 --- a/src/collection/collectionbackend.h +++ b/src/collection/collectionbackend.h @@ -130,6 +130,7 @@ class CollectionBackendInterface : public QObject { // Returns a section of a song with the given filename and beginning. If the section is not present in collection, returns invalid song. // Using default beginning value is suitable when searching for single-section songs. virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0; + virtual Song GetSongByUrlAndTrack(const QUrl &url, const int track) = 0; virtual void AddDirectory(const QString &path) = 0; virtual void RemoveDirectory(const CollectionDirectory &dir) = 0; @@ -203,6 +204,7 @@ class CollectionBackend : public CollectionBackendInterface { SongList GetSongsByUrl(const QUrl &url, const bool unavailable = false) override; Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override; + Song GetSongByUrlAndTrack(const QUrl &url, const int track) override; void AddDirectory(const QString &path) override; void RemoveDirectory(const CollectionDirectory &dir) override; diff --git a/src/playlistparsers/asxiniparser.cpp b/src/playlistparsers/asxiniparser.cpp index ffd96994..44cc9f3a 100644 --- a/src/playlistparsers/asxiniparser.cpp +++ b/src/playlistparsers/asxiniparser.cpp @@ -59,7 +59,7 @@ SongList AsxIniParser::Load(QIODevice *device, const QString &playlist_path, con QString value = line.mid(equals + 1); if (key.startsWith("ref")) { - Song song = LoadSong(value, 0, dir, collection_search); + Song song = LoadSong(value, 0, 0, dir, collection_search); if (song.is_valid()) { ret << song; } diff --git a/src/playlistparsers/asxparser.cpp b/src/playlistparsers/asxparser.cpp index 4bde4164..67345d0a 100644 --- a/src/playlistparsers/asxparser.cpp +++ b/src/playlistparsers/asxparser.cpp @@ -117,7 +117,7 @@ Song ASXParser::ParseTrack(QXmlStreamReader *reader, const QDir &dir, const bool } return_song: - Song song = LoadSong(ref, 0, dir, collection_search); + Song song = LoadSong(ref, 0, 0, dir, collection_search); // Override metadata with what was in the playlist if (song.source() != Song::Source::Collection) { diff --git a/src/playlistparsers/cueparser.cpp b/src/playlistparsers/cueparser.cpp index 3bf20d24..26dd4c5c 100644 --- a/src/playlistparsers/cueparser.cpp +++ b/src/playlistparsers/cueparser.cpp @@ -246,7 +246,7 @@ SongList CueParser::Load(QIODevice *device, const QString &playlist_path, const for (int i = 0; i < entries.length(); i++) { CueEntry entry = entries.at(i); - Song song = LoadSong(entry.file, IndexToMarker(entry.index), dir, collection_search); + Song song = LoadSong(entry.file, IndexToMarker(entry.index), 0, dir, collection_search); // Cue song has mtime equal to qMax(media_file_mtime, cue_sheet_mtime) if (cue_mtime.isValid()) { diff --git a/src/playlistparsers/m3uparser.cpp b/src/playlistparsers/m3uparser.cpp index 82a8766d..7c71436d 100644 --- a/src/playlistparsers/m3uparser.cpp +++ b/src/playlistparsers/m3uparser.cpp @@ -71,7 +71,7 @@ SongList M3UParser::Load(QIODevice *device, const QString &playlist_path, const } } else if (!line.isEmpty()) { - Song song = LoadSong(line, 0, dir, collection_search); + Song song = LoadSong(line, 0, 0, dir, collection_search); if (!current_metadata.title.isEmpty()) { song.set_title(current_metadata.title); } diff --git a/src/playlistparsers/parserbase.cpp b/src/playlistparsers/parserbase.cpp index e8bf4cb0..74d34a4e 100644 --- a/src/playlistparsers/parserbase.cpp +++ b/src/playlistparsers/parserbase.cpp @@ -37,7 +37,7 @@ ParserBase::ParserBase(SharedPtr collection_backend, QObject *parent) : QObject(parent), collection_backend_(collection_backend) {} -void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning, const QDir &dir, Song *song, const bool collection_search) const { +void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning, const int track, const QDir &dir, Song *song, const bool collection_search) const { if (filename_or_url.isEmpty()) { return; @@ -81,7 +81,13 @@ void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning // Search in the collection if (collection_backend_ && collection_search) { - Song collection_song = collection_backend_->GetSongByUrl(url, beginning); + Song collection_song; + if (track > 0) { + collection_song = collection_backend_->GetSongByUrlAndTrack(url, track); + } + if (!collection_song.is_valid()) { + collection_song = collection_backend_->GetSongByUrl(url, beginning); + } // If it was found in the collection then use it, otherwise load metadata from disk. if (collection_song.is_valid()) { *song = collection_song; @@ -89,14 +95,16 @@ void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning } } - TagReaderClient::Instance()->ReadFileBlocking(filename, song); + if (!song->has_cue()) { + TagReaderClient::Instance()->ReadFileBlocking(filename, song); + } } -Song ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning, const QDir &dir, const bool collection_search) const { +Song ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning, const int track, const QDir &dir, const bool collection_search) const { Song song(Song::Source::LocalFile); - LoadSong(filename_or_url, beginning, dir, &song, collection_search); + LoadSong(filename_or_url, beginning, track, dir, &song, collection_search); return song; diff --git a/src/playlistparsers/parserbase.h b/src/playlistparsers/parserbase.h index 31f260cf..d49903d3 100644 --- a/src/playlistparsers/parserbase.h +++ b/src/playlistparsers/parserbase.h @@ -66,8 +66,8 @@ class ParserBase : public QObject { // If it is a filename or a file:// URL then it is made absolute and canonical and set as a file:// url on the song. // Also sets the song's metadata by searching in the Collection, or loading from the file as a fallback. // This function should always be used when loading a playlist. - Song LoadSong(const QString &filename_or_url, const qint64 beginning, const QDir &dir, const bool collection_search) const; - void LoadSong(const QString &filename_or_url, const qint64 beginning, const QDir &dir, Song *song, const bool collection_search) const; + Song LoadSong(const QString &filename_or_url, const qint64 beginning, const int track, const QDir &dir, const bool collection_search) const; + void LoadSong(const QString &filename_or_url, const qint64 beginning, const int track, const QDir &dir, Song *song, const bool collection_search) const; // If the URL is a file:// URL then returns its path, absolute or relative to the directory depending on the path_type option. // Otherwise, returns the URL as is. This function should always be used when saving a playlist. diff --git a/src/playlistparsers/plsparser.cpp b/src/playlistparsers/plsparser.cpp index 88e4314b..405fa699 100644 --- a/src/playlistparsers/plsparser.cpp +++ b/src/playlistparsers/plsparser.cpp @@ -62,7 +62,7 @@ SongList PLSParser::Load(QIODevice *device, const QString &playlist_path, const int n = re_match.captured(0).toInt(); if (key.startsWith("file")) { - Song song = LoadSong(value, 0, dir, collection_search); + Song song = LoadSong(value, 0, 0, dir, collection_search); // Use the title and length we've already loaded if any if (!songs[n].title().isEmpty()) song.set_title(songs[n].title()); diff --git a/src/playlistparsers/wplparser.cpp b/src/playlistparsers/wplparser.cpp index f850814d..f48e9634 100644 --- a/src/playlistparsers/wplparser.cpp +++ b/src/playlistparsers/wplparser.cpp @@ -72,7 +72,7 @@ void WplParser::ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *so if (name == "media") { QString src = reader->attributes().value("src").toString(); if (!src.isEmpty()) { - Song song = LoadSong(src, 0, dir, collection_search); + Song song = LoadSong(src, 0, 0, dir, collection_search); if (song.is_valid()) { songs->append(song); } diff --git a/src/playlistparsers/xspfparser.cpp b/src/playlistparsers/xspfparser.cpp index 7f26e971..240cb361 100644 --- a/src/playlistparsers/xspfparser.cpp +++ b/src/playlistparsers/xspfparser.cpp @@ -120,7 +120,7 @@ Song XSPFParser::ParseTrack(QXmlStreamReader *reader, const QDir &dir, const boo } return_song: - Song song = LoadSong(location, 0, dir, collection_search); + Song song = LoadSong(location, 0, track_num, dir, collection_search); // Override metadata with what was in the playlist if (song.source() != Song::Source::Collection) { @@ -169,10 +169,13 @@ void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, if (song.length_nanosec() != -1) { writer.writeTextElement("duration", QString::number(song.length_nanosec() / kNsecPerMsec)); } - if (song.track() > 0) { - writer.writeTextElement("trackNum", QString::number(song.track())); - } + } + if ((write_metadata || song.has_cue() || (song.is_stream() && !song.is_radio())) && song.track() > 0) { + writer.writeTextElement("trackNum", QString::number(song.track())); + } + + if (write_metadata || (song.is_stream() && !song.is_radio())) { const QUrl cover_url = song.art_manual().isEmpty() || !song.art_manual().isValid() ? song.art_automatic() : song.art_manual(); // Ignore images that are in our resource bundle. if (!cover_url.isEmpty() && cover_url.isValid()) {