From 19e119a73b18c43bbe7ad49d69a286aa8fc06b91 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 9 Aug 2019 23:39:51 +0200 Subject: [PATCH 01/35] hacky recording of image series --- photobooth/StateMachine.py | 98 +++++++++++++++++++++++++++- photobooth/camera/CameraInterface.py | 6 ++ photobooth/camera/__init__.py | 72 +++++++++++++++++++- photobooth/gui/GuiSkeleton.py | 10 +++ photobooth/gui/Qt5Gui/Frames.py | 34 +++++++++- photobooth/gui/Qt5Gui/PyQt5Gui.py | 13 +++- 6 files changed, 225 insertions(+), 8 deletions(-) diff --git a/photobooth/StateMachine.py b/photobooth/StateMachine.py index 681d1a89..eb05c7bd 100644 --- a/photobooth/StateMachine.py +++ b/photobooth/StateMachine.py @@ -27,6 +27,7 @@ def __init__(self, communicator, omit_welcome=False): super().__init__() self._comm = communicator self.is_running = False + self.capturemode = 'static' if omit_welcome: self.state = StartupState() else: @@ -45,6 +46,21 @@ def is_running(self, running): self._is_running = running + @property + def capturemode(self): + + return self._capturemode + + @capturemode.setter + def capturemode(self, mode): + + if (mode != 'boomerang') and (mode != 'static'): + raise TypeError('capturemode must be boomerang or static') + + logging.debug('Context: Set capture mode to "{}"'.format(mode)) + + self._capturemode = mode + @property def state(self): @@ -354,6 +370,11 @@ def handleEvent(self, event, context): if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and event.name == 'trigger'): + context.capturemode = 'static' + context.state = GreeterState() + elif ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and + event.name == 'triggerVideo'): + context.capturemode = 'boomerang' context.state = GreeterState() else: raise TypeError('Unknown Event type "{}"'.format(event)) @@ -374,6 +395,21 @@ def handleEvent(self, event, context): raise TypeError('Unknown Event type "{}"'.format(event)) +class GreeterVideoState(State): + + def __init__(self): + + super().__init__() + + def handleEvent(self, event, context): + + if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and + event.name == 'countdown'): + context.state = CountdownVideoState(1) + else: + raise TypeError('Unknown Event type "{}"'.format(event)) + + class CountdownState(State): def __init__(self, num_picture): @@ -392,24 +428,45 @@ def handleEvent(self, event, context): if isinstance(event, GuiEvent) and event.name == 'countdown': pass elif isinstance(event, GuiEvent) and event.name == 'capture': - context.state = CaptureState(self.num_picture) + context.state = CaptureState(self.num_picture, context.capturemode) + else: + raise TypeError('Unknown Event type "{}"'.format(event)) + +class CountdownVideoState(State): + + def __init__(self, num_picture): + + super().__init__() + + def handleEvent(self, event, context): + + if isinstance(event, GuiEvent) and event.name == 'countdown': + pass + elif isinstance(event, GuiEvent) and event.name == 'capture': + context.state = CaptureVideoState() else: raise TypeError('Unknown Event type "{}"'.format(event)) class CaptureState(State): - def __init__(self, num_picture): + def __init__(self, num_picture, capturemode): super().__init__() self._num_picture = num_picture + self._capturemode = capturemode @property def num_picture(self): return self._num_picture + @property + def capturemode(self): + + return self._capturemode + def handleEvent(self, event, context): if isinstance(event, CameraEvent) and event.name == 'countdown': @@ -420,6 +477,29 @@ def handleEvent(self, event, context): raise TypeError('Unknown Event type "{}"'.format(event)) +class CaptureVideoState(State): + + def __init__(self): + + super().__init__() + + self._num_picture = 1 + + @property + def num_picture(self): + + return self._num_picture + + def handleEvent(self, event, context): + + if isinstance(event, CameraEvent) and event.name == 'countdown': + context.state = AssembleVideoState() + elif isinstance(event, CameraEvent) and event.name == 'assemble': + context.state = AssembleVideoState() + else: + raise TypeError('Unknown Event type "{}"'.format(event)) + + class AssembleState(State): def __init__(self): @@ -434,6 +514,20 @@ def handleEvent(self, event, context): raise TypeError('Unknown Event type "{}"'.format(event)) +class AssembleVideoState(State): + + def __init__(self): + + super().__init__() + + def handleEvent(self, event, context): + + if isinstance(event, CameraEvent) and event.name == 'review': + context.state = ReviewState(event.picture) + else: + raise TypeError('Unknown Event type "{}"'.format(event)) + + class ReviewState(State): def __init__(self, picture): diff --git a/photobooth/camera/CameraInterface.py b/photobooth/camera/CameraInterface.py index f4f1a09a..18713bd1 100644 --- a/photobooth/camera/CameraInterface.py +++ b/photobooth/camera/CameraInterface.py @@ -97,6 +97,12 @@ def getPicture(self): raise NotImplementedError() + def getVideo(selfself): + + # TODO capture video in highres, turn to loop, render to mp4 and gif, mp4 ein paar mal wiederholen + + raise NotImplementedError() + def _initConfig(self): self._cfg = configparser.ConfigParser(interpolation=None) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 2f1ba1f2..07919383 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -105,8 +105,16 @@ def handleState(self, state): self.capturePreview() elif isinstance(state, StateMachine.CaptureState): self.capturePicture(state) + elif isinstance(state, StateMachine.GreeterVideoState): + self.prepareCapture() + elif isinstance(state, StateMachine.CountdownVideoState): + self.capturePreview() + elif isinstance(state, StateMachine.CaptureVideoState): + self.captureVideo(state) elif isinstance(state, StateMachine.AssembleState): self.assemblePicture() + elif isinstance(state, StateMachine.AssembleVideoState): + self.assembleGIF() elif isinstance(state, StateMachine.TeardownState): self.teardown(state) @@ -140,12 +148,60 @@ def capturePreview(self): def capturePicture(self, state): + if state.capturemode == 'static': + self.setIdle() + picture = self._cap.getPicture() + if self._rotation is not None: + picture = picture.transpose(self._rotation) + byte_data = BytesIO() + picture.save(byte_data, format='jpeg') + self._pictures.append(byte_data) + self.setActive() + + if self._is_keep_pictures: + self._comm.send(Workers.WORKER, + StateMachine.CameraEvent('capture', byte_data)) + + if state.num_picture < self._pic_dims.totalNumPictures: + self._comm.send(Workers.MASTER, + StateMachine.CameraEvent('countdown')) + else: + self._comm.send(Workers.MASTER, + StateMachine.CameraEvent('assemble')) + elif state.capturemode == 'boomerang': + logging.debug('entering boomerang capture') + # TODO handle + number_pictures = 0 + + while number_pictures < 4: + picture = self._cap.getPreview() + number_pictures += 1 + if self._rotation is not None: + picture = picture.transpose(self._rotation) + byte_data = BytesIO() + picture.save(byte_data, format='jpeg') + self._pictures.append(byte_data) + self.setActive() + + if self._is_keep_pictures: + self._comm.send(Workers.WORKER, + StateMachine.CameraEvent('capture', byte_data)) + import time + time.sleep(0.2) + + self._comm.send(Workers.MASTER, + StateMachine.CameraEvent('assemble')) + else: + raise TypeError('unknown capturemode in camera') + + def captureVideo(self, state): + self.setIdle() picture = self._cap.getPicture() if self._rotation is not None: picture = picture.transpose(self._rotation) byte_data = BytesIO() - picture.save(byte_data, format='jpeg') + picture.save(byte_data, format='gif') self._pictures.append(byte_data) self.setActive() @@ -175,3 +231,17 @@ def assemblePicture(self): self._comm.send(Workers.MASTER, StateMachine.CameraEvent('review', byte_data)) self._pictures = [] + + def assembleGIF(self): + + self.setIdle() + + picture = [] + picture.append(Image.open(self._pictures[0])) + picture.append(ImageOps.mirror(picture[0])) + + byte_data = BytesIO() + picture[0].save(byte_data, format='GIF', append_images=picture[1:], save_all=True, duration=50, loop=0) + self._comm.send(Workers.MASTER, + StateMachine.CameraEvent('review', byte_data)) + self._pictures = [] diff --git a/photobooth/gui/GuiSkeleton.py b/photobooth/gui/GuiSkeleton.py index ca438140..c3ac020d 100644 --- a/photobooth/gui/GuiSkeleton.py +++ b/photobooth/gui/GuiSkeleton.py @@ -59,6 +59,10 @@ def showCapture(self, state): raise NotImplementedError() + def showCaptureVideo(self, state): + + raise NotImplementedError() + def showAssemble(self, state): raise NotImplementedError() @@ -89,10 +93,16 @@ def handleState(self, state): self.showIdle(state) elif isinstance(state, StateMachine.GreeterState): self.showGreeter(state) + elif isinstance(state, StateMachine.GreeterVideoState): + self.showGreeter(state) elif isinstance(state, StateMachine.CountdownState): self.showCountdown(state) + elif isinstance(state, StateMachine.CountdownVideoState): + self.showCountdown(state) elif isinstance(state, StateMachine.CaptureState): self.showCapture(state) + elif isinstance(state, StateMachine.CaptureVideoState): + self.showCaptureVideo(state) elif isinstance(state, StateMachine.AssembleState): self.showAssemble(state) elif isinstance(state, StateMachine.ReviewState): diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index b49e9a12..0989bcb7 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -79,25 +79,29 @@ def initFrame(self, start_action, set_date_action, settings_action, class IdleMessage(QtWidgets.QFrame): - def __init__(self, trigger_action): + def __init__(self, trigger_action, trigger_video_action): super().__init__() self.setObjectName('IdleMessage') self._message_label = _('Hit the') self._message_button = _('Button!') + self._message_boomerang = ('Boomerang!') - self.initFrame(trigger_action) + self.initFrame(trigger_action, trigger_video_action) - def initFrame(self, trigger_action): + def initFrame(self, trigger_action, trigger_video_action): lbl = QtWidgets.QLabel(self._message_label) btn = QtWidgets.QPushButton(self._message_button) + btn_boomerang = QtWidgets.QPushButton(self._message_boomerang) btn.clicked.connect(trigger_action) + btn_boomerang.clicked.connect(trigger_video_action) lay = QtWidgets.QVBoxLayout() lay.addWidget(lbl) lay.addWidget(btn) + lay.addWidget(btn_boomerang) self.setLayout(lay) @@ -160,6 +164,30 @@ def initFrame(self): self.setLayout(lay) +class CaptureVideoMessage(QtWidgets.QFrame): + + def __init__(self, num_picture, num_x, num_y, skip): + + super().__init__() + self.setObjectName('PoseMessage') + + num_pictures = max(num_x * num_y - len(skip), 1) + if num_pictures > 1: + self._text = _('Picturino {} of {}...').format(num_picture, + num_pictures) + else: + self._text = 'Takerino a photo...' + + self.initFrame() + + def initFrame(self): + + lbl = QtWidgets.QLabel(self._text) + lay = QtWidgets.QVBoxLayout() + lay.addWidget(lbl) + self.setLayout(lay) + + class PictureMessage(QtWidgets.QFrame): def __init__(self, picture): diff --git a/photobooth/gui/Qt5Gui/PyQt5Gui.py b/photobooth/gui/Qt5Gui/PyQt5Gui.py index a5b6b517..a616c232 100644 --- a/photobooth/gui/Qt5Gui/PyQt5Gui.py +++ b/photobooth/gui/Qt5Gui/PyQt5Gui.py @@ -170,7 +170,8 @@ def showIdle(self, state): self._enableEscape() self._enableTrigger() self._setWidget(Frames.IdleMessage( - lambda: self._comm.send(Workers.MASTER, GuiEvent('trigger')))) + lambda: self._comm.send(Workers.MASTER, GuiEvent('trigger')), + lambda: self._comm.send(Workers.MASTER, GuiEvent('triggerVideo')))) def showGreeter(self, state): @@ -180,7 +181,7 @@ def showGreeter(self, state): num_pic = (self._cfg.getInt('Picture', 'num_x'), self._cfg.getInt('Picture', 'num_y')) skip = [i for i in self._cfg.getIntList('Picture', 'skip') - if 1 <= i and i <= num_pic[0] * num_pic[1]] + if 1 <= i <= num_pic[0] * num_pic[1]] greeter_time = self._cfg.getInt('Photobooth', 'greeter_time') * 1000 self._setWidget(Frames.GreeterMessage( @@ -212,6 +213,14 @@ def showCapture(self, state): self._setWidget(Frames.CaptureMessage(state.num_picture, *num_pic, skip)) + def showCaptureVideo(self, state): + + num_pic = (self._cfg.getInt('Picture', 'num_x'), + self._cfg.getInt('Picture', 'num_y')) + skip = [i for i in self._cfg.getIntList('Picture', 'skip') + if 1 <= i and i <= num_pic[0] * num_pic[1]] + self._setWidget(Frames.CaptureVideoMessage(1, 1, 1, [])) + def showAssemble(self, state): self._setWidget(Frames.WaitMessage(_('Processing picture...'))) From 341cf0ae6ab86c5eb12aa12091bee7b75e582928 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 14 Aug 2019 00:31:46 +0200 Subject: [PATCH 02/35] remove intermediate prototype states --- photobooth/StateMachine.py | 67 ------------------------------- photobooth/camera/__init__.py | 8 ---- photobooth/gui/GuiSkeleton.py | 10 ----- photobooth/gui/Qt5Gui/Frames.py | 24 ----------- photobooth/gui/Qt5Gui/PyQt5Gui.py | 8 ---- 5 files changed, 117 deletions(-) diff --git a/photobooth/StateMachine.py b/photobooth/StateMachine.py index eb05c7bd..e68d4cce 100644 --- a/photobooth/StateMachine.py +++ b/photobooth/StateMachine.py @@ -395,21 +395,6 @@ def handleEvent(self, event, context): raise TypeError('Unknown Event type "{}"'.format(event)) -class GreeterVideoState(State): - - def __init__(self): - - super().__init__() - - def handleEvent(self, event, context): - - if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and - event.name == 'countdown'): - context.state = CountdownVideoState(1) - else: - raise TypeError('Unknown Event type "{}"'.format(event)) - - class CountdownState(State): def __init__(self, num_picture): @@ -432,21 +417,6 @@ def handleEvent(self, event, context): else: raise TypeError('Unknown Event type "{}"'.format(event)) -class CountdownVideoState(State): - - def __init__(self, num_picture): - - super().__init__() - - def handleEvent(self, event, context): - - if isinstance(event, GuiEvent) and event.name == 'countdown': - pass - elif isinstance(event, GuiEvent) and event.name == 'capture': - context.state = CaptureVideoState() - else: - raise TypeError('Unknown Event type "{}"'.format(event)) - class CaptureState(State): @@ -477,29 +447,6 @@ def handleEvent(self, event, context): raise TypeError('Unknown Event type "{}"'.format(event)) -class CaptureVideoState(State): - - def __init__(self): - - super().__init__() - - self._num_picture = 1 - - @property - def num_picture(self): - - return self._num_picture - - def handleEvent(self, event, context): - - if isinstance(event, CameraEvent) and event.name == 'countdown': - context.state = AssembleVideoState() - elif isinstance(event, CameraEvent) and event.name == 'assemble': - context.state = AssembleVideoState() - else: - raise TypeError('Unknown Event type "{}"'.format(event)) - - class AssembleState(State): def __init__(self): @@ -514,20 +461,6 @@ def handleEvent(self, event, context): raise TypeError('Unknown Event type "{}"'.format(event)) -class AssembleVideoState(State): - - def __init__(self): - - super().__init__() - - def handleEvent(self, event, context): - - if isinstance(event, CameraEvent) and event.name == 'review': - context.state = ReviewState(event.picture) - else: - raise TypeError('Unknown Event type "{}"'.format(event)) - - class ReviewState(State): def __init__(self, picture): diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 07919383..7bb336ac 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -105,16 +105,8 @@ def handleState(self, state): self.capturePreview() elif isinstance(state, StateMachine.CaptureState): self.capturePicture(state) - elif isinstance(state, StateMachine.GreeterVideoState): - self.prepareCapture() - elif isinstance(state, StateMachine.CountdownVideoState): - self.capturePreview() - elif isinstance(state, StateMachine.CaptureVideoState): - self.captureVideo(state) elif isinstance(state, StateMachine.AssembleState): self.assemblePicture() - elif isinstance(state, StateMachine.AssembleVideoState): - self.assembleGIF() elif isinstance(state, StateMachine.TeardownState): self.teardown(state) diff --git a/photobooth/gui/GuiSkeleton.py b/photobooth/gui/GuiSkeleton.py index c3ac020d..ca438140 100644 --- a/photobooth/gui/GuiSkeleton.py +++ b/photobooth/gui/GuiSkeleton.py @@ -59,10 +59,6 @@ def showCapture(self, state): raise NotImplementedError() - def showCaptureVideo(self, state): - - raise NotImplementedError() - def showAssemble(self, state): raise NotImplementedError() @@ -93,16 +89,10 @@ def handleState(self, state): self.showIdle(state) elif isinstance(state, StateMachine.GreeterState): self.showGreeter(state) - elif isinstance(state, StateMachine.GreeterVideoState): - self.showGreeter(state) elif isinstance(state, StateMachine.CountdownState): self.showCountdown(state) - elif isinstance(state, StateMachine.CountdownVideoState): - self.showCountdown(state) elif isinstance(state, StateMachine.CaptureState): self.showCapture(state) - elif isinstance(state, StateMachine.CaptureVideoState): - self.showCaptureVideo(state) elif isinstance(state, StateMachine.AssembleState): self.showAssemble(state) elif isinstance(state, StateMachine.ReviewState): diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index 0989bcb7..48773403 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -164,30 +164,6 @@ def initFrame(self): self.setLayout(lay) -class CaptureVideoMessage(QtWidgets.QFrame): - - def __init__(self, num_picture, num_x, num_y, skip): - - super().__init__() - self.setObjectName('PoseMessage') - - num_pictures = max(num_x * num_y - len(skip), 1) - if num_pictures > 1: - self._text = _('Picturino {} of {}...').format(num_picture, - num_pictures) - else: - self._text = 'Takerino a photo...' - - self.initFrame() - - def initFrame(self): - - lbl = QtWidgets.QLabel(self._text) - lay = QtWidgets.QVBoxLayout() - lay.addWidget(lbl) - self.setLayout(lay) - - class PictureMessage(QtWidgets.QFrame): def __init__(self, picture): diff --git a/photobooth/gui/Qt5Gui/PyQt5Gui.py b/photobooth/gui/Qt5Gui/PyQt5Gui.py index a616c232..d10e951e 100644 --- a/photobooth/gui/Qt5Gui/PyQt5Gui.py +++ b/photobooth/gui/Qt5Gui/PyQt5Gui.py @@ -213,14 +213,6 @@ def showCapture(self, state): self._setWidget(Frames.CaptureMessage(state.num_picture, *num_pic, skip)) - def showCaptureVideo(self, state): - - num_pic = (self._cfg.getInt('Picture', 'num_x'), - self._cfg.getInt('Picture', 'num_y')) - skip = [i for i in self._cfg.getIntList('Picture', 'skip') - if 1 <= i and i <= num_pic[0] * num_pic[1]] - self._setWidget(Frames.CaptureVideoMessage(1, 1, 1, [])) - def showAssemble(self, state): self._setWidget(Frames.WaitMessage(_('Processing picture...'))) From c79c4a217c47bced8a9456fc7dc0eb328fedfc9a Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 15 Aug 2019 00:47:40 +0200 Subject: [PATCH 03/35] record pictures with time inbetween --- photobooth/camera/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 7bb336ac..5f320702 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -18,6 +18,7 @@ # along with this program. If not, see . import logging +import time from PIL import Image, ImageOps from io import BytesIO @@ -166,23 +167,25 @@ def capturePicture(self, state): number_pictures = 0 while number_pictures < 4: - picture = self._cap.getPreview() + self.setIdle() + # TODO select preview or picture + # picture = self._cap.getPreview() + picture = self._cap.getPicture() number_pictures += 1 if self._rotation is not None: picture = picture.transpose(self._rotation) byte_data = BytesIO() picture.save(byte_data, format='jpeg') self._pictures.append(byte_data) - self.setActive() + self.setActive() if self._is_keep_pictures: self._comm.send(Workers.WORKER, - StateMachine.CameraEvent('capture', byte_data)) - import time + StateMachine.CameraEvent('capture', byte_data)) time.sleep(0.2) self._comm.send(Workers.MASTER, - StateMachine.CameraEvent('assemble')) + StateMachine.CameraEvent('assemble')) else: raise TypeError('unknown capturemode in camera') From 862483e1e44fe66248a1aedc5290d528c3f1fb3c Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 20 Aug 2019 20:19:19 +0200 Subject: [PATCH 04/35] use variables for capturemode --- photobooth/StateMachine.py | 6 +++++- photobooth/camera/__init__.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/photobooth/StateMachine.py b/photobooth/StateMachine.py index e68d4cce..e394e169 100644 --- a/photobooth/StateMachine.py +++ b/photobooth/StateMachine.py @@ -20,6 +20,10 @@ import logging +CAPMODE_STATIC = 'static' +CAPMODE_BOOMERANG = 'boomerang' + + class Context: def __init__(self, communicator, omit_welcome=False): @@ -27,7 +31,7 @@ def __init__(self, communicator, omit_welcome=False): super().__init__() self._comm = communicator self.is_running = False - self.capturemode = 'static' + self.capturemode = CAPMODE_STATIC if omit_welcome: self.state = StartupState() else: diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 5f320702..ee975d52 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -141,7 +141,7 @@ def capturePreview(self): def capturePicture(self, state): - if state.capturemode == 'static': + if state.capturemode == StateMachine.CAPMODE_STATIC: self.setIdle() picture = self._cap.getPicture() if self._rotation is not None: @@ -161,7 +161,7 @@ def capturePicture(self, state): else: self._comm.send(Workers.MASTER, StateMachine.CameraEvent('assemble')) - elif state.capturemode == 'boomerang': + elif state.capturemode == StateMachine.CAPMODE_BOOMERANG: logging.debug('entering boomerang capture') # TODO handle number_pictures = 0 From 8fbf5dab5fcbeb6c7e3872f49e01c96304c2f7bd Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 20 Aug 2019 20:21:55 +0200 Subject: [PATCH 05/35] remove unused stub --- photobooth/camera/CameraInterface.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/photobooth/camera/CameraInterface.py b/photobooth/camera/CameraInterface.py index 18713bd1..f4f1a09a 100644 --- a/photobooth/camera/CameraInterface.py +++ b/photobooth/camera/CameraInterface.py @@ -97,12 +97,6 @@ def getPicture(self): raise NotImplementedError() - def getVideo(selfself): - - # TODO capture video in highres, turn to loop, render to mp4 and gif, mp4 ein paar mal wiederholen - - raise NotImplementedError() - def _initConfig(self): self._cfg = configparser.ConfigParser(interpolation=None) From 4e46d4cb35b3a183924ddc881b90a0374fdc5041 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 20 Aug 2019 20:29:24 +0200 Subject: [PATCH 06/35] fix typo --- photobooth/gui/Qt5Gui/Frames.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index 48773403..a7aa2835 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -86,7 +86,7 @@ def __init__(self, trigger_action, trigger_video_action): self._message_label = _('Hit the') self._message_button = _('Button!') - self._message_boomerang = ('Boomerang!') + self._message_boomerang = _('Boomerang!') self.initFrame(trigger_action, trigger_video_action) From 3692d069c6a3877caf401fb824e42a0edca22d21 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 20 Aug 2019 20:58:44 +0200 Subject: [PATCH 07/35] use variables for capturemode --- photobooth/StateMachine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/photobooth/StateMachine.py b/photobooth/StateMachine.py index e394e169..01326468 100644 --- a/photobooth/StateMachine.py +++ b/photobooth/StateMachine.py @@ -58,7 +58,7 @@ def capturemode(self): @capturemode.setter def capturemode(self, mode): - if (mode != 'boomerang') and (mode != 'static'): + if (mode != CAPMODE_BOOMERANG) and (mode != CAPMODE_STATIC): raise TypeError('capturemode must be boomerang or static') logging.debug('Context: Set capture mode to "{}"'.format(mode)) @@ -374,11 +374,11 @@ def handleEvent(self, event, context): if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and event.name == 'trigger'): - context.capturemode = 'static' + context.capturemode = CAPMODE_STATIC context.state = GreeterState() elif ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and event.name == 'triggerVideo'): - context.capturemode = 'boomerang' + context.capturemode = CAPMODE_BOOMERANG context.state = GreeterState() else: raise TypeError('Unknown Event type "{}"'.format(event)) From bfde5b7fc61d14dcdda44e05345342eb924d8287 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 20 Aug 2019 20:59:10 +0200 Subject: [PATCH 08/35] rename trigger action --- photobooth/gui/Qt5Gui/Frames.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index a7aa2835..46d7fae4 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -79,7 +79,7 @@ def initFrame(self, start_action, set_date_action, settings_action, class IdleMessage(QtWidgets.QFrame): - def __init__(self, trigger_action, trigger_video_action): + def __init__(self, trigger_action, trigger_boomerang_action): super().__init__() self.setObjectName('IdleMessage') @@ -88,15 +88,15 @@ def __init__(self, trigger_action, trigger_video_action): self._message_button = _('Button!') self._message_boomerang = _('Boomerang!') - self.initFrame(trigger_action, trigger_video_action) + self.initFrame(trigger_action, trigger_boomerang_action) - def initFrame(self, trigger_action, trigger_video_action): + def initFrame(self, trigger_action, trigger_boomerang_action): lbl = QtWidgets.QLabel(self._message_label) btn = QtWidgets.QPushButton(self._message_button) btn_boomerang = QtWidgets.QPushButton(self._message_boomerang) btn.clicked.connect(trigger_action) - btn_boomerang.clicked.connect(trigger_video_action) + btn_boomerang.clicked.connect(trigger_boomerang_action) lay = QtWidgets.QVBoxLayout() lay.addWidget(lbl) From c5f60360f91490649d2995ddf2023dc1b8690697 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 20 Aug 2019 21:10:46 +0200 Subject: [PATCH 09/35] adapt stylesheet slightly for prototyping --- photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss b/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss index 5cdc62ed..13c910ff 100644 --- a/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss +++ b/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss @@ -32,7 +32,7 @@ QPushButton:pressed { QFrame#IdleMessage { background-image: url(photobooth/gui/Qt5Gui/images/arrow-1024x600.png); background-repeat: no-repeat; - padding: 80px 400px 120px 80px; + padding: 40px 180px 40px 60px; } QFrame#IdleMessage QLabel { @@ -43,7 +43,7 @@ QFrame#IdleMessage QLabel { QFrame#IdleMessage QPushButton { border: none; color: rgba(255, 27, 0, 200); - font-size: 200px; + font-size: 180px; text-align: center; } From 8e96585f0bbf4a7b2299dd8e121b997fd0724ecc Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 20 Aug 2019 23:24:13 +0200 Subject: [PATCH 10/35] move differentiation for capturemode --- photobooth/camera/CameraInterface.py | 4 ++ photobooth/camera/__init__.py | 86 +++++++++++----------------- 2 files changed, 38 insertions(+), 52 deletions(-) diff --git a/photobooth/camera/CameraInterface.py b/photobooth/camera/CameraInterface.py index f4f1a09a..e63992b1 100644 --- a/photobooth/camera/CameraInterface.py +++ b/photobooth/camera/CameraInterface.py @@ -97,6 +97,10 @@ def getPicture(self): raise NotImplementedError() + def getVideo(self): + + raise NotImplementedError() + def _initConfig(self): self._cfg = configparser.ConfigParser(interpolation=None) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index ee975d52..bc007722 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -105,7 +105,12 @@ def handleState(self, state): elif isinstance(state, StateMachine.CountdownState): self.capturePreview() elif isinstance(state, StateMachine.CaptureState): - self.capturePicture(state) + if state.capturemode == StateMachine.CAPMODE_STATIC: + self.capturePicture(state) + elif state.capturemode == StateMachine.CAPMODE_BOOMERANG: + self.captureVideo(state) + else: + raise TypeError('unknown capturemode in camera') elif isinstance(state, StateMachine.AssembleState): self.assemblePicture() elif isinstance(state, StateMachine.TeardownState): @@ -141,62 +146,12 @@ def capturePreview(self): def capturePicture(self, state): - if state.capturemode == StateMachine.CAPMODE_STATIC: - self.setIdle() - picture = self._cap.getPicture() - if self._rotation is not None: - picture = picture.transpose(self._rotation) - byte_data = BytesIO() - picture.save(byte_data, format='jpeg') - self._pictures.append(byte_data) - self.setActive() - - if self._is_keep_pictures: - self._comm.send(Workers.WORKER, - StateMachine.CameraEvent('capture', byte_data)) - - if state.num_picture < self._pic_dims.totalNumPictures: - self._comm.send(Workers.MASTER, - StateMachine.CameraEvent('countdown')) - else: - self._comm.send(Workers.MASTER, - StateMachine.CameraEvent('assemble')) - elif state.capturemode == StateMachine.CAPMODE_BOOMERANG: - logging.debug('entering boomerang capture') - # TODO handle - number_pictures = 0 - - while number_pictures < 4: - self.setIdle() - # TODO select preview or picture - # picture = self._cap.getPreview() - picture = self._cap.getPicture() - number_pictures += 1 - if self._rotation is not None: - picture = picture.transpose(self._rotation) - byte_data = BytesIO() - picture.save(byte_data, format='jpeg') - self._pictures.append(byte_data) - - self.setActive() - if self._is_keep_pictures: - self._comm.send(Workers.WORKER, - StateMachine.CameraEvent('capture', byte_data)) - time.sleep(0.2) - - self._comm.send(Workers.MASTER, - StateMachine.CameraEvent('assemble')) - else: - raise TypeError('unknown capturemode in camera') - - def captureVideo(self, state): - self.setIdle() picture = self._cap.getPicture() if self._rotation is not None: picture = picture.transpose(self._rotation) byte_data = BytesIO() - picture.save(byte_data, format='gif') + picture.save(byte_data, format='jpeg') self._pictures.append(byte_data) self.setActive() @@ -211,6 +166,33 @@ def captureVideo(self, state): self._comm.send(Workers.MASTER, StateMachine.CameraEvent('assemble')) + def captureVideo(self, state): + + logging.debug('entering boomerang capture') + # TODO handle + number_pictures = 0 + + while number_pictures < 4: + self.setIdle() + # TODO select preview or picture + picture = self._cap.getPreview() + # picture = self._cap.getPicture() + number_pictures += 1 + if self._rotation is not None: + picture = picture.transpose(self._rotation) + byte_data = BytesIO() + picture.save(byte_data, format='jpeg') + self._pictures.append(byte_data) + + self.setActive() + if self._is_keep_pictures: + self._comm.send(Workers.WORKER, + StateMachine.CameraEvent('capture', byte_data)) + time.sleep(0.2) + + self._comm.send(Workers.MASTER, + StateMachine.CameraEvent('assemble')) + def assemblePicture(self): self.setIdle() From 04820ef7970e616f8b0ab7d24d5785a9bd887ce4 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 21 Aug 2019 00:18:44 +0200 Subject: [PATCH 11/35] store gif if available --- photobooth/StateMachine.py | 31 ++++++++++++++++++++---- photobooth/camera/__init__.py | 22 ++++++++++++++--- photobooth/worker/PictureMailer.py | 2 +- photobooth/worker/PictureSaver.py | 7 +++++- photobooth/worker/PictureUploadWebdav.py | 2 +- photobooth/worker/WorkerTask.py | 2 +- photobooth/worker/__init__.py | 6 ++--- 7 files changed, 56 insertions(+), 16 deletions(-) diff --git a/photobooth/StateMachine.py b/photobooth/StateMachine.py index 01326468..fad2d9b6 100644 --- a/photobooth/StateMachine.py +++ b/photobooth/StateMachine.py @@ -197,16 +197,22 @@ class GpioEvent(Event): class CameraEvent(Event): - def __init__(self, name, picture=None): + def __init__(self, name, picture=None, gif=None): super().__init__(name) self._picture = picture + self._gif = gif @property def picture(self): return self._picture + @property + def gif(self): + + return self._gif + class WorkerEvent(Event): @@ -446,37 +452,52 @@ def handleEvent(self, event, context): if isinstance(event, CameraEvent) and event.name == 'countdown': context.state = CountdownState(self.num_picture + 1) elif isinstance(event, CameraEvent) and event.name == 'assemble': - context.state = AssembleState() + context.state = AssembleState(self.capturemode) else: raise TypeError('Unknown Event type "{}"'.format(event)) class AssembleState(State): - def __init__(self): + def __init__(self, capturemode): super().__init__() + self._capturemode = capturemode + + @property + def capturemode(self): + + return self._capturemode def handleEvent(self, event, context): if isinstance(event, CameraEvent) and event.name == 'review': - context.state = ReviewState(event.picture) + if self.capturemode == CAPMODE_BOOMERANG: + context.state = ReviewState(event.picture, event.gif) + else: + context.state = ReviewState(event.picture) else: raise TypeError('Unknown Event type "{}"'.format(event)) class ReviewState(State): - def __init__(self, picture): + def __init__(self, picture, gif=None): super().__init__() self._picture = picture + self._gif = gif @property def picture(self): return self._picture + @property + def gif(self): + + return self._gif + def handleEvent(self, event, context): if isinstance(event, GuiEvent) and event.name == 'postprocess': diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index bc007722..525e7c66 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -112,7 +112,12 @@ def handleState(self, state): else: raise TypeError('unknown capturemode in camera') elif isinstance(state, StateMachine.AssembleState): - self.assemblePicture() + if state.capturemode == StateMachine.CAPMODE_STATIC: + self.assemblePicture() + elif state.capturemode == StateMachine.CAPMODE_BOOMERANG: + self.assembleGIF() + else: + raise TypeError('unknown capturemode in camera') elif isinstance(state, StateMachine.TeardownState): self.teardown(state) @@ -213,12 +218,21 @@ def assembleGIF(self): self.setIdle() + picture = self._template.copy() + for i in range(self._pic_dims.totalNumPictures): + shot = Image.open(self._pictures[i]) + resized = shot.resize(self._pic_dims.thumbnailSize) + picture.paste(resized, self._pic_dims.thumbnailOffset[i]) + + byte_data = BytesIO() + picture.save(byte_data, format='jpeg') + picture = [] picture.append(Image.open(self._pictures[0])) picture.append(ImageOps.mirror(picture[0])) - byte_data = BytesIO() - picture[0].save(byte_data, format='GIF', append_images=picture[1:], save_all=True, duration=50, loop=0) + byte_data_gif = BytesIO() + picture[0].save(byte_data_gif, format='GIF', append_images=picture[1:], save_all=True, duration=50, loop=0) self._comm.send(Workers.MASTER, - StateMachine.CameraEvent('review', byte_data)) + StateMachine.CameraEvent('review', byte_data, byte_data_gif)) self._pictures = [] diff --git a/photobooth/worker/PictureMailer.py b/photobooth/worker/PictureMailer.py index 6f647195..b4cae950 100644 --- a/photobooth/worker/PictureMailer.py +++ b/photobooth/worker/PictureMailer.py @@ -93,7 +93,7 @@ def __init__(self, config): self._password = config.get('Mailer', 'password') self._is_tls = config.getBool('Mailer', 'use_tls') - def do(self, picture, filename): + def do(self, picture, filename, gif=None): logging.info('Sending picture to %s', self._recipient) send_mail(self._sender, self._recipient, self._subject, self._message, diff --git a/photobooth/worker/PictureSaver.py b/photobooth/worker/PictureSaver.py index a84c1216..71254441 100644 --- a/photobooth/worker/PictureSaver.py +++ b/photobooth/worker/PictureSaver.py @@ -34,8 +34,13 @@ def __init__(self, basename): if not os.path.exists(dirname): os.makedirs(dirname) - def do(self, picture, filename): + def do(self, picture, filename, gif=None): logging.info('Saving picture as %s', filename) with open(filename, 'wb') as f: f.write(picture.getbuffer()) + if gif: + gifname = "{file}.gif".format(file=filename) + logging.info('Saving GIF as {}'.format(gifname)) + with open(gifname, 'wb') as f: + f.write(gif.getbuffer()) diff --git a/photobooth/worker/PictureUploadWebdav.py b/photobooth/worker/PictureUploadWebdav.py index 59938879..9af232cc 100644 --- a/photobooth/worker/PictureUploadWebdav.py +++ b/photobooth/worker/PictureUploadWebdav.py @@ -38,7 +38,7 @@ def __init__(self, config): else: self._auth = None - def do(self, picture, filename): + def do(self, picture, filename, gif=None): url = self._baseurl + '/' + Path(filename).name logging.info('Uploading picture as %s', url) diff --git a/photobooth/worker/WorkerTask.py b/photobooth/worker/WorkerTask.py index 659b01e2..79323e64 100644 --- a/photobooth/worker/WorkerTask.py +++ b/photobooth/worker/WorkerTask.py @@ -24,6 +24,6 @@ def __init__(self, **kwargs): assert not kwargs - def do(self, picture): + def do(self, picture, filename, gif=None): raise NotImplementedError() diff --git a/photobooth/worker/__init__.py b/photobooth/worker/__init__.py index 81e03bbb..770b5df1 100644 --- a/photobooth/worker/__init__.py +++ b/photobooth/worker/__init__.py @@ -85,7 +85,7 @@ def handleState(self, state): if isinstance(state, StateMachine.TeardownState): self.teardown(state) elif isinstance(state, StateMachine.ReviewState): - self.doPostprocessTasks(state.picture, self._pic_list.getNext()) + self.doPostprocessTasks(state.picture, self._pic_list.getNext(), state.gif) elif isinstance(state, StateMachine.CameraEvent): if state.name == 'capture': self.doPictureTasks(state.picture, self._shot_list.getNext()) @@ -96,10 +96,10 @@ def teardown(self, state): pass - def doPostprocessTasks(self, picture, filename): + def doPostprocessTasks(self, picture, filename, gif=None): for task in self._postprocess_tasks: - task.do(picture, filename) + task.do(picture, filename, gif) def doPictureTasks(self, picture, filename): From 88782df61a1c1a9acac759ee4a369d4435b5b97a Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 21 Aug 2019 00:23:38 +0200 Subject: [PATCH 12/35] adapt assembleGIF to current usecase --- photobooth/camera/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 525e7c66..988d182e 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -228,8 +228,13 @@ def assembleGIF(self): picture.save(byte_data, format='jpeg') picture = [] + # TODO adapt to number of frames (scale automatically) and make number of frames configurable picture.append(Image.open(self._pictures[0])) - picture.append(ImageOps.mirror(picture[0])) + picture.append(Image.open(self._pictures[1])) + picture.append(Image.open(self._pictures[2])) + picture.append(Image.open(self._pictures[3])) + picture.append(Image.open(self._pictures[2])) + picture.append(Image.open(self._pictures[1])) byte_data_gif = BytesIO() picture[0].save(byte_data_gif, format='GIF', append_images=picture[1:], save_all=True, duration=50, loop=0) From 1c028186266c6c8f565f6ce29cdfbc9512f85fc2 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 21 Aug 2019 01:03:10 +0200 Subject: [PATCH 13/35] make number of frames and duration configurable --- photobooth/camera/__init__.py | 23 ++++++++++++++--------- photobooth/defaults.cfg | 6 ++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 988d182e..39afcac7 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -54,6 +54,10 @@ def __init__(self, config, comm, CameraModule): self._is_preview = self._cfg.getBool('Photobooth', 'show_preview') self._is_keep_pictures = self._cfg.getBool('Storage', 'keep_pictures') + self._gif_num_frames = self._cfg.getInt('GIF', 'num_frames') + self._gif_num_img_to_take = ((self._gif_num_frames - 2) // 2) + 2 + self._gif_frame_duration = self._cfg.getInt('GIF', 'frame_duration') + rot_vals = {0: None, 90: Image.ROTATE_90, 180: Image.ROTATE_180, 270: Image.ROTATE_270} self._rotation = rot_vals[self._cfg.getInt('Camera', 'rotation')] @@ -177,7 +181,7 @@ def captureVideo(self, state): # TODO handle number_pictures = 0 - while number_pictures < 4: + while number_pictures < self._gif_num_img_to_take: self.setIdle() # TODO select preview or picture picture = self._cap.getPreview() @@ -193,7 +197,8 @@ def captureVideo(self, state): if self._is_keep_pictures: self._comm.send(Workers.WORKER, StateMachine.CameraEvent('capture', byte_data)) - time.sleep(0.2) + # time.sleep(0.2) + # TODO timing of capture self._comm.send(Workers.MASTER, StateMachine.CameraEvent('assemble')) @@ -229,15 +234,15 @@ def assembleGIF(self): picture = [] # TODO adapt to number of frames (scale automatically) and make number of frames configurable - picture.append(Image.open(self._pictures[0])) - picture.append(Image.open(self._pictures[1])) - picture.append(Image.open(self._pictures[2])) - picture.append(Image.open(self._pictures[3])) - picture.append(Image.open(self._pictures[2])) - picture.append(Image.open(self._pictures[1])) + for i in range(self._gif_num_img_to_take): + logging.info(i) + picture.append(Image.open(self._pictures[i])) + for i in range((self._gif_num_frames - self._gif_num_img_to_take), 0, -1 ): + logging.info(i) + picture.append(Image.open(self._pictures[i])) byte_data_gif = BytesIO() - picture[0].save(byte_data_gif, format='GIF', append_images=picture[1:], save_all=True, duration=50, loop=0) + picture[0].save(byte_data_gif, format='GIF', append_images=picture[1:], save_all=True, duration=self._gif_frame_duration, loop=0) self._comm.send(Workers.MASTER, StateMachine.CameraEvent('review', byte_data, byte_data_gif)) self._pictures = [] diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index 5e87b307..bb3cb8db 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -85,6 +85,12 @@ skip = # Specify background image (filename, optional) background = +[GIF] +# Number of frames +num_frames = 8 +# duration of one frame (in milliseconds) +frame_duration = 100 + [Storage] # Basedir of output pictures basedir = %Y-%m-%d From 6d0961938a753b6bb28afa9b5a4f64e2eed17794 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 21 Aug 2019 01:08:04 +0200 Subject: [PATCH 14/35] try to show GIF with QMovie --- photobooth/camera/__init__.py | 1 + photobooth/gui/Qt5Gui/Frames.py | 45 +++++++++++++++++++ photobooth/gui/Qt5Gui/PyQt5Gui.py | 26 +++++++---- .../Qt5Gui/stylesheets/pastel-1024x600.qss | 13 +++++- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 39afcac7..f7368421 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -223,6 +223,7 @@ def assembleGIF(self): self.setIdle() + # TODO make something like a "best of" for static output, maybe (depending on output) first, last, in between picture = self._template.copy() for i in range(self._pic_dims.totalNumPictures): shot = Image.open(self._pictures[i]) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index 46d7fae4..4c3656fe 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -25,6 +25,7 @@ from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5 import QtWidgets +from PyQt5 import Qt from .. import modules from ... import camera @@ -193,6 +194,50 @@ def paintEvent(self, event): painter.end() +class GIFMessage(QtWidgets.QFrame): + + def __init__(self, gif): + + super().__init__() + self.setObjectName('GIFMessage') + + self.initFrame(gif) + + def initFrame(self, gif): + + gif.seek(0) + a = QtCore.QByteArray(gif.read()) + b = QtCore.QBuffer(a) + b.open(QtCore.QIODevice.ReadOnly) + self.movie = QtGui.QMovie(b, QtCore.QByteArray(), self) + + size = self.movie.scaledSize() + self.setGeometry(200, 200, size.width(), size.height()) + self.movie_screen = QtWidgets.QLabel('GIF') + self.movie_screen.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Expanding) + #self.movie_screen.setAlignment(Qt.AlignCenter) + lay = QtWidgets.QVBoxLayout() + lay.addWidget(self.movie_screen) + self.setLayout(lay) + self.movie.setCacheMode(QtGui.QMovie.CacheAll) + self.movie_screen.setMovie(self.movie) + self.movie_screen.setScaledContents(True) + #self.movie.setBackgroundColor(Qt.QColor(0, 0, 255, 127)) + print(self.movie.isValid()) + print(self.movie.loopCount()) + print(self.movie.state()) + #print(self.movie.lastError()) + #print(self.movie.lastErrorString()) + #print(self.movie.supportedFormats()) + #self.movie.loopCount() + + def showEvent(self, event): + + # TODO this makes it somehow crash + #self.movie.start() + print(self.movie.state()) + + class WaitMessage(QtWidgets.QFrame): def __init__(self, message): diff --git a/photobooth/gui/Qt5Gui/PyQt5Gui.py b/photobooth/gui/Qt5Gui/PyQt5Gui.py index d10e951e..fb14bc1d 100644 --- a/photobooth/gui/Qt5Gui/PyQt5Gui.py +++ b/photobooth/gui/Qt5Gui/PyQt5Gui.py @@ -219,14 +219,24 @@ def showAssemble(self, state): def showReview(self, state): - picture = Image.open(state.picture) - self._picture = ImageQt.ImageQt(picture) - review_time = self._cfg.getInt('Photobooth', 'display_time') * 1000 - self._setWidget(Frames.PictureMessage(self._picture)) - QtCore.QTimer.singleShot( - review_time, - lambda: self._comm.send(Workers.MASTER, GuiEvent('postprocess'))) - self._postprocess.do(self._picture) + if state.gif: + review_time = self._cfg.getInt('Photobooth', 'display_time') * 1000 + self._setWidget(Frames.GIFMessage(state.gif)) + QtCore.QTimer.singleShot( + review_time, + lambda: self._comm.send(Workers.MASTER, GuiEvent('postprocess'))) + picture = Image.open(state.gif) + self._picture = ImageQt.ImageQt(picture) + self._postprocess.do(self._picture) + else: + picture = Image.open(state.picture) + self._picture = ImageQt.ImageQt(picture) + review_time = self._cfg.getInt('Photobooth', 'display_time') * 1000 + self._setWidget(Frames.PictureMessage(self._picture)) + QtCore.QTimer.singleShot( + review_time, + lambda: self._comm.send(Workers.MASTER, GuiEvent('postprocess'))) + self._postprocess.do(self._picture) def showPostprocess(self, state): diff --git a/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss b/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss index 13c910ff..63aa59bb 100644 --- a/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss +++ b/photobooth/gui/Qt5Gui/stylesheets/pastel-1024x600.qss @@ -123,6 +123,17 @@ QFrame#PictureMessage { margin: 30px; } +/* GIF Screen */ + +QFrame#GIFMessage { + margin: 30px; +} + +QFrame#GIFMessage QLabel { + font-size: 160px; + qproperty-alignment: AlignCenter; +} + /* Overlay message */ QWidget#TransparentOverlay { @@ -170,7 +181,7 @@ QTabWidget::pane { border-color: #eeeeee; color: #333333; padding: 10px; - margin: 0; + margin: 0; } QTabWidget::tab-bar { From 91eb1c0f888453082a67bca94544e7061ae4dccc Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 3 Sep 2019 22:36:29 +0200 Subject: [PATCH 15/35] show GIF with QMovie --- photobooth/gui/Qt5Gui/Frames.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index 4c3656fe..4beb9ddc 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -206,36 +206,28 @@ def __init__(self, gif): def initFrame(self, gif): gif.seek(0) - a = QtCore.QByteArray(gif.read()) - b = QtCore.QBuffer(a) - b.open(QtCore.QIODevice.ReadOnly) - self.movie = QtGui.QMovie(b, QtCore.QByteArray(), self) + self.a = QtCore.QByteArray.fromRawData(gif.getvalue()) + self.b = QtCore.QBuffer(self.a) + self.b.open(QtCore.QIODevice.ReadOnly) + self.movie = QtGui.QMovie(self.b, b'gif', self) + # self.movie = QtGui.QMovie('../photos/2019-09-03/photobooth00049.jpg.gif') size = self.movie.scaledSize() self.setGeometry(200, 200, size.width(), size.height()) self.movie_screen = QtWidgets.QLabel('GIF') - self.movie_screen.setSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Expanding) + self.movie_screen.setSizePolicy(Qt.QSizePolicy.MinimumExpanding, Qt.QSizePolicy.MinimumExpanding) #self.movie_screen.setAlignment(Qt.AlignCenter) lay = QtWidgets.QVBoxLayout() lay.addWidget(self.movie_screen) self.setLayout(lay) self.movie.setCacheMode(QtGui.QMovie.CacheAll) self.movie_screen.setMovie(self.movie) - self.movie_screen.setScaledContents(True) - #self.movie.setBackgroundColor(Qt.QColor(0, 0, 255, 127)) - print(self.movie.isValid()) - print(self.movie.loopCount()) - print(self.movie.state()) - #print(self.movie.lastError()) - #print(self.movie.lastErrorString()) - #print(self.movie.supportedFormats()) - #self.movie.loopCount() - - def showEvent(self, event): - - # TODO this makes it somehow crash - #self.movie.start() - print(self.movie.state()) + self.movie_screen.setScaledContents(False) + # self.movie.setBackgroundColor(Qt.QColor(0, 0, 255, 127)) + # print(self.movie.isValid()) + # print(self.movie.loopCount()) + # print(self.movie.state()) + self.movie.start() class WaitMessage(QtWidgets.QFrame): From 0e4f3e170ee64ffff2e73f0085f43c3d66c45f20 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 3 Sep 2019 23:26:25 +0200 Subject: [PATCH 16/35] make wait time between captures configurable --- photobooth/camera/__init__.py | 17 ++++++++++------- photobooth/defaults.cfg | 6 ++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index f7368421..2b2c3b6b 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -57,6 +57,7 @@ def __init__(self, config, comm, CameraModule): self._gif_num_frames = self._cfg.getInt('GIF', 'num_frames') self._gif_num_img_to_take = ((self._gif_num_frames - 2) // 2) + 2 self._gif_frame_duration = self._cfg.getInt('GIF', 'frame_duration') + self._gif_wait_s_between_cap = self._cfg.getInt('GIF', 'ms_between_capture') // 1000 rot_vals = {0: None, 90: Image.ROTATE_90, 180: Image.ROTATE_180, 270: Image.ROTATE_270} @@ -181,8 +182,9 @@ def captureVideo(self, state): # TODO handle number_pictures = 0 + self.setIdle() while number_pictures < self._gif_num_img_to_take: - self.setIdle() + self.setIdle() # TODO use other method to get new image (capture is "too" fast) # TODO select preview or picture picture = self._cap.getPreview() # picture = self._cap.getPicture() @@ -194,11 +196,12 @@ def captureVideo(self, state): self._pictures.append(byte_data) self.setActive() - if self._is_keep_pictures: - self._comm.send(Workers.WORKER, - StateMachine.CameraEvent('capture', byte_data)) - # time.sleep(0.2) + # if self._is_keep_pictures: + # self._comm.send(Workers.WORKER, + # StateMachine.CameraEvent('capture', byte_data)) + time.sleep(self._gif_wait_s_between_cap) # TODO timing of capture + self.setActive() self._comm.send(Workers.MASTER, StateMachine.CameraEvent('assemble')) @@ -236,10 +239,10 @@ def assembleGIF(self): picture = [] # TODO adapt to number of frames (scale automatically) and make number of frames configurable for i in range(self._gif_num_img_to_take): - logging.info(i) + logging.info("appending frame {}".format(i)) picture.append(Image.open(self._pictures[i])) for i in range((self._gif_num_frames - self._gif_num_img_to_take), 0, -1 ): - logging.info(i) + logging.info("appending frame {}".format(i)) picture.append(Image.open(self._pictures[i])) byte_data_gif = BytesIO() diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index bb3cb8db..325f037c 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -13,7 +13,7 @@ hide_cursor = False style = default [Camera] -# Camera module to use (python-gphoto2, gphoto2-cffi, gphoto2-commandline, +# Camera module to use (python-gphoto2, gphoto2-cffi, gphoto2-commandline, # opencv, picamera, dummy) module = python-gphoto2 # Specify rotation of camera in degree (possible values: 0, 90, 180, 270) @@ -87,9 +87,11 @@ background = [GIF] # Number of frames -num_frames = 8 +num_frames = 12 # duration of one frame (in milliseconds) frame_duration = 100 +# wait time in ms between the capture of two frames +ms_between_capture = 100 [Storage] # Basedir of output pictures From 1d082e9b135c303be1fb01de89dbc098aed4ed9a Mon Sep 17 00:00:00 2001 From: Patrick Kanzler <4189642+patkan@users.noreply.github.com> Date: Thu, 5 Sep 2019 10:12:19 +0200 Subject: [PATCH 17/35] use true division --- photobooth/camera/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 2b2c3b6b..3b6c5932 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -57,7 +57,7 @@ def __init__(self, config, comm, CameraModule): self._gif_num_frames = self._cfg.getInt('GIF', 'num_frames') self._gif_num_img_to_take = ((self._gif_num_frames - 2) // 2) + 2 self._gif_frame_duration = self._cfg.getInt('GIF', 'frame_duration') - self._gif_wait_s_between_cap = self._cfg.getInt('GIF', 'ms_between_capture') // 1000 + self._gif_wait_s_between_cap = self._cfg.getInt('GIF', 'ms_between_capture') / 1000 rot_vals = {0: None, 90: Image.ROTATE_90, 180: Image.ROTATE_180, 270: Image.ROTATE_270} From 31cc10d4e9e958e207cffc62447010babc5af8f3 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 5 Sep 2019 23:00:37 +0200 Subject: [PATCH 18/35] improve debug message --- photobooth/camera/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 3b6c5932..5454f231 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -239,10 +239,10 @@ def assembleGIF(self): picture = [] # TODO adapt to number of frames (scale automatically) and make number of frames configurable for i in range(self._gif_num_img_to_take): - logging.info("appending frame {}".format(i)) + logging.debug("appending frame {}".format(i)) picture.append(Image.open(self._pictures[i])) for i in range((self._gif_num_frames - self._gif_num_img_to_take), 0, -1 ): - logging.info("appending frame {}".format(i)) + logging.debug("appending frame {}".format(i)) picture.append(Image.open(self._pictures[i])) byte_data_gif = BytesIO() From e8d869796593b2356613b16b4353350ced54b866 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 5 Sep 2019 23:50:27 +0200 Subject: [PATCH 19/35] add model for eos450d --- photobooth/camera/models/canoneos450d.cfg | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 photobooth/camera/models/canoneos450d.cfg diff --git a/photobooth/camera/models/canoneos450d.cfg b/photobooth/camera/models/canoneos450d.cfg new file mode 100644 index 00000000..44b4db2d --- /dev/null +++ b/photobooth/camera/models/canoneos450d.cfg @@ -0,0 +1,15 @@ +[Startup] +imageformat = Large Fine JPEG +imageformatsd = Large Fine JPEG +autopoweroff = 0 + +[Shutdown] +imageformat = RAW +imageformatsd = RAW +autopoweroff = 30 + +[Idle] +output = PC + +[Active] +output = PC From 6c506fc9116fe296cb9fb3df00d0419bdd5a67a2 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 9 Sep 2019 21:01:18 +0200 Subject: [PATCH 20/35] show boomerang-specific gui --- photobooth/StateMachine.py | 18 ++++++++++++++++-- photobooth/camera/models/canoneos450d.cfg | 1 + photobooth/gui/Qt5Gui/Frames.py | 12 ++++++++---- photobooth/gui/Qt5Gui/PyQt5Gui.py | 5 +++-- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/photobooth/StateMachine.py b/photobooth/StateMachine.py index fad2d9b6..6a234832 100644 --- a/photobooth/StateMachine.py +++ b/photobooth/StateMachine.py @@ -385,16 +385,22 @@ def handleEvent(self, event, context): elif ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and event.name == 'triggerVideo'): context.capturemode = CAPMODE_BOOMERANG - context.state = GreeterState() + context.state = GreeterState(gif=True) else: raise TypeError('Unknown Event type "{}"'.format(event)) class GreeterState(State): - def __init__(self): + def __init__(self, gif=None): super().__init__() + self._gif = gif + + @property + def gif(self): + + return self._gif def handleEvent(self, event, context): @@ -447,6 +453,14 @@ def capturemode(self): return self._capturemode + @property + def gif(self): + + gif = False + if self.capturemode == CAPMODE_BOOMERANG: + gif = True + return gif + def handleEvent(self, event, context): if isinstance(event, CameraEvent) and event.name == 'countdown': diff --git a/photobooth/camera/models/canoneos450d.cfg b/photobooth/camera/models/canoneos450d.cfg index 44b4db2d..55ec451d 100644 --- a/photobooth/camera/models/canoneos450d.cfg +++ b/photobooth/camera/models/canoneos450d.cfg @@ -9,6 +9,7 @@ imageformatsd = RAW autopoweroff = 30 [Idle] +# TODO Off "breaks" current video implementation output = PC [Active] diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index 4beb9ddc..c0573de6 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -108,7 +108,7 @@ def initFrame(self, trigger_action, trigger_boomerang_action): class GreeterMessage(QtWidgets.QFrame): - def __init__(self, num_x, num_y, skip, countdown_action): + def __init__(self, num_x, num_y, skip, countdown_action, boomerang=None): super().__init__() self.setObjectName('GreeterMessage') @@ -117,7 +117,9 @@ def __init__(self, num_x, num_y, skip, countdown_action): self._text_button = _('Start countdown') num_pictures = max(num_x * num_y - len(skip), 1) - if num_pictures > 1: + if boomerang: + self._text_label = _('for a Boomerang...') + elif num_pictures > 1: self._text_label = _('for {} pictures...').format(num_pictures) else: self._text_label = '' @@ -143,13 +145,15 @@ def initFrame(self, countdown_action): class CaptureMessage(QtWidgets.QFrame): - def __init__(self, num_picture, num_x, num_y, skip): + def __init__(self, num_picture, num_x, num_y, skip, boomerang=None): super().__init__() self.setObjectName('PoseMessage') num_pictures = max(num_x * num_y - len(skip), 1) - if num_pictures > 1: + if boomerang: + self._text = _('Taking a Boomerang...') + elif num_pictures > 1: self._text = _('Picture {} of {}...').format(num_picture, num_pictures) else: diff --git a/photobooth/gui/Qt5Gui/PyQt5Gui.py b/photobooth/gui/Qt5Gui/PyQt5Gui.py index fb14bc1d..084b45d3 100644 --- a/photobooth/gui/Qt5Gui/PyQt5Gui.py +++ b/photobooth/gui/Qt5Gui/PyQt5Gui.py @@ -186,7 +186,8 @@ def showGreeter(self, state): self._setWidget(Frames.GreeterMessage( *num_pic, skip, - lambda: self._comm.send(Workers.MASTER, GuiEvent('countdown')))) + lambda: self._comm.send(Workers.MASTER, GuiEvent('countdown')), + state.gif)) QtCore.QTimer.singleShot( greeter_time, lambda: self._comm.send(Workers.MASTER, GuiEvent('countdown'))) @@ -211,7 +212,7 @@ def showCapture(self, state): skip = [i for i in self._cfg.getIntList('Picture', 'skip') if 1 <= i and i <= num_pic[0] * num_pic[1]] self._setWidget(Frames.CaptureMessage(state.num_picture, *num_pic, - skip)) + skip, state.gif)) def showAssemble(self, state): From f2b5cfe8042cc1357dfc30cbec0a17e9189d2c56 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 00:21:47 +0200 Subject: [PATCH 21/35] instead of waiting, skip all except nth capture frame --- photobooth/camera/CameraInterface.py | 4 ---- photobooth/camera/__init__.py | 32 +++++++++++----------------- photobooth/defaults.cfg | 4 ++-- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/photobooth/camera/CameraInterface.py b/photobooth/camera/CameraInterface.py index e63992b1..f4f1a09a 100644 --- a/photobooth/camera/CameraInterface.py +++ b/photobooth/camera/CameraInterface.py @@ -97,10 +97,6 @@ def getPicture(self): raise NotImplementedError() - def getVideo(self): - - raise NotImplementedError() - def _initConfig(self): self._cfg = configparser.ConfigParser(interpolation=None) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 5454f231..02c9dc4f 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -57,7 +57,7 @@ def __init__(self, config, comm, CameraModule): self._gif_num_frames = self._cfg.getInt('GIF', 'num_frames') self._gif_num_img_to_take = ((self._gif_num_frames - 2) // 2) + 2 self._gif_frame_duration = self._cfg.getInt('GIF', 'frame_duration') - self._gif_wait_s_between_cap = self._cfg.getInt('GIF', 'ms_between_capture') / 1000 + self._gif_use_nth_capture = self._cfg.getInt('GIF', 'use_nth_capture') rot_vals = {0: None, 90: Image.ROTATE_90, 180: Image.ROTATE_180, 270: Image.ROTATE_270} @@ -179,29 +179,21 @@ def capturePicture(self, state): def captureVideo(self, state): logging.debug('entering boomerang capture') - # TODO handle number_pictures = 0 - self.setIdle() + # self.setIdle() + counter = 0 while number_pictures < self._gif_num_img_to_take: - self.setIdle() # TODO use other method to get new image (capture is "too" fast) - # TODO select preview or picture picture = self._cap.getPreview() - # picture = self._cap.getPicture() - number_pictures += 1 - if self._rotation is not None: - picture = picture.transpose(self._rotation) - byte_data = BytesIO() - picture.save(byte_data, format='jpeg') - self._pictures.append(byte_data) - - self.setActive() - # if self._is_keep_pictures: - # self._comm.send(Workers.WORKER, - # StateMachine.CameraEvent('capture', byte_data)) - time.sleep(self._gif_wait_s_between_cap) - # TODO timing of capture - self.setActive() + if counter % self._gif_use_nth_capture == 0: + # skip images inbetween + number_pictures += 1 + if self._rotation is not None: + picture = picture.transpose(self._rotation) + byte_data = BytesIO() + picture.save(byte_data, format='jpeg') + self._pictures.append(byte_data) + counter += 1 self._comm.send(Workers.MASTER, StateMachine.CameraEvent('assemble')) diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index 325f037c..9f11e432 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -90,8 +90,8 @@ background = num_frames = 12 # duration of one frame (in milliseconds) frame_duration = 100 -# wait time in ms between the capture of two frames -ms_between_capture = 100 +# use every n-th frame from the captured stream +use_nth_capture = 5 [Storage] # Basedir of output pictures From daa71131d69d898ca4d3648519479ffe378b0193 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 00:54:29 +0200 Subject: [PATCH 22/35] better handle filenames for gifs --- photobooth/camera/__init__.py | 2 +- photobooth/gui/Qt5Gui/PyQt5Gui.py | 4 ++-- photobooth/worker/PictureList.py | 13 ++++++++++--- photobooth/worker/PictureSaver.py | 7 +------ photobooth/worker/__init__.py | 11 ++++++++--- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 02c9dc4f..4cb9e656 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -240,5 +240,5 @@ def assembleGIF(self): byte_data_gif = BytesIO() picture[0].save(byte_data_gif, format='GIF', append_images=picture[1:], save_all=True, duration=self._gif_frame_duration, loop=0) self._comm.send(Workers.MASTER, - StateMachine.CameraEvent('review', byte_data, byte_data_gif)) + StateMachine.CameraEvent('review', byte_data_gif, True)) self._pictures = [] diff --git a/photobooth/gui/Qt5Gui/PyQt5Gui.py b/photobooth/gui/Qt5Gui/PyQt5Gui.py index 084b45d3..1cccd088 100644 --- a/photobooth/gui/Qt5Gui/PyQt5Gui.py +++ b/photobooth/gui/Qt5Gui/PyQt5Gui.py @@ -222,11 +222,11 @@ def showReview(self, state): if state.gif: review_time = self._cfg.getInt('Photobooth', 'display_time') * 1000 - self._setWidget(Frames.GIFMessage(state.gif)) + self._setWidget(Frames.GIFMessage(state.picture)) QtCore.QTimer.singleShot( review_time, lambda: self._comm.send(Workers.MASTER, GuiEvent('postprocess'))) - picture = Image.open(state.gif) + picture = Image.open(state.picture) self._picture = ImageQt.ImageQt(picture) self._postprocess.do(self._picture) else: diff --git a/photobooth/worker/PictureList.py b/photobooth/worker/PictureList.py index 50b5b83d..3b813dfa 100644 --- a/photobooth/worker/PictureList.py +++ b/photobooth/worker/PictureList.py @@ -29,14 +29,21 @@ class PictureList: of taken and previously existing pictures. """ - def __init__(self, basename): + def __init__(self, basename, suffix=None): """Initialize filenames to the given basename and search for existing files. Set the counter accordingly. """ # Set basename and suffix self._basename = basename - self.suffix = '.jpg' + if suffix: + if (suffix is not 'jpg') and (suffix is not 'gif'): + raise TypeError('suffix must be "jpg" or "gif"') + self.suffix_raw = suffix + else: + self.suffix_raw = 'jpg' + self.suffix = '.{}'.format(self.suffix_raw) + self.count_width = 5 self.findExistingFiles() @@ -60,7 +67,7 @@ def findExistingFiles(self): # Print initial infos logging.info('Number of last existing file: %d', self.counter) logging.info('Saving pictures as "%s%s.%s"', self.basename, - self.count_width * 'X', 'jpg') + self.count_width * 'X', self.suffix_raw) @property def basename(self): diff --git a/photobooth/worker/PictureSaver.py b/photobooth/worker/PictureSaver.py index 71254441..a84c1216 100644 --- a/photobooth/worker/PictureSaver.py +++ b/photobooth/worker/PictureSaver.py @@ -34,13 +34,8 @@ def __init__(self, basename): if not os.path.exists(dirname): os.makedirs(dirname) - def do(self, picture, filename, gif=None): + def do(self, picture, filename): logging.info('Saving picture as %s', filename) with open(filename, 'wb') as f: f.write(picture.getbuffer()) - if gif: - gifname = "{file}.gif".format(file=filename) - logging.info('Saving GIF as {}'.format(gifname)) - with open(gifname, 'wb') as f: - f.write(gif.getbuffer()) diff --git a/photobooth/worker/__init__.py b/photobooth/worker/__init__.py index 770b5df1..b2549c08 100644 --- a/photobooth/worker/__init__.py +++ b/photobooth/worker/__init__.py @@ -41,6 +41,8 @@ def __init__(self, config, comm): config.get('Storage', 'basename')) basename = strftime(path, localtime()) self._pic_list = PictureList(basename) + # Picture list for assembled gifs + self._gif_list = PictureList(basename, 'gif') # Picture list for individual shots path = os.path.join(config.get('Storage', 'basedir'), @@ -85,7 +87,10 @@ def handleState(self, state): if isinstance(state, StateMachine.TeardownState): self.teardown(state) elif isinstance(state, StateMachine.ReviewState): - self.doPostprocessTasks(state.picture, self._pic_list.getNext(), state.gif) + if state.gif: + self.doPostprocessTasks(state.picture, self._gif_list.getNext()) + else: + self.doPostprocessTasks(state.picture, self._pic_list.getNext()) elif isinstance(state, StateMachine.CameraEvent): if state.name == 'capture': self.doPictureTasks(state.picture, self._shot_list.getNext()) @@ -96,10 +101,10 @@ def teardown(self, state): pass - def doPostprocessTasks(self, picture, filename, gif=None): + def doPostprocessTasks(self, picture, filename): for task in self._postprocess_tasks: - task.do(picture, filename, gif) + task.do(picture, filename) def doPictureTasks(self, picture, filename): From cd0a39aa1e2d888c919aea0177bbfb0db9dad24e Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 00:58:27 +0200 Subject: [PATCH 23/35] remove dead code --- photobooth/camera/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 4cb9e656..914c82c3 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -218,18 +218,7 @@ def assembleGIF(self): self.setIdle() - # TODO make something like a "best of" for static output, maybe (depending on output) first, last, in between - picture = self._template.copy() - for i in range(self._pic_dims.totalNumPictures): - shot = Image.open(self._pictures[i]) - resized = shot.resize(self._pic_dims.thumbnailSize) - picture.paste(resized, self._pic_dims.thumbnailOffset[i]) - - byte_data = BytesIO() - picture.save(byte_data, format='jpeg') - picture = [] - # TODO adapt to number of frames (scale automatically) and make number of frames configurable for i in range(self._gif_num_img_to_take): logging.debug("appending frame {}".format(i)) picture.append(Image.open(self._pictures[i])) From b7196b15bdd360a320ffdacc28f15961e542c7f0 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 01:06:23 +0200 Subject: [PATCH 24/35] keep gif stills if configured --- photobooth/camera/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 914c82c3..ae2b7389 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -193,6 +193,9 @@ def captureVideo(self, state): byte_data = BytesIO() picture.save(byte_data, format='jpeg') self._pictures.append(byte_data) + if self._is_keep_pictures: + self._comm.send(Workers.WORKER, + StateMachine.CameraEvent('capture', byte_data)) counter += 1 self._comm.send(Workers.MASTER, From 73270d71f8c2ed649fdad1c36220329814e59f47 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 01:08:39 +0200 Subject: [PATCH 25/35] cleanup --- photobooth/camera/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index ae2b7389..8b16accf 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -181,7 +181,6 @@ def captureVideo(self, state): logging.debug('entering boomerang capture') number_pictures = 0 - # self.setIdle() counter = 0 while number_pictures < self._gif_num_img_to_take: picture = self._cap.getPreview() From 7070a3f2a24c93bd53d03730c27c67c09dfce616 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 01:29:34 +0200 Subject: [PATCH 26/35] call save with optimize --- photobooth/camera/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 8b16accf..c55bbacb 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -229,7 +229,9 @@ def assembleGIF(self): picture.append(Image.open(self._pictures[i])) byte_data_gif = BytesIO() - picture[0].save(byte_data_gif, format='GIF', append_images=picture[1:], save_all=True, duration=self._gif_frame_duration, loop=0) + picture[0].save(byte_data_gif, format='GIF', append_images=picture[1:], + save_all=True, optimize=True, duration=self._gif_frame_duration, + loop=0) self._comm.send(Workers.MASTER, StateMachine.CameraEvent('review', byte_data_gif, True)) self._pictures = [] From f81e6a23bceef1a12c83dfeb245ec05067a85810 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 12:07:09 +0200 Subject: [PATCH 27/35] adapt configs for camera eos 450d --- photobooth/camera/__init__.py | 2 +- photobooth/camera/models/canoneos450d.cfg | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index c55bbacb..63e9aabd 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -224,7 +224,7 @@ def assembleGIF(self): for i in range(self._gif_num_img_to_take): logging.debug("appending frame {}".format(i)) picture.append(Image.open(self._pictures[i])) - for i in range((self._gif_num_frames - self._gif_num_img_to_take), 0, -1 ): + for i in range((self._gif_num_frames - self._gif_num_img_to_take), 0, -1): logging.debug("appending frame {}".format(i)) picture.append(Image.open(self._pictures[i])) diff --git a/photobooth/camera/models/canoneos450d.cfg b/photobooth/camera/models/canoneos450d.cfg index 55ec451d..7b4e0ea0 100644 --- a/photobooth/camera/models/canoneos450d.cfg +++ b/photobooth/camera/models/canoneos450d.cfg @@ -2,15 +2,18 @@ imageformat = Large Fine JPEG imageformatsd = Large Fine JPEG autopoweroff = 0 +whitebalance = Shadow +picturestyle = Neutral [Shutdown] -imageformat = RAW -imageformatsd = RAW +imageformat = RAW + Large Fine JPEG +imageformatsd = RAW + Large Fine JPEG autopoweroff = 30 +whitebalance = Auto +picturestyle = Standard [Idle] -# TODO Off "breaks" current video implementation -output = PC +output = Off [Active] -output = PC +output = PC \ No newline at end of file From 860e846e7d5876cf2deca7c6bdadd54ebb2d530b Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 13:05:14 +0200 Subject: [PATCH 28/35] improve debug output --- photobooth/gui/Qt5Gui/Frames.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index c0573de6..fcfaacd5 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -227,10 +227,8 @@ def initFrame(self, gif): self.movie.setCacheMode(QtGui.QMovie.CacheAll) self.movie_screen.setMovie(self.movie) self.movie_screen.setScaledContents(False) + logging.debug("Return value of movie.isValid: {}".format(self.movie.isValid())) # self.movie.setBackgroundColor(Qt.QColor(0, 0, 255, 127)) - # print(self.movie.isValid()) - # print(self.movie.loopCount()) - # print(self.movie.state()) self.movie.start() From 73486da7c914cb52bf5b4d26b441609685e106bb Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 13:17:24 +0200 Subject: [PATCH 29/35] add different postprocess lists for gif and jpeg --- photobooth/StateMachine.py | 10 ++++++++-- photobooth/gui/GuiPostprocessor.py | 20 +++++++++++++++----- photobooth/gui/Qt5Gui/PyQt5Gui.py | 4 ++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/photobooth/StateMachine.py b/photobooth/StateMachine.py index 6a234832..2dc37dc0 100644 --- a/photobooth/StateMachine.py +++ b/photobooth/StateMachine.py @@ -515,16 +515,22 @@ def gif(self): def handleEvent(self, event, context): if isinstance(event, GuiEvent) and event.name == 'postprocess': - context.state = PostprocessState() + context.state = PostprocessState(self.gif) else: raise TypeError('Unknown Event type "{}"'.format(event)) class PostprocessState(State): - def __init__(self): + def __init__(self, gif=None): super().__init__() + self._gif = gif + + @property + def gif(self): + + return self._gif def handleEvent(self, event, context): diff --git a/photobooth/gui/GuiPostprocessor.py b/photobooth/gui/GuiPostprocessor.py index 236d6d90..742ce6d8 100644 --- a/photobooth/gui/GuiPostprocessor.py +++ b/photobooth/gui/GuiPostprocessor.py @@ -28,7 +28,9 @@ def __init__(self, config): super().__init__() self._get_task_list = [] + self._get_task_list_gif = [] self._do_task_list = [] + self._do_task_list_gif = [] if config.getBool('Printer', 'enable'): module = config.get('Printer', 'module') @@ -42,13 +44,21 @@ def __init__(self, config): self._do_task_list.append( PrintPostprocess(module, paper_size, pdf)) - def get(self, picture): + def get(self, picture, gif=None): - return [task.get(picture) for task in self._get_task_list] + if gif: + tasklist = self._get_task_list_gif + else: + tasklist = self._get_task_list + return [task.get(picture) for task in tasklist] - def do(self, picture): + def do(self, picture, gif=None): - for task in self._do_task_list: + if gif: + tasklist = self._get_task_list_gif + else: + tasklist = self._get_task_list + for task in tasklist: task.get(picture).action() @@ -109,4 +119,4 @@ def __init__(self, printer_module, paper_size, is_pdf, **kwargs): def get(self, picture): - return PostprocessItem('Print', lambda: self._printer.print(picture)) + return PostprocessItem(_('Print'), lambda: self._printer.print(picture)) diff --git a/photobooth/gui/Qt5Gui/PyQt5Gui.py b/photobooth/gui/Qt5Gui/PyQt5Gui.py index 1cccd088..6e5b58ef 100644 --- a/photobooth/gui/Qt5Gui/PyQt5Gui.py +++ b/photobooth/gui/Qt5Gui/PyQt5Gui.py @@ -228,7 +228,7 @@ def showReview(self, state): lambda: self._comm.send(Workers.MASTER, GuiEvent('postprocess'))) picture = Image.open(state.picture) self._picture = ImageQt.ImageQt(picture) - self._postprocess.do(self._picture) + self._postprocess.do(self._picture, gif=True) else: picture = Image.open(state.picture) self._picture = ImageQt.ImageQt(picture) @@ -241,7 +241,7 @@ def showReview(self, state): def showPostprocess(self, state): - tasks = self._postprocess.get(self._picture) + tasks = self._postprocess.get(self._picture, state.gif) postproc_t = self._cfg.getInt('Photobooth', 'postprocess_time') Frames.PostprocessMessage( From 4fe1e3de1e7ea5d48f013466cbb7e1f93acb35c7 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 13:35:16 +0200 Subject: [PATCH 30/35] add settings for GIF --- photobooth/gui/Qt5Gui/Frames.py | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index fcfaacd5..c858c7bc 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -516,6 +516,7 @@ def createTabs(self): tabs.addTab(self.createPhotoboothSettings(), _('Photobooth')) tabs.addTab(self.createCameraSettings(), _('Camera')) tabs.addTab(self.createPictureSettings(), _('Picture')) + tabs.addTab(self.createGIFSettings(), _('GIF')) tabs.addTab(self.createStorageSettings(), _('Storage')) tabs.addTab(self.createGpioSettings(), _('GPIO')) tabs.addTab(self.createPrinterSettings(), _('Printer')) @@ -776,6 +777,43 @@ def file_dialog(): widget.setLayout(layout) return widget + def createGIFSettings(self): + + self.init('GIF') + + num_frames = QtWidgets.QSpinBox() + num_frames.setRange(1, 99) + num_frames.setValue(self._cfg.getInt('GIF', 'num_frames')) + self.add('GIF', 'num_frames', num_frames) + + frame_duration = QtWidgets.QSpinBox() + frame_duration.setRange(1, 9999) + frame_duration.setValue(self._cfg.getInt('GIF', 'frame_duration')) + self.add('GIF', 'frame_duration', frame_duration) + + use_nth_capture = QtWidgets.QSpinBox() + use_nth_capture.setRange(1, 9999) + use_nth_capture.setValue(self._cfg.getInt('GIF', 'use_nth_capture')) + self.add('GIF', 'use_nth_capture', use_nth_capture) + + lay_num = QtWidgets.QHBoxLayout() + lay_num.addWidget(num_frames) + + lay_duration = QtWidgets.QHBoxLayout() + lay_duration.addWidget(frame_duration) + + lay_use_nth = QtWidgets.QHBoxLayout() + lay_use_nth.addWidget(use_nth_capture) + + layout = QtWidgets.QFormLayout() + layout.addRow(_('Number of frames in final GIF:'), lay_num) + layout.addRow(_('Duration of one frame in final GIF:'), lay_duration) + layout.addRow(_('Use every nth image from capture:'), lay_use_nth) + + widget = QtWidgets.QWidget() + widget.setLayout(layout) + return widget + def createStorageSettings(self): self.init('Storage') @@ -811,6 +849,7 @@ def directory_dialog(): widget.setLayout(layout) return widget + def createGpioSettings(self): self.init('Gpio') @@ -1059,6 +1098,10 @@ def storeConfigAndRestart(self): self._cfg.set('Picture', 'background', self.get('Picture', 'background').text()) + self._cfg.set('GIF', 'num_frames', self.get('GIF', 'num_frames').text()) + self._cfg.set('GIF', 'frame_duration', self.get('GIF', 'frame_duration').text()) + self._cfg.set('GIF', 'use_nth_capture', self.get('GIF', 'use_nth_capture').text()) + self._cfg.set('Storage', 'basedir', self.get('Storage', 'basedir').text()) self._cfg.set('Storage', 'basename', From d1318040b7b4860a1e46d8af13f31fc0fc96708c Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 15:21:00 +0200 Subject: [PATCH 31/35] clean up and adapt dark large layout --- photobooth/gui/Qt5Gui/Frames.py | 4 +--- .../gui/Qt5Gui/stylesheets/dark-1024x600.qss | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index c858c7bc..3cd8e447 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -209,18 +209,17 @@ def __init__(self, gif): def initFrame(self, gif): + # make sure that we are at start of stream and load into movie gif.seek(0) self.a = QtCore.QByteArray.fromRawData(gif.getvalue()) self.b = QtCore.QBuffer(self.a) self.b.open(QtCore.QIODevice.ReadOnly) self.movie = QtGui.QMovie(self.b, b'gif', self) - # self.movie = QtGui.QMovie('../photos/2019-09-03/photobooth00049.jpg.gif') size = self.movie.scaledSize() self.setGeometry(200, 200, size.width(), size.height()) self.movie_screen = QtWidgets.QLabel('GIF') self.movie_screen.setSizePolicy(Qt.QSizePolicy.MinimumExpanding, Qt.QSizePolicy.MinimumExpanding) - #self.movie_screen.setAlignment(Qt.AlignCenter) lay = QtWidgets.QVBoxLayout() lay.addWidget(self.movie_screen) self.setLayout(lay) @@ -228,7 +227,6 @@ def initFrame(self, gif): self.movie_screen.setMovie(self.movie) self.movie_screen.setScaledContents(False) logging.debug("Return value of movie.isValid: {}".format(self.movie.isValid())) - # self.movie.setBackgroundColor(Qt.QColor(0, 0, 255, 127)) self.movie.start() diff --git a/photobooth/gui/Qt5Gui/stylesheets/dark-1024x600.qss b/photobooth/gui/Qt5Gui/stylesheets/dark-1024x600.qss index 6f4148e8..b3db663f 100644 --- a/photobooth/gui/Qt5Gui/stylesheets/dark-1024x600.qss +++ b/photobooth/gui/Qt5Gui/stylesheets/dark-1024x600.qss @@ -32,7 +32,7 @@ QPushButton:pressed { QFrame#IdleMessage { background-image: url(photobooth/gui/Qt5Gui/images/arrow-1024x600.png); background-repeat: no-repeat; - padding: 80px 400px 120px 80px; + padding: 40px 180px 40px 60px; } QFrame#IdleMessage QLabel { @@ -43,7 +43,7 @@ QFrame#IdleMessage QLabel { QFrame#IdleMessage QPushButton { border: none; color: rgba(255, 27, 0, 200); - font-size: 200px; + font-size: 180px; text-align: center; } @@ -123,6 +123,19 @@ QFrame#PictureMessage { margin: 30px; } +/* GIF Screen */ + +QFrame#GIFMessage { + margin: 30px; +} + +QFrame#GIFMessage QLabel { + font-size: 160px; + qproperty-alignment: AlignCenter; +} + + + /* Overlay message */ QWidget#TransparentOverlay { From 9e6c5606e8db4a44529a70b6f59ec3b19e4c0aac Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 15:40:23 +0200 Subject: [PATCH 32/35] allow GIF mode to be disabled, standard is off --- photobooth/defaults.cfg | 2 ++ photobooth/gui/Qt5Gui/Frames.py | 21 ++++++++++++++++----- photobooth/gui/Qt5Gui/PyQt5Gui.py | 12 +++++++++--- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index 9f11e432..9985e834 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -86,6 +86,8 @@ skip = background = [GIF] +# Enable/disable GIF mode +enable = False # Number of frames num_frames = 12 # duration of one frame (in milliseconds) diff --git a/photobooth/gui/Qt5Gui/Frames.py b/photobooth/gui/Qt5Gui/Frames.py index 3cd8e447..f43c22bc 100644 --- a/photobooth/gui/Qt5Gui/Frames.py +++ b/photobooth/gui/Qt5Gui/Frames.py @@ -80,7 +80,7 @@ def initFrame(self, start_action, set_date_action, settings_action, class IdleMessage(QtWidgets.QFrame): - def __init__(self, trigger_action, trigger_boomerang_action): + def __init__(self, trigger_action, trigger_boomerang_action=None): super().__init__() self.setObjectName('IdleMessage') @@ -91,18 +91,20 @@ def __init__(self, trigger_action, trigger_boomerang_action): self.initFrame(trigger_action, trigger_boomerang_action) - def initFrame(self, trigger_action, trigger_boomerang_action): + def initFrame(self, trigger_action, trigger_boomerang_action=None): lbl = QtWidgets.QLabel(self._message_label) btn = QtWidgets.QPushButton(self._message_button) - btn_boomerang = QtWidgets.QPushButton(self._message_boomerang) btn.clicked.connect(trigger_action) - btn_boomerang.clicked.connect(trigger_boomerang_action) + if trigger_boomerang_action: + btn_boomerang = QtWidgets.QPushButton(self._message_boomerang) + btn_boomerang.clicked.connect(trigger_boomerang_action) lay = QtWidgets.QVBoxLayout() lay.addWidget(lbl) lay.addWidget(btn) - lay.addWidget(btn_boomerang) + if trigger_boomerang_action: + lay.addWidget(btn_boomerang) self.setLayout(lay) @@ -779,6 +781,10 @@ def createGIFSettings(self): self.init('GIF') + enable = QtWidgets.QCheckBox() + enable.setChecked(self._cfg.getBool('GIF', 'enable')) + self.add('GIF', 'enable', enable) + num_frames = QtWidgets.QSpinBox() num_frames.setRange(1, 99) num_frames.setValue(self._cfg.getInt('GIF', 'num_frames')) @@ -794,6 +800,9 @@ def createGIFSettings(self): use_nth_capture.setValue(self._cfg.getInt('GIF', 'use_nth_capture')) self.add('GIF', 'use_nth_capture', use_nth_capture) + lay_enable = QtWidgets.QHBoxLayout() + lay_enable.addWidget(enable) + lay_num = QtWidgets.QHBoxLayout() lay_num.addWidget(num_frames) @@ -804,6 +813,7 @@ def createGIFSettings(self): lay_use_nth.addWidget(use_nth_capture) layout = QtWidgets.QFormLayout() + layout.addRow(_('Enable:'), lay_enable) layout.addRow(_('Number of frames in final GIF:'), lay_num) layout.addRow(_('Duration of one frame in final GIF:'), lay_duration) layout.addRow(_('Use every nth image from capture:'), lay_use_nth) @@ -1096,6 +1106,7 @@ def storeConfigAndRestart(self): self._cfg.set('Picture', 'background', self.get('Picture', 'background').text()) + self._cfg.set('GIF', 'enable', str(self.get('GIF', 'enable').isChecked())) self._cfg.set('GIF', 'num_frames', self.get('GIF', 'num_frames').text()) self._cfg.set('GIF', 'frame_duration', self.get('GIF', 'frame_duration').text()) self._cfg.set('GIF', 'use_nth_capture', self.get('GIF', 'use_nth_capture').text()) diff --git a/photobooth/gui/Qt5Gui/PyQt5Gui.py b/photobooth/gui/Qt5Gui/PyQt5Gui.py index 6e5b58ef..cde999cb 100644 --- a/photobooth/gui/Qt5Gui/PyQt5Gui.py +++ b/photobooth/gui/Qt5Gui/PyQt5Gui.py @@ -53,6 +53,8 @@ def __init__(self, argv, config, comm): self._picture = None self._postprocess = GuiPostprocessor(self._cfg) + self._is_gif_enabled = self._cfg.getBool('GIF', 'enable') + def run(self): exit_code = self._app.exec_() @@ -169,9 +171,13 @@ def showIdle(self, state): self._enableEscape() self._enableTrigger() - self._setWidget(Frames.IdleMessage( - lambda: self._comm.send(Workers.MASTER, GuiEvent('trigger')), - lambda: self._comm.send(Workers.MASTER, GuiEvent('triggerVideo')))) + if self._is_gif_enabled: + self._setWidget(Frames.IdleMessage( + lambda: self._comm.send(Workers.MASTER, GuiEvent('trigger')), + lambda: self._comm.send(Workers.MASTER, GuiEvent('triggerVideo')))) + else: + self._setWidget(Frames.IdleMessage( + lambda: self._comm.send(Workers.MASTER, GuiEvent('trigger')))) def showGreeter(self, state): From fa02dfb8631f3950165627df0dcf7ae5d058ca95 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 15:46:21 +0200 Subject: [PATCH 33/35] remove unused import --- photobooth/camera/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/photobooth/camera/__init__.py b/photobooth/camera/__init__.py index 63e9aabd..4fa7588c 100644 --- a/photobooth/camera/__init__.py +++ b/photobooth/camera/__init__.py @@ -18,7 +18,6 @@ # along with this program. If not, see . import logging -import time from PIL import Image, ImageOps from io import BytesIO From 3b532313a43fec4b5d17ea908ca3cbbc78f0e6b1 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 10 Sep 2019 16:12:10 +0200 Subject: [PATCH 34/35] update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa433eeb..3fa59d9c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ This is a Python application to build your own photobooth. * Support for external buttons and lamps via GPIO interface * Rudimentary WebDAV upload functionality (saves pictures to WebDAV storage) and mailer feature (mails pictures to a fixed email address) * Theming support using [Qt stylesheets](https://doc.qt.io/qt-5/stylesheet-syntax.html) +* Ability to take a short Boomerang-GIF (looping forwards and backwards) with the Live preview ### Screenshots Screenshots produced using `CameraDummy` that produces unicolor images. @@ -38,7 +39,7 @@ Screenshots produced using `CameraDummy` that produces unicolor images. * Based on [Python 3](https://www.python.org/), [Pillow](https://pillow.readthedocs.io), and [Qt5](https://www.qt.io/developers/) ### History -I started this project for my own wedding in 2015. +I started this project for my own wedding in 2015. See [Version 0.1](https://github.com/reuterbal/photobooth/tree/v0.1) for the original version. Github user [hackerb9](https://github.com/hackerb9/photobooth) forked this version and added a print functionality. However, I was not happy with the original software design and the limited options provided by the previously used [pygame](https://www.pygame.org) GUI library and thus abandoned the original version. From 2bb51ccddcc5c82eef56a584493dc60418c3f89a Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 12 Sep 2019 21:31:50 +0200 Subject: [PATCH 35/35] fix wrong task list in do --- photobooth/gui/GuiPostprocessor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/photobooth/gui/GuiPostprocessor.py b/photobooth/gui/GuiPostprocessor.py index 742ce6d8..019e3fb5 100644 --- a/photobooth/gui/GuiPostprocessor.py +++ b/photobooth/gui/GuiPostprocessor.py @@ -55,9 +55,9 @@ def get(self, picture, gif=None): def do(self, picture, gif=None): if gif: - tasklist = self._get_task_list_gif + tasklist = self._do_task_list_gif else: - tasklist = self._get_task_list + tasklist = self._do_task_list for task in tasklist: task.get(picture).action()