Skip to content
Open
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
54 changes: 38 additions & 16 deletions homeassistant/components/collection_image/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@

import voluptuous as vol

from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
from homeassistant.components.media_player import BrowseError, MediaClass
from homeassistant.components.media_source import async_browse_media
from homeassistant.components.media_source import URI_SCHEME, async_browse_media
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.helpers.selector import MediaSelector

from .const import CONF_MEDIA, DOMAIN

IMAGE_MEDIA_URI = f"{URI_SCHEME}{IMAGE_DOMAIN}"

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_MEDIA): MediaSelector({"accept": ["directory"]}),
vol.Required(CONF_MEDIA): MediaSelector(
{"accept": ["directory"], "multiple": True}
),
Comment thread
karwosts marked this conversation as resolved.
}
)

Expand All @@ -28,24 +33,41 @@ async def async_step_user(
"""Handle the initial step."""
errors: dict[str, str] = {}
placeholders: dict[str, str] = {}
found_pictures = False
title = "Unnamed collection"
if user_input is not None:
user_media = user_input[CONF_MEDIA]
try:
browse = await async_browse_media(
self.hass, user_media["media_content_id"]
)
except BrowseError as err:
errors["media"] = "failed_browse"
placeholders["error"] = str(err)
else:
if browse.children and any(
item.media_class == MediaClass.IMAGE for item in browse.children
):
user_media_list = user_input[CONF_MEDIA]
for user_media in user_media_list:
Comment thread
karwosts marked this conversation as resolved.
if user_media["media_content_id"] == IMAGE_MEDIA_URI:
errors["media"] = "invalid_selection"
placeholders["error"] = IMAGE_MEDIA_URI
break
try:
browse = await async_browse_media(
self.hass, user_media["media_content_id"]
)
except BrowseError as err:
errors["media"] = "failed_browse"
placeholders["error"] = str(err)
break
else:
if (
not found_pictures
and browse.children
and any(
item.media_class == MediaClass.IMAGE
for item in browse.children
)
):
found_pictures = True
if browse.title:
title = f"{browse.title} collection"
if "media" not in errors:
if found_pictures:
return self.async_create_entry(
title=f"{browse.title or 'Unnamed'} collection",
title=title,
data=user_input,
)

errors["media"] = "selected_media_no_images"

return self.async_show_form(
Expand Down
115 changes: 68 additions & 47 deletions homeassistant/components/collection_image/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from homeassistant.components.image import ImageEntity
from homeassistant.components.media_player import (
BrowseError,
BrowseMedia,
MediaClass,
async_process_play_media_url,
)
Expand Down Expand Up @@ -36,11 +37,15 @@ async def async_setup_entry(
) -> None:
"""Set up the Collection Image image entities."""
media = entry.data[CONF_MEDIA]
if isinstance(media, dict):
content_ids = [media["media_content_id"]]
else:
content_ids = [item["media_content_id"] for item in media]
async_add_entities(
[
CollectionImageImageEntity(
name=entry.title,
media_content_id=media["media_content_id"],
media_content_ids=content_ids,
unique_id=entry.entry_id,
hass=hass,
)
Expand All @@ -58,7 +63,7 @@ class CollectionImageImageEntity(ImageEntity):
def __init__(
self,
name: str,
media_content_id: str,
media_content_ids: list[str],
unique_id: str,
hass: HomeAssistant,
) -> None:
Expand All @@ -67,11 +72,10 @@ def __init__(
self.path = None
self._attr_unique_id = unique_id
self._attr_name = name
self.media_content_id = media_content_id
self.media_content_ids = media_content_ids

async def get_next_image(self) -> None:
"""Update the image entity with the next image from the source media."""

"""Update the image entity with a random image from configured media sources."""
self._cached_image = None

def set_unavailable() -> None:
Expand All @@ -81,59 +85,76 @@ def set_unavailable() -> 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
images: list[BrowseMedia] = []

if media.children and (
filtered := [
item for item in media.children if item.media_class == MediaClass.IMAGE
]
):
child = random.choice(filtered)
for media_content_id in self.media_content_ids:
try:
resolved = await async_resolve_media(
self.hass, child.media_content_id, self.entity_id
)
except Unresolvable as err:
media = await async_browse_media(self.hass, 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 resolved.url:
self.path = None
self._attr_image_url = async_process_play_media_url(
self.hass, resolved.url
_LOGGER.info(
"%s: Unable to browse %s: %s",
self.entity_id,
media_content_id,
err,
)
continue
Comment thread
karwosts marked this conversation as resolved.

if media.children:
images.extend(
item
for item in media.children
if item.media_class == MediaClass.IMAGE
)
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:

if not images:
if not self._unavailable_logged:
_LOGGER.info(
"%s: Has become available again",
"%s: No valid images in %s",
self.entity_id,
Comment on lines +110 to 114

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's worthwhile to add extra complexity to avoid just a single extra log line in a pathological case. Right now we get one log per unbrowseable directory, and an overall error if we can't find anything at all. That seems reasonable to me.

self.media_content_ids,
)
self._unavailable_logged = False
self.async_write_ha_state()
set_unavailable()
return

if not self._unavailable_logged:
_LOGGER.info(
"%s: No valid images in %s",
child = random.choice(images)

try:
resolved = await async_resolve_media(
self.hass,
child.media_content_id,
self.entity_id,
self.media_content_id,
)
set_unavailable()
return
except Unresolvable as err:
if not self._unavailable_logged:
_LOGGER.info(
"%s: Unable to resolve %s: %s",
self.entity_id,
child.media_content_id,
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()

@override
async def async_added_to_hass(self) -> None:
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/collection_image/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"config": {
"error": {
"failed_browse": "Failed to browse media: {error}",
"invalid_selection": "Invalid media selected: {error}",
"selected_media_no_images": "The selected media has no images. Please select a media directory with images."
},
"step": {
Expand All @@ -10,9 +11,9 @@
"media": "Media"
},
"data_description": {
"media": "The media directory where images will be retrieved from."
"media": "The media where images will be retrieved from."
},
"description": "The Collection Image integration creates a single image entity by selecting an image from the selected media folder.",
"description": "The Collection Image integration creates an image entity which renders an image from the selected media.",
"submit": "Create"
}
}
Expand Down
Loading
Loading