From 416c9ceb93f656cdd0c248de76fdf2e2138d1615 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Tue, 17 Dec 2019 22:44:15 +0100 Subject: [PATCH] Fixed picamera mjpeg stream --- .../http/app/routes/plugins/camera/pi.py | 20 ++++---- platypush/backend/http/templates/nav.html | 1 + platypush/plugins/camera/__init__.py | 19 ------- platypush/plugins/camera/pi.py | 50 ++++++++++++------- 4 files changed, 42 insertions(+), 48 deletions(-) diff --git a/platypush/backend/http/app/routes/plugins/camera/pi.py b/platypush/backend/http/app/routes/plugins/camera/pi.py index 25f09e40..f440c69f 100644 --- a/platypush/backend/http/app/routes/plugins/camera/pi.py +++ b/platypush/backend/http/app/routes/plugins/camera/pi.py @@ -19,21 +19,20 @@ __routes__ = [ def video_feed(): camera: Optional[CameraPiPlugin] = None + camera_conf = Config.get('camera.pi') or {} + camera = CameraPiPlugin(**camera_conf) - try: - camera_conf = Config.get('camera.pi') or {} - camera = CameraPiPlugin(**camera_conf) - + with camera: while True: - output = camera.get_output_stream() + output = camera.get_stream() + with output.ready: output.ready.wait() + frame = output.frame - yield (b'--frame\r\n' - b'Content-Type: image/jpeg\r\n\r\n' + output.frame + b'\r\n') - finally: - if camera: - camera.close_output_stream() + if frame and len(frame): + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') @camera_pi.route('/camera/pi/frame', methods=['GET']) @@ -52,7 +51,6 @@ def get_frame_img(): @authenticate() def get_stream_feed(): return Response(video_feed(), - headers={'Cache-Control': 'no-cache, private', 'Pragma': 'no-cache', 'Age': 0}, mimetype='multipart/x-mixed-replace; boundary=frame') diff --git a/platypush/backend/http/templates/nav.html b/platypush/backend/http/templates/nav.html index cadfafa4..ef6a7a29 100644 --- a/platypush/backend/http/templates/nav.html +++ b/platypush/backend/http/templates/nav.html @@ -1,6 +1,7 @@ {% with pluginIcons = { 'camera': 'fas fa-camera', + 'camera.pi': 'fab fa-raspberry-pi', 'camera.ir.mlx90640': 'fas fa-sun', 'execute': 'fas fa-play', 'light.hue': 'fa fa-lightbulb', diff --git a/platypush/plugins/camera/__init__.py b/platypush/plugins/camera/__init__.py index d00afa66..5e666c3b 100644 --- a/platypush/plugins/camera/__init__.py +++ b/platypush/plugins/camera/__init__.py @@ -1,4 +1,3 @@ -import io import os import re import shutil @@ -16,24 +15,6 @@ from platypush.message.event.camera import CameraRecordingStartedEvent, \ 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): """ Plugin to control generic cameras over OpenCV. diff --git a/platypush/plugins/camera/pi.py b/platypush/plugins/camera/pi.py index dca463fd..6ffc10b1 100644 --- a/platypush/plugins/camera/pi.py +++ b/platypush/plugins/camera/pi.py @@ -2,6 +2,7 @@ .. moduleauthor:: Fabio Manganiello """ +import io import os import socket import threading @@ -10,7 +11,25 @@ import time from typing import Optional, Union 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): @@ -89,10 +108,12 @@ class CameraPiPlugin(CameraPlugin): """ Close an active connection to the camera. """ - if not self._camera or self._camera.closed: - self.logger.info('Camera connection already closed') + if self._output and self._camera: + self._camera.stop_recording() + + if self._camera and not self._camera.closed: + self._camera.close() - self._camera.close() self._camera = None @action @@ -174,23 +195,16 @@ class CameraPiPlugin(CameraPlugin): if camera and close: self.close() - def get_output_stream(self, resize: Union[tuple, list] = None, **opts) -> StreamingOutput: - if self._output: - return self._output - - camera = self._get_camera(**opts) - capture_opts = {} - if resize: - capture_opts['resize'] = tuple(resize) - + def __enter__(self): + camera = self._get_camera() 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 - def close_output_stream(self): - if self._output: - self._camera.stop_recording() - self._output = None + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() @action def capture_sequence(self, n_images, directory, name_format='image_%04d.jpg', preview=False, warmup_time=2,