From 1fa996ed686a399a299f1e06e893a560e1fcb900 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 25 Mar 2020 13:32:28 +0100 Subject: [PATCH] Fix ONVIF camera snapshot with auth (#33241) --- homeassistant/components/onvif/camera.py | 49 +++++++++++++----------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index c9f592cdba4..0c6a3bffa1b 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -5,13 +5,13 @@ import logging 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 @@ -412,17 +412,12 @@ class ONVIFHassCamera(Camera): 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://:@", 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) @@ -509,25 +504,33 @@ class ONVIFHassCamera(Camera): 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 + 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 - if self._snapshot is None or image is None: ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) - image = await asyncio.shield( ffmpeg.get_image( self._input,