"""Support for Collection Image image.""" import logging from pathlib import Path import random from typing import override from homeassistant.components.image import ImageEntity from homeassistant.components.media_player import ( BrowseError, MediaClass, async_process_play_media_url, ) from homeassistant.components.media_source import ( Unresolvable, async_browse_media, async_resolve_media, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.start import async_at_started from homeassistant.helpers.typing import UNDEFINED from homeassistant.util import dt as dt_util from .const import CONF_MEDIA, DOMAIN _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Collection Image image entities.""" media = entry.data[CONF_MEDIA] async_add_entities( [ CollectionImageImageEntity( name=entry.title, media_content_id=media["media_content_id"], unique_id=entry.entry_id, hass=hass, ) ] ) class CollectionImageImageEntity(ImageEntity): """Implement the image entity for Collection Image.""" _unavailable_logged: bool = False path: Path | None def __init__( self, name: str, media_content_id: str, unique_id: str, hass: HomeAssistant, ) -> None: """Initialize the entity.""" super().__init__(hass) self.path = None self._attr_unique_id = unique_id self._attr_name = name self.media_content_id = media_content_id async def get_next_image(self) -> None: """Update the image entity with the next image from the source media.""" self._cached_image = None def set_unavailable() -> None: self._unavailable_logged = True self._attr_available = False self.path = None self._attr_image_url = UNDEFINED self.async_write_ha_state() try: media = await async_browse_media(self.hass, self.media_content_id) except BrowseError as err: if not self._unavailable_logged: _LOGGER.info("%s: %s", self.entity_id, str(err)) set_unavailable() return if media.children and ( filtered := [ item for item in media.children if item.media_class == MediaClass.IMAGE ] ): child = random.choice(filtered) try: resolved = await async_resolve_media( self.hass, child.media_content_id, self.entity_id ) except Unresolvable as err: if not self._unavailable_logged: _LOGGER.info("%s: %s", self.entity_id, str(err)) set_unavailable() return if resolved.url: self.path = None self._attr_image_url = async_process_play_media_url( self.hass, resolved.url ) else: self.path = resolved.path self._attr_image_url = UNDEFINED self._attr_content_type = resolved.mime_type self._attr_available = True self._attr_image_last_updated = dt_util.utcnow() if self._unavailable_logged: _LOGGER.info( "%s: Has become available again", self.entity_id, ) self._unavailable_logged = False self.async_write_ha_state() return if not self._unavailable_logged: _LOGGER.info( "%s: No valid images in %s", self.entity_id, self.media_content_id, ) set_unavailable() return @override async def async_added_to_hass(self) -> None: """Initialize the first image after entity has been created.""" async def get_next_image_on_start(_hass: HomeAssistant) -> None: await self.get_next_image() self.async_on_remove(async_at_started(self.hass, get_next_image_on_start)) @override def image(self) -> bytes | None: """Return bytes of image.""" if self.path: try: return self.path.read_bytes() except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="image_read_error", translation_placeholders={ "path": str(self.path), "error": str(err), }, ) from err return None