From ea629aedd1f8553690e61ea52b747e6e816985cd Mon Sep 17 00:00:00 2001 From: Rob Stanfield Date: Sat, 20 Dec 2025 09:18:15 -0800 Subject: [PATCH] Get genre metadata for Tidal, Qobuz and Spotify Extract genre information when fetching favorites and search results. Genre is now populated in the collection and playlists for tracks from these streaming services. --- src/qobuz/qobuzrequest.cpp | 38 ++++++++++++++++++++++++++++++++++ src/qobuz/qobuzrequest.h | 1 + src/spotify/spotifyrequest.cpp | 38 ++++++++++++++++++++++++++++++++++ src/spotify/spotifyrequest.h | 2 ++ src/tidal/tidalrequest.cpp | 6 ++++++ 5 files changed, 85 insertions(+) diff --git a/src/qobuz/qobuzrequest.cpp b/src/qobuz/qobuzrequest.cpp index 8bee893a0..cfa781efe 100644 --- a/src/qobuz/qobuzrequest.cpp +++ b/src/qobuz/qobuzrequest.cpp @@ -695,6 +695,16 @@ void QobuzRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_req } album.album = obj_item["title"_L1].toString(); + if (obj_item.contains("genre"_L1)) { + QJsonValue value_genre = obj_item["genre"_L1]; + if (value_genre.isObject()) { + QJsonObject obj_genre = value_genre.toObject(); + if (obj_genre.contains("name"_L1)) { + album.genre = obj_genre["name"_L1].toString(); + } + } + } + if (album_songs_requests_pending_.contains(album.album_id)) continue; QJsonValue value_artist = obj_item["artist"_L1]; @@ -921,6 +931,17 @@ void QobuzRequest::SongsReceived(QNetworkReply *reply, const Artist &artist_requ } } + // Extract genre from album/get response if not already set + if (album.genre.isEmpty() && json_object.contains("genre"_L1)) { + QJsonValue value_genre = json_object["genre"_L1]; + if (value_genre.isObject()) { + QJsonObject obj_genre = value_genre.toObject(); + if (obj_genre.contains("name"_L1)) { + album.genre = obj_genre["name"_L1].toString(); + } + } + } + QJsonValue value_tracks = json_object["tracks"_L1]; if (!value_tracks.isObject()) { Error(u"Json tracks is not an object."_s, json_object); @@ -1053,6 +1074,7 @@ void QobuzRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti // bool streamable = json_obj["streamable"].toBool(); QString composer; QString performer; + QString genre; if (json_obj.contains("media_number"_L1)) { disc = json_obj["media_number"_L1].toInt(); @@ -1118,6 +1140,21 @@ void QobuzRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti song_album.cover_url.setUrl(album_image); } } + + if (obj_album.contains("genre"_L1)) { + QJsonValue value_genre = obj_album["genre"_L1]; + if (value_genre.isObject()) { + QJsonObject obj_genre = value_genre.toObject(); + if (obj_genre.contains("name"_L1)) { + genre = obj_genre["name"_L1].toString(); + } + } + } + } + + // Fall back to genre from the Album struct if not found in the track's album object + if (genre.isEmpty() && !album.genre.isEmpty()) { + genre = album.genre; } if (json_obj.contains("composer"_L1)) { @@ -1180,6 +1217,7 @@ void QobuzRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti song.set_performer(performer); song.set_composer(composer); song.set_comment(copyright); + song.set_genre(genre); song.set_directory_id(0); song.set_filetype(Song::FileType::Stream); song.set_filesize(0); diff --git a/src/qobuz/qobuzrequest.h b/src/qobuz/qobuzrequest.h index c7dd09a8c..0c183fc23 100644 --- a/src/qobuz/qobuzrequest.h +++ b/src/qobuz/qobuzrequest.h @@ -65,6 +65,7 @@ class QobuzRequest : public QobuzBaseRequest { QString album; QUrl cover_url; bool album_explicit; + QString genre; }; struct Request { Request() : offset(0), limit(0) {} diff --git a/src/spotify/spotifyrequest.cpp b/src/spotify/spotifyrequest.cpp index acb7b5ba6..c1c6b48b5 100644 --- a/src/spotify/spotifyrequest.cpp +++ b/src/spotify/spotifyrequest.cpp @@ -496,11 +496,20 @@ void SpotifyRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_ const QString artist_id = object_item["id"_L1].toString(); const QString artist = object_item["name"_L1].toString(); + QString genre; + if (object_item.contains("genres"_L1) && object_item["genres"_L1].isArray()) { + const QJsonArray array_genres = object_item["genres"_L1].toArray(); + if (!array_genres.isEmpty()) { + genre = array_genres.first().toString(); + } + } + if (artist_albums_requests_pending_.contains(artist_id)) continue; ArtistAlbumsRequest request; request.artist.artist_id = artist_id; request.artist.artist = artist; + request.artist.genre = genre; artist_albums_requests_pending_.insert(artist_id, request); } @@ -715,6 +724,12 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a if (artist.artist_id.isEmpty() || artist.artist_id == artist_artist.artist_id) { artist.artist_id = obj_artist["id"_L1].toString(); artist.artist = obj_artist["name"_L1].toString(); + if (obj_artist.contains("genres"_L1) && obj_artist["genres"_L1].isArray()) { + const QJsonArray array_genres = obj_artist["genres"_L1].toArray(); + if (!array_genres.isEmpty()) { + album.genre = array_genres.first().toString(); + } + } if (artist.artist_id == artist_artist.artist_id) { artist_matches = true; break; @@ -730,6 +745,11 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a artist = artist_artist; } + // Fall back to artist's genre if no genre found in album's artist data + if (album.genre.isEmpty() && !artist_artist.genre.isEmpty()) { + album.genre = artist_artist.genre; + } + if (object_item.contains("images"_L1) && object_item["images"_L1].isArray()) { const QJsonArray array_images = object_item["images"_L1].toArray(); for (const QJsonValue &value : array_images) { @@ -1050,6 +1070,7 @@ void SpotifyRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Ar QString artist_id; QString artist_title; + QString genre; if (json_obj.contains("artists"_L1) && json_obj["artists"_L1].isArray()) { const QJsonArray array_artists = json_obj["artists"_L1].toArray(); for (const QJsonValue &value_artist : array_artists) { @@ -1060,6 +1081,12 @@ void SpotifyRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Ar } artist_id = obj_artist["id"_L1].toString(); artist_title = obj_artist["name"_L1].toString(); + if (obj_artist.contains("genres"_L1) && obj_artist["genres"_L1].isArray()) { + const QJsonArray array_genres = obj_artist["genres"_L1].toArray(); + if (!array_genres.isEmpty()) { + genre = array_genres.first().toString(); + } + } break; } } @@ -1102,6 +1129,16 @@ void SpotifyRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Ar cover_url = album.cover_url; } + // Fall back to genre from the Album struct if not found in the track's artist + if (genre.isEmpty() && !album.genre.isEmpty()) { + genre = album.genre; + } + + // Fall back to genre from the Artist struct if still not found + if (genre.isEmpty() && !album_artist.genre.isEmpty()) { + genre = album_artist.genre; + } + QString song_id = json_obj["id"_L1].toString(); QString title = json_obj["name"_L1].toString(); QString uri = json_obj["uri"_L1].toString(); @@ -1130,6 +1167,7 @@ void SpotifyRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Ar song.set_url(url); song.set_length_nanosec(duration); song.set_art_automatic(cover_url); + song.set_genre(genre); song.set_directory_id(0); song.set_filetype(Song::FileType::Stream); song.set_filesize(0); diff --git a/src/spotify/spotifyrequest.h b/src/spotify/spotifyrequest.h index f0004be35..05f7ab6df 100644 --- a/src/spotify/spotifyrequest.h +++ b/src/spotify/spotifyrequest.h @@ -57,11 +57,13 @@ class SpotifyRequest : public SpotifyBaseRequest { struct Artist { QString artist_id; QString artist; + QString genre; }; struct Album { QString album_id; QString album; QUrl cover_url; + QString genre; }; struct Request { Request() : offset(0), limit(0) {} diff --git a/src/tidal/tidalrequest.cpp b/src/tidal/tidalrequest.cpp index 1bec92942..f149a8fd7 100644 --- a/src/tidal/tidalrequest.cpp +++ b/src/tidal/tidalrequest.cpp @@ -1000,6 +1000,11 @@ void TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti const bool stream_ready = json_obj["streamReady"_L1].toBool(); const QString copyright = json_obj["copyright"_L1].toString(); + QString genre; + if (json_obj.contains("genre"_L1)) { + genre = json_obj["genre"_L1].toString(); + } + if (!value_artist.isObject()) { Error(u"Invalid Json reply, track artist is not a object."_s, value_artist); return; @@ -1095,6 +1100,7 @@ void TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti song.set_art_automatic(cover_url); } song.set_comment(copyright); + song.set_genre(genre); song.set_directory_id(0); song.set_filetype(Song::FileType::Stream); song.set_filesize(0);