Set correct BrowseMedia title in immich (#178388)

This commit is contained in:
Josef Zweck
2026-08-10 10:19:40 +02:00
committed by GitHub
parent 45d4067355
commit d4bad247e3
3 changed files with 97 additions and 22 deletions
+36 -19
View File
@@ -140,33 +140,33 @@ class ImmichMediaSource(MediaSource):
if item.identifier:
can_search = bool(ImmichMediaSourceIdentifier(item.identifier).unique_id)
title, children = await self._async_build_immich(item, entries)
return BrowseMediaSource(
domain=DOMAIN,
identifier=item.identifier,
media_class=MediaClass.DIRECTORY,
media_content_type=MediaClass.IMAGE,
title="Immich",
title=title,
can_play=False,
can_expand=True,
can_search=can_search,
search_media_classes=[MediaClass.IMAGE, MediaClass.VIDEO],
children_media_class=MediaClass.DIRECTORY,
children=[
*await self._async_build_immich(item, entries),
],
children=children,
)
async def _async_build_immich(
self, item: MediaSourceItem, entries: list[ConfigEntry]
) -> list[BrowseMediaSource]:
"""Handle browsing different immich instances."""
) -> tuple[str, list[BrowseMediaSource]]:
"""Return the title and the children of the browsed item."""
# --------------------------------------------------------
# root level, render immich instances
# --------------------------------------------------------
if not item.identifier:
LOGGER.debug("Render all Immich instances")
return [
return "Immich", [
BrowseMediaSource(
domain=DOMAIN,
identifier=entry.unique_id,
@@ -193,7 +193,7 @@ class ImmichMediaSource(MediaSource):
if identifier.collection is None:
LOGGER.debug("Render all collections for %s", entry.title)
return [
return entry.title, [
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}|{collection}",
@@ -221,9 +221,9 @@ class ImmichMediaSource(MediaSource):
translation_placeholders={"msg": str(err)},
) from err
except ImmichError:
return []
return identifier.collection, []
return [
return identifier.collection, [
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}|albums|{album.album_id}",
@@ -248,9 +248,9 @@ class ImmichMediaSource(MediaSource):
translation_placeholders={"msg": str(err)},
) from err
except ImmichError:
return []
return identifier.collection, []
return [
return identifier.collection, [
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}|tags|{tag.tag_id}",
@@ -274,9 +274,9 @@ class ImmichMediaSource(MediaSource):
translation_placeholders={"msg": str(err)},
) from err
except ImmichError:
return []
return identifier.collection, []
return [
return identifier.collection, [
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}|people|{person.person_id}",
@@ -295,6 +295,7 @@ class ImmichMediaSource(MediaSource):
# --------------------------------------------------------
assert identifier.collection_id is not None
assets: list[ImmichAsset] = []
title = identifier.collection
if identifier.collection == "albums":
LOGGER.debug(
"Render all assets of album %s for %s",
@@ -302,6 +303,9 @@ class ImmichMediaSource(MediaSource):
entry.title,
)
try:
album = await immich_api.albums.async_get_album_info(
identifier.collection_id
)
assets = await immich_api.search.async_get_all_by_album_ids(
[identifier.collection_id]
)
@@ -312,7 +316,9 @@ class ImmichMediaSource(MediaSource):
translation_placeholders={"msg": str(err)},
) from err
except ImmichError:
return []
return title, []
title = album.album_name
elif identifier.collection == "tags":
LOGGER.debug(
@@ -320,6 +326,9 @@ class ImmichMediaSource(MediaSource):
identifier.collection_id,
)
try:
tag = await immich_api.tags.async_get_tag_by_id(
identifier.collection_id
)
assets = await immich_api.search.async_get_all_by_tag_ids(
[identifier.collection_id]
)
@@ -330,7 +339,9 @@ class ImmichMediaSource(MediaSource):
translation_placeholders={"msg": str(err)},
) from err
except ImmichError:
return []
return title, []
title = tag.name
elif identifier.collection == "people":
LOGGER.debug(
@@ -338,6 +349,9 @@ class ImmichMediaSource(MediaSource):
identifier.collection_id,
)
try:
person = await immich_api.people.async_get_person_by_id(
identifier.collection_id
)
assets = await immich_api.search.async_get_all_by_person_ids(
[identifier.collection_id]
)
@@ -348,7 +362,10 @@ class ImmichMediaSource(MediaSource):
translation_placeholders={"msg": str(err)},
) from err
except ImmichError:
return []
return title, []
title = person.name
elif identifier.collection == "favorites":
LOGGER.debug("Render all assets for favorites collection")
try:
@@ -360,9 +377,9 @@ class ImmichMediaSource(MediaSource):
translation_placeholders={"msg": str(err)},
) from err
except ImmichError:
return []
return title, []
return _parse_assets(assets, identifier)
return title, _parse_assets(assets, identifier)
@override
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
+3
View File
@@ -82,6 +82,7 @@ def mock_immich_albums() -> AsyncMock:
"""Mock the Immich server."""
mock = AsyncMock(spec=ImmichAlbums)
mock.async_get_all_albums.return_value = [ALBUM_DATA]
mock.async_get_album_info.return_value = ALBUM_DATA
mock.async_add_assets_to_album.return_value = [
ImmichAddAssetsToAlbumResponse.from_dict(
{"id": "abcdef-0123456789", "success": True}
@@ -151,6 +152,7 @@ def mock_immich_people() -> AsyncMock:
}
),
]
mock.async_get_person_by_id.return_value = mock.async_get_all_people.return_value[0]
mock.async_get_person_thumbnail.return_value = b"yyyy"
return mock
@@ -257,6 +259,7 @@ def mock_immich_tags() -> AsyncMock:
},
),
]
mock.async_get_tag_by_id.return_value = mock.async_get_all_tags.return_value[0]
return mock
+58 -3
View File
@@ -6,7 +6,7 @@ from unittest.mock import Mock, patch
from aiohttp import web
from aioimmich.assets.models import AssetType
from aioimmich.exceptions import ImmichError, ImmichForbiddenError
from aioimmich.exceptions import ImmichError, ImmichForbiddenError, ImmichNotFoundError
import pytest
from homeassistant.components.immich.const import DOMAIN
@@ -130,6 +130,7 @@ async def test_browse_media_get_root(
root_media_source = await source.async_browse_media(item)
assert root_media_source
assert root_media_source.title == "Immich"
assert root_media_source.can_search is False
assert len(root_media_source.children) == 1
media_file = root_media_source.children[0]
@@ -144,6 +145,7 @@ async def test_browse_media_get_root(
root_media_source = await source.async_browse_media(item)
assert root_media_source
assert root_media_source.title == "Someone"
assert root_media_source.can_search is True
assert len(root_media_source.children) == 4
@@ -226,6 +228,7 @@ async def test_browse_media_collections(
root_media_source = await source.async_browse_media(item)
assert root_media_source
assert root_media_source.title == collection
assert root_media_source.can_search is True
assert len(root_media_source.children) == len(children)
for idx, child in enumerate(children):
@@ -363,11 +366,12 @@ async def test_browse_media_collection_items_error(
@pytest.mark.parametrize(
("collection", "collection_id", "children"),
("collection", "collection_id", "title", "children"),
[
(
"albums",
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
"My Album",
[
{
"original_file_name": "filename.jpg",
@@ -388,6 +392,7 @@ async def test_browse_media_collection_items_error(
],
),
(
"favorites",
"favorites",
"favorites",
[
@@ -412,6 +417,7 @@ async def test_browse_media_collection_items_error(
(
"people",
"6176838a-ac5a-4d1f-9a35-91c591d962d8",
"Me",
[
{
"original_file_name": "20250714_201122.jpg",
@@ -433,7 +439,8 @@ async def test_browse_media_collection_items_error(
),
(
"tags",
"6176838a-ac5a-4d1f-9a35-91c591d962d8",
"67301cb8-cb73-4e8a-99e9-475cb3f7e7b5",
"Halloween",
[
{
"original_file_name": "20110306_025024.jpg",
@@ -461,6 +468,7 @@ async def test_browse_media_collection_get_items(
mock_config_entry: MockConfigEntry,
collection: str,
collection_id: str,
title: str,
children: list[dict],
) -> None:
"""Test browse_media returning albums."""
@@ -480,6 +488,7 @@ async def test_browse_media_collection_get_items(
root_media_source = await source.async_browse_media(item)
assert root_media_source
assert root_media_source.title == title
assert len(root_media_source.children) == len(children)
for idx, child in enumerate(children):
@@ -500,6 +509,52 @@ async def test_browse_media_collection_get_items(
)
@pytest.mark.parametrize(
("collection", "mocked_get_fn"),
[
pytest.param("albums", ("albums", "async_get_album_info"), id="albums"),
pytest.param("people", ("people", "async_get_person_by_id"), id="people"),
pytest.param("tags", ("tags", "async_get_tag_by_id"), id="tags"),
],
)
async def test_browse_media_title_of_unknown_collection_item(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
collection: str,
mocked_get_fn: tuple[str, str],
) -> None:
"""Test browse_media falls back to the collection name for unknown items."""
assert await async_setup_component(hass, "media_source", {})
with patch("homeassistant.components.immich.PLATFORMS", []):
await setup_integration(hass, mock_config_entry)
getattr(
getattr(mock_immich, mocked_get_fn[0]), mocked_get_fn[1]
).side_effect = ImmichNotFoundError(
{
"message": "Not found or no permission",
"error": "Bad Request",
"statusCode": 400,
"correlationId": "e0hlizyl",
}
)
source = await async_get_media_source(hass)
item = MediaSourceItem(
hass,
DOMAIN,
f"{mock_config_entry.unique_id}|{collection}|unknown-id",
None,
)
root_media_source = await source.async_browse_media(item)
assert root_media_source.title == collection
assert len(root_media_source.children) == 0
async def test_media_view(
hass: HomeAssistant,
tmp_path: Path,