core/homeassistant/components/axis/camera.py

135 lines
4.3 KiB
Python

"""Support for Axis camera streaming."""
from urllib.parse import urlencode
from homeassistant.components.camera import CameraEntityFeature
from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import HTTP_DIGEST_AUTHENTICATION
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DEFAULT_STREAM_PROFILE, DEFAULT_VIDEO_SOURCE, DOMAIN as AXIS_DOMAIN
from .device import AxisNetworkDevice
from .entity import AxisEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Axis camera video stream."""
filter_urllib3_logging()
device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id]
if not device.api.vapix.params.image_format:
return
async_add_entities([AxisCamera(device)])
class AxisCamera(AxisEntity, MjpegCamera):
"""Representation of a Axis camera."""
_attr_supported_features = CameraEntityFeature.STREAM
_still_image_url: str
_mjpeg_url: str
_stream_source: str
def __init__(self, device: AxisNetworkDevice) -> None:
"""Initialize Axis Communications camera component."""
AxisEntity.__init__(self, device)
self._generate_sources()
MjpegCamera.__init__(
self,
username=device.username,
password=device.password,
mjpeg_url=self.mjpeg_source,
still_image_url=self.image_source,
authentication=HTTP_DIGEST_AUTHENTICATION,
unique_id=f"{device.unique_id}-camera",
)
async def async_added_to_hass(self) -> None:
"""Subscribe camera events."""
self.async_on_remove(
async_dispatcher_connect(
self.hass, self.device.signal_new_address, self._generate_sources
)
)
await super().async_added_to_hass()
def _generate_sources(self) -> None:
"""Generate sources.
Additionally used when device change IP address.
"""
image_options = self.generate_options(skip_stream_profile=True)
self._still_image_url = (
f"http://{self.device.host}:{self.device.port}/axis-cgi"
f"/jpg/image.cgi{image_options}"
)
mjpeg_options = self.generate_options()
self._mjpeg_url = (
f"http://{self.device.host}:{self.device.port}/axis-cgi"
f"/mjpg/video.cgi{mjpeg_options}"
)
stream_options = self.generate_options(add_video_codec_h264=True)
self._stream_source = (
f"rtsp://{self.device.username}:{self.device.password}"
f"@{self.device.host}/axis-media/media.amp{stream_options}"
)
self.device.additional_diagnostics["camera_sources"] = {
"Image": self._still_image_url,
"MJPEG": self._mjpeg_url,
"Stream": (
f"rtsp://user:pass@{self.device.host}/axis-media"
f"/media.amp{stream_options}"
),
}
@property
def image_source(self) -> str:
"""Return still image URL for device."""
return self._still_image_url
@property
def mjpeg_source(self) -> str:
"""Return mjpeg URL for device."""
return self._mjpeg_url
async def stream_source(self) -> str:
"""Return the stream source."""
return self._stream_source
def generate_options(
self, skip_stream_profile: bool = False, add_video_codec_h264: bool = False
) -> str:
"""Generate options for video stream."""
options_dict = {}
if add_video_codec_h264:
options_dict["videocodec"] = "h264"
if (
not skip_stream_profile
and self.device.option_stream_profile != DEFAULT_STREAM_PROFILE
):
options_dict["streamprofile"] = self.device.option_stream_profile
if self.device.option_video_source != DEFAULT_VIDEO_SOURCE:
options_dict["camera"] = self.device.option_video_source
if not options_dict:
return ""
return f"?{urlencode(options_dict)}"