1 """Support for ONVIF Cameras with FFmpeg as decoder."""
3 from __future__
import annotations
7 from haffmpeg.camera
import CameraMjpeg
8 from onvif.exceptions
import ONVIFError
9 import voluptuous
as vol
17 CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
29 ATTR_CONTINUOUS_DURATION,
52 from .device
import ONVIFDevice
53 from .entity
import ONVIFBaseEntity
54 from .models
import Profile
59 config_entry: ConfigEntry,
60 async_add_entities: AddEntitiesCallback,
62 """Set up the ONVIF camera video stream."""
63 platform = entity_platform.async_get_current_platform()
66 platform.async_register_entity_service(
69 vol.Optional(ATTR_PAN): vol.In([DIR_LEFT, DIR_RIGHT]),
70 vol.Optional(ATTR_TILT): vol.In([DIR_UP, DIR_DOWN]),
71 vol.Optional(ATTR_ZOOM): vol.In([ZOOM_OUT, ZOOM_IN]),
72 vol.Optional(ATTR_DISTANCE, default=0.1): cv.small_float,
73 vol.Optional(ATTR_SPEED, default=0.5): cv.small_float,
74 vol.Optional(ATTR_MOVE_MODE, default=RELATIVE_MOVE): vol.In(
83 vol.Optional(ATTR_CONTINUOUS_DURATION, default=0.5): cv.small_float,
84 vol.Optional(ATTR_PRESET, default=
"0"): cv.string,
89 device = hass.data[DOMAIN][config_entry.unique_id]
96 """Representation of an ONVIF camera."""
98 _attr_supported_features = CameraEntityFeature.STREAM
100 def __init__(self, device: ONVIFDevice, profile: Profile) ->
None:
101 """Initialize ONVIF camera entity."""
102 ONVIFBaseEntity.__init__(self, device)
103 Camera.__init__(self)
105 self.stream_options[CONF_RTSP_TRANSPORT] = device.config_entry.options.get(
106 CONF_RTSP_TRANSPORT, next(iter(RTSP_TRANSPORTS))
108 self.stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = (
109 device.config_entry.options.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
False)
112 device.config_entry.data.get(CONF_SNAPSHOT_AUTH)
113 == HTTP_BASIC_AUTHENTICATION
118 device.max_resolution == profile.video.resolution.width
128 """Whether or not to use stream to generate stills."""
129 return bool(self.
streamstream
and self.
streamstream.dynamic_stream_settings.preload_stream)
132 """Return the stream source."""
136 self, width: int |
None =
None, height: int |
None =
None
138 """Return a still image response from the camera."""
140 if self.device.capabilities.snapshot:
142 if image := await self.device.device.get_snapshot(
146 except ONVIFError
as err:
148 "Fetch snapshot image failed from %s, falling back to FFmpeg; %s",
154 "Fetch snapshot image failed from %s, falling back to FFmpeg",
159 return await ffmpeg.async_get_image(
162 extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS),
168 """Generate an HTTP MJPEG stream from the camera."""
169 LOGGER.debug(
"Handling mjpeg stream from camera '%s'", self.device.name)
172 stream = CameraMjpeg(ffmpeg_manager.binary)
175 await stream.open_camera(
177 extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS),
181 stream_reader = await stream.get_reader()
186 ffmpeg_manager.ffmpeg_stream_content_type,
192 """Return the stream URI."""
197 loop = asyncio.get_running_loop()
200 uri_no_auth = await self.device.async_get_stream_uri(self.
profileprofile)
201 except (TimeoutError, Exception)
as err:
202 LOGGER.error(
"Failed to get stream uri: %s", err)
206 url =
URL(uri_no_auth)
207 url = url.with_user(self.device.username)
208 url = url.with_password(self.device.password)
224 """Perform a PTZ action on the camera."""
None async_perform_ptz(self, distance, speed, move_mode, continuous_duration, preset, pan=None, tilt=None, zoom=None)
def handle_async_mjpeg_stream(self, request)
bool use_stream_for_stills(self)
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
None __init__(self, ONVIFDevice device, Profile profile)
str _async_get_stream_uri(self)
_attr_entity_registry_enabled_default
FFmpegManager get_ffmpeg_manager(HomeAssistant hass)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
web.StreamResponse async_aiohttp_proxy_stream(HomeAssistant hass, web.BaseRequest request, aiohttp.StreamReader stream, str|None content_type, int buffer_size=102400, int timeout=10)