diff --git a/INSTALL.md b/INSTALL.md index 4b600f2..8c20c53 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,3 +1,8 @@ pip install pyqt5 pip install opencv-python -pip install Pillow \ No newline at end of file +pip install Pillow +apt install gphoto2 libgphoto2-dev + +pip install gphoto2 +-or- +pip install gphoto2-cffi \ No newline at end of file diff --git a/photobooth/Camera.py b/photobooth/Camera.py index abe2029..891823f 100644 --- a/photobooth/Camera.py +++ b/photobooth/Camera.py @@ -8,6 +8,22 @@ class Camera: self.hasPreview = False self.hasIdle = False + + def __enter__(self): + + return self + + + def __exit__(self, exc_type, exc_value, traceback): + + self.cleanup() + + + def cleanup(self): + + pass + + @property def hasPreview(self): diff --git a/photobooth/CameraGphoto2.py b/photobooth/CameraGphoto2.py new file mode 100644 index 0000000..8fee51a --- /dev/null +++ b/photobooth/CameraGphoto2.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import io, logging + +from PIL import Image + +import gphoto2 as gp + +from Camera import Camera + + +class CameraGphoto2(Camera): + + def __init__(self): + + super().__init__() + + self.hasPreview = True + self.hasIdle = False + self._isActive = False + + self._setupLogging() + self._setupCamera() + + + def cleanup(self): + + self._cap.exit(self._ctxt) + # self.setIdle() + + + def _setupLogging(self): + + logging.basicConfig( + format='%(levelname)s: %(name)s: %(message)s', + level=logging.ERROR) + gp.check_result(gp.use_python_logging()) + + + def _setupCamera(self): + + self._ctxt = gp.Context() + self._cap = gp.Camera() + self._cap.init(self._ctxt) + + self._printSummary() + + + # get configuration tree + config = gp.check_result(gp.gp_camera_get_config(self._cap)) + + # find the image format config item + OK, image_format = gp.gp_widget_get_child_by_name(config, 'imageformat') + if OK >= gp.GP_OK: + # get current setting + value = gp.check_result(gp.gp_widget_get_value(image_format)) + # make sure it's not raw + if 'raw' in value.lower(): + raise RuntimeError('Camera file format is set to RAW') + + print(config) + + + def _printSummary(self): + + # self.setActive() + + text = self._cap.get_summary(self._ctxt) + print('Summary') + print('=======') + print(str(text)) + + # self.setIdle() + + + # def setActive(self): + + # self._cap.init(self._ctxt) + # if not self._isActive: + # self._cap.init(self._ctxt) + # self._isActive = True + + + # def setIdle(self): + + # self._cap.exit(self._ctxt) + # if self._isActive: + # self._cap.exit(self._ctxt) + # self._isActive = False + + + def getPreview(self): + + # self.setActive() + camera_file = self._cap.capture_preview() #gp.check_result(gp.gp_camera_capture_preview(self._cap)) + file_data = camera_file.get_data_and_size() # gp.check_result(gp.gp_file_get_data_and_size(camera_file)) + return Image.open(io.BytesIO(file_data)) + + + def getPicture(self): + + # self.setActive() + file_path = self._cap.capture(gp.GP_CAPTURE_IMAGE) + camera_file = self._cap.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) + file_data = camera_file.get_data_and_size() + return Image.open(io.BytesIO(file_data)) + diff --git a/photobooth/CameraGphoto2Cffi.py b/photobooth/CameraGphoto2Cffi.py new file mode 100644 index 0000000..ef713a8 --- /dev/null +++ b/photobooth/CameraGphoto2Cffi.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import io + +from PIL import Image + +import gphoto2cffi as gp + +from Camera import Camera + + +class CameraGphoto2Cffi(Camera): + + def __init__(self): + + super().__init__() + + self.hasPreview = True + self.hasIdle = True + + self._setupCamera() + + + def _setupCamera(self): + + self._cap = gp.Camera() + print(self._cap.supported_operations) + + if 'raw' in self._cap.config['imgsettings']['imageformat'].value.lower(): + raise RuntimeError('Camera file format is set to RAW') + + + def setActive(self): + + self._cap._get_config()['actions']['viewfinder'].set(True) + + + def setIdle(self): + + self._cap._get_config()['actions']['viewfinder'].set(False) + + + def getPreview(self): + + return Image.open(io.BytesIO(self._cap.get_preview())) + + + def getPicture(self): + + return Image.open(io.BytesIO(self._cap.capture())) + diff --git a/photobooth/CameraGphoto2CommandLine.py b/photobooth/CameraGphoto2CommandLine.py new file mode 100644 index 0000000..cb430eb --- /dev/null +++ b/photobooth/CameraGphoto2CommandLine.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Camera import Camera + +from PIL import Image +import os, subprocess + +class CameraGphoto2CommandLine(Camera): + + def __init__(self): + + super().__init__() + + self.hasPreview = False + self.hasIdle = False + + if os.access('/dev/shm', os.W_OK): + self._tmp_filename = '/dev/shm/photobooth.jpg' + else: + self._tmp_filename = '/tmp/photobooth.jpg' + + self.setActive() + + + def setActive(self): + + print(self._callGphoto('-a', '/dev/null')) + + + def getPicture(self): + + self._callGphoto('--capture-image-and-download', self._tmp_filename) + return Image.open(self._tmp_filename) + + + def _callGphoto(self, action, filename): + + cmd = 'gphoto2 --force-overwrite --quiet ' + action + ' --filename ' + filename + return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) diff --git a/photobooth/Photobooth.py b/photobooth/Photobooth.py index 73b08d2..4188e09 100644 --- a/photobooth/Photobooth.py +++ b/photobooth/Photobooth.py @@ -9,8 +9,6 @@ from PictureDimensions import PictureDimensions import Gui from PyQt5Gui import PyQt5Gui -from CameraOpenCV import CameraOpenCV as Camera - from PIL import Image, ImageOps from multiprocessing import Pipe, Process @@ -21,11 +19,11 @@ from time import time, sleep, localtime, strftime class Photobooth: - def __init__(self, config): + def __init__(self, config, camera): picture_basename = strftime(config.get('Picture', 'basename'), localtime()) - self._cap = Camera() + self._cap = camera self._pic_dims = PictureDimensions(config, self._cap.getPicture().size) self._pic_list = PictureList(picture_basename) @@ -117,7 +115,9 @@ class Photobooth: def showCounterNoPreview(self): for i in range(self.countdownTime): - self._send.send( Gui.PreviewState(str(i)) ) + self._send.send( Gui.PreviewState( + message = str(i), + picture = Image.new('RGB', (1,1), 'white') ) ) sleep(1) @@ -165,8 +165,20 @@ class Photobooth: def main_photobooth(config, send, recv): - photobooth = Photobooth(config) - return photobooth.run(send, recv) + if config.get('Camera', 'module') == 'python-gphoto2': + from CameraGphoto2 import CameraGphoto2 as Camera + elif config.get('Camera', 'module') == 'gphoto2-cffi': + from CameraGphoto2Cffi import CameraGphoto2Cffi as Camera + elif config.get('Camera', 'module') == 'gphoto2-commandline': + from CameraGphoto2CommandLine import CameraGphoto2CommandLine as Camera + elif config.get('Camera', 'module') == 'opencv': + from CameraOpenCV import CameraOpenCV as Camera + else: + raise ImportError('Unknown camera module "' + config.get('Camera', 'module') + '"') + + with Camera() as cap: + photobooth = Photobooth(config, cap) + return photobooth.run(send, recv) def run(argv): diff --git a/photobooth/PyQt5Gui.py b/photobooth/PyQt5Gui.py index 983f4f5..054b53c 100644 --- a/photobooth/PyQt5Gui.py +++ b/photobooth/PyQt5Gui.py @@ -324,26 +324,27 @@ class PyQt5Settings(QFrame): global cfg - wrapper = QComboBox() - wrapper.addItem('command line') - wrapper.addItem('piggyphoto') - wrapper.addItem('gphoto2-cffi') + self._camera_modules = [ + ('gphoto2-commandline', 'gphoto2 via command line'), + # ('piggyphoto', 'piggyphoto'), + ('gphoto2-cffi', 'gphoto2-cffi'), + ('python-gphoto2', 'python-gphoto2'), + ('opencv', 'OpenCV'), + ('', 'none') ] - current_wrapper = cfg.get('Camera', 'gphoto2_wrapper') - if current_wrapper == 'commandline': - wrapper.setCurrentIndex(0) - elif current_wrapper == 'piggyphoto': - wrapper.setCurrentIndex(1) - elif current_wrapper == 'gphoto2-cffi': - wrapper.setCurrentIndex(2) - else: - wrapper.setCurrentIndex(-1) + wrapper = QComboBox() + for m in self._camera_modules: + wrapper.addItem(m[1]) + + current_wrapper = cfg.get('Camera', 'module') + idx = [x for x, m in enumerate(self._camera_modules) if m[0] == current_wrapper] + wrapper.setCurrentIndex(idx[0] if len(idx) > 0 else -1) self._value_widgets['Camera'] = {} - self._value_widgets['Camera']['gphoto2_wrapper'] = wrapper + self._value_widgets['Camera']['module'] = wrapper layout = QFormLayout() - layout.addRow(QLabel('gPhoto2 wrapper:'), self._value_widgets['Camera']['gphoto2_wrapper']) + layout.addRow(QLabel('Camera module:'), self._value_widgets['Camera']['module']) widget = QGroupBox('Camera settings') widget.setLayout(layout) @@ -467,8 +468,9 @@ class PyQt5Settings(QFrame): cfg.set('Picture', 'min_dist_y', self._value_widgets['Picture']['min_dist_y'].text()) cfg.set('Picture', 'basename', self._value_widgets['Picture']['basename'].text()) - wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ] - cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()]) + # wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi', '' ] + # cfg.set('Camera', 'module', wrapper_idx2val[self._value_widgets['Camera']['module'].currentIndex()]) + cfg.set('Camera', 'module', self._camera_modules[self._value_widgets['Camera']['module'].currentIndex()][0]) cfg.write() self._gui.restart() diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index f665272..7f034dc 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -7,8 +7,8 @@ width = 1024 height = 600 [Camera] -# Wrapper to use gPhoto2 (gphoto2-cffi, piggyphoto, commandline) -gphoto2_wrapper = commandline +# Camera module to use (python-gphoto2, gphoto2-cffi, gphoto2-commandline, opencv) +module = python-gphoto2 [Gpio] # Enable use of GPIO (True/False)