From e078ab53ca5576c1b7fe7e5b507e8da33e3212f3 Mon Sep 17 00:00:00 2001 From: Ryan Turner Date: Sun, 8 Nov 2015 22:15:06 -0600 Subject: [PATCH 1/6] Initial implementation of mjpeg camera --- homeassistant/components/camera/mjpeg.py | 65 ++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 homeassistant/components/camera/mjpeg.py diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py new file mode 100644 index 00000000000..709b15208f3 --- /dev/null +++ b/homeassistant/components/camera/mjpeg.py @@ -0,0 +1,65 @@ +""" +homeassistant.components.camera.mjpeg +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for IP Cameras. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.mjpeg.html +""" +import logging +from requests.auth import HTTPBasicAuth +from homeassistant.helpers import validate_config +from homeassistant.components.camera import DOMAIN +from homeassistant.components.camera import Camera +import urllib.request + +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Adds a mjpeg IP Camera. """ + if not validate_config({DOMAIN: config}, {DOMAIN: ['mjpeg_url']}, + _LOGGER): + return None + + add_devices_callback([MjpegCamera(config)]) + + +# pylint: disable=too-many-instance-attributes +class MjpegCamera(Camera): + """ + A generic implementation of an IP camera that is reachable over a URL. + """ + + def __init__(self, device_info): + super().__init__() + self._name = device_info.get('name', 'Mjpeg Camera') + self._username = device_info.get('username') + self._password = device_info.get('password') + self._mjpeg_url = device_info['mjpeg_url'] + + def camera_image(self): + """ Return a still image reponse from the camera. """ + if self._username and self._password: + password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() + password_mgr.add_password(None, self._mjpeg_url, self._username, self._password) + handler = urllib.request.HTTPBasicAuthHandler(password_mgr) + opener = urllib.request.build_opener(handler) + urllib.request.install_opener(opener) + + stream=urllib.request.urlopen(self._mjpeg_url) + charset = stream.headers.get_param('charset') + bytes = b'' + while True: + bytes += stream.read(1024) + a = bytes.find(b'\xff\xd8') + b = bytes.find(b'\xff\xd9') + if a != -1 and b != -1: + jpg = bytes[a:b+2] + return jpg + + @property + def name(self): + """ Return the name of this device. """ + return self._name From 8541fdb112ed57e3c425ab4f7eec34f3e8183da5 Mon Sep 17 00:00:00 2001 From: Ryan Turner Date: Sun, 8 Nov 2015 22:26:27 -0600 Subject: [PATCH 2/6] Fixed style issue related to failing build --- homeassistant/components/camera/mjpeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 709b15208f3..4d6be4ccd3e 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -48,7 +48,7 @@ class MjpegCamera(Camera): opener = urllib.request.build_opener(handler) urllib.request.install_opener(opener) - stream=urllib.request.urlopen(self._mjpeg_url) + stream = urllib.request.urlopen(self._mjpeg_url) charset = stream.headers.get_param('charset') bytes = b'' while True: From dfa81b0117708a41a64d3bb7d11104d115d35d2b Mon Sep 17 00:00:00 2001 From: Ryan Turner Date: Sun, 8 Nov 2015 23:41:21 -0600 Subject: [PATCH 3/6] Changed camera.mjpeg to use Response and Closing; cleaned up a number of code-clarity issues near that --- homeassistant/components/camera/mjpeg.py | 35 ++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 4d6be4ccd3e..29c817ed159 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -11,7 +11,8 @@ from requests.auth import HTTPBasicAuth from homeassistant.helpers import validate_config from homeassistant.components.camera import DOMAIN from homeassistant.components.camera import Camera -import urllib.request +import requests +from contextlib import closing _LOGGER = logging.getLogger(__name__) @@ -41,23 +42,23 @@ class MjpegCamera(Camera): def camera_image(self): """ Return a still image reponse from the camera. """ - if self._username and self._password: - password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() - password_mgr.add_password(None, self._mjpeg_url, self._username, self._password) - handler = urllib.request.HTTPBasicAuthHandler(password_mgr) - opener = urllib.request.build_opener(handler) - urllib.request.install_opener(opener) - stream = urllib.request.urlopen(self._mjpeg_url) - charset = stream.headers.get_param('charset') - bytes = b'' - while True: - bytes += stream.read(1024) - a = bytes.find(b'\xff\xd8') - b = bytes.find(b'\xff\xd9') - if a != -1 and b != -1: - jpg = bytes[a:b+2] - return jpg + def process_response(response): + data = b'' + for chunk in response.iter_content(1024): + data += chunk + jpg_start = data.find(b'\xff\xd8') + jpg_end = data.find(b'\xff\xd9') + if jpg_start != -1 and jpg_end != -1: + jpg = data[jpg_start:jpg_end + 2] + return jpg + + if self._username and self._password: + with closing(requests.get(self._mjpeg_url, auth=HTTPBasicAuth(self._username, self._password), stream=True)) as response: + return process_response(response) + else: + with closing(requests.get(self._mjpeg_url, stream=True)) as response: + return process_response(response) @property def name(self): From 3a6aa8f3d16d946e118aa00d2eb954821d6b7dde Mon Sep 17 00:00:00 2001 From: Ryan Turner Date: Sun, 8 Nov 2015 23:51:01 -0600 Subject: [PATCH 4/6] Fixed line length issues to make lint happy. Still bummed that I decreased test coverage :( --- homeassistant/components/camera/mjpeg.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 29c817ed159..7e83546b1a8 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -54,10 +54,12 @@ class MjpegCamera(Camera): return jpg if self._username and self._password: - with closing(requests.get(self._mjpeg_url, auth=HTTPBasicAuth(self._username, self._password), stream=True)) as response: + with closing(requests.get(self._mjpeg_url, auth = HTTPBasicAuth( + self._username, self._password), stream = True)) as response: return process_response(response) else: - with closing(requests.get(self._mjpeg_url, stream=True)) as response: + with closing(requests.get(self._mjpeg_url, + stream = True)) as response: return process_response(response) @property From f3352546c6f3eca2511136c873d8ce7c0bb89d28 Mon Sep 17 00:00:00 2001 From: Ryan Turner Date: Mon, 9 Nov 2015 00:00:31 -0600 Subject: [PATCH 5/6] More lint fixes --- homeassistant/components/camera/mjpeg.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 7e83546b1a8..12a2bbcc49e 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -44,6 +44,7 @@ class MjpegCamera(Camera): """ Return a still image reponse from the camera. """ def process_response(response): + """ Take in a response obj, return the jpg from it. """ data = b'' for chunk in response.iter_content(1024): data += chunk @@ -54,12 +55,14 @@ class MjpegCamera(Camera): return jpg if self._username and self._password: - with closing(requests.get(self._mjpeg_url, auth = HTTPBasicAuth( - self._username, self._password), stream = True)) as response: + with closing(requests.get(self._mjpeg_url, + auth=HTTPBasicAuth(self._username, + self._password), + stream=True)) as response: return process_response(response) else: - with closing(requests.get(self._mjpeg_url, - stream = True)) as response: + with closing(requests.get(self._mjpeg_url, + stream=True)) as response: return process_response(response) @property From a36b315927eabf55e1fc184af5e6be1ab2b7d47d Mon Sep 17 00:00:00 2001 From: Ryan Turner Date: Mon, 9 Nov 2015 00:11:11 -0600 Subject: [PATCH 6/6] Fixed indentations hopefully --- homeassistant/components/camera/mjpeg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 12a2bbcc49e..6da1fae7474 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -58,11 +58,11 @@ class MjpegCamera(Camera): with closing(requests.get(self._mjpeg_url, auth=HTTPBasicAuth(self._username, self._password), - stream=True)) as response: + stream=True)) as response: return process_response(response) else: with closing(requests.get(self._mjpeg_url, - stream=True)) as response: + stream=True)) as response: return process_response(response) @property