From 41bdebd0025aba60b6d9edca55f7e5ffe5204032 Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Wed, 4 Aug 2021 02:32:34 +0200 Subject: [PATCH] Save several resolutions of images. --- controlpi_plugins/camera.py | 111 +++++++++++++++++++++++++++--------- setup.py | 1 + 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/controlpi_plugins/camera.py b/controlpi_plugins/camera.py index ea7e06b..8c50749 100644 --- a/controlpi_plugins/camera.py +++ b/controlpi_plugins/camera.py @@ -1,23 +1,31 @@ -import aiofiles import aiofiles.os import asyncio import collections +import concurrent.futures import datetime +import functools import io import os -import picamera +import picamera # type: ignore +import PIL # type: ignore from controlpi import BasePlugin, Message, MessageTemplate +from typing import Deque, Tuple + class Camera(BasePlugin): CONF_SCHEMA = {'properties': {'pause': {'type': 'number'}, 'keep': {'type': 'integer'}, 'path': {'type': 'string'}, - 'resolution': {'type': 'string'}, - 'resize': {'type': 'string'}, - 'iso': {'type': 'integer'}}, + 'iso': {'type': 'integer'}, + 'full': {'type': 'array', 'items': {'type': 'integer'}, + 'minItems': 2, 'maxItems': 2}, + 'web': {'type': 'array', 'items': {'type': 'integer'}, + 'minItems': 2, 'maxItems': 2}, + 'thumb': {'type': 'array', 'items': {'type': 'integer'}, + 'minItems': 2, 'maxItems': 2}}, 'required': ['pause', 'keep', 'path']} async def _receive(self, message: Message) -> None: @@ -33,17 +41,20 @@ class Camera(BasePlugin): def process_conf(self) -> None: """Register plugin as bus client.""" - self._images = collections.deque() + self._images: Deque[Tuple[str, str]] = collections.deque() self._capture = False - self._resolution = '1024x768' - if 'resolution' in self.conf: - self._resolution = self.conf['resolution'] - self._resize = '640x480' - if 'resize' in self.conf: - self._resize = self.conf['resize'] self._iso = 200 if 'iso' in self.conf: self._iso = self.conf['iso'] + self._full = (1024, 768) + if 'full' in self.conf: + self._full = self.conf['full'] + self._web = (640, 480) + if 'web' in self.conf: + self._web = self.conf['web'] + self._thumb = (160, 120) + if 'thumb' in self.conf: + self._thumb = self.conf['thumb'] sends = [MessageTemplate({'event': {'const': 'new image'}, 'image': {'type': 'string'}, 'date': {'type': 'string'}}), @@ -59,9 +70,14 @@ class Camera(BasePlugin): async def run(self) -> None: """Run camera.""" + loop = asyncio.get_running_loop() + executor = concurrent.futures.ThreadPoolExecutor() camera = picamera.PiCamera() - camera.resolution = self._resolution + camera.resolution = (2592, 1944) # Maximal resolution of PiCamera + camera.framerate = 15 # Maximal framerate at maximal resolution camera.iso = self._iso + web_path = os.path.join(self.conf['path'], 'web.jpg') + thumb_path = os.path.join(self.conf['path'], 'thumb.jpg') try: while True: if self._capture: @@ -81,28 +97,69 @@ class Camera(BasePlugin): filepath = os.path.join(self.conf['path'], filename) await aiofiles.os.remove(filepath) + timestamp = datetime.datetime.now() + iso_time = timestamp.strftime('%Y-%m-%d %H:%M:%S') + full_name = timestamp.strftime('%Y%m%d%H%M%S%f')+'.jpg' + full_path = os.path.join(self.conf['path'], full_name) stream = io.BytesIO() - camera.capture(stream, 'jpeg', resize=self._resize) - timestamp = datetime.datetime.utcnow() - filename = timestamp.strftime('%Y%m%d%H%M%S%f') + '.jpg' - filedatetime = timestamp.strftime('%Y-%m-%d %H:%M:%S') - filepath = os.path.join(self.conf['path'], filename) - async with aiofiles.open(filepath, 'wb') as f: - await f.write(stream.getvalue()) + await loop.run_in_executor( + executor, + functools.partial( + camera.capture, stream, 'jpeg')) + stream.seek(0) + raw_image = await loop.run_in_executor( + executor, + functools.partial(PIL.Image.open, stream)) stream.close() - self._images.append((filename, filedatetime)) + full_image = await loop.run_in_executor( + executor, + functools.partial( + raw_image.resize, + self._full, + PIL.Image.ANTIALIAS)) + await loop.run_in_executor( + executor, + functools.partial( + full_image.save, full_path)) + web_image = await loop.run_in_executor( + executor, + functools.partial( + raw_image.resize, + self._web, + PIL.Image.ANTIALIAS)) + await loop.run_in_executor( + executor, + functools.partial( + web_image.save, web_path)) + thumb_image = await loop.run_in_executor( + executor, + functools.partial( + raw_image.resize, + self._thumb, + PIL.Image.ANTIALIAS)) + await loop.run_in_executor( + executor, + functools.partial( + thumb_image.save, thumb_path)) + self._images.append((full_name, iso_time)) await self.bus.send(Message(self.name, {'event': 'new image', - 'image': filename, - 'date': filedatetime})) + 'image': full_name, + 'date': iso_time})) + await asyncio.sleep(self.conf['pause']) camera.stop_preview() await asyncio.sleep(2) except asyncio.CancelledError: camera.stop_preview() camera.close() while len(self._images) > 0: - filename = self._images.popleft()[0] - filepath = os.path.join(self.conf['path'], - filename) - os.remove(filepath) + file_name = self._images.popleft()[0] + file_path = os.path.join(self.conf['path'], + filename) + os.remove(file_path) + if os.path.isfile(web_path): + os.remove(web_path) + if os.path.isfile(thumb_path): + os.remove(thumb_path) + executor.shutdown(True) raise diff --git a/setup.py b/setup.py index 8dfac7e..84a6a93 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ setuptools.setup( install_requires=[ "aiofiles", "picamera", + "Pillow", "controlpi @ git+git://git.graph-it.com/graphit/controlpi.git@master", ], classifiers=[ -- 2.34.1