diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1d4726 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +photobooth.cfg +photobooth/__pycache__ diff --git a/photobooth/Config.py b/photobooth/Config.py new file mode 100644 index 0000000..2e3694c --- /dev/null +++ b/photobooth/Config.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import configparser, os + + +class Config: + + def __init__(self, filename): + + self._filename = filename + + self._cfg = configparser.ConfigParser() + self.defaults() + self.read() + + + @property + def filename(self): + + return self._filename + + + @filename.setter + def filename(self, value): + + self._filename = value + + + def defaults(self): + + self._cfg.read(os.path.join(os.path.dirname(__file__), 'defaults.cfg')) + + + def read(self): + + self._cfg.read(self._filename) + + + def write(self): + + with open(self._filename, 'w') as configfile: + self._cfg.write(configfile) + + + def get(self, section, key): + + return self._cfg[section][key] + + + def getInt(self, section, key): + + return self._cfg.getint(section, key) + + + def getFloat(self, section, key): + + return self._cfg.getfloat(section, key) + + + def getBool(self, section, key): + + return self._cfg.getboolean(section, key) + + + def set(self, section, key, value): + + self._cfg[section][key] = value diff --git a/photobooth/Photobooth.py b/photobooth/Photobooth.py new file mode 100644 index 0000000..ada108a --- /dev/null +++ b/photobooth/Photobooth.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import Config, PyQt5Gui + +def main(argv): + + config = Config.Config('photobooth.cfg') + return PyQt5Gui.run(argv, config) diff --git a/photobooth/PyQt5Gui.py b/photobooth/PyQt5Gui.py new file mode 100644 index 0000000..6c3b8a3 --- /dev/null +++ b/photobooth/PyQt5Gui.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLayout, QLineEdit, QMainWindow, QMessageBox, QPushButton, QVBoxLayout) + +class PyQt5Gui(QMainWindow): + + def __init__(self): + + super().__init__() + + self.initUI() + + + def initUI(self): + + global cfg + + self.showStart() + self.setWindowTitle('Photobooth') + + if cfg.getBool('Gui', 'fullscreen'): + self.showFullScreen() + else: + self.resize(cfg.getInt('Gui', 'width'), + cfg.getInt('Gui', 'height')) + self.show() + + + def showStart(self): + + content = PyQt5Start(self) + self.setCentralWidget(content) + + + def showSettings(self): + + content = PyQt5Settings(self) + self.setCentralWidget(content) + + + def closeEvent(self, e): + + reply = QMessageBox.question(self, 'Confirmation', "Quit Photobooth?", + QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + + if reply == QMessageBox.Yes: + e.accept() + else: + e.ignore() + + + def keyPressEvent(self, e): + pass + # if self.photobooth.status == Status.idle: + # if e.key() == Qt.Key_Escape: + # self.showStart() + # elif e.key() == Qt.Key_Space: + # self.centralWidget().trigger() + + + + +class PyQt5Start(QFrame): + + def __init__(self, parent): + + super().__init__(parent) + + self.initFrame(parent) + + + def initFrame(self, parent): + + grid = QGridLayout() + grid.setSpacing(100) + self.setLayout(grid) + + btnStart = QPushButton('Start Photobooth') + btnStart.resize(btnStart.sizeHint()) + # btnStart.clicked.connect(parent.showPhotobooth) + grid.addWidget(btnStart, 0, 0) + + btnSettings = QPushButton('Settings') + btnSettings.resize(btnSettings.sizeHint()) + btnSettings.clicked.connect(parent.showSettings) + grid.addWidget(btnSettings, 0, 1) + + btnQuit = QPushButton('Quit') + btnQuit.resize(btnQuit.sizeHint()) + btnQuit.clicked.connect(parent.close) + grid.addWidget(btnQuit, 0, 2) + + + +class PyQt5Settings(QFrame): + + def __init__(self, parent): + + super().__init__(parent) + + self._parent = parent + self.initFrame() + + + def initFrame(self): + + self._value_widgets = {} + + grid = QGridLayout() + grid.addWidget(self.createGuiSettings(), 0, 0) + grid.addWidget(self.createGpioSettings(), 1, 0) + grid.addWidget(self.createCameraSettings(), 0, 1) + + layout = QVBoxLayout() + layout.addLayout(grid) + layout.addStretch(1) + layout.addWidget(self.createButtons()) + self.setLayout(layout) + + + def createGuiSettings(self): + + global cfg + + self._value_widgets['Gui'] = {} + self._value_widgets['Gui']['fullscreen'] = QCheckBox('Enable fullscreen') + if cfg.getBool('Gui', 'fullscreen'): + self._value_widgets['Gui']['fullscreen'].toggle() + self._value_widgets['Gui']['width'] = QLineEdit(cfg.get('Gui', 'width')) + self._value_widgets['Gui']['height'] = QLineEdit(cfg.get('Gui', 'height')) + + layout = QFormLayout() + layout.addRow(self._value_widgets['Gui']['fullscreen']) + layout.addRow(QLabel('Width:'), self._value_widgets['Gui']['width']) + layout.addRow(QLabel('Height:'), self._value_widgets['Gui']['height']) + + widget = QGroupBox('Interface settings (restart required)') + widget.setLayout(layout) + return widget + + + def createGpioSettings(self): + + global cfg + + self._value_widgets['Gpio'] = {} + self._value_widgets['Gpio']['enable'] = QCheckBox('Enable GPIO (restart required)') + if cfg.getBool('Gpio', 'enable'): + self._value_widgets['Gpio']['enable'].toggle() + self._value_widgets['Gpio']['exit_channel'] = QLineEdit(cfg.get('Gpio', 'exit_channel')) + self._value_widgets['Gpio']['trigger_channel'] = QLineEdit(cfg.get('Gpio', 'trigger_channel')) + self._value_widgets['Gpio']['lamp_channel'] = QLineEdit(cfg.get('Gpio', 'lamp_channel')) + + layout = QFormLayout() + layout.addRow(self._value_widgets['Gpio']['enable']) + layout.addRow(QLabel('Exit channel:'), self._value_widgets['Gpio']['exit_channel']) + layout.addRow(QLabel('Trigger channel'), self._value_widgets['Gpio']['trigger_channel']) + layout.addRow(QLabel('Lamp channel'), self._value_widgets['Gpio']['lamp_channel']) + + widget = QGroupBox('GPIO settings') + widget.setLayout(layout) + return widget + + + def createCameraSettings(self): + + global cfg + + wrapper = QComboBox() + wrapper.addItem('command line') + wrapper.addItem('piggyphoto') + wrapper.addItem('gphoto2-cffi') + + 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) + + self._value_widgets['Camera'] = {} + self._value_widgets['Camera']['gphoto2_wrapper'] = wrapper + + layout = QFormLayout() + layout.addRow(QLabel('gPhoto2 wrapper:'), self._value_widgets['Camera']['gphoto2_wrapper']) + + widget = QGroupBox('Camera settings') + widget.setLayout(layout) + return widget + + + def createButtons(self): + + layout = QHBoxLayout() + layout.addStretch(1) + + btnSave = QPushButton('Save') + btnSave.resize(btnSave.sizeHint()) + btnSave.clicked.connect(self.storeConfig) + layout.addWidget(btnSave) + + btnCancel = QPushButton('Cancel') + btnCancel.resize(btnCancel.sizeHint()) + btnCancel.clicked.connect(self._parent.showStart) + layout.addWidget(btnCancel) + + btnRestore = QPushButton('Restore defaults') + btnRestore.resize(btnRestore.sizeHint()) + btnRestore.clicked.connect(self.restoreDefaults) + layout.addWidget(btnRestore) + + widget = QGroupBox() + widget.setLayout(layout) + return widget + + + def storeConfig(self): + + global cfg + + cfg.set('Gui', 'fullscreen', str(self._value_widgets['Gui']['fullscreen'].isChecked())) + cfg.set('Gui', 'width', self._value_widgets['Gui']['width'].text()) + cfg.set('Gui', 'height', self._value_widgets['Gui']['height'].text()) + + cfg.set('Gpio', 'enable', str(self._value_widgets['Gpio']['enable'].isChecked())) + cfg.set('Gpio', 'exit_channel', self._value_widgets['Gpio']['exit_channel'].text()) + cfg.set('Gpio', 'trigger_channel', self._value_widgets['Gpio']['trigger_channel'].text()) + cfg.set('Gpio', 'lamp_channel', self._value_widgets['Gpio']['lamp_channel'].text()) + + wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ] + cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()]) + + cfg.write() + self._parent.showStart() + + + def restoreDefaults(self): + + global cfg + + cfg.defaults() + self._parent.showSettings() + + +def run(argv, config): + + global cfg + cfg = config + + app = QApplication(argv) + p = PyQt5Gui() + return app.exec_() diff --git a/photobooth/__init__.py b/photobooth/__init__.py new file mode 100644 index 0000000..3c3bd52 --- /dev/null +++ b/photobooth/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import sys, Photobooth + +if __name__ == "__main__": + sys.exit(Photobooth.main(sys.argv)) + \ No newline at end of file diff --git a/photobooth/__main__.py b/photobooth/__main__.py new file mode 100644 index 0000000..19f672a --- /dev/null +++ b/photobooth/__main__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import sys, Photobooth + +sys.exit(Photobooth.main(sys.argv)) diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg new file mode 100644 index 0000000..f720b88 --- /dev/null +++ b/photobooth/defaults.cfg @@ -0,0 +1,21 @@ +[Gui] +# Start Photobooth in fullscreen mode (True/False) +fullscreen = False +# Width of Photobooth (if not fullscreen) +width = 1024 +# Height of Photobooth (if not fullscreen) +height = 600 + +[Camera] +# Wrapper to use gPhoto2 (gphoto2-cffi, piggyphoto, commandline) +gphoto2_wrapper = commandline + +[Gpio] +# Enable use of GPIO (True/False) +enable = True +# Pin 18 (Channel 24) lets you return to start screen +exit_channel = 24 +# Pin 16 (Channel 23) triggers capturing pictures +trigger_channel = 23 +# Pin 7 (Channel 4) switches the lamp on and off +lamp_channel = 4