Allow generic camera conf without still_image_url (#62611)

* Allow generic config with no CONF_STILL_IMAGE_URL
* Use Stream.async_get_image when no CONF_STILL_IMAGE_URL
* Remove GenericCamera.camera_image
This commit is contained in:
uvjustin 2021-12-26 15:53:14 +08:00 committed by GitHub
parent 3e3fb52dfa
commit 08a3140e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 14 deletions

View File

@ -1,7 +1,6 @@
"""Support for IP Cameras."""
from __future__ import annotations
import asyncio
import logging
import httpx
@ -45,8 +44,8 @@ GET_IMAGE_TIMEOUT = 10
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_STILL_IMAGE_URL): cv.template,
vol.Optional(CONF_STREAM_SOURCE): cv.template,
vol.Required(vol.Any(CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE)): cv.template,
vol.Optional(vol.Any(CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE)): cv.template,
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In(
[HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]
),
@ -81,9 +80,10 @@ class GenericCamera(Camera):
self.hass = hass
self._authentication = device_info.get(CONF_AUTHENTICATION)
self._name = device_info.get(CONF_NAME)
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url = device_info.get(CONF_STILL_IMAGE_URL)
if self._still_image_url:
self._still_image_url.hass = hass
self._stream_source = device_info.get(CONF_STREAM_SOURCE)
self._still_image_url.hass = hass
if self._stream_source is not None:
self._stream_source.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
@ -120,18 +120,16 @@ class GenericCamera(Camera):
"""Return the interval between frames of the mjpeg stream."""
return self._frame_interval
def camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return bytes of camera image."""
return asyncio.run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop
).result()
async def async_camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return a still image response from the camera."""
if not self._still_image_url:
if not self.stream:
await self.async_create_stream()
if self.stream:
return await self.stream.async_get_image(width, height)
return None
try:
url = self._still_image_url.async_render(parse_result=False)
except TemplateError as err:

View File

@ -437,6 +437,8 @@ class Stream:
hass.add_executor_job underneath the hood.
"""
self.add_provider(HLS_PROVIDER)
self.start()
return await self._keyframe_converter.async_get_image(
width=width, height=height
)

View File

@ -14,7 +14,7 @@ from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.const import SERVICE_RELOAD
from homeassistant.setup import async_setup_component
from tests.common import get_fixture_path
from tests.common import AsyncMock, Mock, get_fixture_path
@respx.mock
@ -459,3 +459,48 @@ async def test_timeout_cancelled(hass, hass_client):
assert respx.calls.call_count == total_calls
assert resp.status == HTTPStatus.OK
assert await resp.text() == "hello world"
async def test_no_still_image_url(hass, hass_client):
"""Test that the component can grab images from stream with no still_image_url."""
assert await async_setup_component(
hass,
"camera",
{
"camera": {
"name": "config_test",
"platform": "generic",
"stream_source": "rtsp://example.com:554/rtsp/",
},
},
)
await hass.async_block_till_done()
client = await hass_client()
with patch(
"homeassistant.components.generic.camera.GenericCamera.stream_source",
return_value=None,
) as mock_stream_source:
# First test when there is no stream_source should fail
resp = await client.get("/api/camera_proxy/camera.config_test")
await hass.async_block_till_done()
mock_stream_source.assert_called_once()
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
with patch("homeassistant.components.camera.create_stream") as mock_create_stream:
# Now test when creating the stream succeeds
mock_stream = Mock()
mock_stream.async_get_image = AsyncMock()
mock_stream.async_get_image.return_value = b"stream_keyframe_image"
mock_create_stream.return_value = mock_stream
# should start the stream and get the image
resp = await client.get("/api/camera_proxy/camera.config_test")
await hass.async_block_till_done()
mock_create_stream.assert_called_once()
mock_stream.async_get_image.assert_called_once()
assert resp.status == HTTPStatus.OK
assert await resp.read() == b"stream_keyframe_image"