diff --git a/homeassistant/components/youtube/coordinator.py b/homeassistant/components/youtube/coordinator.py index 190e79e3393..d3430d8329c 100644 --- a/homeassistant/components/youtube/coordinator.py +++ b/homeassistant/components/youtube/coordinator.py @@ -85,9 +85,16 @@ class YouTubeDataUpdateCoordinator(DataUpdateCoordinator): ATTR_PUBLISHED_AT: video["snippet"]["publishedAt"], ATTR_TITLE: video["snippet"]["title"], ATTR_DESCRIPTION: video["snippet"]["description"], - ATTR_THUMBNAIL: video["snippet"]["thumbnails"]["standard"]["url"], + ATTR_THUMBNAIL: self._get_thumbnail(video), ATTR_VIDEO_ID: video["contentDetails"]["videoId"], }, ATTR_SUBSCRIBER_COUNT: int(channel["statistics"]["subscriberCount"]), } return data + + def _get_thumbnail(self, video: dict[str, Any]) -> str | None: + thumbnails = video["snippet"]["thumbnails"] + for size in ("standard", "high", "medium", "default"): + if size in thumbnails: + return thumbnails[size]["url"] + return None diff --git a/homeassistant/components/youtube/sensor.py b/homeassistant/components/youtube/sensor.py index c605b960475..4560dcfda8c 100644 --- a/homeassistant/components/youtube/sensor.py +++ b/homeassistant/components/youtube/sensor.py @@ -30,7 +30,7 @@ class YouTubeMixin: """Mixin for required keys.""" value_fn: Callable[[Any], StateType] - entity_picture_fn: Callable[[Any], str] + entity_picture_fn: Callable[[Any], str | None] attributes_fn: Callable[[Any], dict[str, Any]] | None @@ -87,7 +87,7 @@ class YouTubeSensor(YouTubeChannelEntity, SensorEntity): return self.entity_description.value_fn(self.coordinator.data[self._channel_id]) @property - def entity_picture(self) -> str: + def entity_picture(self) -> str | None: """Return the value reported by the sensor.""" return self.entity_description.entity_picture_fn( self.coordinator.data[self._channel_id] diff --git a/tests/components/youtube/fixtures/thumbnail/default.json b/tests/components/youtube/fixtures/thumbnail/default.json new file mode 100644 index 00000000000..6b5d66d6501 --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/default.json @@ -0,0 +1,42 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/high.json b/tests/components/youtube/fixtures/thumbnail/high.json new file mode 100644 index 00000000000..430ad3715cc --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/high.json @@ -0,0 +1,52 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/hqdefault.jpg", + "width": 480, + "height": 360 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/medium.json b/tests/components/youtube/fixtures/thumbnail/medium.json new file mode 100644 index 00000000000..21cb09bd886 --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/medium.json @@ -0,0 +1,47 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", + "width": 320, + "height": 180 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/none.json b/tests/components/youtube/fixtures/thumbnail/none.json new file mode 100644 index 00000000000..d4c28730cab --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/none.json @@ -0,0 +1,36 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": {}, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/standard.json b/tests/components/youtube/fixtures/thumbnail/standard.json new file mode 100644 index 00000000000..bdbedfcf4c9 --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/standard.json @@ -0,0 +1,57 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/hqdefault.jpg", + "width": 480, + "height": 360 + }, + "standard": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/sddefault.jpg", + "width": 640, + "height": 480 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/test_sensor.py b/tests/components/youtube/test_sensor.py index 3462e291af8..6bd99399952 100644 --- a/tests/components/youtube/test_sensor.py +++ b/tests/components/youtube/test_sensor.py @@ -3,6 +3,7 @@ from datetime import timedelta from unittest.mock import patch from google.auth.exceptions import RefreshError +import pytest from homeassistant import config_entries from homeassistant.components.youtube import DOMAIN @@ -87,3 +88,38 @@ async def test_sensor_reauth_trigger( assert flow["step_id"] == "reauth_confirm" assert flow["handler"] == DOMAIN assert flow["context"]["source"] == config_entries.SOURCE_REAUTH + + +@pytest.mark.parametrize( + ("fixture", "url", "has_entity_picture"), + [ + ("standard", "https://i.ytimg.com/vi/wysukDrMdqU/sddefault.jpg", True), + ("high", "https://i.ytimg.com/vi/wysukDrMdqU/hqdefault.jpg", True), + ("medium", "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", True), + ("default", "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", True), + ("none", None, False), + ], +) +async def test_thumbnail( + hass: HomeAssistant, + setup_integration: ComponentSetup, + fixture: str, + url: str | None, + has_entity_picture: bool, +) -> None: + """Test if right thumbnail is selected.""" + await setup_integration() + + with patch( + "homeassistant.components.youtube.api.build", + return_value=MockService( + playlist_items_fixture=f"youtube/thumbnail/{fixture}.json" + ), + ): + future = dt_util.utcnow() + timedelta(minutes=15) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + state = hass.states.get("sensor.google_for_developers_latest_upload") + assert state + assert ("entity_picture" in state.attributes) is has_entity_picture + assert state.attributes.get("entity_picture") == url