Add support for mjpeg component to proxy it's own stream
parent
fb6aded2e1
commit
f700635445
|
@ -33,8 +33,6 @@ SWITCH_ACTION_SNAPSHOT = 'snapshot'
|
|||
|
||||
SERVICE_CAMERA = 'camera_service'
|
||||
|
||||
STATE_RECORDING = 'recording'
|
||||
|
||||
DEFAULT_RECORDING_SECONDS = 30
|
||||
|
||||
# Maps discovered services to their platforms
|
||||
|
@ -46,6 +44,7 @@ DIR_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S'
|
|||
REC_DIR_PREFIX = 'recording-'
|
||||
REC_IMG_PREFIX = 'recording_image-'
|
||||
|
||||
STATE_RECORDING = 'recording'
|
||||
STATE_STREAMING = 'streaming'
|
||||
STATE_IDLE = 'idle'
|
||||
|
||||
|
@ -121,33 +120,7 @@ def setup(hass, config):
|
|||
try:
|
||||
camera.is_streaming = True
|
||||
camera.update_ha_state()
|
||||
|
||||
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)
|
||||
camera.mjpeg_stream(handler)
|
||||
|
||||
except (requests.RequestException, IOError):
|
||||
camera.is_streaming = False
|
||||
|
@ -190,6 +163,34 @@ class Camera(Entity):
|
|||
""" Return bytes of camera image. """
|
||||
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
|
||||
def state(self):
|
||||
""" Returns the state of the entity. """
|
||||
|
|
|
@ -14,6 +14,9 @@ from requests.auth import HTTPBasicAuth
|
|||
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.components.camera import DOMAIN, Camera
|
||||
from homeassistant.const import HTTP_OK
|
||||
|
||||
CONTENT_TYPE_HEADER = 'Content-Type'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -41,6 +44,17 @@ class MjpegCamera(Camera):
|
|||
self._password = device_info.get('password')
|
||||
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):
|
||||
""" Return a still image response from the camera. """
|
||||
|
||||
|
@ -55,16 +69,22 @@ class MjpegCamera(Camera):
|
|||
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)
|
||||
with closing(self.camera_stream()) as response:
|
||||
return process_response(response)
|
||||
|
||||
def mjpeg_stream(self, handler):
|
||||
""" Generate an HTTP MJPEG stream from the camera. """
|
||||
response = self.camera_stream()
|
||||
content_type = response.headers[CONTENT_TYPE_HEADER]
|
||||
|
||||
handler.send_response(HTTP_OK)
|
||||
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
|
||||
def name(self):
|
||||
|
|
Loading…
Reference in New Issue