1 """Component to interface with cameras."""
3 from __future__
import annotations
7 from collections.abc
import Awaitable, Callable, Coroutine
8 from contextlib
import suppress
9 from dataclasses
import asdict, dataclass
10 from datetime
import datetime, timedelta
11 from enum
import IntFlag
12 from functools
import partial
15 from random
import SystemRandom
17 from typing
import Any, Final, final
19 from aiohttp
import hdrs, web
21 from propcache
import cached_property, under_cached_property
22 import voluptuous
as vol
23 from webrtc_models
import RTCIceCandidateInit, RTCIceServer
28 ATTR_MEDIA_CONTENT_ID,
29 ATTR_MEDIA_CONTENT_TYPE,
45 CONTENT_TYPE_MULTIPART,
46 EVENT_HOMEASSISTANT_STARTED,
47 EVENT_HOMEASSISTANT_STOP,
55 DeprecatedConstantEnum,
56 all_with_deprecated_constants,
57 check_if_deprecated_constant,
59 dir_with_deprecated_constants,
71 _DEPRECATED_STREAM_TYPE_HLS,
72 _DEPRECATED_STREAM_TYPE_WEB_RTC,
74 CAMERA_STREAM_SOURCE_TIMEOUT,
86 from .helper
import get_camera_from_entity_id
87 from .img_util
import scale_jpeg_camera_image
88 from .prefs
import CameraPreferences, DynamicStreamSettings
91 CameraWebRTCLegacyProvider,
95 WebRTCClientConfiguration,
99 async_get_supported_legacy_provider,
100 async_get_supported_provider,
101 async_register_ice_servers,
102 async_register_rtsp_to_web_rtc_provider,
103 async_register_webrtc_provider,
107 _LOGGER = logging.getLogger(__name__)
110 ENTITY_ID_FORMAT: Final = DOMAIN +
".{}"
111 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
112 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
115 SERVICE_ENABLE_MOTION: Final =
"enable_motion_detection"
116 SERVICE_DISABLE_MOTION: Final =
"disable_motion_detection"
117 SERVICE_SNAPSHOT: Final =
"snapshot"
118 SERVICE_PLAY_STREAM: Final =
"play_stream"
120 ATTR_FILENAME: Final =
"filename"
121 ATTR_MEDIA_PLAYER: Final =
"media_player"
122 ATTR_FORMAT: Final =
"format"
132 """Supported features of the camera entity."""
141 CameraEntityFeature.ON_OFF,
"2025.1"
144 CameraEntityFeature.STREAM,
"2025.1"
148 DEFAULT_CONTENT_TYPE: Final =
"image/jpeg"
149 ENTITY_IMAGE_URL: Final =
"/api/camera_proxy/{0}?token={1}"
152 _RND: Final = SystemRandom()
154 MIN_STREAM_INTERVAL: Final = 0.5
156 CAMERA_SERVICE_SNAPSHOT: VolDictType = {vol.Required(ATTR_FILENAME): cv.template}
158 CAMERA_SERVICE_PLAY_STREAM: VolDictType = {
159 vol.Required(ATTR_MEDIA_PLAYER): cv.entities_domain(DOMAIN_MP),
160 vol.Optional(ATTR_FORMAT, default=
"hls"): vol.In(OUTPUT_FORMATS),
163 CAMERA_SERVICE_RECORD: VolDictType = {
164 vol.Required(CONF_FILENAME): cv.template,
165 vol.Optional(CONF_DURATION, default=30): vol.Coerce(int),
166 vol.Optional(CONF_LOOKBACK, default=0): vol.Coerce(int),
171 """A class that describes camera entities."""
176 """Represent an image."""
178 content_type: str = attr.ib()
179 content: bytes = attr.ib()
182 @dataclass(frozen=True)
184 """Camera capabilities."""
186 frontend_stream_types: set[StreamType]
191 """Request a stream for a camera entity."""
199 width: int |
None =
None,
200 height: int |
None =
None,
202 """Fetch a snapshot image from a camera.
204 If width and height are passed, an attempt to scale
205 the image will be made on a best effort basis.
206 Not all cameras can scale images or return jpegs
207 that we can scale, however the majority of cases
210 with suppress(asyncio.CancelledError, TimeoutError):
211 async
with asyncio.timeout(timeout):
214 camera, width=width, height=height, wait_for_next_keyframe=
False
216 if camera.use_stream_for_stills
217 else await camera.async_camera_image(width=width, height=height)
220 content_type = camera.content_type
221 image =
Image(content_type, image_bytes)
224 and height
is not None
225 and (
"jpeg" in content_type
or "jpg" in content_type)
227 assert width
is not None
228 assert height
is not None
243 width: int |
None =
None,
244 height: int |
None =
None,
246 """Fetch an image from a camera entity.
248 width and height will be passed to the underlying camera.
256 width: int |
None =
None,
257 height: int |
None =
None,
258 wait_for_next_keyframe: bool =
False,
260 if not camera.stream
and CameraEntityFeature.STREAM
in camera.supported_features:
261 camera.stream = await camera.async_create_stream()
263 return await camera.stream.async_get_image(
264 width=width, height=height, wait_for_next_keyframe=wait_for_next_keyframe
271 """Fetch the stream source for a camera entity."""
273 return await camera.stream_source()
278 hass: HomeAssistant, request: web.Request, entity_id: str
279 ) -> web.StreamResponse |
None:
280 """Fetch an mjpeg stream from a camera entity."""
284 stream = await camera.handle_async_mjpeg_stream(request)
285 except ConnectionResetError:
287 _LOGGER.debug(
"Error while writing MJPEG stream to transport")
292 request: web.Request,
293 image_cb: Callable[[], Awaitable[bytes |
None]],
296 ) -> web.StreamResponse:
297 """Generate an HTTP MJPEG stream from camera images.
299 This method must be run in the event loop.
301 response = web.StreamResponse()
302 response.content_type = CONTENT_TYPE_MULTIPART.format(
"--frameboundary")
303 await response.prepare(request)
305 async
def write_to_mjpeg_stream(img_bytes: bytes) ->
None:
306 """Write image to stream."""
307 await response.write(
309 "--frameboundary\r\n"
310 f
"Content-Type: {content_type}\r\n"
311 f
"Content-Length: {len(img_bytes)}\r\n\r\n",
321 last_fetch = time.monotonic()
322 img_bytes = await image_cb()
326 if img_bytes != last_image:
327 await write_to_mjpeg_stream(img_bytes)
335 if last_image
is None:
336 await write_to_mjpeg_stream(img_bytes)
337 last_image = img_bytes
339 next_fetch = last_fetch + interval
340 now = time.monotonic()
342 sleep_time = next_fetch - now
343 await asyncio.sleep(sleep_time)
348 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
349 """Set up the camera component."""
350 component = hass.data[DATA_COMPONENT] = EntityComponent[Camera](
351 _LOGGER, DOMAIN, hass, SCAN_INTERVAL
355 await prefs.async_load()
356 hass.data[DATA_CAMERA_PREFS] = prefs
361 websocket_api.async_register_command(hass, ws_camera_stream)
362 websocket_api.async_register_command(hass, websocket_get_prefs)
363 websocket_api.async_register_command(hass, websocket_update_prefs)
364 websocket_api.async_register_command(hass, ws_camera_capabilities)
367 await component.async_setup(config)
369 async
def preload_stream(_event: Event) ->
None:
370 """Load stream prefs and start stream if preload_stream is True."""
371 for camera
in list(component.entities):
372 stream_prefs = await prefs.get_dynamic_stream_settings(camera.entity_id)
373 if not stream_prefs.preload_stream:
375 stream = await camera.async_create_stream()
378 stream.add_provider(
"hls")
381 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, preload_stream)
384 def update_tokens(t: datetime) ->
None:
385 """Update tokens of the entities."""
386 for entity
in component.entities:
387 entity.async_update_token()
388 entity.async_write_ha_state()
391 hass, update_tokens, TOKEN_CHANGE_INTERVAL, name=
"Camera update tokens"
395 def unsub_track_time_interval(_event: Event) ->
None:
396 """Unsubscribe track time interval timer."""
399 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unsub_track_time_interval)
401 component.async_register_entity_service(
402 SERVICE_ENABLE_MOTION,
None,
"async_enable_motion_detection"
404 component.async_register_entity_service(
405 SERVICE_DISABLE_MOTION,
None,
"async_disable_motion_detection"
407 component.async_register_entity_service(SERVICE_TURN_OFF,
None,
"async_turn_off")
408 component.async_register_entity_service(SERVICE_TURN_ON,
None,
"async_turn_on")
409 component.async_register_entity_service(
410 SERVICE_SNAPSHOT, CAMERA_SERVICE_SNAPSHOT, async_handle_snapshot_service
412 component.async_register_entity_service(
414 CAMERA_SERVICE_PLAY_STREAM,
415 async_handle_play_stream_service,
417 component.async_register_entity_service(
418 SERVICE_RECORD, CAMERA_SERVICE_RECORD, async_handle_record_service
422 def get_ice_servers() -> list[RTCIceServer]:
423 if hass.config.webrtc.ice_servers:
424 return hass.config.webrtc.ice_servers
428 "stun:stun.home-assistant.io:80",
429 "stun:stun.home-assistant.io:3478",
439 """Set up a config entry."""
444 """Unload a config entry."""
448 CACHED_PROPERTIES_WITH_ATTR_ = {
451 "frontend_stream_type",
456 "motion_detection_enabled",
457 "supported_features",
462 """The base class for camera entities."""
464 _entity_component_unrecorded_attributes = frozenset(
465 {
"access_token",
"entity_picture"}
469 _attr_brand: str |
None =
None
470 _attr_frame_interval: float = MIN_STREAM_INTERVAL
472 _attr_frontend_stream_type: StreamType |
None
473 _attr_is_on: bool =
True
474 _attr_is_recording: bool =
False
475 _attr_is_streaming: bool =
False
476 _attr_model: str |
None =
None
477 _attr_motion_detection_enabled: bool =
False
478 _attr_should_poll: bool =
False
479 _attr_state:
None =
None
482 __supports_stream: CameraEntityFeature |
None =
None
485 """Initialize a camera."""
486 self._cache: dict[str, Any] = {}
487 self.
streamstream: Stream |
None =
None
488 self.stream_options: dict[str, str | bool | float] = {}
489 self.content_type: str = DEFAULT_CONTENT_TYPE
490 self.access_tokens: collections.deque = collections.deque([], 2)
497 type(self).async_handle_web_rtc_offer != Camera.async_handle_web_rtc_offer
500 type(self).async_handle_async_webrtc_offer
501 != Camera.async_handle_async_webrtc_offer
504 if type(self).frontend_stream_type != Camera.frontend_stream_type:
507 f
"is overwriting the 'frontend_stream_type' property in the {type(self).__name__} class,"
508 " which is deprecated and will be removed in Home Assistant 2025.6, "
510 core_integration_behavior=ReportBehavior.ERROR,
511 exclude_integrations={DOMAIN},
516 """Return a link to the camera feed as entity picture."""
517 if self._attr_entity_picture
is not None:
518 return self._attr_entity_picture
519 return ENTITY_IMAGE_URL.format(self.
entity_identity_id, self.access_tokens[-1])
523 """Whether or not to use stream to generate stills."""
528 """Flag supported features."""
529 return self._attr_supported_features
533 """Return the supported features as CameraEntityFeature.
535 Remove this compatibility shim in 2025.1 or later.
538 if type(features)
is int:
546 """Return true if the device is recording."""
547 return self._attr_is_recording
551 """Return true if the device is streaming."""
552 return self._attr_is_streaming
556 """Return the camera brand."""
557 return self._attr_brand
561 """Return the camera motion detection status."""
562 return self._attr_motion_detection_enabled
566 """Return the camera model."""
567 return self._attr_model
571 """Return the interval between frames of the mjpeg stream."""
572 return self._attr_frame_interval
576 """Return the type of stream supported by this camera.
578 A camera may have a single stream type which is used to inform the
579 frontend which camera attributes and player to use. The default type
580 is to use HLS, and components can override to change the type.
584 if hasattr(self,
"_attr_frontend_stream_type"):
588 f
"is setting the '_attr_frontend_stream_type' attribute in the {type(self).__name__} class,"
589 " which is deprecated and will be removed in Home Assistant 2025.6, "
591 core_integration_behavior=ReportBehavior.ERROR,
592 exclude_integrations={DOMAIN},
596 return self._attr_frontend_stream_type
605 return StreamType.WEB_RTC
606 return StreamType.HLS
610 """Return True if entity is available."""
611 if (stream := self.
streamstream)
and not stream.available:
613 return super().available
616 """Create a Stream for stream_source."""
622 async
with asyncio.timeout(CAMERA_STREAM_SOURCE_TIMEOUT):
629 options=self.stream_options,
630 dynamic_stream_settings=await self.
hasshass.data[
632 ].get_dynamic_stream_settings(self.
entity_identity_id),
639 """Return the source of the stream.
641 This is used by cameras with CameraEntityFeature.STREAM
647 """Handle the WebRTC offer and return an answer.
649 This is used by cameras with CameraEntityFeature.STREAM
650 and StreamType.WEB_RTC.
652 Integrations can override with a native WebRTC implementation.
656 self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
658 """Handle the async WebRTC offer.
660 Async means that it could take some time to process the offer and responses/message
661 will be sent with the send_message callback.
662 This method is used by cameras with CameraEntityFeature.STREAM.
663 An integration overriding this method must also implement async_on_webrtc_candidate.
665 Integrations can override with a native WebRTC implementation.
669 answer = await deprecated_function(
670 "async_handle_async_webrtc_offer",
671 breaks_in_ha_version=
"2025.6",
673 except ValueError
as ex:
674 _LOGGER.error(
"Error handling WebRTC offer: %s", ex)
677 "webrtc_offer_failed",
683 _LOGGER.error(
"Timeout handling WebRTC offer")
686 "webrtc_offer_failed",
687 "Timeout handling WebRTC offer",
694 _LOGGER.error(
"Error handling WebRTC offer: No answer")
697 "webrtc_offer_failed",
698 "No answer on WebRTC offer",
705 self, offer_sdp, session_id, send_message
719 self, width: int |
None =
None, height: int |
None =
None
721 """Return bytes of camera image."""
722 raise NotImplementedError
725 self, width: int |
None =
None, height: int |
None =
None
727 """Return bytes of camera image."""
728 return await self.
hasshass.async_add_executor_job(
729 partial(self.
camera_imagecamera_image, width=width, height=height)
733 self, request: web.Request, interval: float
734 ) -> web.StreamResponse:
735 """Generate an HTTP MJPEG stream from camera images."""
741 self, request: web.Request
742 ) -> web.StreamResponse |
None:
743 """Serve an HTTP MJPEG stream from the camera.
745 This method can be overridden by camera platforms to proxy
746 a direct stream from the camera.
753 """Return the camera state."""
755 return CameraState.RECORDING
757 return CameraState.STREAMING
758 return CameraState.IDLE
762 """Return true if on."""
763 return self._attr_is_on
766 """Turn off camera."""
767 raise NotImplementedError
770 """Turn off camera."""
771 await self.
hasshass.async_add_executor_job(self.
turn_offturn_off)
774 """Turn on camera."""
775 raise NotImplementedError
778 """Turn on camera."""
779 await self.
hasshass.async_add_executor_job(self.
turn_onturn_on)
782 """Enable motion detection in the camera."""
783 raise NotImplementedError
786 """Call the job and enable motion detection."""
790 """Disable motion detection in camera."""
791 raise NotImplementedError
794 """Call the job and disable motion detection."""
800 """Return the camera state attributes."""
801 attrs = {
"access_token": self.access_tokens[-1]}
803 if model := self.
modelmodel:
804 attrs[
"model_name"] = model
806 if brand := self.
brandbrand:
807 attrs[
"brand"] = brand
810 attrs[
"motion_detection"] = motion_detection_enabled
813 attrs[
"frontend_stream_type"] = frontend_stream_type
819 """Update the used token."""
820 self.access_tokens.append(hex(_RND.getrandbits(256))[2:])
821 self.__dict__.pop(
"entity_picture",
None)
824 """Run when entity about to be added to hass."""
832 """Determine if any of the registered providers are suitable for this entity.
834 This affects state attributes, so it should be invoked any time the registered
835 providers or inputs to the state attributes change.
840 new_legacy_provider =
None
847 new_provider = await self._async_get_supported_webrtc_provider(
848 async_get_supported_provider
851 if new_provider
is None:
853 new_legacy_provider = await self._async_get_supported_webrtc_provider(
854 async_get_supported_legacy_provider
857 if old_provider != new_provider
or old_legacy_provider != new_legacy_provider:
864 async
def _async_get_supported_webrtc_provider[_T](
865 self, fn: Callable[[HomeAssistant, Camera], Coroutine[
None,
None, _T |
None]]
867 """Get first provider that supports this camera."""
871 return await fn(self.
hasshass, self)
875 """Return the WebRTC client configuration adjustable per integration."""
876 return WebRTCClientConfiguration()
881 """Return the WebRTC client configuration and extend it with the registered ice servers."""
889 for servers
in self.
hasshass.data.get(DATA_ICE_SERVERS, [])
890 for server
in servers()
892 config.configuration.ice_servers.extend(ice_servers)
894 config.get_candidates_upfront = (
902 self, session_id: str, candidate: RTCIceCandidateInit
904 """Handle a WebRTC candidate."""
912 """Close a WebRTC session."""
918 """Invalidate the camera capabilities cache."""
919 self._cache.pop(
"camera_capabilities",
None)
922 @under_cached_property
924 """Return the camera capabilities."""
925 frontend_stream_types = set()
929 frontend_stream_types.add(StreamType.WEB_RTC)
931 frontend_stream_types.add(StreamType.HLS)
934 frontend_stream_types.add(StreamType.WEB_RTC)
940 """Write the state to the state machine.
942 Schedules async_refresh_providers if support of streams have changed.
947 & CameraEntityFeature.STREAM
955 """Base CameraView."""
957 requires_auth =
False
959 def __init__(self, component: EntityComponent[Camera]) ->
None:
960 """Initialize a basic camera view."""
963 async
def get(self, request: web.Request, entity_id: str) -> web.StreamResponse:
964 """Start a GET request."""
966 raise web.HTTPNotFound
969 request[KEY_AUTHENTICATED]
970 or request.query.get(
"token")
in camera.access_tokens
973 if not authenticated:
976 if hdrs.AUTHORIZATION
in request.headers:
977 raise web.HTTPUnauthorized
979 raise web.HTTPForbidden
982 _LOGGER.debug(
"Camera is off")
983 raise web.HTTPServiceUnavailable
985 return await self.
handlehandle(request, camera)
987 async
def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
988 """Handle the camera request."""
989 raise NotImplementedError
993 """Camera view to serve an image."""
995 url =
"/api/camera_proxy/{entity_id}"
996 name =
"api:camera:image"
998 async
def handle(self, request: web.Request, camera: Camera) -> web.Response:
999 """Serve camera image."""
1000 width = request.query.get(
"width")
1001 height = request.query.get(
"height")
1005 CAMERA_IMAGE_TIMEOUT,
1006 int(width)
if width
else None,
1007 int(height)
if height
else None,
1009 except (HomeAssistantError, ValueError)
as ex:
1010 raise web.HTTPInternalServerError
from ex
1012 return web.Response(body=image.content, content_type=image.content_type)
1016 """Camera View to serve an MJPEG stream."""
1018 url =
"/api/camera_proxy_stream/{entity_id}"
1019 name =
"api:camera:stream"
1021 async
def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
1022 """Serve camera stream, possibly with interval."""
1023 if (interval_str := request.query.get(
"interval"))
is None:
1025 stream = await camera.handle_async_mjpeg_stream(request)
1026 except ConnectionResetError:
1028 _LOGGER.debug(
"Error while writing MJPEG stream to transport")
1030 raise web.HTTPBadGateway
1035 interval =
float(interval_str)
1036 if interval < MIN_STREAM_INTERVAL:
1037 raise ValueError(f
"Stream interval must be > {MIN_STREAM_INTERVAL}")
1038 return await camera.handle_async_still_stream(request, interval)
1039 except ValueError
as err:
1040 raise web.HTTPBadRequest
from err
1043 @websocket_api.websocket_command(
{
vol.Required("type"):
"camera/capabilities",
1044 vol.Required(
"entity_id"): cv.entity_id,
1047 @websocket_api.async_response
1049 hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1051 """Handle get camera capabilities websocket command.
1056 connection.send_result(msg[
"id"], asdict(camera.camera_capabilities))
1059 @websocket_api.websocket_command(
{
vol.Required("type"):
"camera/stream",
1060 vol.Required(
"entity_id"): cv.entity_id,
1061 vol.Optional(
"format", default=
"hls"): vol.In(OUTPUT_FORMATS),
1064 @websocket_api.async_response
1066 hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1068 """Handle get camera stream websocket command.
1073 entity_id = msg[
"entity_id"]
1076 connection.send_result(msg[
"id"], {
"url": url})
1077 except HomeAssistantError
as ex:
1078 _LOGGER.error(
"Error requesting stream: %s", ex)
1079 connection.send_error(msg[
"id"],
"start_stream_failed",
str(ex))
1080 except TimeoutError:
1081 _LOGGER.error(
"Timeout getting stream source")
1082 connection.send_error(
1083 msg[
"id"],
"start_stream_failed",
"Timeout getting stream source"
1087 @websocket_api.websocket_command(
{vol.Required("type"):
"camera/get_prefs", vol.Required(
"entity_id"): cv.entity_id}
1089 @websocket_api.async_response
1091 hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1093 """Handle request for account info."""
1094 stream_prefs = await hass.data[DATA_CAMERA_PREFS].get_dynamic_stream_settings(
1097 connection.send_result(msg[
"id"], asdict(stream_prefs))
1100 @websocket_api.websocket_command(
{
vol.Required("type"):
"camera/update_prefs",
1101 vol.Required(
"entity_id"): cv.entity_id,
1102 vol.Optional(PREF_PRELOAD_STREAM): bool,
1103 vol.Optional(PREF_ORIENTATION): vol.Coerce(Orientation),
1106 @websocket_api.async_response
1108 hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1110 """Handle request for account info."""
1114 entity_id = changes.pop(
"entity_id")
1116 entity_prefs = await hass.data[DATA_CAMERA_PREFS].
async_update(
1117 entity_id, **changes
1119 except HomeAssistantError
as ex:
1120 _LOGGER.error(
"Error setting camera preferences: %s", ex)
1121 connection.send_error(msg[
"id"],
"update_failed",
str(ex))
1123 connection.send_result(msg[
"id"], entity_prefs)
1127 """Class to warn when the `entity_id` template variable is accessed.
1129 Can be removed in HA Core 2025.6.
1132 def __init__(self, camera: Camera, service: str) ->
None:
1136 self.
_hass_hass = camera.hass
1140 """Create a repair issue."""
1141 ir.async_create_issue(
1144 f
"deprecated_filename_template_{self._entity_id}_{self._service}",
1145 breaks_in_ha_version=
"2025.6.0",
1147 severity=ir.IssueSeverity.WARNING,
1148 translation_key=
"deprecated_filename_template",
1149 translation_placeholders={
1151 "service": f
"{DOMAIN}.{self._service}",
1156 """Forward to the camera entity."""
1158 return getattr(self.
_camera_camera, name)
1161 """Forward to the camera entity."""
1167 camera: Camera, service_call: ServiceCall
1169 """Handle snapshot services calls."""
1171 filename: Template = service_call.data[ATTR_FILENAME]
1173 snapshot_file = filename.async_render(
1178 if not hass.config.is_allowed_path(snapshot_file):
1180 f
"Cannot write `{snapshot_file}`, no access to path; `allowlist_external_dirs` may need to be adjusted in `configuration.yaml`"
1183 async
with asyncio.timeout(CAMERA_IMAGE_TIMEOUT):
1186 if camera.use_stream_for_stills
1187 else await camera.async_camera_image()
1193 def _write_image(to_file: str, image_data: bytes) ->
None:
1194 """Executor helper to write image."""
1195 os.makedirs(os.path.dirname(to_file), exist_ok=
True)
1196 with open(to_file,
"wb")
as img_file:
1197 img_file.write(image_data)
1200 await hass.async_add_executor_job(_write_image, snapshot_file, image)
1201 except OSError
as err:
1202 _LOGGER.error(
"Can't write image to file: %s", err)
1206 camera: Camera, service_call: ServiceCall
1208 """Handle play stream services calls."""
1210 fmt = service_call.data[ATTR_FORMAT]
1212 url = f
"{get_url(hass)}{url}"
1214 await hass.services.async_call(
1218 ATTR_ENTITY_ID: service_call.data[ATTR_MEDIA_PLAYER],
1219 ATTR_MEDIA_CONTENT_ID: url,
1220 ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt],
1223 context=service_call.context,
1228 hass: HomeAssistant, camera: Camera, fmt: str
1230 stream = await camera.async_create_stream()
1233 f
"{camera.entity_id} does not support play stream service"
1236 stream.add_provider(fmt)
1237 await stream.start()
1238 return stream.endpoint_url(fmt)
1242 camera: Camera, service_call: ServiceCall
1244 """Handle stream recording service calls."""
1245 stream = await camera.async_create_stream()
1250 filename = service_call.data[CONF_FILENAME]
1251 video_path = filename.async_render(
1255 await stream.async_record(
1257 duration=service_call.data[CONF_DURATION],
1258 lookback=service_call.data[CONF_LOOKBACK],
1263 __getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
1265 dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
1268
web.Response handle(self, web.Request request, Camera camera)
web.StreamResponse handle(self, web.Request request, Camera camera)
web.StreamResponse handle(self, web.Request request, Camera camera)
web.StreamResponse get(self, web.Request request, str entity_id)
None __init__(self, EntityComponent[Camera] component)
str|None async_handle_web_rtc_offer(self, str offer_sdp)
None async_refresh_providers(self, *bool write_state=True)
None disable_motion_detection(self)
WebRTCClientConfiguration _async_get_webrtc_client_configuration(self)
None async_internal_added_to_hass(self)
_supports_native_async_webrtc
None async_handle_async_webrtc_offer(self, str offer_sdp, str session_id, WebRTCSendMessage send_message)
bytes|None camera_image(self, int|None width=None, int|None height=None)
float frame_interval(self)
bool use_stream_for_stills(self)
None async_disable_motion_detection(self)
StreamType|None frontend_stream_type(self)
CameraEntityFeature supported_features(self)
web.StreamResponse|None handle_async_mjpeg_stream(self, web.Request request)
None _invalidate_camera_capabilities_cache(self)
None enable_motion_detection(self)
None async_on_webrtc_candidate(self, str session_id, RTCIceCandidateInit candidate)
CameraCapabilities camera_capabilities(self)
None async_write_ha_state(self)
_deprecate_attr_frontend_stream_type_logged
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
web.StreamResponse handle_async_still_stream(self, web.Request request, float interval)
None close_webrtc_session(self, str session_id)
WebRTCClientConfiguration async_get_webrtc_client_configuration(self)
None async_enable_motion_detection(self)
CameraEntityFeature supported_features_compat(self)
str|None stream_source(self)
_supports_native_sync_webrtc
Stream|None async_create_stream(self)
None async_update_token(self)
bool motion_detection_enabled(self)
None async_turn_off(self)
dict[str, str|None] state_attributes(self)
Any __getattr__(self, str name)
None _report_deprecated_supported_features_values(self, IntFlag replacement)
None async_write_ha_state(self)
int|None supported_features(self)
CalendarEntity get_entity(HomeAssistant hass, str entity_id)
Camera get_camera_from_entity_id(HomeAssistant hass, str entity_id)
bytes scale_jpeg_camera_image(Image cam_image, int width, int height)
Callable[[], None] async_register_ice_servers(HomeAssistant hass, Callable[[], Iterable[RTCIceServer]] get_ice_server_fn)
None async_register_ws(HomeAssistant hass)
None ws_camera_capabilities(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
None async_handle_play_stream_service(Camera camera, ServiceCall service_call)
Image async_get_image(HomeAssistant hass, str entity_id, int timeout=10, int|None width=None, int|None height=None)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
str|None async_get_stream_source(HomeAssistant hass, str entity_id)
None async_handle_snapshot_service(Camera camera, ServiceCall service_call)
None websocket_update_prefs(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
None async_handle_record_service(Camera camera, ServiceCall service_call)
Image _async_get_image(Camera camera, int timeout=10, int|None width=None, int|None height=None)
bytes|None _async_get_stream_image(Camera camera, int|None width=None, int|None height=None, bool wait_for_next_keyframe=False)
str _async_stream_endpoint_url(HomeAssistant hass, Camera camera, str fmt)
bool async_setup(HomeAssistant hass, ConfigType config)
str async_request_stream(HomeAssistant hass, str entity_id, str fmt)
web.StreamResponse|None async_get_mjpeg_stream(HomeAssistant hass, web.Request request, str entity_id)
None ws_camera_stream(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
web.StreamResponse async_get_still_stream(web.Request request, Callable[[], Awaitable[bytes|None]] image_cb, str content_type, float interval)
None websocket_get_prefs(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
None __init__(self, _HAFFmpegT ffmpeg, bool initial_state=True)
None open(self, **Any kwargs)
Stream create_stream(HomeAssistant hass, str stream_source, Mapping[str, str|bool|float] options, DynamicStreamSettings dynamic_stream_settings, str|None stream_label=None)
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)