From 5e2739bec8f442587e81f18789ad3fd5361269a1 Mon Sep 17 00:00:00 2001 From: Balthasar Reuter Date: Mon, 14 May 2018 21:13:56 +0200 Subject: [PATCH] Logging added --- photobooth/Config.py | 8 +++- photobooth/Photobooth.py | 46 +++++++++---------- photobooth/PictureList.py | 10 ++-- photobooth/Worker.py | 23 +++++----- photobooth/camera/CameraGphoto2.py | 10 ++-- photobooth/camera/CameraGphoto2Cffi.py | 7 +-- photobooth/camera/CameraGphoto2CommandLine.py | 6 ++- photobooth/camera/CameraOpenCV.py | 4 ++ photobooth/gui/GuiPostprocess.py | 19 +++++--- photobooth/gui/GuiState.py | 20 +++++++- photobooth/gui/PyQt5Gui.py | 10 ++-- photobooth/main.py | 26 ++++++++--- photobooth/printer/PrinterPyQt5.py | 8 +++- 13 files changed, 122 insertions(+), 75 deletions(-) diff --git a/photobooth/Config.py b/photobooth/Config.py index 82fc896..10cc7e6 100644 --- a/photobooth/Config.py +++ b/photobooth/Config.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import configparser, os +import configparser, os, logging class Config: @@ -29,16 +29,20 @@ class Config: def defaults(self): - self._cfg.read(os.path.join(os.path.dirname(__file__), 'defaults.cfg')) + filename = os.path.join(os.path.dirname(__file__), 'defaults.cfg') + logging.info('Reading config file "%s"', filename) + self._cfg.read(filename) def read(self): + logging.info('Reading config file "%s"', self._filename) self._cfg.read(self._filename) def write(self): + logging.info('Writing config file "%s"', self._filename) with open(self._filename, 'w') as configfile: self._cfg.write(configfile) diff --git a/photobooth/Photobooth.py b/photobooth/Photobooth.py index 8de0cbd..191ba44 100644 --- a/photobooth/Photobooth.py +++ b/photobooth/Photobooth.py @@ -1,16 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# from time import time, localtime, strftime +import logging from PIL import Image, ImageOps -from .PictureList import PictureList from .PictureDimensions import PictureDimensions from . import gui -from.Worker import PictureSaver +from .Worker import PictureSaver class TeardownException(Exception): @@ -40,30 +39,35 @@ class Photobooth: self._cap = camera self._pic_dims = PictureDimensions(config, self._cap.getPicture().size) - # picture_basename = strftime(config.get('Picture', 'basename'), localtime()) - # self._pic_list = PictureList(picture_basename) - # self._get_next_filename = self._pic_list.getNext - if ( config.getBool('Photobooth', 'show_preview') and self._cap.hasPreview ): + logging.info('Countdown with preview activated') self._show_counter = self.showCounterPreview else: + logging.info('Countdown without preview activated') self._show_counter = self.showCounterNoPreview def initGpio(self, config): if config.getBool('Gpio', 'enable'): + lamp_pin = config.getInt('Gpio', 'lamp_pin') + trigger_pin = config.getInt('Gpio', 'trigger_pin') + exit_pin = config.getInt('Gpio', 'exit_pin') + + logging.info('GPIO enabled (lamp_pin=%d, trigger_pin=%d, exit_pin=%d)', + lamp_pin, trigger_pin, exit_pin) + from Gpio import Gpio self._gpio = Gpio() - lamp = self._gpio.setLamp(config.getInt('Gpio', 'lamp_pin')) + lamp = self._gpio.setLamp(lamp_pin) self._lampOn = lambda : self._gpio.lampOn(lamp) self._lampOff = lambda : self._gpio.lampOff(lamp) - self._gpio.setButton(config.getInt('Gpio', 'trigger_pin'), self.gpioTrigger) - self._gpio.setButton(config.getInt('Gpio', 'exit_pin'), self.gpioExit) + self._gpio.setButton(triger_pin, self.gpioTrigger) + self._gpio.setButton(exit_pin, self.gpioExit) else: self._lampOn = lambda : None self._lampOff = lambda : None @@ -71,7 +75,7 @@ class Photobooth: def teardown(self): - print('Camera teardown') + logging.info('Teardown of camera') self.triggerOff() self.setCameraIdle() @@ -83,7 +87,7 @@ class Photobooth: try: event_idx = expected.index(str(event)) except ValueError: - print('Photobooth: Unknown event received: ' + str(event)) + logging.error('Unknown event received: %s', str(event)) raise ValueError('Unknown event received', str(event)) return event_idx @@ -94,7 +98,7 @@ class Photobooth: events = ['ack', 'cancel', 'teardown'] if self.recvEvent(events) != 0: - print('Teardown of Photobooth requested') + logging.info('Teardown of camera requested') raise TeardownException() @@ -103,16 +107,10 @@ class Photobooth: events = ['triggered', 'teardown'] if self.recvEvent(events) != 0: - print('Teardown of Photobooth requested') + logging.info('Teardown of camera requested') raise TeardownException() - # @property - # def getNextFilename(self): - - # return self._get_next_filename - - @property def showCounter(self): @@ -140,7 +138,7 @@ class Photobooth: try: self.trigger() except RuntimeError as e: - print('Camera error: ' + str(e)) + logging.error('Camera error: %s', str(e)) self._conn.send( gui.ErrorState('Camera error', str(e)) ) self.recvAck() @@ -175,7 +173,6 @@ class Photobooth: self._conn.send(gui.CountdownState()) self.recvAck() - print('ack received') def showPose(self): @@ -209,11 +206,13 @@ class Photobooth: def enqueueWorkerTasks(self, picture): for task in self._worker_list: - self._queue.put(( task.do, (picture, ) )) + self._queue.put(task.get(picture)) def trigger(self): + logging.info('Photobooth triggered') + self._conn.send(gui.GreeterState()) self.triggerOff() self.setCameraActive() @@ -224,7 +223,6 @@ class Photobooth: self._conn.send(gui.AssembleState()) img = self.assemblePictures(pics) - # img.save(self.getNextFilename(), 'JPEG') self._conn.send(gui.PictureState(img)) self.enqueueWorkerTasks(img) diff --git a/photobooth/PictureList.py b/photobooth/PictureList.py index b9ef563..15eebfb 100644 --- a/photobooth/PictureList.py +++ b/photobooth/PictureList.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import os +import os, logging from glob import glob @@ -43,11 +43,13 @@ class PictureList: else: pictures.sort() last_picture = pictures[-1] - self.counter = int(last_picture[-(self.count_width+len(self.suffix)):-len(self.suffix)]) + self.counter = int(last_picture[ + -(self.count_width+len(self.suffix)):-len(self.suffix)]) # Print initial infos - print('Info: Number of last existing file: ' + str(self.counter)) - print('Info: Saving assembled pictures as: ' + self.basename + (self.count_width * 'X') + '.jpg') + logging.info('Number of last existing file: %d', self.counter) + logging.info('Saving assembled pictures as "%s%s.%s"', self.basename, + self.count_width * 'X', 'jpg') def getFilename(self, count): diff --git a/photobooth/Worker.py b/photobooth/Worker.py index 61de332..0a70a5f 100644 --- a/photobooth/Worker.py +++ b/photobooth/Worker.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import logging + from time import localtime, strftime from .PictureList import PictureList @@ -13,7 +15,7 @@ class WorkerTask: assert not kwargs - def do(self, picture): + def get(self, picture): raise NotImplementedError() @@ -25,21 +27,20 @@ class PictureSaver(WorkerTask): super().__init__() - picture_basename = strftime(config.get('Picture', 'basename'), localtime()) - self._pic_list = PictureList(picture_basename) - self._get_next_filename = self._pic_list.getNext + basename = strftime(config.get('Picture', 'basename'), localtime()) + self._pic_list = PictureList(basename) - @property - def getNextFilename(self): + @staticmethod + def do(picture, filename): - return self._get_next_filename + logging.info('Saving picture as %s', filename) + picture.save(filename, 'JPEG') - def do(self, picture): + def get(self, picture): - print('saving picture') - picture.save(self.getNextFilename(), 'JPEG') + return (self.do, (picture, self._pic_list.getNext())) @@ -52,7 +53,7 @@ class Worker: def run(self): - for func, args in iter(self._queue.get, ('teardown', None)): + for func, args in iter(self._queue.get, 'teardown'): func(*args) return 0 diff --git a/photobooth/camera/CameraGphoto2.py b/photobooth/camera/CameraGphoto2.py index 949a5d8..b0d9a51 100644 --- a/photobooth/camera/CameraGphoto2.py +++ b/photobooth/camera/CameraGphoto2.py @@ -20,6 +20,8 @@ class CameraGphoto2(Camera): self.hasIdle = False self._isActive = False + logging.info('Using python-gphoto2 bindings') + self._setupLogging() self._setupCamera() @@ -27,14 +29,10 @@ class CameraGphoto2(Camera): 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()) @@ -67,9 +65,7 @@ class CameraGphoto2(Camera): # self.setActive() text = self._cap.get_summary(self._ctxt) - print('Summary') - print('=======') - print(str(text)) + logging.info('Camera summary: %s', str(text)) # self.setIdle() diff --git a/photobooth/camera/CameraGphoto2Cffi.py b/photobooth/camera/CameraGphoto2Cffi.py index 2c32d1a..6eda16f 100644 --- a/photobooth/camera/CameraGphoto2Cffi.py +++ b/photobooth/camera/CameraGphoto2Cffi.py @@ -19,10 +19,7 @@ class CameraGphoto2Cffi(Camera): self.hasPreview = True self.hasIdle = True - # Avoid output cluttered - logging.basicConfig( - format='%(levelname)s: %(name)s: %(message)s', - level=logging.CRITICAL) + logging.info('Using gphoto2-cffi bindings') self._setupCamera() @@ -30,7 +27,7 @@ class CameraGphoto2Cffi(Camera): def _setupCamera(self): self._cap = gp.Camera() - print(self._cap.supported_operations) + logging.info('Supported operations: %s', self._cap.supported_operations) if 'raw' in self._cap.config['imgsettings']['imageformat'].value.lower(): raise RuntimeError('Camera file format is set to RAW') diff --git a/photobooth/camera/CameraGphoto2CommandLine.py b/photobooth/camera/CameraGphoto2CommandLine.py index 30513b7..9aa75e6 100644 --- a/photobooth/camera/CameraGphoto2CommandLine.py +++ b/photobooth/camera/CameraGphoto2CommandLine.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from PIL import Image -import os, subprocess +import os, subprocess, logging from . import Camera @@ -15,9 +15,13 @@ class CameraGphoto2CommandLine(Camera): self.hasPreview = False self.hasIdle = False + logging.info('Using gphoto2 via command line') + if os.access('/dev/shm', os.W_OK): + logging.debug('Storing temp files to "/dev/shm/photobooth.jpg"') self._tmp_filename = '/dev/shm/photobooth.jpg' else: + logging.debug('Storing temp files to "/tmp/photobooth.jpg"') self._tmp_filename = '/tmp/photobooth.jpg' self.setActive() diff --git a/photobooth/camera/CameraOpenCV.py b/photobooth/camera/CameraOpenCV.py index 8d56294..29d3259 100644 --- a/photobooth/camera/CameraOpenCV.py +++ b/photobooth/camera/CameraOpenCV.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import logging + from PIL import Image import cv2 @@ -16,6 +18,8 @@ class CameraOpenCV(Camera): self.hasPreview = True self.hasIdle = True + logging.info('Using OpenCV') + self._cap = cv2.VideoCapture() diff --git a/photobooth/gui/GuiPostprocess.py b/photobooth/gui/GuiPostprocess.py index 9143406..96620d9 100644 --- a/photobooth/gui/GuiPostprocess.py +++ b/photobooth/gui/GuiPostprocess.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import logging + from PyQt5.QtWidgets import QMessageBox from .. import printer @@ -19,6 +21,11 @@ class GuiPostprocess: raise NotImplementedError() + def confirm(self, picture): + + raise NotImplementedError() + + class PrintPostprocess(GuiPostprocess): @@ -33,17 +40,15 @@ class PrintPostprocess(GuiPostprocess): def get(self, picture): - return PrintState(lambda : self.do(picture)) + return PrintState(lambda : self.do(picture), False) - # reply = QMessageBox.question(parent, 'Print?', - # 'Do you want to print the picture?', - # QMessageBox.Yes | QMessageBox.No, QMessageBox.No) - # if reply == QMessageBox.Yes: - # self._printer.print(picture) + def confirm(self, picture): + + return PrintState(lambda : None, True) def do(self, picture): - print('Printing') + logging.info('Printing picture') self._printer.print(picture) diff --git a/photobooth/gui/GuiState.py b/photobooth/gui/GuiState.py index e006cff..6209424 100644 --- a/photobooth/gui/GuiState.py +++ b/photobooth/gui/GuiState.py @@ -147,11 +147,12 @@ class TeardownState(GuiState): class PrintState(GuiState): - def __init__(self, handler, **kwargs): + def __init__(self, handler, confirmed, **kwargs): super().__init__(**kwargs) self.handler = handler + self.confirmed = confirmed @property @@ -166,4 +167,19 @@ class PrintState(GuiState): if not callable(handler): raise ValueError('handler must be callable') - self._handler = handler \ No newline at end of file + self._handler = handler + + + @property + def confirmed(self): + + return self._confirmed + + @confirmed.setter + def confirmed(self, confirmed): + + if not isinstance(confirmed, bool): + raise ValueError('confirmed status must be bool') + + self._confirmed = confirmed + \ No newline at end of file diff --git a/photobooth/gui/PyQt5Gui.py b/photobooth/gui/PyQt5Gui.py index a7d720b..261e1ca 100644 --- a/photobooth/gui/PyQt5Gui.py +++ b/photobooth/gui/PyQt5Gui.py @@ -3,6 +3,7 @@ import multiprocessing as mp import queue +import logging from PIL import ImageQt @@ -141,7 +142,6 @@ class PyQt5Gui(Gui): elif isinstance(state, PictureState): img = ImageQt.ImageQt(state.picture) self._p.setCentralWidget(PyQt5PictureMessage('', img)) - # QTimer.singleShot(cfg.getInt('Photobooth', 'display_time') * 1000, self.sendAck) QTimer.singleShot(cfg.getInt('Photobooth', 'display_time') * 1000, lambda : self.postprocessPicture(state.picture)) @@ -179,6 +179,8 @@ class PyQt5Gui(Gui): QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: task.handler() + QMessageBox.information(self._p, 'Printing', + 'Picture sent to printer.', QMessageBox.Ok) else: raise ValueError('Unknown task') @@ -217,9 +219,9 @@ class PyQt5Gui(Gui): def showError(self, title, message): - print('ERROR: ' + title + ': ' + message) - reply = QMessageBox.warning(self._p, title, message, QMessageBox.Close | QMessageBox.Retry, - QMessageBox.Retry) + logging.error('%s: %s', title, message) + reply = QMessageBox.warning(self._p, title, message, + QMessageBox.Close | QMessageBox.Retry, QMessageBox.Retry) if reply == QMessageBox.Retry: self.sendAck() self._lastState() diff --git a/photobooth/main.py b/photobooth/main.py index b25e771..deaa3fa 100644 --- a/photobooth/main.py +++ b/photobooth/main.py @@ -7,8 +7,9 @@ try: except DistributionNotFound: __version__ = 'unknown' -import multiprocessing as mp import sys +import multiprocessing as mp +import logging from . import camera, gui from .Config import Config @@ -44,7 +45,7 @@ class CameraProcess(mp.Process): if str(event) in ('cancel', 'ack'): return 123 else: - print('Unknown event received: ' + str(event)) + logging.error('Unknown event received: %s', str(event)) raise RuntimeError('Unknown event received', str(event)) @@ -56,10 +57,11 @@ class CameraProcess(mp.Process): event = self.conn.recv() if str(event) != 'start': + logging.warning('Unknown event received: %s', str(event)) continue status_code = self.run_camera() - print('Camera exit: ', str(status_code)) + logging.info('Camera exited with status code %d', status_code) sys.exit(status_code) @@ -100,13 +102,16 @@ class GuiProcess(mp.Process): def run(argv): - print('Photobooth version:', __version__) + logging.info('Photobooth version: %s', __version__) + # Load configuration config = Config('photobooth.cfg') + # Create communication objects gui_conn, camera_conn = mp.Pipe() worker_queue = mp.SimpleQueue() + # Initialize processes camera_proc = CameraProcess(config, camera_conn, worker_queue) camera_proc.start() @@ -116,11 +121,13 @@ def run(argv): gui_proc = GuiProcess(argv, config, gui_conn, worker_queue) gui_proc.start() + # Close endpoints gui_conn.close() camera_conn.close() - gui_proc.join() - worker_queue.put(('teardown', None)) + # Wait for processes to finish + gui_proc.join() + worker_queue.put('teardown') worker_proc.join() camera_proc.join(5) return gui_proc.exitcode @@ -128,6 +135,9 @@ def run(argv): def main(argv): + logging.basicConfig(filename='photobooth.log', level=logging.INFO) + logging.getLogger().addHandler(logging.StreamHandler()) + known_status_codes = { 999: 'Initializing photobooth', 123: 'Restarting photobooth and reloading config' @@ -136,8 +146,10 @@ def main(argv): status_code = 999 while status_code in known_status_codes: - print(known_status_codes[status_code]) + logging.info(known_status_codes[status_code]) status_code = run(argv) + logging.info('Exiting photobooth with status code %d', status_code) + return status_code diff --git a/photobooth/printer/PrinterPyQt5.py b/photobooth/printer/PrinterPyQt5.py index e97022c..3a5be7c 100644 --- a/photobooth/printer/PrinterPyQt5.py +++ b/photobooth/printer/PrinterPyQt5.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import logging + from PIL import ImageQt from PyQt5.QtCore import Qt, QPoint, QSizeF @@ -18,8 +20,11 @@ class PrinterPyQt5(Printer): self._printer = QPrinter(QPrinter.HighResolution) self._printer.setPageSize(QPageSize(QSizeF(*page_size), QPageSize.Millimeter)) + logging.info('Using printer "%s"', self._printer.printerName()) + self._print_pdf = print_pdf if self._print_pdf: + logging.info('Using PDF printer') self._counter = 0 self._printer.setOutputFormat(QPrinter.PdfFormat) self._printer.setFullPage(True) @@ -32,7 +37,8 @@ class PrinterPyQt5(Printer): self._counter += 1 img = ImageQt.ImageQt(picture) - img = img.scaled(self._printer.pageRect().size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) + img = img.scaled(self._printer.pageRect().size(), Qt.KeepAspectRatio, + Qt.SmoothTransformation) printable_size = self._printer.pageRect(QPrinter.DevicePixel) origin = ( (printable_size.width() - img.width()) // 2,