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'
|
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. """
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue