mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
Add people and tags collections to Immich media source (#149340)
This commit is contained in:
parent
cf05f1046d
commit
596f6cd216
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from logging import getLogger
|
||||
|
||||
from aiohttp.web import HTTPNotFound, Request, Response, StreamResponse
|
||||
from aioimmich.assets.models import ImmichAsset
|
||||
from aioimmich.exceptions import ImmichError
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
@ -83,6 +84,10 @@ class ImmichMediaSource(MediaSource):
|
||||
self, item: MediaSourceItem, entries: list[ConfigEntry]
|
||||
) -> list[BrowseMediaSource]:
|
||||
"""Handle browsing different immich instances."""
|
||||
|
||||
# --------------------------------------------------------
|
||||
# root level, render immich instances
|
||||
# --------------------------------------------------------
|
||||
if not item.identifier:
|
||||
LOGGER.debug("Render all Immich instances")
|
||||
return [
|
||||
@ -97,6 +102,10 @@ class ImmichMediaSource(MediaSource):
|
||||
)
|
||||
for entry in entries
|
||||
]
|
||||
|
||||
# --------------------------------------------------------
|
||||
# 1st level, render collections overview
|
||||
# --------------------------------------------------------
|
||||
identifier = ImmichMediaSourceIdentifier(item.identifier)
|
||||
entry: ImmichConfigEntry | None = (
|
||||
self.hass.config_entries.async_entry_for_domain_unique_id(
|
||||
@ -111,50 +120,127 @@ class ImmichMediaSource(MediaSource):
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{identifier.unique_id}|albums",
|
||||
identifier=f"{identifier.unique_id}|{collection}",
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title="albums",
|
||||
title=collection,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
)
|
||||
for collection in ("albums", "people", "tags")
|
||||
]
|
||||
|
||||
# --------------------------------------------------------
|
||||
# 2nd level, render collection
|
||||
# --------------------------------------------------------
|
||||
if identifier.collection_id is None:
|
||||
LOGGER.debug("Render all albums for %s", entry.title)
|
||||
if identifier.collection == "albums":
|
||||
LOGGER.debug("Render all albums for %s", entry.title)
|
||||
try:
|
||||
albums = await immich_api.albums.async_get_all_albums()
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{identifier.unique_id}|albums|{album.album_id}",
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title=album.album_name,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
thumbnail=f"/immich/{identifier.unique_id}/{album.album_thumbnail_asset_id}/thumbnail/image/jpg",
|
||||
)
|
||||
for album in albums
|
||||
]
|
||||
|
||||
if identifier.collection == "tags":
|
||||
LOGGER.debug("Render all tags for %s", entry.title)
|
||||
try:
|
||||
tags = await immich_api.tags.async_get_all_tags()
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{identifier.unique_id}|tags|{tag.tag_id}",
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title=tag.name,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
)
|
||||
for tag in tags
|
||||
]
|
||||
|
||||
if identifier.collection == "people":
|
||||
LOGGER.debug("Render all people for %s", entry.title)
|
||||
try:
|
||||
people = await immich_api.people.async_get_all_people()
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{identifier.unique_id}|people|{person.person_id}",
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title=person.name,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
thumbnail=f"/immich/{identifier.unique_id}/{person.person_id}/person/image/jpg",
|
||||
)
|
||||
for person in people
|
||||
]
|
||||
|
||||
# --------------------------------------------------------
|
||||
# final level, render assets
|
||||
# --------------------------------------------------------
|
||||
assert identifier.collection_id is not None
|
||||
assets: list[ImmichAsset] = []
|
||||
if identifier.collection == "albums":
|
||||
LOGGER.debug(
|
||||
"Render all assets of album %s for %s",
|
||||
identifier.collection_id,
|
||||
entry.title,
|
||||
)
|
||||
try:
|
||||
albums = await immich_api.albums.async_get_all_albums()
|
||||
album_info = await immich_api.albums.async_get_album_info(
|
||||
identifier.collection_id
|
||||
)
|
||||
assets = album_info.assets
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{identifier.unique_id}|albums|{album.album_id}",
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title=album.album_name,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
thumbnail=f"/immich/{identifier.unique_id}/{album.album_thumbnail_asset_id}/thumbnail/image/jpg",
|
||||
)
|
||||
for album in albums
|
||||
]
|
||||
|
||||
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.collection_id
|
||||
elif identifier.collection == "tags":
|
||||
LOGGER.debug(
|
||||
"Render all assets with tag %s",
|
||||
identifier.collection_id,
|
||||
)
|
||||
except ImmichError:
|
||||
return []
|
||||
try:
|
||||
assets = await immich_api.search.async_get_all_by_tag_ids(
|
||||
[identifier.collection_id]
|
||||
)
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
elif identifier.collection == "people":
|
||||
LOGGER.debug(
|
||||
"Render all assets for person %s",
|
||||
identifier.collection_id,
|
||||
)
|
||||
try:
|
||||
assets = await immich_api.search.async_get_all_by_person_ids(
|
||||
[identifier.collection_id]
|
||||
)
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
ret: list[BrowseMediaSource] = []
|
||||
for asset in album_info.assets:
|
||||
for asset in assets:
|
||||
if not (mime_type := asset.original_mime_type) or not mime_type.startswith(
|
||||
("image/", "video/")
|
||||
):
|
||||
@ -173,7 +259,8 @@ class ImmichMediaSource(MediaSource):
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=(
|
||||
f"{identifier.unique_id}|albums|"
|
||||
f"{identifier.unique_id}|"
|
||||
f"{identifier.collection}|"
|
||||
f"{identifier.collection_id}|"
|
||||
f"{asset.asset_id}|"
|
||||
f"{asset.original_file_name}|"
|
||||
@ -257,7 +344,10 @@ class ImmichMediaView(HomeAssistantView):
|
||||
|
||||
# web response for images
|
||||
try:
|
||||
image = await immich_api.assets.async_view_asset(asset_id, size)
|
||||
if size == "person":
|
||||
image = await immich_api.people.async_get_person_thumbnail(asset_id)
|
||||
else:
|
||||
image = await immich_api.assets.async_view_asset(asset_id, size)
|
||||
except ImmichError as exc:
|
||||
raise HTTPNotFound from exc
|
||||
return Response(body=image, content_type=f"{mime_type_base}/{mime_type_format}")
|
||||
|
@ -4,15 +4,25 @@ from collections.abc import AsyncGenerator, Generator
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from aioimmich import ImmichAlbums, ImmichAssests, ImmichServer, ImmichUsers
|
||||
from aioimmich import (
|
||||
ImmichAlbums,
|
||||
ImmichAssests,
|
||||
ImmichPeople,
|
||||
ImmichSearch,
|
||||
ImmichServer,
|
||||
ImmichTags,
|
||||
ImmichUsers,
|
||||
)
|
||||
from aioimmich.albums.models import ImmichAddAssetsToAlbumResponse
|
||||
from aioimmich.assets.models import ImmichAssetUploadResponse
|
||||
from aioimmich.people.models import ImmichPerson
|
||||
from aioimmich.server.models import (
|
||||
ImmichServerAbout,
|
||||
ImmichServerStatistics,
|
||||
ImmichServerStorage,
|
||||
ImmichServerVersionCheck,
|
||||
)
|
||||
from aioimmich.tags.models import ImmichTag
|
||||
from aioimmich.users.models import ImmichUserObject
|
||||
import pytest
|
||||
|
||||
@ -29,7 +39,12 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.aiohttp import MockStreamReaderChunked
|
||||
|
||||
from .const import MOCK_ALBUM_WITH_ASSETS, MOCK_ALBUM_WITHOUT_ASSETS
|
||||
from .const import (
|
||||
MOCK_ALBUM_WITH_ASSETS,
|
||||
MOCK_ALBUM_WITHOUT_ASSETS,
|
||||
MOCK_PEOPLE_ASSETS,
|
||||
MOCK_TAGS_ASSETS,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -87,6 +102,58 @@ def mock_immich_assets() -> AsyncMock:
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_people() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
mock = AsyncMock(spec=ImmichPeople)
|
||||
mock.async_get_all_people.return_value = [
|
||||
ImmichPerson.from_dict(
|
||||
{
|
||||
"id": "6176838a-ac5a-4d1f-9a35-91c591d962d8",
|
||||
"name": "Me",
|
||||
"birthDate": None,
|
||||
"thumbnailPath": "upload/thumbs/e7ef5713-9dab-4bd4-b899-715b0ca4379e/61/76/6176838a-ac5a-4d1f-9a35-91c591d962d8.jpeg",
|
||||
"isHidden": False,
|
||||
"isFavorite": False,
|
||||
"updatedAt": "2025-05-11T11:07:41.651Z",
|
||||
}
|
||||
),
|
||||
ImmichPerson.from_dict(
|
||||
{
|
||||
"id": "3e66aa4a-a4a8-41a4-86fe-2ae5e490078f",
|
||||
"name": "I",
|
||||
"birthDate": None,
|
||||
"thumbnailPath": "upload/thumbs/e7ef5713-9dab-4bd4-b899-715b0ca4379e/3e/66/3e66aa4a-a4a8-41a4-86fe-2ae5e490078f.jpeg",
|
||||
"isHidden": False,
|
||||
"isFavorite": False,
|
||||
"updatedAt": "2025-05-19T22:10:21.953Z",
|
||||
}
|
||||
),
|
||||
ImmichPerson.from_dict(
|
||||
{
|
||||
"id": "a3c83297-684a-4576-82dc-b07432e8a18f",
|
||||
"name": "Myself",
|
||||
"birthDate": None,
|
||||
"thumbnailPath": "upload/thumbs/e7ef5713-9dab-4bd4-b899-715b0ca4379e/a3/c8/a3c83297-684a-4576-82dc-b07432e8a18f.jpeg",
|
||||
"isHidden": False,
|
||||
"isFavorite": False,
|
||||
"updatedAt": "2025-05-12T21:07:04.044Z",
|
||||
}
|
||||
),
|
||||
]
|
||||
mock.async_get_person_thumbnail.return_value = b"yyyy"
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_search() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
mock = AsyncMock(spec=ImmichSearch)
|
||||
mock.async_get_all_by_person_ids.return_value = MOCK_PEOPLE_ASSETS
|
||||
mock.async_get_all_by_tag_ids.return_value = MOCK_TAGS_ASSETS
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_server() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
@ -153,6 +220,33 @@ def mock_immich_server() -> AsyncMock:
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_tags() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
mock = AsyncMock(spec=ImmichTags)
|
||||
mock.async_get_all_tags.return_value = [
|
||||
ImmichTag.from_dict(
|
||||
{
|
||||
"id": "67301cb8-cb73-4e8a-99e9-475cb3f7e7b5",
|
||||
"name": "Halloween",
|
||||
"value": "Halloween",
|
||||
"createdAt": "2025-05-12T20:00:45.220Z",
|
||||
"updatedAt": "2025-05-12T20:00:47.224Z",
|
||||
},
|
||||
),
|
||||
ImmichTag.from_dict(
|
||||
{
|
||||
"id": "69bd487f-dc1e-4420-94c6-656f0515773d",
|
||||
"name": "Holidays",
|
||||
"value": "Holidays",
|
||||
"createdAt": "2025-05-12T20:00:49.967Z",
|
||||
"updatedAt": "2025-05-12T20:00:55.575Z",
|
||||
},
|
||||
),
|
||||
]
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_user() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
@ -185,7 +279,10 @@ def mock_immich_user() -> AsyncMock:
|
||||
async def mock_immich(
|
||||
mock_immich_albums: AsyncMock,
|
||||
mock_immich_assets: AsyncMock,
|
||||
mock_immich_people: AsyncMock,
|
||||
mock_immich_search: AsyncMock,
|
||||
mock_immich_server: AsyncMock,
|
||||
mock_immich_tags: AsyncMock,
|
||||
mock_immich_user: AsyncMock,
|
||||
) -> AsyncGenerator[AsyncMock]:
|
||||
"""Mock the Immich API."""
|
||||
@ -196,7 +293,10 @@ async def mock_immich(
|
||||
client = mock_immich.return_value
|
||||
client.albums = mock_immich_albums
|
||||
client.assets = mock_immich_assets
|
||||
client.people = mock_immich_people
|
||||
client.search = mock_immich_search
|
||||
client.server = mock_immich_server
|
||||
client.tags = mock_immich_tags
|
||||
client.users = mock_immich_user
|
||||
yield client
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Constants for the Immich integration tests."""
|
||||
|
||||
from aioimmich.albums.models import ImmichAlbum
|
||||
from aioimmich.assets.models import ImmichAsset
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
@ -113,3 +114,131 @@ MOCK_ALBUM_WITH_ASSETS = ImmichAlbum.from_dict(
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
MOCK_PEOPLE_ASSETS = [
|
||||
ImmichAsset.from_dict(
|
||||
{
|
||||
"id": "2242eda3-94c2-49ee-86d4-e9e071b6fbf4",
|
||||
"deviceAssetId": "1000092019",
|
||||
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"deviceId": "5933dd9394fc6bf0493a26b4e38acca1076f30ab246442976d2917f1d57d99a1",
|
||||
"libraryId": None,
|
||||
"type": "IMAGE",
|
||||
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/8e/a3/8ea31ee8-49c3-4be9-aa9d-b8ef26ba0abe.jpg",
|
||||
"originalFileName": "20250714_201122.jpg",
|
||||
"originalMimeType": "image/jpeg",
|
||||
"thumbhash": "XRgGDILGeMlPaJaMWIeagJcJSA==",
|
||||
"fileCreatedAt": "2025-07-14T18:11:22.648Z",
|
||||
"fileModifiedAt": "2025-07-14T18:11:25.000Z",
|
||||
"localDateTime": "2025-07-14T20:11:22.648Z",
|
||||
"updatedAt": "2025-07-26T10:16:39.131Z",
|
||||
"isFavorite": False,
|
||||
"isArchived": False,
|
||||
"isTrashed": False,
|
||||
"visibility": "timeline",
|
||||
"duration": "0:00:00.00000",
|
||||
"livePhotoVideoId": None,
|
||||
"people": [],
|
||||
"unassignedFaces": [],
|
||||
"checksum": "GcBJkDFoXx9d/wyl1xH89R4/NBQ=",
|
||||
"isOffline": False,
|
||||
"hasMetadata": True,
|
||||
"duplicateId": None,
|
||||
"resized": True,
|
||||
}
|
||||
),
|
||||
ImmichAsset.from_dict(
|
||||
{
|
||||
"id": "046ac0d9-8acd-44d8-953f-ecb3c786358a",
|
||||
"deviceAssetId": "1000092018",
|
||||
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"deviceId": "5933dd9394fc6bf0493a26b4e38acca1076f30ab246442976d2917f1d57d99a1",
|
||||
"libraryId": None,
|
||||
"type": "IMAGE",
|
||||
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/f5/b4/f5b4b200-47dd-45e8-98a4-4128df3f9189.jpg",
|
||||
"originalFileName": "20250714_201121.jpg",
|
||||
"originalMimeType": "image/jpeg",
|
||||
"thumbhash": "XRgGDILHeMlPeJaMSJmKgJcIWQ==",
|
||||
"fileCreatedAt": "2025-07-14T18:11:21.582Z",
|
||||
"fileModifiedAt": "2025-07-14T18:11:24.000Z",
|
||||
"localDateTime": "2025-07-14T20:11:21.582Z",
|
||||
"updatedAt": "2025-07-26T10:16:39.131Z",
|
||||
"isFavorite": False,
|
||||
"isArchived": False,
|
||||
"isTrashed": False,
|
||||
"visibility": "timeline",
|
||||
"duration": "0:00:00.00000",
|
||||
"livePhotoVideoId": None,
|
||||
"people": [],
|
||||
"unassignedFaces": [],
|
||||
"checksum": "X6kMpPulu/HJQnKmTqCoQYl3Sjc=",
|
||||
"isOffline": False,
|
||||
"hasMetadata": True,
|
||||
"duplicateId": None,
|
||||
"resized": True,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
MOCK_TAGS_ASSETS = [
|
||||
ImmichAsset.from_dict(
|
||||
{
|
||||
"id": "ae3d82fc-beb5-4abc-ae83-11fcfa5e7629",
|
||||
"deviceAssetId": "2132393",
|
||||
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"deviceId": "CLI",
|
||||
"libraryId": None,
|
||||
"type": "IMAGE",
|
||||
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/07/d0/07d04d86-7188-4335-95ca-9bd9fd2b399d.JPG",
|
||||
"originalFileName": "20110306_025024.jpg",
|
||||
"originalMimeType": "image/jpeg",
|
||||
"thumbhash": "WCgSFYRXaYdQiYineIiHd4SghQUY",
|
||||
"fileCreatedAt": "2011-03-06T01:50:24.000Z",
|
||||
"fileModifiedAt": "2011-03-06T01:50:24.000Z",
|
||||
"localDateTime": "2011-03-06T02:50:24.000Z",
|
||||
"updatedAt": "2025-07-26T10:16:39.477Z",
|
||||
"isFavorite": False,
|
||||
"isArchived": False,
|
||||
"isTrashed": False,
|
||||
"visibility": "timeline",
|
||||
"duration": "0:00:00.00000",
|
||||
"livePhotoVideoId": None,
|
||||
"people": [],
|
||||
"checksum": "eNwN0AN2hEYZJJkonl7ylGzJzko=",
|
||||
"isOffline": False,
|
||||
"hasMetadata": True,
|
||||
"duplicateId": None,
|
||||
"resized": True,
|
||||
},
|
||||
),
|
||||
ImmichAsset.from_dict(
|
||||
{
|
||||
"id": "b71d0d08-6727-44ae-8bba-83c190f95df4",
|
||||
"deviceAssetId": "2142137",
|
||||
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"deviceId": "CLI",
|
||||
"libraryId": None,
|
||||
"type": "IMAGE",
|
||||
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/4a/f4/4af42484-86f8-47a0-958a-f32da89ee03a.JPG",
|
||||
"originalFileName": "20110306_024053.jpg",
|
||||
"originalMimeType": "image/jpeg",
|
||||
"thumbhash": "4AcKFYZPZnhSmGl5daaYeG859ytT",
|
||||
"fileCreatedAt": "2011-03-06T01:40:53.000Z",
|
||||
"fileModifiedAt": "2011-03-06T01:40:52.000Z",
|
||||
"localDateTime": "2011-03-06T02:40:53.000Z",
|
||||
"updatedAt": "2025-07-26T10:16:39.474Z",
|
||||
"isFavorite": False,
|
||||
"isArchived": False,
|
||||
"isTrashed": False,
|
||||
"visibility": "timeline",
|
||||
"duration": "0:00:00.00000",
|
||||
"livePhotoVideoId": None,
|
||||
"people": [],
|
||||
"checksum": "VtokCjIwKqnHBFzH3kHakIJiq5I=",
|
||||
"isOffline": False,
|
||||
"hasMetadata": True,
|
||||
"duplicateId": None,
|
||||
"resized": True,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -26,7 +26,6 @@ from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.aiohttp import MockRequest, MockStreamReaderChunked
|
||||
|
||||
from . import setup_integration
|
||||
from .const import MOCK_ALBUM_WITHOUT_ASSETS
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -143,7 +142,8 @@ async def test_browse_media_get_root(
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == 1
|
||||
assert len(result.children) == 3
|
||||
|
||||
media_file = result.children[0]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.title == "albums"
|
||||
@ -151,174 +151,289 @@ async def test_browse_media_get_root(
|
||||
"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,
|
||||
) -> None:
|
||||
"""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, f"{mock_config_entry.unique_id}|albums", None)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
|
||||
async def test_browse_media_get_album_items_error(
|
||||
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)
|
||||
|
||||
# unknown album
|
||||
mock_immich.albums.async_get_album_info.return_value = MOCK_ALBUM_WITHOUT_ASSETS
|
||||
item = MediaSourceItem(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums|721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
# exception in async_get_album_info()
|
||||
mock_immich.albums.async_get_album_info.side_effect = ImmichError(
|
||||
{
|
||||
"message": "Not found or no album.read access",
|
||||
"error": "Bad Request",
|
||||
"statusCode": 400,
|
||||
"correlationId": "e0hlizyl",
|
||||
}
|
||||
)
|
||||
item = MediaSourceItem(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums|721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
|
||||
async def test_browse_media_get_album_items(
|
||||
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|721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == 2
|
||||
media_file = result.children[0]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.identifier == (
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums|"
|
||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6|"
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4|filename.jpg|image/jpeg"
|
||||
)
|
||||
assert media_file.title == "filename.jpg"
|
||||
assert media_file.media_class == MediaClass.IMAGE
|
||||
assert media_file.media_content_type == "image/jpeg"
|
||||
assert media_file.can_play is False
|
||||
assert not media_file.can_expand
|
||||
assert media_file.thumbnail == (
|
||||
"/immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/thumbnail/image/jpeg"
|
||||
)
|
||||
|
||||
media_file = result.children[1]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.identifier == (
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums|"
|
||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6|"
|
||||
"2e65a5f2-db83-44c4-81ab-f5ff20c9bd7b|filename.mp4|video/mp4"
|
||||
assert media_file.title == "people"
|
||||
assert media_file.media_content_id == (
|
||||
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e|people"
|
||||
)
|
||||
assert media_file.title == "filename.mp4"
|
||||
assert media_file.media_class == MediaClass.VIDEO
|
||||
assert media_file.media_content_type == "video/mp4"
|
||||
assert media_file.can_play is True
|
||||
assert not media_file.can_expand
|
||||
assert media_file.thumbnail == (
|
||||
"/immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
|
||||
"2e65a5f2-db83-44c4-81ab-f5ff20c9bd7b/thumbnail/image/jpeg"
|
||||
|
||||
media_file = result.children[2]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.title == "tags"
|
||||
assert media_file.media_content_id == (
|
||||
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e|tags"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("collection", "children"),
|
||||
[
|
||||
(
|
||||
"albums",
|
||||
[{"title": "My Album", "asset_id": "721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"}],
|
||||
),
|
||||
(
|
||||
"people",
|
||||
[
|
||||
{"title": "Me", "asset_id": "6176838a-ac5a-4d1f-9a35-91c591d962d8"},
|
||||
{"title": "I", "asset_id": "3e66aa4a-a4a8-41a4-86fe-2ae5e490078f"},
|
||||
{"title": "Myself", "asset_id": "a3c83297-684a-4576-82dc-b07432e8a18f"},
|
||||
],
|
||||
),
|
||||
(
|
||||
"tags",
|
||||
[
|
||||
{
|
||||
"title": "Halloween",
|
||||
"asset_id": "67301cb8-cb73-4e8a-99e9-475cb3f7e7b5",
|
||||
},
|
||||
{
|
||||
"title": "Holidays",
|
||||
"asset_id": "69bd487f-dc1e-4420-94c6-656f0515773d",
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_browse_media_collections(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
collection: str,
|
||||
children: list[dict],
|
||||
) -> None:
|
||||
"""Test browse through collections."""
|
||||
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, f"{mock_config_entry.unique_id}|{collection}", None
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == len(children)
|
||||
for idx, child in enumerate(children):
|
||||
media_file = result.children[idx]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.title == child["title"]
|
||||
assert media_file.media_content_id == (
|
||||
"media-source://immich/"
|
||||
f"{mock_config_entry.unique_id}|{collection}|"
|
||||
f"{child['asset_id']}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("collection", "mocked_get_fn"),
|
||||
[
|
||||
("albums", ("albums", "async_get_all_albums")),
|
||||
("people", ("people", "async_get_all_people")),
|
||||
("tags", ("tags", "async_get_all_tags")),
|
||||
],
|
||||
)
|
||||
async def test_browse_media_collections_error(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
collection: str,
|
||||
mocked_get_fn: tuple[str, str],
|
||||
) -> None:
|
||||
"""Test browse_media with unknown collection."""
|
||||
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 = 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, f"{mock_config_entry.unique_id}|{collection}", None
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("collection", "mocked_get_fn"),
|
||||
[
|
||||
("albums", ("albums", "async_get_album_info")),
|
||||
("people", ("search", "async_get_all_by_person_ids")),
|
||||
("tags", ("search", "async_get_all_by_tag_ids")),
|
||||
],
|
||||
)
|
||||
async def test_browse_media_collection_items_error(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
collection: str,
|
||||
mocked_get_fn: tuple[str, str],
|
||||
) -> 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)
|
||||
|
||||
getattr(
|
||||
getattr(mock_immich, mocked_get_fn[0]), mocked_get_fn[1]
|
||||
).side_effect = ImmichError(
|
||||
{
|
||||
"message": "Not found or no album.read access",
|
||||
"error": "Bad Request",
|
||||
"statusCode": 400,
|
||||
"correlationId": "e0hlizyl",
|
||||
}
|
||||
)
|
||||
item = MediaSourceItem(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"{mock_config_entry.unique_id}|{collection}|721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("collection", "collection_id", "children"),
|
||||
[
|
||||
(
|
||||
"albums",
|
||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
[
|
||||
{
|
||||
"original_file_name": "filename.jpg",
|
||||
"asset_id": "2e94c203-50aa-4ad2-8e29-56dd74e0eff4",
|
||||
"media_class": MediaClass.IMAGE,
|
||||
"media_content_type": "image/jpeg",
|
||||
"thumb_mime_type": "image/jpeg",
|
||||
"can_play": False,
|
||||
},
|
||||
{
|
||||
"original_file_name": "filename.mp4",
|
||||
"asset_id": "2e65a5f2-db83-44c4-81ab-f5ff20c9bd7b",
|
||||
"media_class": MediaClass.VIDEO,
|
||||
"media_content_type": "video/mp4",
|
||||
"thumb_mime_type": "image/jpeg",
|
||||
"can_play": True,
|
||||
},
|
||||
],
|
||||
),
|
||||
(
|
||||
"people",
|
||||
"6176838a-ac5a-4d1f-9a35-91c591d962d8",
|
||||
[
|
||||
{
|
||||
"original_file_name": "20250714_201122.jpg",
|
||||
"asset_id": "2242eda3-94c2-49ee-86d4-e9e071b6fbf4",
|
||||
"media_class": MediaClass.IMAGE,
|
||||
"media_content_type": "image/jpeg",
|
||||
"thumb_mime_type": "image/jpeg",
|
||||
"can_play": False,
|
||||
},
|
||||
{
|
||||
"original_file_name": "20250714_201121.jpg",
|
||||
"asset_id": "046ac0d9-8acd-44d8-953f-ecb3c786358a",
|
||||
"media_class": MediaClass.IMAGE,
|
||||
"media_content_type": "image/jpeg",
|
||||
"thumb_mime_type": "image/jpeg",
|
||||
"can_play": False,
|
||||
},
|
||||
],
|
||||
),
|
||||
(
|
||||
"tags",
|
||||
"6176838a-ac5a-4d1f-9a35-91c591d962d8",
|
||||
[
|
||||
{
|
||||
"original_file_name": "20110306_025024.jpg",
|
||||
"asset_id": "ae3d82fc-beb5-4abc-ae83-11fcfa5e7629",
|
||||
"media_class": MediaClass.IMAGE,
|
||||
"media_content_type": "image/jpeg",
|
||||
"thumb_mime_type": "image/jpeg",
|
||||
"can_play": False,
|
||||
},
|
||||
{
|
||||
"original_file_name": "20110306_024053.jpg",
|
||||
"asset_id": "b71d0d08-6727-44ae-8bba-83c190f95df4",
|
||||
"media_class": MediaClass.IMAGE,
|
||||
"media_content_type": "image/jpeg",
|
||||
"thumb_mime_type": "image/jpeg",
|
||||
"can_play": False,
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_browse_media_collection_get_items(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
collection: str,
|
||||
collection_id: str,
|
||||
children: list[dict],
|
||||
) -> 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,
|
||||
f"{mock_config_entry.unique_id}|{collection}|{collection_id}",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == len(children)
|
||||
|
||||
for idx, child in enumerate(children):
|
||||
media_file = result.children[idx]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.identifier == (
|
||||
f"{mock_config_entry.unique_id}|{collection}|{collection_id}|"
|
||||
f"{child['asset_id']}|{child['original_file_name']}|{child['media_content_type']}"
|
||||
)
|
||||
assert media_file.title == child["original_file_name"]
|
||||
assert media_file.media_class == child["media_class"]
|
||||
assert media_file.media_content_type == child["media_content_type"]
|
||||
assert media_file.can_play is child["can_play"]
|
||||
assert not media_file.can_expand
|
||||
assert media_file.thumbnail == (
|
||||
f"/immich/{mock_config_entry.unique_id}/"
|
||||
f"{child['asset_id']}/thumbnail/{child['thumb_mime_type']}"
|
||||
)
|
||||
|
||||
|
||||
async def test_media_view(
|
||||
hass: HomeAssistant,
|
||||
tmp_path: Path,
|
||||
@ -362,6 +477,22 @@ async def test_media_view(
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/thumbnail/image/jpeg",
|
||||
)
|
||||
|
||||
# exception in async_get_person_thumbnail()
|
||||
mock_immich.people.async_get_person_thumbnail.side_effect = ImmichError(
|
||||
{
|
||||
"message": "Not found or no asset.read access",
|
||||
"error": "Bad Request",
|
||||
"statusCode": 400,
|
||||
"correlationId": "e0hlizyl",
|
||||
}
|
||||
)
|
||||
with pytest.raises(web.HTTPNotFound):
|
||||
await view.get(
|
||||
request,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/person/image/jpeg",
|
||||
)
|
||||
|
||||
# exception in async_play_video_stream()
|
||||
mock_immich.assets.async_play_video_stream.side_effect = ImmichError(
|
||||
{
|
||||
@ -396,6 +527,24 @@ async def test_media_view(
|
||||
)
|
||||
assert isinstance(result, web.Response)
|
||||
|
||||
mock_immich.people.async_get_person_thumbnail.side_effect = None
|
||||
mock_immich.people.async_get_person_thumbnail.return_value = b"xxxx"
|
||||
with patch.object(tempfile, "tempdir", tmp_path):
|
||||
result = await view.get(
|
||||
request,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/person/image/jpeg",
|
||||
)
|
||||
assert isinstance(result, web.Response)
|
||||
|
||||
with patch.object(tempfile, "tempdir", tmp_path):
|
||||
result = await view.get(
|
||||
request,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/fullsize/image/jpeg",
|
||||
)
|
||||
assert isinstance(result, web.Response)
|
||||
|
||||
mock_immich.assets.async_play_video_stream.side_effect = None
|
||||
mock_immich.assets.async_play_video_stream.return_value = MockStreamReaderChunked(
|
||||
b"xxxx"
|
||||
|
Loading…
x
Reference in New Issue
Block a user