diff --git a/api/funkwhale_api/subsonic/renderers.py b/api/funkwhale_api/subsonic/renderers.py
index f11940222..e9703d48d 100644
--- a/api/funkwhale_api/subsonic/renderers.py
+++ b/api/funkwhale_api/subsonic/renderers.py
@@ -32,9 +32,11 @@ ET._serialize_xml = ET._serialize["xml"] = _serialize_xml
def structure_payload(data):
payload = {
"funkwhaleVersion": funkwhale_api.__version__,
+ "serverVersion": funkwhale_api.__version__,
"status": "ok",
"type": "funkwhale",
"version": "1.16.0",
+ "openSubsonic": "true",
}
payload.update(data)
if "detail" in payload:
diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py
index 5a615126b..12255d622 100644
--- a/api/funkwhale_api/subsonic/serializers.py
+++ b/api/funkwhale_api/subsonic/serializers.py
@@ -126,6 +126,7 @@ def get_track_data(album, track, upload):
"albumId": album.pk if album else "",
"artistId": album.artist.pk if album else track.artist.pk,
"type": "music",
+ "musicBrainzId": str(track.mbid or ""),
}
if album and album.attachment_cover_id:
data["coverArt"] = f"al-{album.id}"
@@ -149,13 +150,16 @@ def get_album2_data(album):
"created": to_subsonic_date(album.creation_date),
"duration": album.duration,
"playCount": album.tracks.aggregate(l=Count("listenings"))["l"] or 0,
+ "musicBrainzId": str(album.mbid or ""),
}
if album.attachment_cover_id:
payload["coverArt"] = f"al-{album.id}"
if album.tagged_items:
+ genres = [{"name": i.tag.name} for i in album.tagged_items.all()]
# exposes only first genre since the specification uses singular noun
- first_genre = album.tagged_items.first()
- payload["genre"] = first_genre.tag.name if first_genre else ""
+ payload["genre"] = genres[0]["name"] if len(genres) > 0 else ""
+ # OpenSubsonic full genre list
+ payload["genres"] = genres
if album.release_date:
payload["year"] = album.release_date.year
try:
diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py
index 316cce706..c859ed7de 100644
--- a/api/funkwhale_api/subsonic/views.py
+++ b/api/funkwhale_api/subsonic/views.py
@@ -180,6 +180,20 @@ class SubsonicViewSet(viewsets.GenericViewSet):
}
return response.Response(data, status=200)
+ @action(
+ detail=False,
+ methods=["get", "post"],
+ url_name="get_open_subsonic_extensions",
+ permission_classes=[],
+ url_path="getOpenSubsonicExtensions",
+ )
+ def get_open_subsonic_extensions(self, request, *args, **kwargs):
+ data = {
+ # No specific extensions are currently supported
+ "openSubsonicExtensions": [""],
+ }
+ return response.Response(data, status=200)
+
@action(
detail=False,
methods=["get", "post"],
diff --git a/api/tests/subsonic/test_renderers.py b/api/tests/subsonic/test_renderers.py
index 551f5572c..1b49753e4 100644
--- a/api/tests/subsonic/test_renderers.py
+++ b/api/tests/subsonic/test_renderers.py
@@ -17,6 +17,8 @@ from funkwhale_api.subsonic import renderers
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
+ "serverVersion": funkwhale_api.__version__,
+ "openSubsonic": "true",
"hello": "world",
},
),
@@ -30,6 +32,8 @@ from funkwhale_api.subsonic import renderers
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
+ "serverVersion": funkwhale_api.__version__,
+ "openSubsonic": "true",
"hello": "world",
"error": {"code": 10, "message": "something went wrong"},
},
@@ -41,6 +45,8 @@ from funkwhale_api.subsonic import renderers
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
+ "serverVersion": funkwhale_api.__version__,
+ "openSubsonic": "true",
"hello": "world",
"error": {"code": 0, "message": "something went wrong"},
},
@@ -59,6 +65,8 @@ def test_json_renderer():
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
+ "serverVersion": funkwhale_api.__version__,
+ "openSubsonic": "true",
"hello": "world",
}
}
@@ -81,8 +89,9 @@ def test_xml_renderer_dict_to_xml():
def test_xml_renderer():
payload = {"hello": "world"}
- expected = '\n' # noqa
- expected = expected.format(funkwhale_api.__version__).encode()
+ expected = '\n' # noqa
+ version = funkwhale_api.__version__
+ expected = expected.format(version, version).encode()
renderer = renderers.SubsonicXMLRenderer()
rendered = renderer.render(payload)
diff --git a/api/tests/subsonic/test_serializers.py b/api/tests/subsonic/test_serializers.py
index bb419e61d..7c54bddd7 100644
--- a/api/tests/subsonic/test_serializers.py
+++ b/api/tests/subsonic/test_serializers.py
@@ -184,6 +184,8 @@ def test_get_album_serializer(factories):
"year": album.release_date.year,
"coverArt": f"al-{album.id}",
"genre": tagged_item.tag.name,
+ "genres": [{"name": tagged_item.tag.name}],
+ "musicBrainzId": album.mbid,
"duration": 43,
"playCount": album.tracks.aggregate(l=Count("listenings"))["l"] or 0,
"song": [
@@ -207,6 +209,7 @@ def test_get_album_serializer(factories):
"albumId": album.pk,
"artistId": artist.pk,
"type": "music",
+ "musicBrainzId": track.mbid,
}
],
}
diff --git a/changes/changelog.d/opensubsonic.feature b/changes/changelog.d/opensubsonic.feature
new file mode 100644
index 000000000..8d28a02cb
--- /dev/null
+++ b/changes/changelog.d/opensubsonic.feature
@@ -0,0 +1 @@
+Extend Subsonic API with OpenSubsonic support (#2270)