Save several resolutions of images.
authorBenjamin Braatz <bb@bbraatz.eu>
Wed, 4 Aug 2021 00:32:34 +0000 (02:32 +0200)
committerBenjamin Braatz <bb@bbraatz.eu>
Wed, 4 Aug 2021 00:32:34 +0000 (02:32 +0200)
controlpi_plugins/camera.py
setup.py

index ea7e06bf40f202753e7937bfbcaa9187cbcd256c..8c507496aa50c25cf3523e485b9f4cfebf58c194 100644 (file)
@@ -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
index 8dfac7e05042eeaee627f25ce4aa9797fa7a1037..84a6a937241fb5b037ea614b1d2f4a9a8921096d 100644 (file)
--- 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=[