Plenty of configuration options added to config file and settings dialog

This commit is contained in:
Balthasar Reuter
2018-04-03 23:04:11 +02:00
parent dc57fd26b5
commit 90ec84e286
5 changed files with 200 additions and 85 deletions

View File

@@ -10,7 +10,7 @@ class Config:
self._filename = filename self._filename = filename
self._cfg = configparser.ConfigParser() self._cfg = configparser.ConfigParser(interpolation=None)
self.defaults() self.defaults()
self.read() self.read()

View File

@@ -4,6 +4,7 @@
from Config import Config from Config import Config
from PictureList import PictureList from PictureList import PictureList
from PictureDimensions import PictureDimensions
import Gui import Gui
from PyQt5Gui import PyQt5Gui from PyQt5Gui import PyQt5Gui
@@ -17,27 +18,29 @@ from multiprocessing import Pipe, Process
from time import time, sleep, localtime, strftime from time import time, sleep, localtime, strftime
output_size = (1920, 1080)
min_distance = (10, 10)
# num_pictures = (2, 2)
pose_time = 2
picture_basename = strftime('%Y-%m-%d/photobooth', localtime())
class Photobooth: class Photobooth:
def __init__(self, config): def __init__(self, config):
picture_basename = strftime(config.get('Picture', 'basename'), localtime())
self._cap = Camera() self._cap = Camera()
self._cfg = config self._pic_dims = PictureDimensions(config, self._cap.getPicture().size)
self._pic_list = PictureList(picture_basename)
if ( self._cfg.getBool('Photobooth', 'show_preview') self._pose_time = config.getInt('Photobooth', 'pose_time')
self._countdown_time = config.getInt('Photobooth', 'countdown_time')
self._display_time = config.getInt('Photobooth', 'display_time')
if ( config.getBool('Photobooth', 'show_preview')
and self._cap.hasPreview ): and self._cap.hasPreview ):
self.showCounter = self.showCounterPreview self._show_counter = self.showCounterPreview
else: else:
self.showCounter = self.showCounterNoPreview self._show_counter = self.showCounterNoPreview
self._get_next_filename = self._pic_list.getNext
self.numPictures = ( self._cfg.getInt('Photobooth', 'num_pictures_x') ,
self._cfg.getInt('Photobooth', 'num_pictures_y') )
@property @property
def getNextFilename(self): def getNextFilename(self):
@@ -45,48 +48,34 @@ class Photobooth:
return self._get_next_filename return self._get_next_filename
@getNextFilename.setter
def getNextFilename(self, func):
if not callable(func):
raise ValueError('getNextFilename must be callable')
self._get_next_filename = func
@property @property
def showCounter(self): def showCounter(self):
return self._show_counter return self._show_counter
@showCounter.setter @property
def showCounter(self, func): def poseTime(self):
if not callable(func): return self._pose_time
raise ValueError('showCounter must be callable')
self._show_counter = func
@property @property
def numPictures(self): def countdownTime(self):
return self._num_pictures return self._countdown_time
@numPictures.setter @property
def numPictures(self, num_pictures): def displayTime(self):
if len(num_pictures) != 2: return self._display_time
raise ValueError('num_pictures must have two entries')
self._num_pictures = num_pictures
def run(self, send, recv): def run(self, send, recv):
self._send = send self._send = send
self.setCameraIdle()
while True: while True:
try: try:
@@ -118,16 +107,16 @@ class Photobooth:
tic, toc = time(), 0 tic, toc = time(), 0
while toc < pose_time: while toc < self.countdownTime:
self._send.send( Gui.PreviewState( self._send.send( Gui.PreviewState(
message = str(pose_time - int(toc)), message = str(self.countdownTime - int(toc)),
picture = ImageOps.mirror(self._cap.getPreview()) ) ) picture = ImageOps.mirror(self._cap.getPreview()) ) )
toc = time() - tic toc = time() - tic
def showCounterNoPreview(self): def showCounterNoPreview(self):
for i in range(pose_time): for i in range(self.countdownTime):
self._send.send( Gui.PreviewState(str(i)) ) self._send.send( Gui.PreviewState(str(i)) )
sleep(1) sleep(1)
@@ -141,37 +130,18 @@ class Photobooth:
def assemblePictures(self, pictures): def assemblePictures(self, pictures):
# TODO: determine sizes only once output_image = Image.new('RGB', self._pic_dims.outputSize, (255, 255, 255))
picture_size = pictures[0].size
resize_factor = min( ( ( for i in range(self._pic_dims.totalNumPictures):
( output_size[i] - (self.numPictures[i] + 1) * min_distance[i] ) / output_image.paste(pictures[i].resize(self._pic_dims.thumbnailSize),
( self.numPictures[i] * picture_size[i]) ) for i in range(2) ) ) self._pic_dims.thumbnailOffset[i])
output_picture_size = tuple( int(picture_size[i] * resize_factor)
for i in range(2) )
output_picture_dist = tuple( ( output_size[i] - self.numPictures[i] *
output_picture_size[i] ) // (self.numPictures[i] + 1)
for i in range(2) )
output_image = Image.new('RGB', output_size, (255, 255, 255))
idx = 0
for img in pictures:
pos = (idx % self.numPictures[0], idx // self.numPictures[0])
img = img.resize(output_picture_size)
offset = tuple( (pos[i] + 1) * output_picture_dist[i] +
pos[i] * output_picture_size[i] for i in range(2) )
output_image.paste(img, offset)
idx += 1
return output_image return output_image
def capturePictures(self): def capturePictures(self):
pictures = [self.captureSinglePicture() pictures = [ self.captureSinglePicture() for _ in range(self._pic_dims.totalNumPictures) ]
for i in range(2) for _ in range(self.numPictures[i])]
return self.assemblePictures(pictures) return self.assemblePictures(pictures)
@@ -180,7 +150,7 @@ class Photobooth:
self._send.send(Gui.PoseState()) self._send.send(Gui.PoseState())
self.setCameraActive() self.setCameraActive()
sleep(2) sleep(self.poseTime)
img = self.capturePictures() img = self.capturePictures()
img.save(self.getNextFilename(), 'JPEG') img.save(self.getNextFilename(), 'JPEG')
@@ -188,18 +158,14 @@ class Photobooth:
self.setCameraIdle() self.setCameraIdle()
sleep(5) sleep(self.displayTime)
self._send.send(Gui.IdleState()) self._send.send(Gui.IdleState())
def main_photobooth(config, send, recv): def main_photobooth(config, send, recv):
picture_list = PictureList(picture_basename)
photobooth = Photobooth(config) photobooth = Photobooth(config)
photobooth.getNextFilename = picture_list.getNext
return photobooth.run(send, recv) return photobooth.run(send, recv)

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class PictureDimensions:
def __init__(self, config, capture_size):
self._num_pictures = ( config.getInt('Picture', 'num_x') ,
config.getInt('Picture', 'num_y') )
self._capture_size = capture_size
self._output_size = ( config.getInt('Picture', 'size_x') ,
config.getInt('Picture', 'size_y') )
self._min_distance = ( config.getInt('Picture', 'min_dist_x') ,
config.getInt('Picture', 'min_dist_y') )
self.computeThumbnailDimensions()
def computeThumbnailDimensions(self):
resize_factor = min( ( (
( self.outputSize[i] - (self.numPictures[i] + 1) * self.minDistance[i] ) /
( self.numPictures[i] * self.captureSize[i]) ) for i in range(2) ) )
self._thumb_size = tuple( int(self.captureSize[i] * resize_factor)
for i in range(2) )
output_picture_dist = tuple( ( self.outputSize[i] - self.numPictures[i] *
self.thumbnailSize[i] ) // (self.numPictures[i] + 1)
for i in range(2) )
self._thumb_offsets = []
for i in range(self.totalNumPictures):
pos = (i % self.numPictures[0], i // self.numPictures[0])
self._thumb_offsets.append( tuple(
(pos[j] + 1) * output_picture_dist[j] +
pos[j] * self.thumbnailSize[j] for j in range(2) ) )
@property
def numPictures(self):
return self._num_pictures
@property
def totalNumPictures(self):
return self._num_pictures[0] * self._num_pictures[1]
@property
def captureSize(self):
return self._capture_size
@property
def outputSize(self):
return self._output_size
@property
def minDistance(self):
return self._min_distance
@property
def thumbnailSize(self):
return self._thumb_size
@property
def thumbnailOffset(self):
return self._thumb_offsets

View File

@@ -59,8 +59,8 @@ class PyQt5Gui(Gui.Gui):
self.showIdle() self.showIdle()
elif isinstance(state, Gui.PoseState): elif isinstance(state, Gui.PoseState):
global cfg global cfg
num_pictures = ( cfg.getInt('Photobooth', 'num_pictures_x') * num_pictures = ( cfg.getInt('Picture', 'num_x') *
cfg.getInt('Photobooth', 'num_pictures_y') ) cfg.getInt('Picture', 'num_y') )
self._p.setCentralWidget( self._p.setCentralWidget(
PyQt5PictureMessage('Will capture {} pictures!'.format(num_pictures))) PyQt5PictureMessage('Will capture {} pictures!'.format(num_pictures)))
elif isinstance(state, Gui.PreviewState): elif isinstance(state, Gui.PreviewState):
@@ -259,6 +259,7 @@ class PyQt5Settings(QFrame):
grid.addWidget(self.createGpioSettings(), 1, 0) grid.addWidget(self.createGpioSettings(), 1, 0)
grid.addWidget(self.createCameraSettings(), 0, 1) grid.addWidget(self.createCameraSettings(), 0, 1)
grid.addWidget(self.createPhotoboothSettings(), 1, 1) grid.addWidget(self.createPhotoboothSettings(), 1, 1)
grid.addWidget(self.createPictureSettings(), 2, 1)
layout = QVBoxLayout() layout = QVBoxLayout()
layout.addLayout(grid) layout.addLayout(grid)
@@ -303,8 +304,8 @@ class PyQt5Settings(QFrame):
layout = QFormLayout() layout = QFormLayout()
layout.addRow(self._value_widgets['Gpio']['enable']) layout.addRow(self._value_widgets['Gpio']['enable'])
layout.addRow(QLabel('Exit channel:'), self._value_widgets['Gpio']['exit_channel']) layout.addRow(QLabel('Exit channel:'), self._value_widgets['Gpio']['exit_channel'])
layout.addRow(QLabel('Trigger channel'), self._value_widgets['Gpio']['trigger_channel']) layout.addRow(QLabel('Trigger channel:'), self._value_widgets['Gpio']['trigger_channel'])
layout.addRow(QLabel('Lamp channel'), self._value_widgets['Gpio']['lamp_channel']) layout.addRow(QLabel('Lamp channel:'), self._value_widgets['Gpio']['lamp_channel'])
widget = QGroupBox('GPIO settings') widget = QGroupBox('GPIO settings')
widget.setLayout(layout) widget.setLayout(layout)
@@ -349,24 +350,64 @@ class PyQt5Settings(QFrame):
self._value_widgets['Photobooth']['show_preview'] = QCheckBox('Show preview while countdown (restart required)') self._value_widgets['Photobooth']['show_preview'] = QCheckBox('Show preview while countdown (restart required)')
if cfg.getBool('Photobooth', 'show_preview'): if cfg.getBool('Photobooth', 'show_preview'):
self._value_widgets['Photobooth']['show_preview'].toggle() self._value_widgets['Photobooth']['show_preview'].toggle()
self._value_widgets['Photobooth']['num_pictures_x'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_x')) self._value_widgets['Photobooth']['pose_time'] = QLineEdit(cfg.get('Photobooth', 'pose_time'))
self._value_widgets['Photobooth']['num_pictures_y'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_y')) self._value_widgets['Photobooth']['countdown_time'] = QLineEdit(cfg.get('Photobooth', 'countdown_time'))
self._value_widgets['Photobooth']['display_time'] = QLineEdit(cfg.get('Photobooth', 'display_time'))
layout = QFormLayout() layout = QFormLayout()
layout.addRow(self._value_widgets['Photobooth']['show_preview']) layout.addRow(self._value_widgets['Photobooth']['show_preview'])
layout.addRow(QLabel('Pose time [s]:'), self._value_widgets['Photobooth']['pose_time'])
sublayout = QHBoxLayout() layout.addRow(QLabel('Countdown time [s]:'), self._value_widgets['Photobooth']['countdown_time'])
sublayout.addWidget(QLabel('Number of pictures')) layout.addRow(QLabel('Display time [s]:'), self._value_widgets['Photobooth']['display_time'])
sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_x'])
sublayout.addWidget(QLabel('x'))
sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_y'])
layout.addRow(sublayout)
widget = QGroupBox('Photobooth settings') widget = QGroupBox('Photobooth settings')
widget.setLayout(layout) widget.setLayout(layout)
return widget return widget
def createPictureSettings(self):
global cfg
self._value_widgets['Picture'] = {}
self._value_widgets['Picture']['num_x'] = QLineEdit(cfg.get('Picture', 'num_x'))
self._value_widgets['Picture']['num_y'] = QLineEdit(cfg.get('Picture', 'num_y'))
self._value_widgets['Picture']['size_x'] = QLineEdit(cfg.get('Picture', 'size_x'))
self._value_widgets['Picture']['size_y'] = QLineEdit(cfg.get('Picture', 'size_y'))
self._value_widgets['Picture']['min_dist_x'] = QLineEdit(cfg.get('Picture', 'min_dist_x'))
self._value_widgets['Picture']['min_dist_y'] = QLineEdit(cfg.get('Picture', 'min_dist_y'))
self._value_widgets['Picture']['basename'] = QLineEdit(cfg.get('Picture', 'basename'))
layout = QFormLayout()
sublayout_num = QHBoxLayout()
sublayout_num.addWidget(QLabel('Number of shots per picture:'))
sublayout_num.addWidget(self._value_widgets['Picture']['num_x'])
sublayout_num.addWidget(QLabel('x'))
sublayout_num.addWidget(self._value_widgets['Picture']['num_y'])
layout.addRow(sublayout_num)
sublayout_size = QHBoxLayout()
sublayout_size.addWidget(QLabel('Size of assembled picture:'))
sublayout_size.addWidget(self._value_widgets['Picture']['size_x'])
sublayout_size.addWidget(QLabel('x'))
sublayout_size.addWidget(self._value_widgets['Picture']['size_y'])
layout.addRow(sublayout_size)
sublayout_dist = QHBoxLayout()
sublayout_dist.addWidget(QLabel('Min. distance between shots in picture:'))
sublayout_dist.addWidget(self._value_widgets['Picture']['min_dist_x'])
sublayout_dist.addWidget(QLabel('x'))
sublayout_dist.addWidget(self._value_widgets['Picture']['min_dist_y'])
layout.addRow(sublayout_dist)
layout.addRow(QLabel('Basename of output files:'), self._value_widgets['Picture']['basename'])
widget = QGroupBox('Picture settings')
widget.setLayout(layout)
return widget
def createButtons(self): def createButtons(self):
layout = QHBoxLayout() layout = QHBoxLayout()
@@ -406,8 +447,17 @@ class PyQt5Settings(QFrame):
cfg.set('Gpio', 'lamp_channel', self._value_widgets['Gpio']['lamp_channel'].text()) cfg.set('Gpio', 'lamp_channel', self._value_widgets['Gpio']['lamp_channel'].text())
cfg.set('Photobooth', 'show_preview', str(self._value_widgets['Photobooth']['show_preview'].isChecked())) cfg.set('Photobooth', 'show_preview', str(self._value_widgets['Photobooth']['show_preview'].isChecked()))
cfg.set('Photobooth', 'num_pictures_x', self._value_widgets['Photobooth']['num_pictures_x'].text()) cfg.set('Photobooth', 'pose_time', str(self._value_widgets['Photobooth']['pose_time'].text()))
cfg.set('Photobooth', 'num_pictures_y', self._value_widgets['Photobooth']['num_pictures_y'].text()) cfg.set('Photobooth', 'countdown_time', str(self._value_widgets['Photobooth']['countdown_time'].text()))
cfg.set('Photobooth', 'display_time', str(self._value_widgets['Photobooth']['display_time'].text()))
cfg.set('Picture', 'num_x', self._value_widgets['Picture']['num_x'].text())
cfg.set('Picture', 'num_y', self._value_widgets['Picture']['num_y'].text())
cfg.set('Picture', 'size_x', self._value_widgets['Picture']['size_x'].text())
cfg.set('Picture', 'size_y', self._value_widgets['Picture']['size_y'].text())
cfg.set('Picture', 'min_dist_x', self._value_widgets['Picture']['min_dist_x'].text())
cfg.set('Picture', 'min_dist_y', self._value_widgets['Picture']['min_dist_y'].text())
cfg.set('Picture', 'basename', self._value_widgets['Picture']['basename'].text())
wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ] wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ]
cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()]) cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()])

View File

@@ -23,7 +23,25 @@ lamp_channel = 4
[Photobooth] [Photobooth]
# Show preview while posing time (True/False) # Show preview while posing time (True/False)
show_preview = True show_preview = True
# Pose time in seconds (shown before countdown)
pose_time = 3
# Countdown length in seconds (shown before every shot)
countdown_time = 2
# Display time of assembled picture (shown after last shot)
display_time = 5
[Picture]
# Basename of output pictures
basename = %Y-%m-%d/photobooth
# Number of pictures in horizontal direction # Number of pictures in horizontal direction
num_pictures_x = 2 num_x = 2
# Number of pictures in vertical direction # Number of pictures in vertical direction
num_pictures_y = 2 num_y = 2
# Size of output picture in horizontal direction
size_x = 1920
# Size of output picture in vertical direction
size_y = 1080
# Minimum distance between thumbnails in horizontal direction
min_dist_x = 20
# Minimum distance between thumbnails in vertical direction
min_dist_y = 20