From 4ca5ed25bcc136e20802fe17f4b906a2cba0557e Mon Sep 17 00:00:00 2001 From: Eugenio Panadero Date: Sun, 25 Jun 2017 21:25:14 +0200 Subject: [PATCH] add option to set content_type in camera.generic to support 'svg cameras' (#8188) * add custom content_type to support 'generic svg cameras' * add unittest to check content_type for svg generic camera * Tweak tests --- homeassistant/components/camera/__init__.py | 14 +++++--- homeassistant/components/camera/generic.py | 6 +++- tests/components/camera/test_generic.py | 39 ++++++++++++++++++++- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 7c21b99ddda..952f4378598 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -38,6 +38,7 @@ STATE_RECORDING = 'recording' STATE_STREAMING = 'streaming' STATE_IDLE = 'idle' +DEFAULT_CONTENT_TYPE = 'image/jpeg' ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' TOKEN_CHANGE_INTERVAL = timedelta(minutes=5) @@ -101,6 +102,7 @@ class Camera(Entity): def __init__(self): """Initialize a camera.""" self.is_streaming = False + self.content_type = DEFAULT_CONTENT_TYPE self.access_tokens = collections.deque([], 2) self.async_update_token() @@ -149,16 +151,17 @@ class Camera(Entity): response = web.StreamResponse() response.content_type = ('multipart/x-mixed-replace; ' - 'boundary=--jpegboundary') + 'boundary=--frameboundary') yield from response.prepare(request) def write(img_bytes): """Write image to stream.""" response.write(bytes( - '--jpegboundary\r\n' - 'Content-Type: image/jpeg\r\n' + '--frameboundary\r\n' + 'Content-Type: {}\r\n' 'Content-Length: {}\r\n\r\n'.format( - len(img_bytes)), 'utf-8') + img_bytes + b'\r\n') + self.content_type, len(img_bytes)), + 'utf-8') + img_bytes + b'\r\n') last_image = None @@ -269,7 +272,8 @@ class CameraImageView(CameraView): image = yield from camera.async_camera_image() if image: - return web.Response(body=image, content_type='image/jpeg') + return web.Response(body=image, + content_type=camera.content_type) return web.Response(status=500) diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index 8a9854ab97e..3f8c4bedc75 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -17,13 +17,15 @@ from homeassistant.const import ( CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) from homeassistant.exceptions import TemplateError -from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) +from homeassistant.components.camera import ( + PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, Camera) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_validation as cv from homeassistant.util.async import run_coroutine_threadsafe _LOGGER = logging.getLogger(__name__) +CONF_CONTENT_TYPE = 'content_type' CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change' CONF_STILL_IMAGE_URL = 'still_image_url' @@ -37,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PASSWORD): cv.string, vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string, }) @@ -59,6 +62,7 @@ class GenericCamera(Camera): self._still_image_url = device_info[CONF_STILL_IMAGE_URL] self._still_image_url.hass = hass self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE] + self.content_type = device_info[CONF_CONTENT_TYPE] username = device_info.get(CONF_USERNAME) password = device_info.get(CONF_PASSWORD) diff --git a/tests/components/camera/test_generic.py b/tests/components/camera/test_generic.py index 7b1263dd3da..52bc3d9e048 100644 --- a/tests/components/camera/test_generic.py +++ b/tests/components/camera/test_generic.py @@ -2,7 +2,7 @@ import asyncio from unittest import mock -from homeassistant.setup import setup_component +from homeassistant.setup import setup_component, async_setup_component @asyncio.coroutine @@ -99,3 +99,40 @@ def test_limit_refetch(aioclient_mock, hass, test_client): assert resp.status == 200 body = yield from resp.text() assert body == 'hello planet' + + +@asyncio.coroutine +def test_camera_content_type(aioclient_mock, hass, test_client): + """Test generic camera with custom content_type.""" + svg_image = '' + urlsvg = 'https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg' + aioclient_mock.get(urlsvg, text=svg_image) + + cam_config_svg = { + 'name': 'config_test_svg', + 'platform': 'generic', + 'still_image_url': urlsvg, + 'content_type': 'image/svg+xml', + } + cam_config_normal = cam_config_svg.copy() + cam_config_normal.pop('content_type') + cam_config_normal['name'] = 'config_test_jpg' + + yield from async_setup_component(hass, 'camera', { + 'camera': [cam_config_svg, cam_config_normal]}) + + client = yield from test_client(hass.http.app) + + resp_1 = yield from client.get('/api/camera_proxy/camera.config_test_svg') + assert aioclient_mock.call_count == 1 + assert resp_1.status == 200 + assert resp_1.content_type == 'image/svg+xml' + body = yield from resp_1.text() + assert body == svg_image + + resp_2 = yield from client.get('/api/camera_proxy/camera.config_test_jpg') + assert aioclient_mock.call_count == 2 + assert resp_2.status == 200 + assert resp_2.content_type == 'image/jpeg' + body = yield from resp_2.text() + assert body == svg_image