Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ONVIF camera snapshot with auth #33241

Merged
merged 1 commit into from
Mar 25, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 27 additions & 24 deletions homeassistant/components/onvif/camera.py
Expand Up @@ -5,13 +5,13 @@
import os
from typing import Optional

from aiohttp import ClientError
from aiohttp.client_exceptions import ClientConnectionError, ServerDisconnectedError
import async_timeout
from haffmpeg.camera import CameraMjpeg
from haffmpeg.tools import IMAGE_JPEG, ImageFrame
import onvif
from onvif import ONVIFCamera, exceptions
import requests
from requests.auth import HTTPDigestAuth
import voluptuous as vol
from zeep.asyncio import AsyncTransport
from zeep.exceptions import Fault
Expand Down Expand Up @@ -412,17 +412,12 @@ async def async_obtain_snapshot_uri(self):
req.ProfileToken = profiles[self._profile_index].token

snapshot_uri = await media_service.GetSnapshotUri(req)
uri_no_auth = snapshot_uri.Uri
uri_for_log = uri_no_auth.replace("http://", "http://<user>:<password>@", 1)
# Same authentication as rtsp
self._snapshot = uri_no_auth.replace(
"http://", f"http://{self._username}:{self._password}@", 1
)
self._snapshot = snapshot_uri.Uri

_LOGGER.debug(
"ONVIF Camera Using the following URL for %s snapshot: %s",
self._name,
uri_for_log,
self._snapshot,
)
except exceptions.ONVIFError as err:
_LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err)
Expand Down Expand Up @@ -509,25 +504,33 @@ async def async_added_to_hass(self):

async def async_camera_image(self):
"""Return a still image response from the camera."""

_LOGGER.debug("Retrieving image from camera '%s'", self._name)
image = None

if self._snapshot is not None:
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10):
response = await websession.get(self._snapshot)
image = await response.read()
except asyncio.TimeoutError:
_LOGGER.error("Timeout getting image from: %s", self._name)
image = None
except ClientError as err:
_LOGGER.error("Error getting new camera image: %s", err)
image = None

if self._snapshot is None or image is None:
ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop)
auth = None
if self._username and self._password:
auth = HTTPDigestAuth(self._username, self._password)

def fetch():
"""Read image from a URL."""
try:
response = requests.get(self._snapshot, timeout=5, auth=auth)
return response.content
except requests.exceptions.RequestException as error:
_LOGGER.error(
"Fetch snapshot image failed from %s, falling back to FFmpeg; %s",
self._name,
error,
)

image = await self.hass.async_add_job(fetch)

if image is None:
# Don't keep trying the snapshot URL
self._snapshot = None

ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop)
image = await asyncio.shield(
ffmpeg.get_image(
self._input,
Expand Down