Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions homeassistant/components/immich/media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,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):
Expand Down Expand Up @@ -87,6 +88,7 @@ async def _async_build_immich(
) -> list[BrowseMediaSource]:
"""Handle browsing different immich instances."""
if not item.identifier:
LOGGER.debug("Render all Immich instances")
return [
BrowseMediaSource(
domain=DOMAIN,
Expand All @@ -108,8 +110,22 @@ async def _async_build_immich(
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:
Expand All @@ -118,7 +134,7 @@ async def _async_build_immich(
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,
Expand All @@ -129,10 +145,14 @@ async def _async_build_immich(
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 []
Expand All @@ -141,8 +161,8 @@ async def _async_build_immich(
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}"
),
Expand All @@ -161,8 +181,8 @@ async def _async_build_immich(
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}"
),
Expand Down
105 changes: 61 additions & 44 deletions tests/components/immich/test_media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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",
),
Expand All @@ -95,96 +95,113 @@ 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 with unknown album."""
"""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)

# exception in get_albums()
mock_immich.albums.async_get_all_albums.side_effect = ImmichError(
{
"message": "Not found or no album.read access",
"error": "Bad Request",
"statusCode": 400,
"correlationId": "e0hlizyl",
}
)

source = await async_get_media_source(hass)

item = MediaSourceItem(hass, DOMAIN, mock_config_entry.unique_id, None)
# get root
item = MediaSourceItem(hass, DOMAIN, "", None)
result = await source.async_browse_media(item)

assert result
assert result.identifier is None
assert len(result.children) == 0
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)

async def test_browse_media_get_root(
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 root media sources."""
"""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, "", None)
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 == "Someone"
assert media_file.title == "My Album"
assert media_file.media_content_id == (
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e"
"media-source://immich/"
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/albums/"
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"
)


async def test_browse_media_get_albums(
async def test_browse_media_get_albums_error(
hass: HomeAssistant,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test browse_media returning albums."""
"""Test browse_media with unknown album."""
assert await async_setup_component(hass, "media_source", {})

with patch("homeassistant.components.immich.PLATFORMS", []):
await setup_integration(hass, mock_config_entry)

# exception in get_albums()
mock_immich.albums.async_get_all_albums.side_effect = ImmichError(
{
"message": "Not found or no album.read access",
"error": "Bad Request",
"statusCode": 400,
"correlationId": "e0hlizyl",
}
)

source = await async_get_media_source(hass)
item = MediaSourceItem(hass, DOMAIN, "e7ef5713-9dab-4bd4-b899-715b0ca4379e", None)

item = MediaSourceItem(hass, DOMAIN, f"{mock_config_entry.unique_id}/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/"
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"
)
assert result.identifier is None
assert len(result.children) == 0


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,
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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"
)
Expand All @@ -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"
)
Expand Down