pibooth-template-chooser 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pibooth_template_chooser-1.0.0/LICENSE +21 -0
- pibooth_template_chooser-1.0.0/PKG-INFO +71 -0
- pibooth_template_chooser-1.0.0/README.md +47 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.egg-info/PKG-INFO +71 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.egg-info/SOURCES.txt +11 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.egg-info/dependency_links.txt +1 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.egg-info/entry_points.txt +2 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.egg-info/not-zip-safe +1 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.egg-info/requires.txt +1 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.egg-info/top_level.txt +1 -0
- pibooth_template_chooser-1.0.0/pibooth_template_chooser.py +359 -0
- pibooth_template_chooser-1.0.0/setup.cfg +4 -0
- pibooth_template_chooser-1.0.0/setup.py +22 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ceeeeb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pibooth_template_chooser
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Pibooth plugin letting guests choose the picture template on the wait screen
|
|
5
|
+
Home-page: https://github.com/ceeeeb/pibooth-template-chooser
|
|
6
|
+
Author: Ceeeeb
|
|
7
|
+
License: MIT license
|
|
8
|
+
Keywords: Raspberry Pi,photobooth,pibooth,template
|
|
9
|
+
Platform: unix
|
|
10
|
+
Platform: linux
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: pibooth-ceeeeb>=2.0.8.3
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: description
|
|
16
|
+
Dynamic: description-content-type
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: keywords
|
|
19
|
+
Dynamic: license
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: platform
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# pibooth-template-chooser
|
|
26
|
+
|
|
27
|
+
Pibooth plugin letting guests choose the picture template on the wait screen.
|
|
28
|
+
|
|
29
|
+
The wait screen shows the selected template in place of the last picture:
|
|
30
|
+
the first capture of the last picture assembled again in the single photo page
|
|
31
|
+
of the template, or grey photo holders until a picture exists. A discreet
|
|
32
|
+
caption at the top gives its name and position in the list. Guests change the
|
|
33
|
+
template by swiping horizontally on the screen, by tapping the `<` `>` arrows
|
|
34
|
+
of the caption, or with the arrow keys. The chosen template is used for the
|
|
35
|
+
next pictures; the saved and printed picture is not changed.
|
|
36
|
+
|
|
37
|
+
The templates are the draw.io files of the
|
|
38
|
+
[pibooth-picture-template](https://github.com/pibooth/pibooth-picture-template)
|
|
39
|
+
plugin, which must be installed (with `pip install --no-deps` to keep the
|
|
40
|
+
pibooth fork).
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
```ini
|
|
45
|
+
[TEMPLATE_CHOOSER]
|
|
46
|
+
# Directory of the picture templates guests choose from on the wait screen
|
|
47
|
+
directory = ~/.config/pibooth/templates
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Every `*.xml` file of the directory is offered. The template set in
|
|
51
|
+
`[PICTURE] template` is selected at startup when it belongs to the directory.
|
|
52
|
+
|
|
53
|
+
## Template style
|
|
54
|
+
|
|
55
|
+
A template may come with a style file of the same name (`hollywood.xml` and
|
|
56
|
+
`hollywood.cfg`) giving the name displayed in the caption and the style of the
|
|
57
|
+
texts, which replaces the `[PICTURE]` values while the template is selected:
|
|
58
|
+
|
|
59
|
+
```ini
|
|
60
|
+
[TEMPLATE]
|
|
61
|
+
name = Hollywood
|
|
62
|
+
text_colors = ((255, 255, 255), (230, 200, 205))
|
|
63
|
+
text_fonts = ('../fonts/Montserrat-Bold.ttf', '../fonts/Montserrat-SemiBold.ttf')
|
|
64
|
+
text_alignments = center
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Relative font paths are resolved from the style file directory. Without a
|
|
68
|
+
style file, the name comes from the file name and the `[PICTURE]` style is kept.
|
|
69
|
+
|
|
70
|
+
The texts themselves (`footer_text1`, `footer_text2`) stay in `pibooth.cfg`,
|
|
71
|
+
shared by all the templates of an event.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# pibooth-template-chooser
|
|
2
|
+
|
|
3
|
+
Pibooth plugin letting guests choose the picture template on the wait screen.
|
|
4
|
+
|
|
5
|
+
The wait screen shows the selected template in place of the last picture:
|
|
6
|
+
the first capture of the last picture assembled again in the single photo page
|
|
7
|
+
of the template, or grey photo holders until a picture exists. A discreet
|
|
8
|
+
caption at the top gives its name and position in the list. Guests change the
|
|
9
|
+
template by swiping horizontally on the screen, by tapping the `<` `>` arrows
|
|
10
|
+
of the caption, or with the arrow keys. The chosen template is used for the
|
|
11
|
+
next pictures; the saved and printed picture is not changed.
|
|
12
|
+
|
|
13
|
+
The templates are the draw.io files of the
|
|
14
|
+
[pibooth-picture-template](https://github.com/pibooth/pibooth-picture-template)
|
|
15
|
+
plugin, which must be installed (with `pip install --no-deps` to keep the
|
|
16
|
+
pibooth fork).
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
```ini
|
|
21
|
+
[TEMPLATE_CHOOSER]
|
|
22
|
+
# Directory of the picture templates guests choose from on the wait screen
|
|
23
|
+
directory = ~/.config/pibooth/templates
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Every `*.xml` file of the directory is offered. The template set in
|
|
27
|
+
`[PICTURE] template` is selected at startup when it belongs to the directory.
|
|
28
|
+
|
|
29
|
+
## Template style
|
|
30
|
+
|
|
31
|
+
A template may come with a style file of the same name (`hollywood.xml` and
|
|
32
|
+
`hollywood.cfg`) giving the name displayed in the caption and the style of the
|
|
33
|
+
texts, which replaces the `[PICTURE]` values while the template is selected:
|
|
34
|
+
|
|
35
|
+
```ini
|
|
36
|
+
[TEMPLATE]
|
|
37
|
+
name = Hollywood
|
|
38
|
+
text_colors = ((255, 255, 255), (230, 200, 205))
|
|
39
|
+
text_fonts = ('../fonts/Montserrat-Bold.ttf', '../fonts/Montserrat-SemiBold.ttf')
|
|
40
|
+
text_alignments = center
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Relative font paths are resolved from the style file directory. Without a
|
|
44
|
+
style file, the name comes from the file name and the `[PICTURE]` style is kept.
|
|
45
|
+
|
|
46
|
+
The texts themselves (`footer_text1`, `footer_text2`) stay in `pibooth.cfg`,
|
|
47
|
+
shared by all the templates of an event.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pibooth_template_chooser
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Pibooth plugin letting guests choose the picture template on the wait screen
|
|
5
|
+
Home-page: https://github.com/ceeeeb/pibooth-template-chooser
|
|
6
|
+
Author: Ceeeeb
|
|
7
|
+
License: MIT license
|
|
8
|
+
Keywords: Raspberry Pi,photobooth,pibooth,template
|
|
9
|
+
Platform: unix
|
|
10
|
+
Platform: linux
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: pibooth-ceeeeb>=2.0.8.3
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: description
|
|
16
|
+
Dynamic: description-content-type
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: keywords
|
|
19
|
+
Dynamic: license
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: platform
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# pibooth-template-chooser
|
|
26
|
+
|
|
27
|
+
Pibooth plugin letting guests choose the picture template on the wait screen.
|
|
28
|
+
|
|
29
|
+
The wait screen shows the selected template in place of the last picture:
|
|
30
|
+
the first capture of the last picture assembled again in the single photo page
|
|
31
|
+
of the template, or grey photo holders until a picture exists. A discreet
|
|
32
|
+
caption at the top gives its name and position in the list. Guests change the
|
|
33
|
+
template by swiping horizontally on the screen, by tapping the `<` `>` arrows
|
|
34
|
+
of the caption, or with the arrow keys. The chosen template is used for the
|
|
35
|
+
next pictures; the saved and printed picture is not changed.
|
|
36
|
+
|
|
37
|
+
The templates are the draw.io files of the
|
|
38
|
+
[pibooth-picture-template](https://github.com/pibooth/pibooth-picture-template)
|
|
39
|
+
plugin, which must be installed (with `pip install --no-deps` to keep the
|
|
40
|
+
pibooth fork).
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
```ini
|
|
45
|
+
[TEMPLATE_CHOOSER]
|
|
46
|
+
# Directory of the picture templates guests choose from on the wait screen
|
|
47
|
+
directory = ~/.config/pibooth/templates
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Every `*.xml` file of the directory is offered. The template set in
|
|
51
|
+
`[PICTURE] template` is selected at startup when it belongs to the directory.
|
|
52
|
+
|
|
53
|
+
## Template style
|
|
54
|
+
|
|
55
|
+
A template may come with a style file of the same name (`hollywood.xml` and
|
|
56
|
+
`hollywood.cfg`) giving the name displayed in the caption and the style of the
|
|
57
|
+
texts, which replaces the `[PICTURE]` values while the template is selected:
|
|
58
|
+
|
|
59
|
+
```ini
|
|
60
|
+
[TEMPLATE]
|
|
61
|
+
name = Hollywood
|
|
62
|
+
text_colors = ((255, 255, 255), (230, 200, 205))
|
|
63
|
+
text_fonts = ('../fonts/Montserrat-Bold.ttf', '../fonts/Montserrat-SemiBold.ttf')
|
|
64
|
+
text_alignments = center
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Relative font paths are resolved from the style file directory. Without a
|
|
68
|
+
style file, the name comes from the file name and the `[PICTURE]` style is kept.
|
|
69
|
+
|
|
70
|
+
The texts themselves (`footer_text1`, `footer_text2`) stay in `pibooth.cfg`,
|
|
71
|
+
shared by all the templates of an event.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pibooth_template_chooser.py
|
|
4
|
+
setup.py
|
|
5
|
+
pibooth_template_chooser.egg-info/PKG-INFO
|
|
6
|
+
pibooth_template_chooser.egg-info/SOURCES.txt
|
|
7
|
+
pibooth_template_chooser.egg-info/dependency_links.txt
|
|
8
|
+
pibooth_template_chooser.egg-info/entry_points.txt
|
|
9
|
+
pibooth_template_chooser.egg-info/not-zip-safe
|
|
10
|
+
pibooth_template_chooser.egg-info/requires.txt
|
|
11
|
+
pibooth_template_chooser.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pibooth-ceeeeb>=2.0.8.3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pibooth_template_chooser
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""Pibooth plugin letting guests choose the picture template on the wait screen.
|
|
4
|
+
|
|
5
|
+
The templates are the draw.io files of the ``pibooth-picture-template`` plugin,
|
|
6
|
+
read from a directory. A template may come with a style file of the same name
|
|
7
|
+
(``hollywood.xml`` and ``hollywood.cfg``) giving its displayed name and the
|
|
8
|
+
fonts and colors of its texts::
|
|
9
|
+
|
|
10
|
+
[TEMPLATE]
|
|
11
|
+
name = Hollywood
|
|
12
|
+
text_colors = ((255, 255, 255), (230, 200, 205))
|
|
13
|
+
text_fonts = ('../fonts/Montserrat-Bold.ttf', '../fonts/Montserrat-SemiBold.ttf')
|
|
14
|
+
|
|
15
|
+
Relative font paths are resolved from the style file directory.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import ast
|
|
19
|
+
import base64
|
|
20
|
+
import glob
|
|
21
|
+
import os.path as osp
|
|
22
|
+
from configparser import ConfigParser
|
|
23
|
+
from io import BytesIO
|
|
24
|
+
|
|
25
|
+
import pygame
|
|
26
|
+
from PIL import Image, ImageDraw
|
|
27
|
+
|
|
28
|
+
import pibooth
|
|
29
|
+
from pibooth.utils import LOGGER
|
|
30
|
+
from pibooth_picture_template import TemplateParser, TemplatePictureFactory, TemplateShapeParser
|
|
31
|
+
|
|
32
|
+
__version__ = "1.0.0"
|
|
33
|
+
|
|
34
|
+
SECTION = 'TEMPLATE_CHOOSER'
|
|
35
|
+
STYLED_OPTIONS = ('text_colors', 'text_fonts', 'text_alignments')
|
|
36
|
+
CAPTION_HEIGHT_RATIO = 0.07
|
|
37
|
+
PLACEHOLDER_SIZE = 1200
|
|
38
|
+
# A horizontal move longer than this share of the screen width is a swipe
|
|
39
|
+
SWIPE_RATIO = 0.08
|
|
40
|
+
PREVIOUS_KEYS = (pygame.K_UP, pygame.K_LEFT)
|
|
41
|
+
NEXT_KEYS = (pygame.K_DOWN, pygame.K_RIGHT)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TemplateChoice(object):
|
|
45
|
+
|
|
46
|
+
"""A template file, its parsed layout and its text style."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, path):
|
|
49
|
+
self.path = path
|
|
50
|
+
self.parser = TemplateParser(path)
|
|
51
|
+
self.name = osp.splitext(osp.basename(path))[0].replace('-', ' ').capitalize()
|
|
52
|
+
self.style = {}
|
|
53
|
+
style_path = osp.splitext(path)[0] + '.cfg'
|
|
54
|
+
if osp.isfile(style_path):
|
|
55
|
+
self._read_style(style_path)
|
|
56
|
+
self.placeholder = render_placeholder(self.parser)
|
|
57
|
+
|
|
58
|
+
def _read_style(self, style_path):
|
|
59
|
+
style = ConfigParser()
|
|
60
|
+
style.read(style_path, encoding='utf-8')
|
|
61
|
+
if not style.has_section('TEMPLATE'):
|
|
62
|
+
return
|
|
63
|
+
self.name = style.get('TEMPLATE', 'name', fallback=self.name)
|
|
64
|
+
for option in STYLED_OPTIONS:
|
|
65
|
+
if style.has_option('TEMPLATE', option):
|
|
66
|
+
self.style[option] = style.get('TEMPLATE', option)
|
|
67
|
+
if 'text_fonts' in self.style:
|
|
68
|
+
self.style['text_fonts'] = resolve_fonts(self.style['text_fonts'], osp.dirname(style_path))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve_fonts(value, directory):
|
|
72
|
+
"""Make the relative font paths of a style file absolute."""
|
|
73
|
+
fonts = ast.literal_eval(value)
|
|
74
|
+
if isinstance(fonts, str):
|
|
75
|
+
fonts = (fonts,)
|
|
76
|
+
resolved = []
|
|
77
|
+
for font in fonts:
|
|
78
|
+
candidate = osp.normpath(osp.join(directory, font))
|
|
79
|
+
resolved.append(candidate if not osp.isabs(font) and osp.isfile(candidate) else font)
|
|
80
|
+
return str(tuple(resolved))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def render_placeholder(parser):
|
|
84
|
+
"""Draw the page of the smallest capture number, the one guests are shown
|
|
85
|
+
until a picture exists: embedded images as they are, capture holders in grey.
|
|
86
|
+
"""
|
|
87
|
+
orientation, number = min(((orientation, number) for orientation, pages in parser.data.items()
|
|
88
|
+
for number in pages), key=lambda item: item[1])
|
|
89
|
+
width, height = parser.get_size(number, orientation)
|
|
90
|
+
image = Image.new('RGB', (width, height), (255, 255, 255))
|
|
91
|
+
draw = ImageDraw.Draw(image)
|
|
92
|
+
for shape in parser.get_rects(number, orientation):
|
|
93
|
+
box = (shape.x, shape.y, shape.x + shape.width, shape.y + shape.height)
|
|
94
|
+
if shape.type == TemplateShapeParser.TYPE_IMAGE:
|
|
95
|
+
data = base64.b64decode(shape.image.split(',', 1)[1])
|
|
96
|
+
picture = Image.open(BytesIO(data)).convert('RGBA').resize((shape.width, shape.height))
|
|
97
|
+
image.paste(picture, box[:2], picture)
|
|
98
|
+
elif shape.type == TemplateShapeParser.TYPE_CAPTURE:
|
|
99
|
+
draw.rectangle(box, fill=(170, 170, 175))
|
|
100
|
+
image.thumbnail((PLACEHOLDER_SIZE, PLACEHOLDER_SIZE))
|
|
101
|
+
return image
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TemplateChooser(object):
|
|
105
|
+
|
|
106
|
+
"""Keep the list of templates and apply the selected one to the configuration."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, cfg, choices):
|
|
109
|
+
self.cfg = cfg
|
|
110
|
+
self.choices = choices
|
|
111
|
+
self.defaults = {option: cfg.get('PICTURE', option) for option in STYLED_OPTIONS}
|
|
112
|
+
current = cfg.getpath('PICTURE', 'template')
|
|
113
|
+
paths = [choice.path for choice in choices]
|
|
114
|
+
self.index = paths.index(current) if current in paths else 0
|
|
115
|
+
self.apply()
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def current(self):
|
|
119
|
+
return self.choices[self.index]
|
|
120
|
+
|
|
121
|
+
def select(self, step):
|
|
122
|
+
self.index = (self.index + step) % len(self.choices)
|
|
123
|
+
self.apply()
|
|
124
|
+
LOGGER.info("Template selected: %s", self.current.name)
|
|
125
|
+
|
|
126
|
+
def apply(self):
|
|
127
|
+
"""Point the picture-template plugin to the selected template, and
|
|
128
|
+
set its text style (or restore the configured one)."""
|
|
129
|
+
choice = self.current
|
|
130
|
+
self.cfg.set('PICTURE', 'template', choice.path)
|
|
131
|
+
self.cfg.template = choice.parser
|
|
132
|
+
for option in STYLED_OPTIONS:
|
|
133
|
+
self.cfg.set('PICTURE', option, choice.style.get(option, self.defaults[option]))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Preview(object):
|
|
137
|
+
|
|
138
|
+
"""First capture and texts of the last picture, assembled again in the
|
|
139
|
+
single photo page of any template to show guests what they choose. The
|
|
140
|
+
capture is kept after the other plugins processed it (background removal),
|
|
141
|
+
so previews cost no processing."""
|
|
142
|
+
|
|
143
|
+
MAX_CAPTURE_WIDTH = 1600
|
|
144
|
+
|
|
145
|
+
def __init__(self):
|
|
146
|
+
self.images = self.texts = None
|
|
147
|
+
self.opt_index = 0
|
|
148
|
+
self.cache = {}
|
|
149
|
+
self.waiting = False
|
|
150
|
+
|
|
151
|
+
def expect(self):
|
|
152
|
+
"""Keep the next picture factory, the one of the final picture."""
|
|
153
|
+
self.waiting = True
|
|
154
|
+
|
|
155
|
+
def store(self, factory, opt_index):
|
|
156
|
+
self.waiting = False
|
|
157
|
+
self.images = [self._reduce(factory._images[0])]
|
|
158
|
+
self.texts = [text[0] for text in factory._texts]
|
|
159
|
+
self.opt_index = opt_index
|
|
160
|
+
self.cache = {}
|
|
161
|
+
|
|
162
|
+
def _reduce(self, image):
|
|
163
|
+
image = image.copy()
|
|
164
|
+
image.thumbnail((self.MAX_CAPTURE_WIDTH, self.MAX_CAPTURE_WIDTH))
|
|
165
|
+
return image
|
|
166
|
+
|
|
167
|
+
def build(self, choice, cfg):
|
|
168
|
+
"""Return the last capture assembled in the given template, its
|
|
169
|
+
placeholder while no picture exists, or None if it can not be built."""
|
|
170
|
+
if not self.images:
|
|
171
|
+
return choice.placeholder
|
|
172
|
+
if choice.path not in self.cache:
|
|
173
|
+
try:
|
|
174
|
+
self.cache[choice.path] = self._assemble(choice.parser, cfg)
|
|
175
|
+
except Exception as ex: # No page for this capture number, broken template...
|
|
176
|
+
LOGGER.warning("No preview of template '%s': %s", choice.name, ex)
|
|
177
|
+
self.cache[choice.path] = None
|
|
178
|
+
return self.cache[choice.path]
|
|
179
|
+
|
|
180
|
+
def _assemble(self, parser, cfg):
|
|
181
|
+
orientation = parser.get_best_orientation(self.images)
|
|
182
|
+
factory = TemplatePictureFactory(parser, orientation, *self.images)
|
|
183
|
+
factory.set_background(cfg.gettuple('PICTURE', 'backgrounds', ('color', 'path'), 2)[self.opt_index])
|
|
184
|
+
if cfg.getboolean('PICTURE', 'captures_cropping'):
|
|
185
|
+
factory.set_cropping()
|
|
186
|
+
count = len(self.texts)
|
|
187
|
+
styles = zip(self.texts, cfg.gettuple('PICTURE', 'text_fonts', str, count),
|
|
188
|
+
cfg.gettuple('PICTURE', 'text_colors', 'color', count),
|
|
189
|
+
cfg.gettuple('PICTURE', 'text_alignments', str, count))
|
|
190
|
+
for text, font, color, align in styles:
|
|
191
|
+
if text:
|
|
192
|
+
factory.add_text(text, font, color, align)
|
|
193
|
+
return factory.build()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class Caption(object):
|
|
197
|
+
|
|
198
|
+
"""Discreet label at the top of the wait screen, "< name 2/9 >": the
|
|
199
|
+
picture below already shows the template. Its arrows can be tapped."""
|
|
200
|
+
|
|
201
|
+
def __init__(self):
|
|
202
|
+
self.rect = self.previous_rect = self.next_rect = None
|
|
203
|
+
|
|
204
|
+
def draw(self, surface, chooser):
|
|
205
|
+
screen = surface.get_rect()
|
|
206
|
+
font = pygame.font.Font(None, int(screen.height * CAPTION_HEIGHT_RATIO))
|
|
207
|
+
text = font.render("{} {}/{}".format(chooser.current.name, chooser.index + 1, len(chooser.choices)),
|
|
208
|
+
True, (255, 255, 255))
|
|
209
|
+
arrow_width = text.get_height() * 2
|
|
210
|
+
self.rect = pygame.Rect(0, 0, text.get_width() + 2 * arrow_width, int(text.get_height() * 1.5))
|
|
211
|
+
self.rect.midtop = (screen.centerx, int(screen.height * 0.02))
|
|
212
|
+
self.previous_rect = pygame.Rect(self.rect.left, self.rect.top, arrow_width, self.rect.height)
|
|
213
|
+
self.next_rect = pygame.Rect(self.rect.right - arrow_width, self.rect.top, arrow_width, self.rect.height)
|
|
214
|
+
|
|
215
|
+
background = pygame.Surface(self.rect.size, pygame.SRCALPHA)
|
|
216
|
+
pygame.draw.rect(background, (0, 0, 0, 150), background.get_rect(), border_radius=self.rect.height // 2)
|
|
217
|
+
surface.blit(background, self.rect.topleft)
|
|
218
|
+
surface.blit(text, text.get_rect(center=self.rect.center))
|
|
219
|
+
for rect, label in ((self.previous_rect, '<'), (self.next_rect, '>')):
|
|
220
|
+
arrow = font.render(label, True, (255, 255, 255))
|
|
221
|
+
surface.blit(arrow, arrow.get_rect(center=rect.center))
|
|
222
|
+
|
|
223
|
+
def step_for(self, pos):
|
|
224
|
+
"""Return -1 or 1 if the position hits an arrow, 0 elsewhere on the
|
|
225
|
+
label, None outside of it (or before it is drawn)."""
|
|
226
|
+
if self.rect is None or not self.rect.collidepoint(pos):
|
|
227
|
+
return None
|
|
228
|
+
if self.previous_rect.collidepoint(pos):
|
|
229
|
+
return -1
|
|
230
|
+
return 1 if self.next_rect.collidepoint(pos) else 0
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def event_position(event, surface):
|
|
234
|
+
if event.type in (pygame.FINGERDOWN, pygame.FINGERUP):
|
|
235
|
+
width, height = surface.get_size()
|
|
236
|
+
return (event.x * width, event.y * height)
|
|
237
|
+
return event.pos
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def is_touch(event):
|
|
241
|
+
return (event.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP) and event.button in (1, 2, 3)) \
|
|
242
|
+
or event.type in (pygame.FINGERDOWN, pygame.FINGERUP)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class Swipe(object):
|
|
246
|
+
|
|
247
|
+
"""Track a touch from press to release to recognize horizontal swipes."""
|
|
248
|
+
|
|
249
|
+
def __init__(self):
|
|
250
|
+
self.start = None
|
|
251
|
+
|
|
252
|
+
def press(self, pos):
|
|
253
|
+
self.start = pos
|
|
254
|
+
|
|
255
|
+
def release(self, pos, width):
|
|
256
|
+
"""Return 1 for a swipe to the left (next), -1 to the right, 0 otherwise."""
|
|
257
|
+
start, self.start = self.start, None
|
|
258
|
+
if start is None:
|
|
259
|
+
return 0
|
|
260
|
+
dx, dy = pos[0] - start[0], pos[1] - start[1]
|
|
261
|
+
if abs(dx) < width * SWIPE_RATIO or abs(dx) < abs(dy):
|
|
262
|
+
return 0
|
|
263
|
+
return 1 if dx < 0 else -1
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def selection_step(caption, swipe, surface, events):
|
|
267
|
+
"""Return the requested move in the template list (arrow keys, swipe,
|
|
268
|
+
caption arrows). The touches used for it are consumed so that they trigger
|
|
269
|
+
no capture; a simple tap elsewhere keeps its pibooth meaning."""
|
|
270
|
+
step, consumed = 0, []
|
|
271
|
+
for event in events:
|
|
272
|
+
if event.type == pygame.KEYDOWN and event.key in PREVIOUS_KEYS + NEXT_KEYS:
|
|
273
|
+
step += -1 if event.key in PREVIOUS_KEYS else 1
|
|
274
|
+
consumed.append(event)
|
|
275
|
+
elif is_touch(event):
|
|
276
|
+
pos = event_position(event, surface)
|
|
277
|
+
if event.type in (pygame.MOUSEBUTTONDOWN, pygame.FINGERDOWN):
|
|
278
|
+
swipe.press(pos)
|
|
279
|
+
continue
|
|
280
|
+
swiped = swipe.release(pos, surface.get_width())
|
|
281
|
+
touched = caption.step_for(pos)
|
|
282
|
+
if swiped or touched is not None:
|
|
283
|
+
step += swiped or touched
|
|
284
|
+
consumed.append(event)
|
|
285
|
+
for event in consumed:
|
|
286
|
+
events.remove(event)
|
|
287
|
+
return step
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@pibooth.hookimpl
|
|
291
|
+
def pibooth_configure(cfg):
|
|
292
|
+
cfg.add_option(SECTION, 'directory', '~/.config/pibooth/templates',
|
|
293
|
+
"Directory of the picture templates guests choose from on the wait screen")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@pibooth.hookimpl
|
|
297
|
+
def pibooth_startup(cfg, app):
|
|
298
|
+
paths = sorted(glob.glob(osp.join(cfg.getpath(SECTION, 'directory'), '*.xml')))
|
|
299
|
+
choices = []
|
|
300
|
+
for path in paths:
|
|
301
|
+
try:
|
|
302
|
+
choices.append(TemplateChoice(path))
|
|
303
|
+
except Exception as ex: # A broken template must not prevent the others
|
|
304
|
+
LOGGER.warning("Template '%s' ignored: %s", path, ex)
|
|
305
|
+
app.template_chooser = TemplateChooser(cfg, choices) if choices else None
|
|
306
|
+
app.template_caption = Caption()
|
|
307
|
+
app.template_swipe = Swipe()
|
|
308
|
+
app.template_preview = Preview()
|
|
309
|
+
cfg.template_preview = app.template_preview # The factory hook only receives cfg
|
|
310
|
+
LOGGER.info("Template chooser: %s templates", len(choices))
|
|
311
|
+
if app.template_chooser and app.previous_picture is None:
|
|
312
|
+
app.previous_picture = app.template_chooser.current.placeholder
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@pibooth.hookimpl
|
|
316
|
+
def state_wait_validate(app):
|
|
317
|
+
"""Enter the wait state again so that every plugin redraws the screen
|
|
318
|
+
around the new preview (picture, gallery QR code...)."""
|
|
319
|
+
if getattr(app, 'template_redraw', False):
|
|
320
|
+
app.template_redraw = False
|
|
321
|
+
return 'wait'
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@pibooth.hookimpl
|
|
325
|
+
def state_processing_enter(app):
|
|
326
|
+
if getattr(app, 'template_chooser', None):
|
|
327
|
+
app.template_preview.expect()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@pibooth.hookimpl(hookwrapper=True, tryfirst=True)
|
|
331
|
+
def pibooth_setup_picture_factory(cfg, opt_index, factory):
|
|
332
|
+
"""Keep the captures and texts of the final picture, once the other
|
|
333
|
+
plugins have set them up: as the outermost wrapper, this one resumes last,
|
|
334
|
+
after pibooth added the texts. The next calls build animation frames."""
|
|
335
|
+
outcome = yield
|
|
336
|
+
preview = getattr(cfg, 'template_preview', None)
|
|
337
|
+
if preview and preview.waiting:
|
|
338
|
+
preview.store(outcome.get_result(), opt_index)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@pibooth.hookimpl(hookwrapper=True)
|
|
342
|
+
def state_wait_do(cfg, app, win, events):
|
|
343
|
+
"""Handle the selection before the other plugins see the events, and draw
|
|
344
|
+
the caption once they have drawn the screen."""
|
|
345
|
+
chooser = getattr(app, 'template_chooser', None)
|
|
346
|
+
if chooser:
|
|
347
|
+
step = selection_step(app.template_caption, app.template_swipe, win.surface, events)
|
|
348
|
+
if step:
|
|
349
|
+
chooser.select(step)
|
|
350
|
+
preview = app.template_preview.build(chooser.current, cfg)
|
|
351
|
+
if preview:
|
|
352
|
+
# Displayed only: the saved and printed file is the real picture
|
|
353
|
+
app.previous_picture = preview
|
|
354
|
+
app.previous_animated = None
|
|
355
|
+
app.template_redraw = True
|
|
356
|
+
yield
|
|
357
|
+
if chooser:
|
|
358
|
+
app.template_caption.draw(win.surface, chooser)
|
|
359
|
+
pygame.display.update()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from setuptools import setup
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name='pibooth_template_chooser',
|
|
8
|
+
version='1.0.0',
|
|
9
|
+
description="Pibooth plugin letting guests choose the picture template on the wait screen",
|
|
10
|
+
long_description=open('README.md', encoding='utf-8').read(),
|
|
11
|
+
long_description_content_type='text/markdown',
|
|
12
|
+
author="Ceeeeb",
|
|
13
|
+
url="https://github.com/ceeeeb/pibooth-template-chooser",
|
|
14
|
+
license='MIT license',
|
|
15
|
+
platforms=['unix', 'linux'],
|
|
16
|
+
keywords=['Raspberry Pi', 'photobooth', 'pibooth', 'template'],
|
|
17
|
+
py_modules=['pibooth_template_chooser'],
|
|
18
|
+
# pibooth-picture-template requires the upstream pibooth: install it with --no-deps
|
|
19
|
+
install_requires=['pibooth-ceeeeb>=2.0.8.3'],
|
|
20
|
+
zip_safe=False,
|
|
21
|
+
entry_points={'pibooth': ["pibooth_template_chooser = pibooth_template_chooser"]},
|
|
22
|
+
)
|