Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
45 changes: 30 additions & 15 deletions homeassistant/components/collection_image/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

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 +30,37 @@ 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.
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
4 changes: 2 additions & 2 deletions homeassistant/components/collection_image/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,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
1 change: 1 addition & 0 deletions homeassistant/components/image/media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ async def async_browse_media(
can_expand=False,
)
for image in self.hass.data[DATA_COMPONENT].entities
if self.hass.states.get(image.entity_id)
Comment thread
karwosts marked this conversation as resolved.
Outdated
Comment thread
karwosts marked this conversation as resolved.
Outdated
]

return BrowseMediaSource(
Expand Down
Loading
Loading