From 7e49ae64105aeb7d422fe8c999f86cddfa1c653c Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 13 May 2022 07:43:24 +0800 Subject: [PATCH] Add use_wallclock_as_timestamps option to generic (#71245) Co-authored-by: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/components/generic/camera.py | 5 ++++ .../components/generic/config_flow.py | 20 ++++++++++++- homeassistant/components/generic/const.py | 6 +++- homeassistant/components/generic/strings.json | 4 +++ .../components/generic/translations/en.json | 6 ++-- tests/components/generic/test_config_flow.py | 30 +++++++++++++++++++ 6 files changed, 67 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index d20032e2607..197890efad3 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -37,6 +37,7 @@ from .const import ( CONF_RTSP_TRANSPORT, CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, DEFAULT_NAME, FFMPEG_OPTION_MAP, GET_IMAGE_TIMEOUT, @@ -160,6 +161,10 @@ class GenericCamera(Camera): CONF_RTSP_TRANSPORT ] self._auth = generate_auth(device_info) + if device_info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS): + self.stream_options[ + FFMPEG_OPTION_MAP[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] + ] = "1" self._last_url = None self._last_image = None diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 086262aa0a1..0a49393d9cc 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -41,6 +41,7 @@ from .const import ( CONF_RTSP_TRANSPORT, CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, DEFAULT_NAME, DOMAIN, FFMPEG_OPTION_MAP, @@ -64,6 +65,7 @@ SUPPORTED_IMAGE_TYPES = {"png", "jpeg", "gif", "svg+xml", "webp"} def build_schema( user_input: dict[str, Any] | MappingProxyType[str, Any], is_options_flow: bool = False, + show_advanced_options=False, ): """Create schema for camera config setup.""" spec = { @@ -106,6 +108,13 @@ def build_schema( default=user_input.get(CONF_LIMIT_REFETCH_TO_URL_CHANGE, False), ) ] = bool + if show_advanced_options: + spec[ + vol.Required( + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, + default=user_input.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False), + ) + ] = bool return vol.Schema(spec) @@ -199,6 +208,8 @@ async def async_test_stream(hass, info) -> dict[str, str]: } if rtsp_transport := info.get(CONF_RTSP_TRANSPORT): stream_options[FFMPEG_OPTION_MAP[CONF_RTSP_TRANSPORT]] = rtsp_transport + if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS): + stream_options[FFMPEG_OPTION_MAP[CONF_USE_WALLCLOCK_AS_TIMESTAMPS]] = "1" _LOGGER.debug("Attempting to open stream %s", stream_source) container = await hass.async_add_executor_job( partial( @@ -356,6 +367,9 @@ class GenericOptionsFlowHandler(OptionsFlow): ], CONF_FRAMERATE: user_input[CONF_FRAMERATE], CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL], + CONF_USE_WALLCLOCK_AS_TIMESTAMPS: user_input.get( + CONF_USE_WALLCLOCK_AS_TIMESTAMPS + ), } return self.async_create_entry( title=title, @@ -363,6 +377,10 @@ class GenericOptionsFlowHandler(OptionsFlow): ) return self.async_show_form( step_id="init", - data_schema=build_schema(user_input or self.config_entry.options, True), + data_schema=build_schema( + user_input or self.config_entry.options, + True, + self.show_advanced_options, + ), errors=errors, ) diff --git a/homeassistant/components/generic/const.py b/homeassistant/components/generic/const.py index 60b4cec61a6..8ae5f16c4c4 100644 --- a/homeassistant/components/generic/const.py +++ b/homeassistant/components/generic/const.py @@ -8,7 +8,11 @@ CONF_STILL_IMAGE_URL = "still_image_url" CONF_STREAM_SOURCE = "stream_source" CONF_FRAMERATE = "framerate" CONF_RTSP_TRANSPORT = "rtsp_transport" -FFMPEG_OPTION_MAP = {CONF_RTSP_TRANSPORT: "rtsp_transport"} +CONF_USE_WALLCLOCK_AS_TIMESTAMPS = "use_wallclock_as_timestamps" +FFMPEG_OPTION_MAP = { + CONF_RTSP_TRANSPORT: "rtsp_transport", + CONF_USE_WALLCLOCK_AS_TIMESTAMPS: "use_wallclock_as_timestamps", +} RTSP_TRANSPORTS = { "tcp": "TCP", "udp": "UDP", diff --git a/homeassistant/components/generic/strings.json b/homeassistant/components/generic/strings.json index 01b1fe48a82..0954656f71d 100644 --- a/homeassistant/components/generic/strings.json +++ b/homeassistant/components/generic/strings.json @@ -55,9 +55,13 @@ "authentication": "[%key:component::generic::config::step::user::data::authentication%]", "limit_refetch_to_url_change": "[%key:component::generic::config::step::user::data::limit_refetch_to_url_change%]", "password": "[%key:common::config_flow::data::password%]", + "use_wallclock_as_timestamps": "Use wallclock as timestamps", "username": "[%key:common::config_flow::data::username%]", "framerate": "[%key:component::generic::config::step::user::data::framerate%]", "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + }, + "data_description": { + "use_wallclock_as_timestamps": "This option may correct segmenting or crashing issues arising from buggy timestamp implementations on some cameras" } }, "content_type": { diff --git a/homeassistant/components/generic/translations/en.json b/homeassistant/components/generic/translations/en.json index b158488f178..b552c780d29 100644 --- a/homeassistant/components/generic/translations/en.json +++ b/homeassistant/components/generic/translations/en.json @@ -32,7 +32,6 @@ "user": { "data": { "authentication": "Authentication", - "content_type": "Content Type", "framerate": "Frame Rate (Hz)", "limit_refetch_to_url_change": "Limit refetch to url change", "password": "Password", @@ -72,15 +71,18 @@ "init": { "data": { "authentication": "Authentication", - "content_type": "Content Type", "framerate": "Frame Rate (Hz)", "limit_refetch_to_url_change": "Limit refetch to url change", "password": "Password", "rtsp_transport": "RTSP transport protocol", "still_image_url": "Still Image URL (e.g. http://...)", "stream_source": "Stream Source URL (e.g. rtsp://...)", + "use_wallclock_as_timestamps": "Use wallclock as timestamps", "username": "Username", "verify_ssl": "Verify SSL certificate" + }, + "data_description": { + "use_wallclock_as_timestamps": "This option may correct segmenting or crashing issues arising from buggy timestamp implementations on some cameras" } } } diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index 457cac26aa5..dd53cb8548e 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -18,6 +18,7 @@ from homeassistant.components.generic.const import ( CONF_RTSP_TRANSPORT, CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, DOMAIN, ) from homeassistant.const import ( @@ -653,3 +654,32 @@ async def test_migrate_existing_ids(hass) -> None: entity_entry = registry.async_get(entity_id) assert entity_entry.unique_id == new_unique_id + + +@respx.mock +async def test_use_wallclock_as_timestamps_option(hass, fakeimg_png, mock_av_open): + """Test the use_wallclock_as_timestamps option flow.""" + + mock_entry = MockConfigEntry( + title="Test Camera", + domain=DOMAIN, + data={}, + options=TESTDATA, + ) + + with mock_av_open: + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init( + mock_entry.entry_id, context={"show_advanced_options": True} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY