Fixed picamera mjpeg stream
This commit is contained in:
parent
634aeec58d
commit
416c9ceb93
4 changed files with 42 additions and 48 deletions
|
@ -19,21 +19,20 @@ __routes__ = [
|
||||||
|
|
||||||
def video_feed():
|
def video_feed():
|
||||||
camera: Optional[CameraPiPlugin] = None
|
camera: Optional[CameraPiPlugin] = None
|
||||||
|
|
||||||
try:
|
|
||||||
camera_conf = Config.get('camera.pi') or {}
|
camera_conf = Config.get('camera.pi') or {}
|
||||||
camera = CameraPiPlugin(**camera_conf)
|
camera = CameraPiPlugin(**camera_conf)
|
||||||
|
|
||||||
|
with camera:
|
||||||
while True:
|
while True:
|
||||||
output = camera.get_output_stream()
|
output = camera.get_stream()
|
||||||
|
|
||||||
with output.ready:
|
with output.ready:
|
||||||
output.ready.wait()
|
output.ready.wait()
|
||||||
|
frame = output.frame
|
||||||
|
|
||||||
|
if frame and len(frame):
|
||||||
yield (b'--frame\r\n'
|
yield (b'--frame\r\n'
|
||||||
b'Content-Type: image/jpeg\r\n\r\n' + output.frame + b'\r\n')
|
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
|
||||||
finally:
|
|
||||||
if camera:
|
|
||||||
camera.close_output_stream()
|
|
||||||
|
|
||||||
|
|
||||||
@camera_pi.route('/camera/pi/frame', methods=['GET'])
|
@camera_pi.route('/camera/pi/frame', methods=['GET'])
|
||||||
|
@ -52,7 +51,6 @@ def get_frame_img():
|
||||||
@authenticate()
|
@authenticate()
|
||||||
def get_stream_feed():
|
def get_stream_feed():
|
||||||
return Response(video_feed(),
|
return Response(video_feed(),
|
||||||
headers={'Cache-Control': 'no-cache, private', 'Pragma': 'no-cache', 'Age': 0},
|
|
||||||
mimetype='multipart/x-mixed-replace; boundary=frame')
|
mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{%
|
{%
|
||||||
with pluginIcons = {
|
with pluginIcons = {
|
||||||
'camera': 'fas fa-camera',
|
'camera': 'fas fa-camera',
|
||||||
|
'camera.pi': 'fab fa-raspberry-pi',
|
||||||
'camera.ir.mlx90640': 'fas fa-sun',
|
'camera.ir.mlx90640': 'fas fa-sun',
|
||||||
'execute': 'fas fa-play',
|
'execute': 'fas fa-play',
|
||||||
'light.hue': 'fa fa-lightbulb',
|
'light.hue': 'fa fa-lightbulb',
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -16,24 +15,6 @@ from platypush.message.event.camera import CameraRecordingStartedEvent, \
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
|
||||||
|
|
||||||
class StreamingOutput:
|
|
||||||
def __init__(self):
|
|
||||||
self.frame = None
|
|
||||||
self.buffer = io.BytesIO()
|
|
||||||
self.ready = threading.Condition()
|
|
||||||
|
|
||||||
def write(self, buf):
|
|
||||||
if buf.startswith(b'\xff\xd8'):
|
|
||||||
# New frame, copy the existing buffer's content and notify all clients that it's available
|
|
||||||
self.buffer.truncate()
|
|
||||||
with self.ready:
|
|
||||||
self.frame = self.buffer.getvalue()
|
|
||||||
self.ready.notify_all()
|
|
||||||
self.buffer.seek(0)
|
|
||||||
|
|
||||||
return self.buffer.write(buf)
|
|
||||||
|
|
||||||
|
|
||||||
class CameraPlugin(Plugin):
|
class CameraPlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
Plugin to control generic cameras over OpenCV.
|
Plugin to control generic cameras over OpenCV.
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
@ -10,7 +11,25 @@ import time
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
from platypush.plugins.camera import CameraPlugin, StreamingOutput
|
from platypush.plugins.camera import CameraPlugin
|
||||||
|
|
||||||
|
|
||||||
|
class StreamingOutput:
|
||||||
|
def __init__(self):
|
||||||
|
self.frame = None
|
||||||
|
self.buffer = io.BytesIO()
|
||||||
|
self.ready = threading.Condition()
|
||||||
|
|
||||||
|
def write(self, buf):
|
||||||
|
if buf.startswith(b'\xff\xd8'):
|
||||||
|
# New frame, copy the existing buffer's content and notify all clients that it's available
|
||||||
|
self.buffer.truncate()
|
||||||
|
with self.ready:
|
||||||
|
self.frame = self.buffer.getvalue()
|
||||||
|
self.ready.notify_all()
|
||||||
|
self.buffer.seek(0)
|
||||||
|
|
||||||
|
return self.buffer.write(buf)
|
||||||
|
|
||||||
|
|
||||||
class CameraPiPlugin(CameraPlugin):
|
class CameraPiPlugin(CameraPlugin):
|
||||||
|
@ -89,10 +108,12 @@ class CameraPiPlugin(CameraPlugin):
|
||||||
"""
|
"""
|
||||||
Close an active connection to the camera.
|
Close an active connection to the camera.
|
||||||
"""
|
"""
|
||||||
if not self._camera or self._camera.closed:
|
if self._output and self._camera:
|
||||||
self.logger.info('Camera connection already closed')
|
self._camera.stop_recording()
|
||||||
|
|
||||||
|
if self._camera and not self._camera.closed:
|
||||||
self._camera.close()
|
self._camera.close()
|
||||||
|
|
||||||
self._camera = None
|
self._camera = None
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -174,23 +195,16 @@ class CameraPiPlugin(CameraPlugin):
|
||||||
if camera and close:
|
if camera and close:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def get_output_stream(self, resize: Union[tuple, list] = None, **opts) -> StreamingOutput:
|
def __enter__(self):
|
||||||
if self._output:
|
camera = self._get_camera()
|
||||||
return self._output
|
|
||||||
|
|
||||||
camera = self._get_camera(**opts)
|
|
||||||
capture_opts = {}
|
|
||||||
if resize:
|
|
||||||
capture_opts['resize'] = tuple(resize)
|
|
||||||
|
|
||||||
self._output = StreamingOutput()
|
self._output = StreamingOutput()
|
||||||
camera.start_recording(self._output, format='mjpeg', **capture_opts)
|
camera.start_recording(self._output, format='mjpeg')
|
||||||
|
|
||||||
|
def get_stream(self):
|
||||||
return self._output
|
return self._output
|
||||||
|
|
||||||
def close_output_stream(self):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
if self._output:
|
self.close()
|
||||||
self._camera.stop_recording()
|
|
||||||
self._output = None
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def capture_sequence(self, n_images, directory, name_format='image_%04d.jpg', preview=False, warmup_time=2,
|
def capture_sequence(self, n_images, directory, name_format='image_%04d.jpg', preview=False, warmup_time=2,
|
||||||
|
|
Loading…
Reference in a new issue