Add support for mjpeg component to proxy it's own stream

pull/1160/head
St. John Johnson 2016-02-07 01:08:55 -08:00
parent fb6aded2e1
commit f700635445
2 changed files with 60 additions and 39 deletions

View File

@ -33,8 +33,6 @@ SWITCH_ACTION_SNAPSHOT = 'snapshot'
SERVICE_CAMERA = 'camera_service' SERVICE_CAMERA = 'camera_service'
STATE_RECORDING = 'recording'
DEFAULT_RECORDING_SECONDS = 30 DEFAULT_RECORDING_SECONDS = 30
# Maps discovered services to their platforms # Maps discovered services to their platforms
@ -46,6 +44,7 @@ DIR_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S'
REC_DIR_PREFIX = 'recording-' REC_DIR_PREFIX = 'recording-'
REC_IMG_PREFIX = 'recording_image-' REC_IMG_PREFIX = 'recording_image-'
STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming' STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle' STATE_IDLE = 'idle'
@ -121,33 +120,7 @@ def setup(hass, config):
try: try:
camera.is_streaming = True camera.is_streaming = True
camera.update_ha_state() camera.update_ha_state()
camera.mjpeg_stream(handler)
handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8'))
handler.request.sendall(bytes(
'Content-type: multipart/x-mixed-replace; \
boundary=--jpgboundary\r\n\r\n', 'utf-8'))
handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8'))
# MJPEG_START_HEADER.format()
while True:
img_bytes = camera.camera_image()
if img_bytes is None:
continue
headers_str = '\r\n'.join((
'Content-length: {}'.format(len(img_bytes)),
'Content-type: image/jpeg',
)) + '\r\n\r\n'
handler.request.sendall(
bytes(headers_str, 'utf-8') +
img_bytes +
bytes('\r\n', 'utf-8'))
handler.request.sendall(
bytes('--jpgboundary\r\n', 'utf-8'))
time.sleep(0.5)
except (requests.RequestException, IOError): except (requests.RequestException, IOError):
camera.is_streaming = False camera.is_streaming = False
@ -190,6 +163,34 @@ class Camera(Entity):
""" Return bytes of camera image. """ """ Return bytes of camera image. """
raise NotImplementedError() raise NotImplementedError()
def mjpeg_stream(self, handler):
""" Generate an HTTP MJPEG stream from camera images. """
handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8'))
handler.request.sendall(bytes(
'Content-type: multipart/x-mixed-replace; \
boundary=--jpgboundary\r\n\r\n', 'utf-8'))
handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8'))
# MJPEG_START_HEADER.format()
while True:
img_bytes = self.camera_image()
if img_bytes is None:
continue
headers_str = '\r\n'.join((
'Content-length: {}'.format(len(img_bytes)),
'Content-type: image/jpeg',
)) + '\r\n\r\n'
handler.request.sendall(
bytes(headers_str, 'utf-8') +
img_bytes +
bytes('\r\n', 'utf-8'))
handler.request.sendall(
bytes('--jpgboundary\r\n', 'utf-8'))
time.sleep(0.5)
@property @property
def state(self): def state(self):
""" Returns the state of the entity. """ """ Returns the state of the entity. """

View File

@ -14,6 +14,9 @@ from requests.auth import HTTPBasicAuth
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN, Camera from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.const import HTTP_OK
CONTENT_TYPE_HEADER = 'Content-Type'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -41,6 +44,17 @@ class MjpegCamera(Camera):
self._password = device_info.get('password') self._password = device_info.get('password')
self._mjpeg_url = device_info['mjpeg_url'] self._mjpeg_url = device_info['mjpeg_url']
def camera_stream(self):
""" Return a mjpeg stream image response directly from the camera. """
if self._username and self._password:
return requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username,
self._password),
stream=True)
else:
return requests.get(self._mjpeg_url,
stream=True)
def camera_image(self): def camera_image(self):
""" Return a still image response from the camera. """ """ Return a still image response from the camera. """
@ -55,16 +69,22 @@ class MjpegCamera(Camera):
jpg = data[jpg_start:jpg_end + 2] jpg = data[jpg_start:jpg_end + 2]
return jpg return jpg
if self._username and self._password: with closing(self.camera_stream()) as response:
with closing(requests.get(self._mjpeg_url, return process_response(response)
auth=HTTPBasicAuth(self._username,
self._password), def mjpeg_stream(self, handler):
stream=True)) as response: """ Generate an HTTP MJPEG stream from the camera. """
return process_response(response) response = self.camera_stream()
else: content_type = response.headers[CONTENT_TYPE_HEADER]
with closing(requests.get(self._mjpeg_url,
stream=True)) as response: handler.send_response(HTTP_OK)
return process_response(response) handler.send_header(CONTENT_TYPE_HEADER, content_type)
handler.end_headers()
for chunk in response.iter_content(chunk_size=1024):
if not chunk:
break
handler.wfile.write(chunk)
@property @property
def name(self): def name(self):