Fix Sonos browse album art lookup for multi-segment A:ALBUM IDs (#163786)

This commit is contained in:
Mattias Michaux
2026-02-24 16:45:27 +01:00
committed by GitHub
parent 0c6d635e83
commit b5a55ec032
2 changed files with 98 additions and 1 deletions
@@ -585,10 +585,30 @@ def get_media(
item_id = "A:ALBUMARTIST/" + "/".join(item_id.split("/")[2:])
if item_id.startswith("A:ALBUM/") or search_type == "tracks":
search_term = urllib.parse.unquote(item_id.split("/")[-1])
# Some Sonos libraries return album ids in the shape:
# A:ALBUM/<album>/<artist>, where the artist part disambiguates results.
# Use the album segment for searching.
if item_id.startswith("A:ALBUM/"):
splits = item_id.split("/")
search_term = urllib.parse.unquote(splits[1]) if len(splits) > 1 else ""
album_title: str | None = search_term
else:
search_term = urllib.parse.unquote(item_id.split("/")[-1])
album_title = None
matches = media_library.get_music_library_information(
search_type, search_term=search_term, full_album_art_uri=True
)
if item_id.startswith("A:ALBUM/") and len(matches) > 1:
if result := next(
(item for item in matches if item_id == item.item_id), None
):
matches = [result]
elif album_title:
if result := next(
(item for item in matches if album_title == item.title), None
):
matches = [result]
elif search_type == SONOS_SHARE:
# In order to get the MusicServiceItem, we browse the parent folder
# and find one that matches on item_id.
@@ -1,6 +1,7 @@
"""Tests for the Sonos Media Browser."""
from functools import partial
from unittest.mock import MagicMock
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -15,6 +16,7 @@ from homeassistant.components.media_player import (
from homeassistant.components.sonos.const import MEDIA_TYPE_DIRECTORY
from homeassistant.components.sonos.media_browser import (
build_item_response,
get_media,
get_thumbnail_url_full,
)
from homeassistant.const import ATTR_ENTITY_ID
@@ -109,6 +111,81 @@ async def test_build_item_response(
)
def test_get_media_multisegment_album_id_uses_album_segment() -> None:
"""Test `A:ALBUM/<album>/<artist>` uses album name as lookup search term."""
music_library = MagicMock()
music_library.get_music_library_information.return_value = []
result = get_media(
music_library,
"A:ALBUM/Abbey%20Road/The%20Beatles",
"album",
)
assert result is None
assert music_library.get_music_library_information.call_count == 1
assert music_library.get_music_library_information.call_args.args == ("albums",)
assert music_library.get_music_library_information.call_args.kwargs == {
"search_term": "Abbey Road",
"full_album_art_uri": True,
}
def test_get_media_multisegment_album_id_prefers_exact_item_id_match() -> None:
"""Test multi-match disambiguation prefers exact `item_id`."""
music_library = MagicMock()
exact_item = MockMusicServiceItem(
"Abbey Road (Remaster)",
"A:ALBUM/Abbey%20Road/The%20Beatles",
"A:ALBUM",
"object.container.album.musicAlbum",
)
music_library.get_music_library_information.return_value = [
MockMusicServiceItem(
"Abbey Road",
"A:ALBUM/Abbey%20Road/Someone%20Else",
"A:ALBUM",
"object.container.album.musicAlbum",
),
exact_item,
]
result = get_media(
music_library,
"A:ALBUM/Abbey%20Road/The%20Beatles",
"album",
)
assert result is exact_item
def test_get_media_multisegment_album_id_falls_back_to_exact_title_match() -> None:
"""Test multi-match disambiguation falls back to exact title match."""
music_library = MagicMock()
title_match_item = MockMusicServiceItem(
"Abbey Road",
"A:ALBUM/Abbey%20Road/The%20Beatles%20(Remaster)",
"A:ALBUM",
"object.container.album.musicAlbum",
)
music_library.get_music_library_information.return_value = [
MockMusicServiceItem(
"Abbey Road (Live)",
"A:ALBUM/Abbey%20Road/The%20Beatles%20(Live)",
"A:ALBUM",
"object.container.album.musicAlbum",
),
title_match_item,
]
result = get_media(
music_library,
"A:ALBUM/Abbey%20Road/The%20Beatles",
"album",
)
assert result is title_match_item
async def test_browse_media_root(
hass: HomeAssistant,
soco_factory: SoCoMockFactory,