mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
* More robust MJPEG parser. Fixes ##13138. * Reimplement image extraction from mjpeg without ascy generator to support python 3.5
This commit is contained in:
parent
c971d61422
commit
223bc187dc
@ -56,34 +56,6 @@ async def async_setup_platform(hass, config, async_add_devices,
|
|||||||
async_add_devices([ProxyCamera(hass, config)])
|
async_add_devices([ProxyCamera(hass, config)])
|
||||||
|
|
||||||
|
|
||||||
async def _read_frame(req):
|
|
||||||
"""Read a single frame from an MJPEG stream."""
|
|
||||||
# based on https://gist.github.com/russss/1143799
|
|
||||||
import cgi
|
|
||||||
# Read in HTTP headers:
|
|
||||||
stream = req.content
|
|
||||||
# multipart/x-mixed-replace; boundary=--frameboundary
|
|
||||||
_mimetype, options = cgi.parse_header(req.headers['content-type'])
|
|
||||||
boundary = options.get('boundary').encode('utf-8')
|
|
||||||
if not boundary:
|
|
||||||
_LOGGER.error("Malformed MJPEG missing boundary")
|
|
||||||
raise Exception("Can't find content-type")
|
|
||||||
|
|
||||||
line = await stream.readline()
|
|
||||||
# Seek ahead to the first chunk
|
|
||||||
while line.strip() != boundary:
|
|
||||||
line = await stream.readline()
|
|
||||||
# Read in chunk headers
|
|
||||||
while line.strip() != b'':
|
|
||||||
parts = line.split(b':')
|
|
||||||
if len(parts) > 1 and parts[0].lower() == b'content-length':
|
|
||||||
# Grab chunk length
|
|
||||||
length = int(parts[1].strip())
|
|
||||||
line = await stream.readline()
|
|
||||||
image = await stream.read(length)
|
|
||||||
return image
|
|
||||||
|
|
||||||
|
|
||||||
def _resize_image(image, opts):
|
def _resize_image(image, opts):
|
||||||
"""Resize image."""
|
"""Resize image."""
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@ -227,9 +199,9 @@ class ProxyCamera(Camera):
|
|||||||
'boundary=--frameboundary')
|
'boundary=--frameboundary')
|
||||||
await response.prepare(request)
|
await response.prepare(request)
|
||||||
|
|
||||||
def write(img_bytes):
|
async def write(img_bytes):
|
||||||
"""Write image to stream."""
|
"""Write image to stream."""
|
||||||
response.write(bytes(
|
await response.write(bytes(
|
||||||
'--frameboundary\r\n'
|
'--frameboundary\r\n'
|
||||||
'Content-Type: {}\r\n'
|
'Content-Type: {}\r\n'
|
||||||
'Content-Length: {}\r\n\r\n'.format(
|
'Content-Length: {}\r\n\r\n'.format(
|
||||||
@ -240,13 +212,23 @@ class ProxyCamera(Camera):
|
|||||||
req = await stream_coro
|
req = await stream_coro
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# This would be nicer as an async generator
|
||||||
|
# But that would only be supported for python >=3.6
|
||||||
|
data = b''
|
||||||
|
stream = req.content
|
||||||
while True:
|
while True:
|
||||||
image = await _read_frame(req)
|
chunk = await stream.read(102400)
|
||||||
if not image:
|
if not chunk:
|
||||||
break
|
break
|
||||||
image = await self.hass.async_add_job(
|
data += chunk
|
||||||
_resize_image, image, self._stream_opts)
|
jpg_start = data.find(b'\xff\xd8')
|
||||||
write(image)
|
jpg_end = data.find(b'\xff\xd9')
|
||||||
|
if jpg_start != -1 and jpg_end != -1:
|
||||||
|
image = data[jpg_start:jpg_end + 2]
|
||||||
|
image = await self.hass.async_add_job(
|
||||||
|
_resize_image, image, self._stream_opts)
|
||||||
|
await write(image)
|
||||||
|
data = data[jpg_end + 2:]
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
_LOGGER.debug("Stream closed by frontend.")
|
_LOGGER.debug("Stream closed by frontend.")
|
||||||
req.close()
|
req.close()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user