180 lines
6.6 KiB
Python
180 lines
6.6 KiB
Python
import threading
|
|
import time
|
|
|
|
from typing import Optional, List, Tuple, Union
|
|
|
|
from platypush.plugins import action
|
|
from platypush.plugins.camera import CameraPlugin, Camera
|
|
from platypush.plugins.camera.pi.model import PiCameraInfo, PiCamera
|
|
|
|
|
|
class CameraPiPlugin(CameraPlugin):
|
|
"""
|
|
Plugin to control a Pi camera.
|
|
|
|
Requires:
|
|
|
|
* **picamera** (``pip install picamera``)
|
|
* **numpy** (``pip install numpy``)
|
|
* **Pillow** (``pip install Pillow``)
|
|
|
|
"""
|
|
|
|
_camera_class = PiCamera
|
|
_camera_info_class = PiCameraInfo
|
|
|
|
def __init__(self, device: int = 0, fps: float = 30., warmup_seconds: float = 2., sharpness: int = 0,
|
|
contrast: int = 0, brightness: int = 50, video_stabilization: bool = False, iso: int = 0,
|
|
exposure_compensation: int = 0, exposure_mode: str = 'auto', meter_mode: str = 'average',
|
|
awb_mode: str = 'auto', image_effect: str = 'none', led_pin: Optional[int] = None,
|
|
color_effects: Optional[Union[str, List[str]]] = None,
|
|
zoom: Tuple[float, float, float, float] = (0.0, 0.0, 1.0, 1.0), **camera):
|
|
"""
|
|
See https://www.raspberrypi.org/documentation/usage/camera/python/README.md
|
|
for a detailed reference about the Pi camera options.
|
|
|
|
:param camera: Options for the base camera plugin (see :class:`platypush.plugins.camera.CameraPlugin`).
|
|
"""
|
|
super().__init__(device=device, fps=fps, warmup_seconds=warmup_seconds, **camera)
|
|
|
|
self.camera_info.sharpness = sharpness
|
|
self.camera_info.contrast = contrast
|
|
self.camera_info.brightness = brightness
|
|
self.camera_info.video_stabilization = video_stabilization
|
|
self.camera_info.iso = iso
|
|
self.camera_info.exposure_compensation = exposure_compensation
|
|
self.camera_info.meter_mode = meter_mode
|
|
self.camera_info.exposure_mode = exposure_mode
|
|
self.camera_info.awb_mode = awb_mode
|
|
self.camera_info.image_effect = image_effect
|
|
self.camera_info.color_effects = color_effects
|
|
self.camera_info.zoom = zoom
|
|
self.camera_info.led_pin = led_pin
|
|
|
|
# noinspection DuplicatedCode
|
|
def prepare_device(self, device: PiCamera):
|
|
# noinspection PyUnresolvedReferences
|
|
import picamera
|
|
|
|
camera = picamera.PiCamera(camera_num=device.info.device, resolution=device.info.resolution,
|
|
framerate=device.info.fps, led_pin=device.info.led_pin)
|
|
|
|
camera.hflip = device.info.horizontal_flip
|
|
camera.vflip = device.info.vertical_flip
|
|
camera.sharpness = device.info.sharpness
|
|
camera.contrast = device.info.contrast
|
|
camera.brightness = device.info.brightness
|
|
camera.video_stabilization = device.info.video_stabilization
|
|
camera.iso = device.info.iso
|
|
camera.exposure_compensation = device.info.exposure_compensation
|
|
camera.exposure_mode = device.info.exposure_mode
|
|
camera.meter_mode = device.info.meter_mode
|
|
camera.awb_mode = device.info.awb_mode
|
|
camera.image_effect = device.info.image_effect
|
|
camera.color_effects = device.info.color_effects
|
|
camera.rotation = device.info.rotate or 0
|
|
camera.zoom = device.info.zoom
|
|
|
|
return camera
|
|
|
|
def release_device(self, device: PiCamera):
|
|
# noinspection PyUnresolvedReferences
|
|
import picamera
|
|
|
|
if device.object:
|
|
try:
|
|
device.object.stop_recording()
|
|
except picamera.PiCameraNotRecording:
|
|
pass
|
|
|
|
if device.object and not device.object.closed:
|
|
try:
|
|
device.object.close()
|
|
except picamera.PiCameraClosed:
|
|
pass
|
|
|
|
def capture_frame(self, camera: Camera, *args, **kwargs):
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
shape = (camera.info.resolution[1] + (camera.info.resolution[1] % 16),
|
|
camera.info.resolution[0] + (camera.info.resolution[0] % 32),
|
|
3)
|
|
|
|
frame = np.empty(shape, dtype=np.uint8)
|
|
camera.object.capture(frame, 'rgb')
|
|
return Image.fromarray(frame)
|
|
|
|
def start_preview(self, camera: Camera):
|
|
"""
|
|
Start camera preview.
|
|
"""
|
|
camera.object.start_preview()
|
|
|
|
def stop_preview(self, camera: Camera):
|
|
"""
|
|
Stop camera preview.
|
|
"""
|
|
try:
|
|
camera.object.stop_preview()
|
|
except Exception as e:
|
|
self.logger.warning(str(e))
|
|
|
|
@action
|
|
def capture_preview(self, duration: Optional[float] = None, n_frames: Optional[int] = None, **camera) -> dict:
|
|
camera = self.open_device(**camera)
|
|
self.start_preview(camera)
|
|
|
|
if n_frames:
|
|
duration = n_frames * (camera.info.fps or 0)
|
|
if duration:
|
|
threading.Timer(duration, lambda: self.stop_preview(camera))
|
|
|
|
return self.status()
|
|
|
|
def streaming_thread(self, camera: Camera, stream_format: str, duration: Optional[float] = None):
|
|
server_socket = self._prepare_server_socket(camera)
|
|
sock = None
|
|
streaming_started_time = time.time()
|
|
self.logger.info('Starting streaming on port {}'.format(camera.info.listen_port))
|
|
|
|
try:
|
|
while camera.stream_event.is_set():
|
|
if duration and time.time() - streaming_started_time >= duration:
|
|
break
|
|
|
|
sock = self._accept_client(server_socket)
|
|
if not sock:
|
|
continue
|
|
|
|
try:
|
|
camera.object.start_recording(sock, format=stream_format)
|
|
while camera.stream_event.is_set():
|
|
camera.object.wait_recording(1)
|
|
except ConnectionError:
|
|
self.logger.info('Client closed connection')
|
|
finally:
|
|
if sock:
|
|
sock.close()
|
|
finally:
|
|
self._cleanup_stream(camera, server_socket, sock)
|
|
try:
|
|
camera.object.stop_recording()
|
|
except Exception as e:
|
|
self.logger.warning('Error while stopping camera recording: {}'.format(str(e)))
|
|
|
|
try:
|
|
camera.object.close()
|
|
except Exception as e:
|
|
self.logger.warning('Error while closing camera: {}'.format(str(e)))
|
|
|
|
self.logger.info('Stopped camera stream')
|
|
|
|
@action
|
|
def start_streaming(self, duration: Optional[float] = None, stream_format: str = 'h264', **camera) -> dict:
|
|
camera = self.open_device(stream_format=stream_format, **camera)
|
|
return self._start_streaming(camera, duration, stream_format)
|
|
|
|
|
|
# vim:sw=4:ts=4:et:
|