forked from platypush/platypush
New picamera plugin
This commit is contained in:
parent
65ef7ca027
commit
b153d212e3
2 changed files with 433 additions and 48 deletions
|
@ -1,6 +1,5 @@
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
import time
|
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
@ -8,6 +7,7 @@ from threading import Thread
|
||||||
from platypush.backend import Backend
|
from platypush.backend import Backend
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
|
|
||||||
|
|
||||||
class CameraPiBackend(Backend):
|
class CameraPiBackend(Backend):
|
||||||
"""
|
"""
|
||||||
Backend to interact with a Raspberry Pi camera. It can start and stop
|
Backend to interact with a Raspberry Pi camera. It can start and stop
|
||||||
|
@ -29,12 +29,13 @@ class CameraPiBackend(Backend):
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.value == other
|
return self.value == other
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
def __init__(self, listen_port, x_resolution=640, y_resolution=480,
|
def __init__(self, listen_port, x_resolution=640, y_resolution=480,
|
||||||
redis_queue='platypush/camera/pi',
|
redis_queue='platypush/camera/pi',
|
||||||
start_recording_on_startup=True,
|
start_recording_on_startup=True,
|
||||||
framerate=24, hflip=False, vflip=False,
|
framerate=24, hflip=False, vflip=False,
|
||||||
sharpness=0, contrast=0, brightness=50,
|
sharpness=0, contrast=0, brightness=50,
|
||||||
video_stabilization=False, ISO=0, exposure_compensation=0,
|
video_stabilization=False, iso=0, exposure_compensation=0,
|
||||||
exposure_mode='auto', meter_mode='average', awb_mode='auto',
|
exposure_mode='auto', meter_mode='average', awb_mode='auto',
|
||||||
image_effect='none', color_effects=None, rotation=0,
|
image_effect='none', color_effects=None, rotation=0,
|
||||||
crop=(0.0, 0.0, 1.0, 1.0), **kwargs):
|
crop=(0.0, 0.0, 1.0, 1.0), **kwargs):
|
||||||
|
@ -46,7 +47,6 @@ class CameraPiBackend(Backend):
|
||||||
:type listen_port: int
|
:type listen_port: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import picamera
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.listen_port = listen_port
|
self.listen_port = listen_port
|
||||||
|
@ -54,6 +54,8 @@ class CameraPiBackend(Backend):
|
||||||
self.server_socket.bind(('0.0.0.0', self.listen_port))
|
self.server_socket.bind(('0.0.0.0', self.listen_port))
|
||||||
self.server_socket.listen(0)
|
self.server_socket.listen(0)
|
||||||
|
|
||||||
|
import picamera
|
||||||
|
|
||||||
self.camera = picamera.PiCamera()
|
self.camera = picamera.PiCamera()
|
||||||
self.camera.resolution = (x_resolution, y_resolution)
|
self.camera.resolution = (x_resolution, y_resolution)
|
||||||
self.camera.framerate = framerate
|
self.camera.framerate = framerate
|
||||||
|
@ -63,7 +65,7 @@ class CameraPiBackend(Backend):
|
||||||
self.camera.contrast = contrast
|
self.camera.contrast = contrast
|
||||||
self.camera.brightness = brightness
|
self.camera.brightness = brightness
|
||||||
self.camera.video_stabilization = video_stabilization
|
self.camera.video_stabilization = video_stabilization
|
||||||
self.camera.ISO = ISO
|
self.camera.ISO = iso
|
||||||
self.camera.exposure_compensation = exposure_compensation
|
self.camera.exposure_compensation = exposure_compensation
|
||||||
self.camera.exposure_mode = exposure_mode
|
self.camera.exposure_mode = exposure_mode
|
||||||
self.camera.meter_mode = meter_mode
|
self.camera.meter_mode = meter_mode
|
||||||
|
@ -77,7 +79,6 @@ class CameraPiBackend(Backend):
|
||||||
self.redis_queue = redis_queue
|
self.redis_queue = redis_queue
|
||||||
self._recording_thread = None
|
self._recording_thread = None
|
||||||
|
|
||||||
|
|
||||||
def send_camera_action(self, action, **kwargs):
|
def send_camera_action(self, action, **kwargs):
|
||||||
action = {
|
action = {
|
||||||
'action': action.value,
|
'action': action.value,
|
||||||
|
@ -97,17 +98,20 @@ class CameraPiBackend(Backend):
|
||||||
self.camera.capture(image_file)
|
self.camera.capture(image_file)
|
||||||
self.logger.info('Captured camera snapshot to {}'.format(image_file))
|
self.logger.info('Captured camera snapshot to {}'.format(image_file))
|
||||||
|
|
||||||
|
# noinspection PyShadowingBuiltins
|
||||||
def start_recording(self, video_file=None, format='h264'):
|
def start_recording(self, video_file=None, format='h264'):
|
||||||
"""
|
"""
|
||||||
Start a recording.
|
Start a recording.
|
||||||
|
|
||||||
:param video_file: Output video file. If specified, the video will be recorded to file, otherwise it will be served via TCP/IP on the listen_port. Use ``stop_recording`` to stop the recording.
|
:param video_file: Output video file. If specified, the video will be recorded to file, otherwise it will be
|
||||||
|
served via TCP/IP on the listen_port. Use ``stop_recording`` to stop the recording.
|
||||||
:type video_file: str
|
:type video_file: str
|
||||||
|
|
||||||
:param format: Video format (default: h264)
|
:param format: Video format (default: h264)
|
||||||
:type format: str
|
:type format: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# noinspection PyBroadException
|
||||||
def recording_thread():
|
def recording_thread():
|
||||||
if video_file:
|
if video_file:
|
||||||
self.camera.start_recording(video_file, format=format)
|
self.camera.start_recording(video_file, format=format)
|
||||||
|
@ -116,8 +120,7 @@ class CameraPiBackend(Backend):
|
||||||
else:
|
else:
|
||||||
while True:
|
while True:
|
||||||
connection = self.server_socket.accept()[0].makefile('wb')
|
connection = self.server_socket.accept()[0].makefile('wb')
|
||||||
self.logger.info('Accepted client connection on port {}'.
|
self.logger.info('Accepted client connection on port {}'.format(self.listen_port))
|
||||||
format(self.listen_port))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.camera.start_recording(connection, format=format)
|
self.camera.start_recording(connection, format=format)
|
||||||
|
@ -125,21 +128,18 @@ class CameraPiBackend(Backend):
|
||||||
self.camera.wait_recording(2)
|
self.camera.wait_recording(2)
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
self.logger.info('Client closed connection')
|
self.logger.info('Client closed connection')
|
||||||
try: self.stop_recording()
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
try: connection.close()
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
self.send_camera_action(self.CameraAction.START_RECORDING)
|
|
||||||
|
|
||||||
self._recording_thread = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.camera.stop_recording()
|
self.stop_recording()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.send_camera_action(self.CameraAction.START_RECORDING)
|
||||||
|
|
||||||
if self._recording_thread:
|
if self._recording_thread:
|
||||||
self.logger.info('Recording already running')
|
self.logger.info('Recording already running')
|
||||||
return
|
return
|
||||||
|
@ -149,7 +149,6 @@ class CameraPiBackend(Backend):
|
||||||
name='PiCameraRecorder')
|
name='PiCameraRecorder')
|
||||||
self._recording_thread.start()
|
self._recording_thread.start()
|
||||||
|
|
||||||
|
|
||||||
def stop_recording(self):
|
def stop_recording(self):
|
||||||
""" Stops recording """
|
""" Stops recording """
|
||||||
|
|
||||||
|
@ -187,4 +186,3 @@ class CameraPiBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
|
@ -2,48 +2,435 @@
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from platypush.context import get_backend
|
import os
|
||||||
from platypush.plugins import Plugin, action
|
import socket
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from platypush.plugins import action
|
||||||
|
from platypush.plugins.camera import CameraPlugin
|
||||||
|
|
||||||
|
|
||||||
class CameraPiPlugin(Plugin):
|
class CameraPiPlugin(CameraPlugin):
|
||||||
"""
|
"""
|
||||||
Plugin to control a Pi camera.
|
Plugin to control a Pi camera.
|
||||||
It acts as a wrapper around the :mod:`platypush.backend.camera.pi` backend
|
|
||||||
to programmatically control the status.
|
Requires:
|
||||||
|
|
||||||
|
* **picamera** (``pip install picamera``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
_default_resolution = (800, 600)
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
def __init__(self, resolution=(_default_resolution[0], _default_resolution[1]), framerate=24,
|
||||||
|
hflip=False, vflip=False, sharpness=0, contrast=0, brightness=50, video_stabilization=False, iso=0,
|
||||||
|
exposure_compensation=0, exposure_mode='auto', meter_mode='average', awb_mode='auto',
|
||||||
|
image_effect='none', color_effects=None, rotation=0, crop=(0.0, 0.0, 1.0, 1.0), **kwargs):
|
||||||
|
"""
|
||||||
|
See https://www.raspberrypi.org/documentation/usage/camera/python/README.md
|
||||||
|
for a detailed reference about the Pi camera options.
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.camera_args = {
|
||||||
|
'resolution': tuple(resolution),
|
||||||
|
'framerate': framerate,
|
||||||
|
'hflip': hflip,
|
||||||
|
'vflip': vflip,
|
||||||
|
'sharpness': sharpness,
|
||||||
|
'contrast': contrast,
|
||||||
|
'brightness': brightness,
|
||||||
|
'video_stabilization': video_stabilization,
|
||||||
|
'ISO': iso,
|
||||||
|
'exposure_compensation': exposure_compensation,
|
||||||
|
'exposure_mode': exposure_mode,
|
||||||
|
'meter_mode': meter_mode,
|
||||||
|
'awb_mode': awb_mode,
|
||||||
|
'image_effect': image_effect,
|
||||||
|
'color_effects': color_effects,
|
||||||
|
'rotation': rotation,
|
||||||
|
'crop': tuple(crop),
|
||||||
|
}
|
||||||
|
|
||||||
|
self._camera = None
|
||||||
|
|
||||||
|
self._time_lapse_thread = None
|
||||||
|
self._recording_thread = None
|
||||||
|
self._streaming_thread = None
|
||||||
|
self._time_lapse_stop_condition = threading.Condition()
|
||||||
|
self._recording_stop_condition = threading.Condition()
|
||||||
|
self._streaming_stop_condition = threading.Condition()
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
|
def _get_camera(self, **opts):
|
||||||
|
if self._camera:
|
||||||
|
return self._camera
|
||||||
|
|
||||||
|
import picamera
|
||||||
|
self._camera = picamera.PiCamera()
|
||||||
|
|
||||||
|
for (attr, value) in self.camera_args:
|
||||||
|
setattr(self._camera, attr, value)
|
||||||
|
for (attr, value) in opts:
|
||||||
|
setattr(self._camera, attr, value)
|
||||||
|
|
||||||
|
return self._camera
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_recording(self):
|
def take_picture(self, image_file, preview=False, warmup_time=2, resize=None, **opts):
|
||||||
"""
|
|
||||||
Start recording
|
|
||||||
"""
|
|
||||||
camera = get_backend('camera.pi')
|
|
||||||
camera.send_camera_action(camera.CameraAction.START_RECORDING)
|
|
||||||
|
|
||||||
@action
|
|
||||||
def stop_recording(self):
|
|
||||||
"""
|
|
||||||
Stop recording
|
|
||||||
"""
|
|
||||||
camera = get_backend('camera.pi')
|
|
||||||
camera.send_camera_action(camera.CameraAction.STOP_RECORDING)
|
|
||||||
|
|
||||||
@action
|
|
||||||
def take_picture(self, image_file):
|
|
||||||
"""
|
"""
|
||||||
Take a picture.
|
Take a picture.
|
||||||
|
|
||||||
:param image_file: Path where the output image will be stored.
|
:param image_file: Path where the output image will be stored.
|
||||||
:type image_file: str
|
:type image_file: str
|
||||||
|
|
||||||
|
:param preview: Show a preview before taking the picture (default: False)
|
||||||
|
:type preview: bool
|
||||||
|
|
||||||
|
:param warmup_time: Time before taking the picture (default: 2 seconds)
|
||||||
|
:type warmup_time: float
|
||||||
|
|
||||||
|
:param resize: Set if you want to resize the picture to a new format
|
||||||
|
:type resize: list or tuple (with two elements)
|
||||||
|
|
||||||
|
:param opts: Extra options to pass to the camera (see
|
||||||
|
https://www.raspberrypi.org/documentation/usage/camera/python/README.md)
|
||||||
|
|
||||||
|
:return: dict::
|
||||||
|
|
||||||
|
{"image_file": path_to_the_image}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
camera = get_backend('camera.pi')
|
|
||||||
camera.send_camera_action(camera.CameraAction.TAKE_PICTURE, image_file=image_file)
|
camera = self._get_camera(**opts)
|
||||||
|
image_file = os.path.abspath(os.path.expanduser(image_file))
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
camera.start_preview()
|
||||||
|
|
||||||
|
if warmup_time:
|
||||||
|
time.sleep(warmup_time)
|
||||||
|
|
||||||
|
capture_opts = {}
|
||||||
|
if resize:
|
||||||
|
capture_opts['resize'] = tuple(resize)
|
||||||
|
|
||||||
|
camera.capture(image_file, **capture_opts)
|
||||||
return {'image_file': image_file}
|
return {'image_file': image_file}
|
||||||
|
|
||||||
|
@action
|
||||||
|
def capture_sequence(self, n_images, directory, name_format='image_%04d.jpg', preview=False, warmup_time=2,
|
||||||
|
resize=None, **opts):
|
||||||
|
"""
|
||||||
|
Capture a sequence of images
|
||||||
|
|
||||||
|
:param n_images: Number of images to capture
|
||||||
|
:type n_images: int
|
||||||
|
|
||||||
|
:param directory: Path where the images will be stored
|
||||||
|
:type directory: str
|
||||||
|
|
||||||
|
:param name_format: Format for the name of the stored images. Use %d or any other format string for representing
|
||||||
|
the image index (default: image_%04d.jpg)
|
||||||
|
:type name_format: str
|
||||||
|
|
||||||
|
:param preview: Show a preview before taking the picture (default: False)
|
||||||
|
:type preview: bool
|
||||||
|
|
||||||
|
:param warmup_time: Time before taking the picture (default: 2 seconds)
|
||||||
|
:type warmup_time: float
|
||||||
|
|
||||||
|
:param resize: Set if you want to resize the picture to a new format
|
||||||
|
:type resize: list or tuple (with two elements)
|
||||||
|
|
||||||
|
:param opts: Extra options to pass to the camera (see
|
||||||
|
https://www.raspberrypi.org/documentation/usage/camera/python/README.md)
|
||||||
|
|
||||||
|
:return: dict::
|
||||||
|
|
||||||
|
{"image_files": [list of captured images]}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
camera = self._get_camera(**opts)
|
||||||
|
directory = os.path.abspath(os.path.expanduser(directory))
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
camera.start_preview()
|
||||||
|
|
||||||
|
if warmup_time:
|
||||||
|
time.sleep(warmup_time)
|
||||||
|
camera.exposure_mode = 'off'
|
||||||
|
|
||||||
|
camera.shutter_speed = camera.exposure_speed
|
||||||
|
g = camera.awb_gains
|
||||||
|
camera.awb_mode = 'off'
|
||||||
|
camera.awb_gains = g
|
||||||
|
capture_opts = {}
|
||||||
|
|
||||||
|
if resize:
|
||||||
|
capture_opts['resize'] = tuple(resize)
|
||||||
|
|
||||||
|
images = [os.path.join(directory, name_format % str(i+1)) for i in range(0, n_images)]
|
||||||
|
camera.capture_sequence(images, **capture_opts)
|
||||||
|
return {'image_files': images}
|
||||||
|
|
||||||
|
@action
|
||||||
|
def start_time_lapse(self, directory, n_images=None, interval=0, warmup_time=2,
|
||||||
|
resize=None, **opts):
|
||||||
|
"""
|
||||||
|
Start a time lapse capture
|
||||||
|
|
||||||
|
:param directory: Path where the images will be stored
|
||||||
|
:type directory: str
|
||||||
|
|
||||||
|
:param n_images: Number of images to capture (default: None, capture until stop_time_lapse)
|
||||||
|
:type n_images: int
|
||||||
|
|
||||||
|
:param interval: Interval in seconds between two pictures (default: 0)
|
||||||
|
:type interval: float
|
||||||
|
|
||||||
|
:param warmup_time: Time before taking the picture (default: 2 seconds)
|
||||||
|
:type warmup_time: float
|
||||||
|
|
||||||
|
:param resize: Set if you want to resize the picture to a new format
|
||||||
|
:type resize: list or tuple (with two elements)
|
||||||
|
|
||||||
|
:param opts: Extra options to pass to the camera (see
|
||||||
|
https://www.raspberrypi.org/documentation/usage/camera/python/README.md)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._time_lapse_thread:
|
||||||
|
return None, 'A time lapse thread is already running'
|
||||||
|
|
||||||
|
camera = self._get_camera(**opts)
|
||||||
|
directory = os.path.abspath(os.path.expanduser(directory))
|
||||||
|
|
||||||
|
if warmup_time:
|
||||||
|
time.sleep(warmup_time)
|
||||||
|
|
||||||
|
capture_opts = {}
|
||||||
|
if resize:
|
||||||
|
capture_opts['resize'] = tuple(resize)
|
||||||
|
|
||||||
|
def capture_thread():
|
||||||
|
self.logger.info('Starting time lapse recording to directory {}'.format(directory))
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
for filename in camera.capture_continuous(os.path.join(directory, 'image_{counter:04d}.jpg')):
|
||||||
|
i += 1
|
||||||
|
self.logger.debug('Captured {}'.format(filename))
|
||||||
|
|
||||||
|
if i >= n_images:
|
||||||
|
break
|
||||||
|
|
||||||
|
self._time_lapse_stop_condition.acquire()
|
||||||
|
should_stop = self._time_lapse_stop_condition.wait(timeout=interval)
|
||||||
|
self._time_lapse_stop_condition.release()
|
||||||
|
|
||||||
|
if should_stop:
|
||||||
|
break
|
||||||
|
|
||||||
|
self._time_lapse_thread = None
|
||||||
|
self.logger.info('Stopped time lapse recording')
|
||||||
|
|
||||||
|
self._time_lapse_thread = threading.Thread(target=capture_thread)
|
||||||
|
self._time_lapse_thread.start()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def stop_time_lapse(self):
|
||||||
|
"""
|
||||||
|
Stop a time lapse sequence if it's running
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self._time_lapse_thread:
|
||||||
|
self.logger.info('No time lapse thread is running')
|
||||||
|
return
|
||||||
|
|
||||||
|
self._time_lapse_stop_condition.acquire()
|
||||||
|
self._time_lapse_stop_condition.notify_all()
|
||||||
|
self._time_lapse_stop_condition.release()
|
||||||
|
|
||||||
|
if self._time_lapse_thread:
|
||||||
|
self._time_lapse_thread.join()
|
||||||
|
|
||||||
|
# noinspection PyMethodOverriding
|
||||||
|
@action
|
||||||
|
def start_recording(self, video_file=None, directory=None, name_format='video_%04d.h264', duration=None,
|
||||||
|
split_duration=None, **opts):
|
||||||
|
"""
|
||||||
|
Start recording to a video file or to multiple video files
|
||||||
|
|
||||||
|
:param video_file: Path of the video file, if you want to keep the recording all in one file
|
||||||
|
:type video_file: str
|
||||||
|
|
||||||
|
:param directory: Path of the directory that will store the video files, if you want to split the recording
|
||||||
|
on multiple files. Note that you need to specify either video_file (to save the recording to one single
|
||||||
|
file) or directory (to split the recording on multiple files)
|
||||||
|
:type directory: str
|
||||||
|
|
||||||
|
:param name_format: If you're splitting the recording to multiple files, then you can specify the name format
|
||||||
|
for those files (default: 'video_%04d.h264')
|
||||||
|
on multiple files. Note that you need to specify either video_file (to save the recording to one single
|
||||||
|
file) or directory (to split the recording on multiple files)
|
||||||
|
:type name_format: str
|
||||||
|
|
||||||
|
:param duration: Video duration in seconds (default: None, record until stop_recording is called)
|
||||||
|
:type duration: float
|
||||||
|
|
||||||
|
:param split_duration: If you're splitting the recording to multiple files, then you should specify how long
|
||||||
|
each video should be in seconds
|
||||||
|
:type split_duration: float
|
||||||
|
|
||||||
|
:param opts: Extra options to pass to the camera (see
|
||||||
|
https://www.raspberrypi.org/documentation/usage/camera/python/README.md)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._recording_thread:
|
||||||
|
return None, 'A recording thread is already running'
|
||||||
|
|
||||||
|
multifile = not video_file
|
||||||
|
if multifile and not (directory and split_duration):
|
||||||
|
return None, 'No video_file specified for single file capture and no directory/split_duration specified for multifile split'
|
||||||
|
|
||||||
|
camera = self._get_camera(**opts)
|
||||||
|
video_file = os.path.abspath(os.path.expanduser(video_file))
|
||||||
|
|
||||||
|
def recording_thread():
|
||||||
|
if not multifile:
|
||||||
|
self.logger.info('Starting recording to video file {}'.format(video_file))
|
||||||
|
camera.start_recording(video_file)
|
||||||
|
self._recording_stop_condition.acquire()
|
||||||
|
self._recording_stop_condition.wait(timeout=duration)
|
||||||
|
self._recording_stop_condition.release()
|
||||||
|
self.logger.info('Video recorded to {}'.format(video_file))
|
||||||
|
else:
|
||||||
|
self.logger.info('Starting recording video files to directory {}'.format(directory))
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
start_time = time.time()
|
||||||
|
end_time = None
|
||||||
|
if duration is not None:
|
||||||
|
end_time = time.time() + duration
|
||||||
|
|
||||||
|
camera.start_recording(name_format % i)
|
||||||
|
self._recording_stop_condition.acquire()
|
||||||
|
self._recording_stop_condition.wait(timeout=split_duration)
|
||||||
|
self._recording_stop_condition.release()
|
||||||
|
self.logger.info('Video file {} saved'.format(name_format % i))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
i += 1
|
||||||
|
remaining_duration = None
|
||||||
|
|
||||||
|
if end_time:
|
||||||
|
remaining_duration = end_time - time.time()
|
||||||
|
split_duration = min(split_duration, remaining_duration)
|
||||||
|
if remaining_duration <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
camera.split_recording(name_format % i)
|
||||||
|
self._recording_stop_condition.acquire()
|
||||||
|
should_stop = self._recording_stop_condition.wait(timeout=split_duration)
|
||||||
|
self._recording_stop_condition.release()
|
||||||
|
self.logger.info('Video file {} saved'.format(name_format % i))
|
||||||
|
|
||||||
|
if should_stop:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.camera.stop_recording()
|
||||||
|
self._recording_thread = None
|
||||||
|
|
||||||
|
self._recording_thread = threading.Thread(target=recording_thread)
|
||||||
|
self._recording_thread.start()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def stop_recording(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Stop a camera recording
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self._recording_thread:
|
||||||
|
self.logger.info('No recording thread is running')
|
||||||
|
return
|
||||||
|
|
||||||
|
self._recording_stop_condition.acquire()
|
||||||
|
self._recording_stop_condition.notify_all()
|
||||||
|
self._recording_stop_condition.release()
|
||||||
|
|
||||||
|
if self._recording_thread:
|
||||||
|
self._recording_thread.join()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def start_streaming(self, listen_port=5000, duration=None, format='h264' **opts):
|
||||||
|
"""
|
||||||
|
Start recording to a network stream
|
||||||
|
|
||||||
|
:param listen_port: TCP listen port (default: 5000)
|
||||||
|
:type listen_port: int
|
||||||
|
|
||||||
|
:param duration: Stream duration in seconds (default: None, record until stop_streaming is called)
|
||||||
|
:type duration: float
|
||||||
|
|
||||||
|
:param format: Video stream format (default: h264)
|
||||||
|
:type format: str
|
||||||
|
|
||||||
|
:param opts: Extra options to pass to the camera (see
|
||||||
|
https://www.raspberrypi.org/documentation/usage/camera/python/README.md)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._streaming_thread:
|
||||||
|
return None, 'A streaming thread is already running'
|
||||||
|
|
||||||
|
camera = self._get_camera(**opts)
|
||||||
|
server_socket = socket.socket()
|
||||||
|
server_socket.bind(('0.0.0.0', listen_port))
|
||||||
|
server_socket.listen(0)
|
||||||
|
|
||||||
|
def streaming_thread():
|
||||||
|
self.logger.info('Starting streaming on listen port {}'.format(listen_port))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
socket = server_socket.accept()[0].makefile('wb')
|
||||||
|
self.logger.info('Accepted client connection from {}'.format(socket.getpeername()))
|
||||||
|
|
||||||
|
try:
|
||||||
|
camera.start_recording(socket, format=format)
|
||||||
|
self._streaming_stop_condition.acquire()
|
||||||
|
self._streaming_stop_condition.wait(timeout=duration)
|
||||||
|
self._streaming_stop_condition.release()
|
||||||
|
break
|
||||||
|
except ConnectionError:
|
||||||
|
self.logger.info('Client closed connection')
|
||||||
|
|
||||||
|
try:
|
||||||
|
socket.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.camera.stop_recording()
|
||||||
|
self._streaming_thread = None
|
||||||
|
self.logger.info('Stopped camera stream')
|
||||||
|
|
||||||
|
self._streaming_thread = threading.Thread(target=streaming_thread)
|
||||||
|
self._streaming_thread.start()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def stop_streaming(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Stop a camera streaming session
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self._streaming_thread:
|
||||||
|
self.logger.info('No recording thread is running')
|
||||||
|
return
|
||||||
|
|
||||||
|
self._streaming_stop_condition.acquire()
|
||||||
|
self._streaming_stop_condition.notify_all()
|
||||||
|
self._streaming_stop_condition.release()
|
||||||
|
|
||||||
|
if self._streaming_thread:
|
||||||
|
self._streaming_thread.join()
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue