From 0e5249868c608a474d135276c8664b81ff5f68d8 Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 10 Oct 2023 23:30:40 +0200 Subject: [PATCH] Subsonic API: fixed extensions + genres --- SUBSONIC.md | 4 +-- src/libs/subsonic/CMakeLists.txt | 1 + .../impl/entrypoints/AlbumSongLists.cpp | 1 - src/libs/subsonic/impl/entrypoints/System.cpp | 2 +- src/libs/subsonic/impl/responses/Album.cpp | 22 +++++++++++- src/libs/subsonic/impl/responses/Genre.hpp | 2 -- .../subsonic/impl/responses/ItemGenre.cpp | 34 +++++++++++++++++++ .../subsonic/impl/responses/ItemGenre.hpp | 33 ++++++++++++++++++ src/libs/subsonic/impl/responses/Song.cpp | 24 +++++++++++-- 9 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 src/libs/subsonic/impl/responses/ItemGenre.cpp create mode 100644 src/libs/subsonic/impl/responses/ItemGenre.hpp diff --git a/SUBSONIC.md b/SUBSONIC.md index 36e2ba6f..7bb4b93c 100644 --- a/SUBSONIC.md +++ b/SUBSONIC.md @@ -18,9 +18,9 @@ The following extra fields are implemented: * `moods` * `originalReleaseDate` * `isCompilation` - * `discTitles` + * `discTitles`: discs with no subtitle are omitted * `Child` response: - * `musicBrainzId`: note this is actually the recording MBID + * `musicBrainzId`: note this is actually the recording MBID when this response refers to a song * `genres` * `artists` * `albumArtists` diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt index d4aa3f0e..8964a561 100644 --- a/src/libs/subsonic/CMakeLists.txt +++ b/src/libs/subsonic/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(lmssubsonic SHARED impl/responses/Bookmark.cpp impl/responses/Contributor.cpp impl/responses/DiscTitle.cpp + impl/responses/ItemGenre.cpp impl/responses/Genre.cpp impl/responses/Playlist.cpp impl/responses/ReplayGain.cpp diff --git a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp index 362bf311..e0b096db 100644 --- a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp +++ b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp @@ -176,7 +176,6 @@ namespace API::Subsonic } return response; - } } // namespace diff --git a/src/libs/subsonic/impl/entrypoints/System.cpp b/src/libs/subsonic/impl/entrypoints/System.cpp index 6af37fd7..11d7b350 100644 --- a/src/libs/subsonic/impl/entrypoints/System.cpp +++ b/src/libs/subsonic/impl/entrypoints/System.cpp @@ -24,7 +24,7 @@ namespace API::Subsonic Response response{ Response::createOkResponse(context.serverProtocolVersion) }; { - Response::Node& transcodeOffsetNode{ response.createNode("openSubsonicExtensions") }; + Response::Node& transcodeOffsetNode{ response.createArrayNode("openSubsonicExtensions") }; transcodeOffsetNode.setAttribute("name", "transcodeOffset"); transcodeOffsetNode.addArrayValue("versions", 1); } diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index 6033a966..8694f06b 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -29,6 +29,7 @@ #include "responses/Artist.hpp" #include "responses/DiscTitle.hpp" +#include "responses/ItemGenre.hpp" #include "SubsonicId.hpp" namespace API::Subsonic @@ -160,9 +161,28 @@ namespace API::Subsonic } } }; - addClusters("genres", "GENRE"); addClusters("moods", "MOOD"); + // Genres + { + albumNode.createEmptyArrayChild("genres"); + + ClusterType::pointer clusterType{ ClusterType::find(dbSession, "GENRE") }; + if (clusterType) + { + Cluster::FindParameters params; + params.setRelease(release->getId()); + params.setClusterType(clusterType->getId()); + + for (const ClusterId clusterId : Cluster::find(dbSession, params).results) + { + Cluster::pointer cluster{ Cluster::find(dbSession, clusterId) }; + if (cluster) + albumNode.addArrayChild("genres", createItemGenreNode(cluster)); + } + } + } + albumNode.createEmptyArrayChild("artists"); for (const Artist::pointer& artist : release->getReleaseArtists()) albumNode.addArrayChild("artists", createArtistNode(artist)); diff --git a/src/libs/subsonic/impl/responses/Genre.hpp b/src/libs/subsonic/impl/responses/Genre.hpp index 33577f71..6d217304 100644 --- a/src/libs/subsonic/impl/responses/Genre.hpp +++ b/src/libs/subsonic/impl/responses/Genre.hpp @@ -19,8 +19,6 @@ #pragma once -#include "services/database/Cluster.hpp" - #include "services/database/Object.hpp" #include "SubsonicResponse.hpp" diff --git a/src/libs/subsonic/impl/responses/ItemGenre.cpp b/src/libs/subsonic/impl/responses/ItemGenre.cpp new file mode 100644 index 00000000..a5b3387d --- /dev/null +++ b/src/libs/subsonic/impl/responses/ItemGenre.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "responses/ItemGenre.hpp" + +#include "services/database/Cluster.hpp" + +namespace API::Subsonic +{ + Response::Node createItemGenreNode(const Database::Cluster::pointer& cluster) + { + Response::Node genreNode; + + genreNode.setAttribute("name", cluster->getName()); + + return genreNode; + } +} \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/ItemGenre.hpp b/src/libs/subsonic/impl/responses/ItemGenre.hpp new file mode 100644 index 00000000..f4c1dda9 --- /dev/null +++ b/src/libs/subsonic/impl/responses/ItemGenre.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include "services/database/Object.hpp" +#include "SubsonicResponse.hpp" + +namespace Database +{ + class Cluster; +} + +namespace API::Subsonic +{ + Response::Node createItemGenreNode(const Database::ObjectPtr& cluster); +} diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 2eb7ed74..ac4d3246 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -32,6 +32,7 @@ #include "utils/String.hpp" #include "responses/Artist.hpp" #include "responses/Contributor.hpp" +#include "responses/ItemGenre.hpp" #include "responses/ReplayGain.hpp" #include "SubsonicId.hpp" #include "Utils.hpp" @@ -212,14 +213,33 @@ namespace API::Subsonic { Cluster::pointer cluster {Cluster::find(dbSession, clusterId)}; if (cluster) - trackResponse.addArrayValue(field, cluster->getName()); + trackResponse.addArrayValue(field, cluster->getName()); } } } }; - addClusters("genres", "GENRE"); addClusters("moods", "MOOD"); + // Genres + { + trackResponse.createEmptyArrayChild("genres"); + + ClusterType::pointer clusterType{ ClusterType::find(dbSession, "GENRE") }; + if (clusterType) + { + Cluster::FindParameters params; + params.setTrack(track->getId()); + params.setClusterType(clusterType->getId()); + + for (const ClusterId clusterId : Cluster::find(dbSession, params).results) + { + Cluster::pointer cluster{ Cluster::find(dbSession, clusterId) }; + if (cluster) + trackResponse.addArrayChild("genres", createItemGenreNode(cluster)); + } + } + } + trackResponse.addChild("replayGain", createReplayGainNode(track)); return trackResponse;