From a2e177b411c7e2a18d61bf91b4e8e7596ca9abe3 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 15 Jul 2025 17:17:24 +0200 Subject: [PATCH] Add support for stream orientation in go2rtc --- homeassistant/components/camera/__init__.py | 12 ++++---- homeassistant/components/camera/helper.py | 10 ++++++- homeassistant/components/go2rtc/__init__.py | 33 ++++++++++++++++++++- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 4286e7462cc..aae892610ba 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -79,7 +79,7 @@ from .const import ( CameraState, StreamType, ) -from .helper import get_camera_from_entity_id +from .helper import get_camera_from_entity_id, get_dynamic_camera_stream_settings from .img_util import scale_jpeg_camera_image from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401 from .webrtc import ( @@ -550,9 +550,9 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): self.hass, source, options=self.stream_options, - dynamic_stream_settings=await self.hass.data[ - DATA_CAMERA_PREFS - ].get_dynamic_stream_settings(self.entity_id), + dynamic_stream_settings=await get_dynamic_camera_stream_settings( + self.hass, self.entity_id + ), stream_label=self.entity_id, ) self.stream.set_update_callback(self.async_write_ha_state) @@ -942,9 +942,7 @@ async def websocket_get_prefs( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Handle request for account info.""" - stream_prefs = await hass.data[DATA_CAMERA_PREFS].get_dynamic_stream_settings( - msg["entity_id"] - ) + stream_prefs = await get_dynamic_camera_stream_settings(hass, msg["entity_id"]) connection.send_result(msg["id"], asdict(stream_prefs)) diff --git a/homeassistant/components/camera/helper.py b/homeassistant/components/camera/helper.py index 5e84b18dda8..334d74b7a88 100644 --- a/homeassistant/components/camera/helper.py +++ b/homeassistant/components/camera/helper.py @@ -7,10 +7,11 @@ from typing import TYPE_CHECKING from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from .const import DATA_COMPONENT +from .const import DATA_CAMERA_PREFS, DATA_COMPONENT if TYPE_CHECKING: from . import Camera + from .prefs import DynamicStreamSettings def get_camera_from_entity_id(hass: HomeAssistant, entity_id: str) -> Camera: @@ -26,3 +27,10 @@ def get_camera_from_entity_id(hass: HomeAssistant, entity_id: str) -> Camera: raise HomeAssistantError("Camera is off") return camera + + +async def get_dynamic_camera_stream_settings( + hass: HomeAssistant, entity_id: str +) -> DynamicStreamSettings: + """Get dynamic stream settings for a camera entity.""" + return await hass.data[DATA_CAMERA_PREFS].get_dynamic_stream_settings(entity_id) diff --git a/homeassistant/components/go2rtc/__init__.py b/homeassistant/components/go2rtc/__init__.py index 4e15b93330c..78b325e1ca6 100644 --- a/homeassistant/components/go2rtc/__init__.py +++ b/homeassistant/components/go2rtc/__init__.py @@ -31,7 +31,9 @@ from homeassistant.components.camera import ( WebRTCSendMessage, async_register_webrtc_provider, ) +from homeassistant.components.camera.helper import get_dynamic_camera_stream_settings from homeassistant.components.default_config import DOMAIN as DEFAULT_CONFIG_DOMAIN +from homeassistant.components.stream import Orientation from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -57,12 +59,13 @@ from .server import Server _LOGGER = logging.getLogger(__name__) +_FFMPEG = "ffmpeg" _SUPPORTED_STREAMS = frozenset( ( "bubble", "dvrip", "expr", - "ffmpeg", + _FFMPEG, "gopro", "homekit", "http", @@ -310,6 +313,34 @@ class WebRTCProvider(CameraWebRTCProvider): await self.teardown() raise HomeAssistantError("Stream source is not supported by go2rtc") + camera_prefs = await get_dynamic_camera_stream_settings( + self._hass, camera.entity_id + ) + if camera_prefs.orientation is not Orientation.NO_TRANSFORM: + # Camera orientation manually set by user + if not stream_source.startswith(_FFMPEG): + stream_source = _FFMPEG + ":" + stream_source + stream_source += "#video=h264#audio=copy" + match camera_prefs.orientation: + case Orientation.MIRROR: + stream_source += "#raw=-vf hflip" + case Orientation.ROTATE_180: + stream_source += "#rotate=180" + case Orientation.FLIP: + stream_source += "#raw=-vf vflip" + case Orientation.ROTATE_LEFT_AND_FLIP: + # Cannot use any filter when using raw -vf + # https://github.com/AlexxIT/go2rtc/issues/487 + stream_source += "#raw=-vf transpose=2,vflip" + case Orientation.ROTATE_LEFT: + stream_source += "#rotate=-90" + case Orientation.ROTATE_RIGHT_AND_FLIP: + # Cannot use any filter when using raw -vf + # https://github.com/AlexxIT/go2rtc/issues/487 + stream_source += "#raw=-vf transpose=1,vflip" + case Orientation.ROTATE_RIGHT: + stream_source += "#rotate=90" + streams = await self._rest_client.streams.list() if (stream := streams.get(camera.entity_id)) is None or not any(