Add level of collections in Immich media source tree (#145734)

* add layer for collections in media source tree

* re-arange tests, add test for collection layer

* fix
This commit is contained in:
Michael 2025-05-28 20:51:27 +02:00 committed by GitHub
parent 2708c1c94c
commit afa97f8ec1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 116 additions and 79 deletions

View File

@ -40,11 +40,12 @@ class ImmichMediaSourceIdentifier:
def __init__(self, identifier: str) -> None:
"""Split identifier into parts."""
parts = identifier.split("/")
# coonfig_entry.unique_id/album_id/asset_it/filename
# config_entry.unique_id/collection/collection_id/asset_id/file_name
self.unique_id = parts[0]
self.album_id = parts[1] if len(parts) > 1 else None
self.asset_id = parts[2] if len(parts) > 2 else None
self.file_name = parts[3] if len(parts) > 2 else None
self.collection = parts[1] if len(parts) > 1 else None
self.collection_id = parts[2] if len(parts) > 2 else None
self.asset_id = parts[3] if len(parts) > 3 else None
self.file_name = parts[4] if len(parts) > 3 else None
class ImmichMediaSource(MediaSource):
@ -83,6 +84,7 @@ class ImmichMediaSource(MediaSource):
) -> list[BrowseMediaSource]:
"""Handle browsing different immich instances."""
if not item.identifier:
LOGGER.debug("Render all Immich instances")
return [
BrowseMediaSource(
domain=DOMAIN,
@ -104,8 +106,22 @@ class ImmichMediaSource(MediaSource):
assert entry
immich_api = entry.runtime_data.api
if identifier.album_id is None:
# Get Albums
if identifier.collection is None:
LOGGER.debug("Render all collections for %s", entry.title)
return [
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}/albums",
media_class=MediaClass.DIRECTORY,
media_content_type=MediaClass.IMAGE,
title="albums",
can_play=False,
can_expand=True,
)
]
if identifier.collection_id is None:
LOGGER.debug("Render all albums for %s", entry.title)
try:
albums = await immich_api.albums.async_get_all_albums()
except ImmichError:
@ -114,7 +130,7 @@ class ImmichMediaSource(MediaSource):
return [
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{item.identifier}/{album.album_id}",
identifier=f"{identifier.unique_id}/albums/{album.album_id}",
media_class=MediaClass.DIRECTORY,
media_content_type=MediaClass.IMAGE,
title=album.name,
@ -125,10 +141,14 @@ class ImmichMediaSource(MediaSource):
for album in albums
]
# Request items of album
LOGGER.debug(
"Render all assets of album %s for %s",
identifier.collection_id,
entry.title,
)
try:
album_info = await immich_api.albums.async_get_album_info(
identifier.album_id
identifier.collection_id
)
except ImmichError:
return []
@ -137,8 +157,8 @@ class ImmichMediaSource(MediaSource):
BrowseMediaSource(
domain=DOMAIN,
identifier=(
f"{identifier.unique_id}/"
f"{identifier.album_id}/"
f"{identifier.unique_id}/albums/"
f"{identifier.collection_id}/"
f"{asset.asset_id}/"
f"{asset.file_name}"
),
@ -157,8 +177,8 @@ class ImmichMediaSource(MediaSource):
BrowseMediaSource(
domain=DOMAIN,
identifier=(
f"{identifier.unique_id}/"
f"{identifier.album_id}/"
f"{identifier.unique_id}/albums/"
f"{identifier.collection_id}/"
f"{asset.asset_id}/"
f"{asset.file_name}"
),

View File

@ -44,8 +44,8 @@ async def test_get_media_source(hass: HomeAssistant) -> None:
("identifier", "exception_msg"),
[
("unique_id", "No file name"),
("unique_id/album_id", "No file name"),
("unique_id/album_id/asset_id/filename", "No file extension"),
("unique_id/albums/album_id", "No file name"),
("unique_id/albums/album_id/asset_id/filename", "No file extension"),
],
)
async def test_resolve_media_bad_identifier(
@ -64,12 +64,12 @@ async def test_resolve_media_bad_identifier(
("identifier", "url", "mime_type"),
[
(
"unique_id/album_id/asset_id/filename.jpg",
"unique_id/albums/album_id/asset_id/filename.jpg",
"/immich/unique_id/asset_id/filename.jpg/fullsize",
"image/jpeg",
),
(
"unique_id/album_id/asset_id/filename.png",
"unique_id/albums/album_id/asset_id/filename.png",
"/immich/unique_id/asset_id/filename.png/fullsize",
"image/png",
),
@ -95,13 +95,82 @@ async def test_browse_media_unconfigured(hass: HomeAssistant) -> None:
source = await async_get_media_source(hass)
item = MediaSourceItem(
hass, DOMAIN, "unique_id/album_id/asset_id/filename.png", None
hass, DOMAIN, "unique_id/albums/album_id/asset_id/filename.png", None
)
with pytest.raises(BrowseError, match="Immich is not configured"):
await source.async_browse_media(item)
async def test_browse_media_album_error(
async def test_browse_media_get_root(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test browse_media returning root media sources."""
assert await async_setup_component(hass, "media_source", {})
with patch("homeassistant.components.immich.PLATFORMS", []):
await setup_integration(hass, mock_config_entry)
source = await async_get_media_source(hass)
# get root
item = MediaSourceItem(hass, DOMAIN, "", None)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
media_file = result.children[0]
assert isinstance(media_file, BrowseMedia)
assert media_file.title == "Someone"
assert media_file.media_content_id == (
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e"
)
# get collections
item = MediaSourceItem(hass, DOMAIN, "e7ef5713-9dab-4bd4-b899-715b0ca4379e", None)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
media_file = result.children[0]
assert isinstance(media_file, BrowseMedia)
assert media_file.title == "albums"
assert media_file.media_content_id == (
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums"
)
async def test_browse_media_get_albums(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test browse_media returning albums."""
assert await async_setup_component(hass, "media_source", {})
with patch("homeassistant.components.immich.PLATFORMS", []):
await setup_integration(hass, mock_config_entry)
source = await async_get_media_source(hass)
item = MediaSourceItem(
hass, DOMAIN, "e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums", None
)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
media_file = result.children[0]
assert isinstance(media_file, BrowseMedia)
assert media_file.title == "My Album"
assert media_file.media_content_id == (
"media-source://immich/"
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums/"
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"
)
async def test_browse_media_get_albums_error(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
@ -124,7 +193,7 @@ async def test_browse_media_album_error(
source = await async_get_media_source(hass)
item = MediaSourceItem(hass, DOMAIN, mock_config_entry.unique_id, None)
item = MediaSourceItem(hass, DOMAIN, f"{mock_config_entry.unique_id}/albums", None)
result = await source.async_browse_media(item)
assert result
@ -132,59 +201,7 @@ async def test_browse_media_album_error(
assert len(result.children) == 0
async def test_browse_media_get_root(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test browse_media returning root media sources."""
assert await async_setup_component(hass, "media_source", {})
with patch("homeassistant.components.immich.PLATFORMS", []):
await setup_integration(hass, mock_config_entry)
source = await async_get_media_source(hass)
item = MediaSourceItem(hass, DOMAIN, "", None)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
media_file = result.children[0]
assert isinstance(media_file, BrowseMedia)
assert media_file.title == "Someone"
assert media_file.media_content_id == (
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e"
)
async def test_browse_media_get_albums(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test browse_media returning albums."""
assert await async_setup_component(hass, "media_source", {})
with patch("homeassistant.components.immich.PLATFORMS", []):
await setup_integration(hass, mock_config_entry)
source = await async_get_media_source(hass)
item = MediaSourceItem(hass, DOMAIN, "e7ef5713-9dab-4bd4-b899-715b0ca4379e", None)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
media_file = result.children[0]
assert isinstance(media_file, BrowseMedia)
assert media_file.title == "My Album"
assert media_file.media_content_id == (
"media-source://immich/"
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"
)
async def test_browse_media_get_items_error(
async def test_browse_media_get_album_items_error(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
@ -202,7 +219,7 @@ async def test_browse_media_get_items_error(
item = MediaSourceItem(
hass,
DOMAIN,
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
None,
)
result = await source.async_browse_media(item)
@ -223,7 +240,7 @@ async def test_browse_media_get_items_error(
item = MediaSourceItem(
hass,
DOMAIN,
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
None,
)
result = await source.async_browse_media(item)
@ -233,7 +250,7 @@ async def test_browse_media_get_items_error(
assert len(result.children) == 0
async def test_browse_media_get_items(
async def test_browse_media_get_album_items(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
@ -249,7 +266,7 @@ async def test_browse_media_get_items(
item = MediaSourceItem(
hass,
DOMAIN,
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
None,
)
result = await source.async_browse_media(item)
@ -259,7 +276,7 @@ async def test_browse_media_get_items(
media_file = result.children[0]
assert isinstance(media_file, BrowseMedia)
assert media_file.identifier == (
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums/"
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6/"
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/filename.jpg"
)
@ -276,7 +293,7 @@ async def test_browse_media_get_items(
media_file = result.children[1]
assert isinstance(media_file, BrowseMedia)
assert media_file.identifier == (
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums/"
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6/"
"2e65a5f2-db83-44c4-81ab-f5ff20c9bd7b/filename.mp4"
)