working on dev (not raspberry yet)
This commit is contained in:
commit
9ba3e58f33
49
ConsolePrinter.py
Normal file
49
ConsolePrinter.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from escpos.printer import Dummy
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePrinter(Dummy):
|
||||||
|
def __init__(self, width=42):
|
||||||
|
super().__init__()
|
||||||
|
self.width = width # characters per line
|
||||||
|
self.align = 'left'
|
||||||
|
|
||||||
|
def set(self, align='left', font='a', **kwargs):
|
||||||
|
self.align = align
|
||||||
|
|
||||||
|
def text(self, txt):
|
||||||
|
lines = txt.splitlines()
|
||||||
|
for line in lines:
|
||||||
|
formatted = self._format_line(line)
|
||||||
|
print(formatted)
|
||||||
|
|
||||||
|
def _format_line(self, line: str) -> str:
|
||||||
|
# Apply alignment
|
||||||
|
if self.align == 'center':
|
||||||
|
return line.center(self.width)
|
||||||
|
elif self.align == 'right':
|
||||||
|
return line.rjust(self.width)
|
||||||
|
else:
|
||||||
|
return line.ljust(self.width)
|
||||||
|
|
||||||
|
def qr(self, content, center=False, **kwargs):
|
||||||
|
self.set(align='center' if center else 'left', **kwargs)
|
||||||
|
print(self._format_line("+------------------------------------+"))
|
||||||
|
print(self._format_line("+ ##### ##### +"))
|
||||||
|
print(self._format_line("+ ## ## ## ## +"))
|
||||||
|
print(self._format_line("+ ##### ##### +"))
|
||||||
|
print(self._format_line("+ +"))
|
||||||
|
print(self._format_line("+ +"))
|
||||||
|
print(self._format_line("+ +"))
|
||||||
|
print(self._format_line("+ +"))
|
||||||
|
print(self._format_line("+ +"))
|
||||||
|
print(self._format_line("+ +"))
|
||||||
|
print(self._format_line("+ ##### +"))
|
||||||
|
print(self._format_line("+ ## ## +"))
|
||||||
|
print(self._format_line("+ ##### +"))
|
||||||
|
print(self._format_line("+------------------------------------+"))
|
||||||
|
|
||||||
|
def cut(self, mode='FULL', feed=True):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_online(self):
|
||||||
|
return True
|
||||||
55
Event.py
Normal file
55
Event.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from config import pil_image_to_surface, WIDTH, HEIGHT, SCREEN_RECT, DOMAIN, THUMB_WIDTH, THUMB_HEIGHT
|
||||||
|
|
||||||
|
|
||||||
|
class Event:
|
||||||
|
|
||||||
|
def __init__(self, data: json):
|
||||||
|
self.code = data['code']
|
||||||
|
self.title = data['title']
|
||||||
|
self.date = datetime.strptime(data['date'], '%Y-%m-%d')
|
||||||
|
self.password = data['password']
|
||||||
|
self.frame_factor = float(data['frame_factor']) if 'frame_factor' in data else 1.0
|
||||||
|
self.frame_original = Image.open("./data/frames/" + data['frame'])
|
||||||
|
self.frame = pil_image_to_surface(shrink_cover(self.frame_original, int(WIDTH * self.frame_factor), int(HEIGHT * self.frame_factor)))
|
||||||
|
self.frame_rect = self.frame.get_rect(center=SCREEN_RECT.center)
|
||||||
|
self.url_without_protocol = "%s/e/%s" % (DOMAIN, self.code)
|
||||||
|
|
||||||
|
|
||||||
|
def load_event() -> Event | None:
|
||||||
|
try:
|
||||||
|
with Path("./data/event.json").open("r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return Event(data)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def shrink_inside(img: Image, width: float, height: float):
|
||||||
|
if img.width <= width and img.height <= height:
|
||||||
|
return img
|
||||||
|
r = img.width / img.height
|
||||||
|
w = width
|
||||||
|
h = width / r
|
||||||
|
if h > height:
|
||||||
|
h = height
|
||||||
|
w = height * r
|
||||||
|
return img.resize((int(w), int(h)), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
|
||||||
|
def shrink_cover(img: Image, width: float, height: float):
|
||||||
|
if img.width <= width and img.height <= height:
|
||||||
|
return img
|
||||||
|
r = img.width / img.height
|
||||||
|
w = width
|
||||||
|
h = width / r
|
||||||
|
if h < height:
|
||||||
|
h = height
|
||||||
|
w = height * r
|
||||||
|
return img.resize((int(w), int(h)), Image.Resampling.LANCZOS)
|
||||||
243
Fotobox.py
Normal file
243
Fotobox.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
from typing import List
|
||||||
|
from PIL import Image
|
||||||
|
from escpos.escpos import Escpos
|
||||||
|
from ConsolePrinter import ConsolePrinter
|
||||||
|
from serial import SerialException
|
||||||
|
|
||||||
|
from Event import Event, load_event
|
||||||
|
from Photo import Photo
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from config import read, file_increase, iso, now
|
||||||
|
from timer import Timer
|
||||||
|
|
||||||
|
|
||||||
|
class State(Enum):
|
||||||
|
IDLE = 0
|
||||||
|
COUNTDOWN = 1
|
||||||
|
SHOOTING = 2
|
||||||
|
PREPARE = 3
|
||||||
|
CHOOSE = 4
|
||||||
|
QR = 5
|
||||||
|
|
||||||
|
|
||||||
|
class Fotobox:
|
||||||
|
|
||||||
|
def __init__(self, camera):
|
||||||
|
self._camera = camera
|
||||||
|
self._running: bool = True
|
||||||
|
self._state: State = State.IDLE
|
||||||
|
self._photos: List[Photo] = []
|
||||||
|
self._choice: Photo | None = None
|
||||||
|
self._countdown_timer: Timer = Timer("countdown", 0, 1, COUNTDOWN_COUNT + 1, self._countdown_callback)
|
||||||
|
self._shooting_timer: Timer = Timer("shooting", 0, SHOOTING_INTERVAL, SHOOTING_COUNT, self._shooting_callback)
|
||||||
|
|
||||||
|
self._fotobox_uuid = read("./data/fotobox.uuid")
|
||||||
|
print("Starting fotobox: F-%s" % self._fotobox_uuid)
|
||||||
|
|
||||||
|
self._runtime_datetime = now()
|
||||||
|
self._runtime_number = file_increase("./data/runtime.number")
|
||||||
|
print("Starting runtime: R%04d-%s" % (self._runtime_number, iso(self._runtime_datetime)))
|
||||||
|
|
||||||
|
self._event: Event | None = load_event()
|
||||||
|
self._idle_url_without_protocol = "%s/e/%s" % (DOMAIN, self._event.code) if self._event is not None else DOMAIN
|
||||||
|
self._idle_url_with_protocol = "http%s://%s/" % ('s' if HTTPS else '', self._idle_url_without_protocol)
|
||||||
|
self._idle_qr = qr_create(self._idle_url_with_protocol)
|
||||||
|
self._idle_qr_rect = self._idle_qr.get_rect(center=SCREEN_RECT.center)
|
||||||
|
self._idle_url = FONT_URL.render(self._idle_url_without_protocol, True, (255, 255, 255))
|
||||||
|
self._idle_url_rect = self._idle_url.get_rect(centerx=SCREEN_RECT.centerx, bottom=HEIGHT - BORDER)
|
||||||
|
|
||||||
|
self._shooting_number = 0
|
||||||
|
self._shooting_datetime = None
|
||||||
|
|
||||||
|
self._printer: Escpos | None = None
|
||||||
|
|
||||||
|
def _countdown_callback(self):
|
||||||
|
if self._countdown_timer.complete():
|
||||||
|
self._set_state(State.SHOOTING)
|
||||||
|
else:
|
||||||
|
print("COUNTDOWN: %d" % self._countdown_timer.rest())
|
||||||
|
|
||||||
|
def _shooting_callback(self):
|
||||||
|
print("SHOT: %d/%d" % (self._shooting_timer.current(), SHOOTING_COUNT))
|
||||||
|
|
||||||
|
screen.fill((255, 255, 255))
|
||||||
|
pygame.display.flip()
|
||||||
|
if not DEBUG:
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
stream = io.BytesIO()
|
||||||
|
self._camera.capture(stream, format='rgb')
|
||||||
|
stream.seek(0)
|
||||||
|
self._photos.append(Photo(self._shooting_timer.current(), Image.open(stream)))
|
||||||
|
|
||||||
|
screen.fill((0, 0, 0))
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
|
if self._shooting_timer.complete():
|
||||||
|
self._set_state(State.PREPARE)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if DEBUG:
|
||||||
|
self._set_state(State.SHOOTING)
|
||||||
|
while self._running:
|
||||||
|
self._events()
|
||||||
|
self._timer()
|
||||||
|
self._draw()
|
||||||
|
|
||||||
|
def _events(self):
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
self._running = False
|
||||||
|
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
if self._state == State.IDLE:
|
||||||
|
self._set_state(State.COUNTDOWN)
|
||||||
|
elif self._state == State.CHOOSE:
|
||||||
|
if CANCEL_RECT.collidepoint(event.pos):
|
||||||
|
print("CANCEL")
|
||||||
|
self._set_state(State.IDLE)
|
||||||
|
elif SAVE_RECT.collidepoint(event.pos) and self._choice is not None:
|
||||||
|
print("SAVE")
|
||||||
|
self._set_state(State.QR)
|
||||||
|
else:
|
||||||
|
for photo in self._photos:
|
||||||
|
if photo.thumb_rect.collidepoint(event.pos):
|
||||||
|
self._choice = photo
|
||||||
|
print("CHOSE: %d" % self._choice.number)
|
||||||
|
elif self._state == State.QR:
|
||||||
|
if DONE_RECT.collidepoint(event.pos):
|
||||||
|
print("DONE")
|
||||||
|
self._set_state(State.IDLE)
|
||||||
|
elif self._printer is not None and not self._printed and PRINT_RECT.collidepoint(event.pos):
|
||||||
|
print("PRINT")
|
||||||
|
try:
|
||||||
|
self._printer.set(align='center', font='b', width=2, height=2)
|
||||||
|
self._printer.text("Fotobox\n")
|
||||||
|
self._printer.set(align='center', font='a', width=1, height=1)
|
||||||
|
if self._event is not None:
|
||||||
|
self._printer.text(self._event.date.strftime("%d.%m.%Y") + "\n")
|
||||||
|
self._printer.text(self._event.title + "\n")
|
||||||
|
self._printer.text(self._event.url_without_protocol + "\n")
|
||||||
|
self._printer.text("Passwort: " + self._event.password + "\n")
|
||||||
|
else:
|
||||||
|
self._printer.text(datetime.now().strftime("%d.%m.%Y") + "\n")
|
||||||
|
self._printer.text(self._idle_url_without_protocol + "\n")
|
||||||
|
self._printer.qr(self._choice.urlWithProtocol, center=True)
|
||||||
|
self._printer.text(self._choice.urlWithoutProtocol)
|
||||||
|
self._printer.cut()
|
||||||
|
self._printed = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def _timer(self):
|
||||||
|
dt: float = clock.tick(20) / 1000
|
||||||
|
self._shooting_timer.tick(dt)
|
||||||
|
self._countdown_timer.tick(dt)
|
||||||
|
|
||||||
|
def _draw(self):
|
||||||
|
screen.fill((0, 0, 0))
|
||||||
|
if self._state == State.IDLE:
|
||||||
|
screen.blit(FOTOBOX_TITLE_SURFACE, FOTOBOX_TITLE_RECT)
|
||||||
|
screen.blit(self._idle_qr, self._idle_qr_rect)
|
||||||
|
screen.blit(self._idle_url, self._idle_url_rect)
|
||||||
|
elif self._state == State.COUNTDOWN:
|
||||||
|
screen.blit(self._event.frame, self._event.frame_rect)
|
||||||
|
if self._countdown_timer.rest() == 3:
|
||||||
|
screen.blit(FOTOBOX_COUNTDOWN3_SURFACE, FOTOBOX_COUNTDOWN3_RECT)
|
||||||
|
elif self._countdown_timer.rest() == 2:
|
||||||
|
screen.blit(FOTOBOX_COUNTDOWN2_SURFACE, FOTOBOX_COUNTDOWN2_RECT)
|
||||||
|
elif self._countdown_timer.rest() == 1:
|
||||||
|
screen.blit(FOTOBOX_COUNTDOWN1_SURFACE, FOTOBOX_COUNTDOWN1_RECT)
|
||||||
|
elif self._state == State.SHOOTING:
|
||||||
|
screen.blit(self._event.frame, self._event.frame_rect)
|
||||||
|
elif self._state == State.PREPARE:
|
||||||
|
screen.blit(LOADING_SURFACE, LOADING_RECT)
|
||||||
|
elif self._state == State.CHOOSE:
|
||||||
|
screen.blit(CHOOSE_SURFACE, CHOOSE_RECT)
|
||||||
|
screen.blit(CANCEL_SURFACE, CANCEL_RECT)
|
||||||
|
if self._choice is not None:
|
||||||
|
screen.blit(SAVE_SURFACE, SAVE_RECT)
|
||||||
|
for photo in self._photos:
|
||||||
|
screen.blit(photo.thumb, photo.thumb_rect)
|
||||||
|
if self._choice == photo:
|
||||||
|
rect = pygame.Rect(
|
||||||
|
photo.thumb_rect.x - CHOICE_BORDER,
|
||||||
|
photo.thumb_rect.y - CHOICE_BORDER,
|
||||||
|
photo.thumb_rect.width + 2 * CHOICE_BORDER,
|
||||||
|
photo.thumb_rect.height + 2 * CHOICE_BORDER
|
||||||
|
)
|
||||||
|
pygame.draw.rect(screen, (0, 255, 0), rect, width=CHOICE_BORDER)
|
||||||
|
elif self._state == State.QR:
|
||||||
|
screen.blit(self._qr, self._qr_rect)
|
||||||
|
screen.blit(self._choice.chosen, self._choice.chosen_rect)
|
||||||
|
if self._printer is not None and not self._printed:
|
||||||
|
screen.blit(PRINT_SURFACE, PRINT_RECT)
|
||||||
|
screen.blit(DONE_SURFACE, DONE_RECT)
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
|
def _set_state(self, new_state: State):
|
||||||
|
print(new_state)
|
||||||
|
self._state = new_state
|
||||||
|
if new_state == State.COUNTDOWN:
|
||||||
|
self._camera.start_preview()
|
||||||
|
self._countdown_timer.restart()
|
||||||
|
elif new_state == State.SHOOTING:
|
||||||
|
self._photos = []
|
||||||
|
self._shooting_number += 1
|
||||||
|
self._shooting_datetime = now()
|
||||||
|
print("Starting shooting: S%04d-%s" % (self._shooting_number, iso(self._shooting_datetime)))
|
||||||
|
self._shooting_timer.restart()
|
||||||
|
elif new_state == State.PREPARE:
|
||||||
|
self._camera.stop_preview()
|
||||||
|
for photo in self._photos:
|
||||||
|
photo.prepare(self._event)
|
||||||
|
self._set_state(State.CHOOSE)
|
||||||
|
elif new_state == State.CHOOSE:
|
||||||
|
self._choice = None
|
||||||
|
elif new_state == State.QR:
|
||||||
|
self._printed = False
|
||||||
|
try:
|
||||||
|
if self._printer is None or not self._printer.is_online():
|
||||||
|
# self._printer = Serial(devfile='/dev/ttyUSB0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1.00, dsrdtr=True)
|
||||||
|
self._printer = ConsolePrinter()
|
||||||
|
self._printer.profile.media['width']['pixels'] = 512
|
||||||
|
print("Printer AVAILABLE")
|
||||||
|
except SerialException:
|
||||||
|
self._printer = None
|
||||||
|
print("No printer available")
|
||||||
|
|
||||||
|
self._qr = qr_create(self._choice.urlWithProtocol)
|
||||||
|
self._qr_rect = self._qr.get_rect(left=2 * BORDER, centery=SCREEN_RECT.centery)
|
||||||
|
|
||||||
|
filename = "F-%s---R%04d-%s---S%04d-%s---P%04d-%s---%s.jpg" % (
|
||||||
|
self._fotobox_uuid,
|
||||||
|
self._runtime_number,
|
||||||
|
iso(self._runtime_datetime),
|
||||||
|
self._shooting_number,
|
||||||
|
iso(self._shooting_datetime),
|
||||||
|
self._choice.number,
|
||||||
|
iso(self._choice.datetime),
|
||||||
|
self._choice.code,
|
||||||
|
)
|
||||||
|
|
||||||
|
original = Path("./data/photos/original/F-%s/R%04d-%s/%s" % (
|
||||||
|
self._fotobox_uuid,
|
||||||
|
|
||||||
|
self._runtime_number,
|
||||||
|
iso(self._runtime_datetime),
|
||||||
|
|
||||||
|
filename,
|
||||||
|
))
|
||||||
|
original.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self._choice.img.save(original, format='JPEG', quality=95)
|
||||||
|
|
||||||
|
upload = Path("./data/photos/upload/%s" % (filename,))
|
||||||
|
upload.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
os.link(original, upload)
|
||||||
|
|
||||||
|
print("Photo saved: %s" % original.absolute())
|
||||||
45
Photo.py
Normal file
45
Photo.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from pygame import Surface
|
||||||
|
|
||||||
|
from Event import Event, shrink_inside
|
||||||
|
from config import THUMB_WIDTH, pil_image_to_surface, SCREEN_RECT, BORDER, WIDTH, now, generate_code, HTTPS, DOMAIN, THUMB_HEIGHT
|
||||||
|
|
||||||
|
|
||||||
|
class Photo:
|
||||||
|
|
||||||
|
def __init__(self, number: int, img: Image):
|
||||||
|
self.number = number
|
||||||
|
self.datetime = now()
|
||||||
|
self.code = generate_code(4)
|
||||||
|
self.urlWithoutProtocol = "%s/p/%s" % (DOMAIN, self.code)
|
||||||
|
self.urlWithProtocol = "http%s://%s" % ('s' if HTTPS else '', self.urlWithoutProtocol)
|
||||||
|
self.img = img
|
||||||
|
self.thumb = None
|
||||||
|
self.thumb_rect = None
|
||||||
|
self.chosen = None
|
||||||
|
self.chosen_rect = None
|
||||||
|
|
||||||
|
def prepare(self, event: Event):
|
||||||
|
frame = self._resize2(event.frame_original, THUMB_WIDTH)
|
||||||
|
photo = shrink_inside(self.img, frame.width / (event.frame_factor - 0.01), frame.height / (event.frame_factor - 0.01))
|
||||||
|
self.thumb = Surface((frame.width, frame.height))
|
||||||
|
self.thumb.blit(pil_image_to_surface(photo), ((frame.width - photo.width) / 2, (frame.height - photo.height) / 2))
|
||||||
|
self.thumb.blit(pil_image_to_surface(frame), (0, 0))
|
||||||
|
self.thumb_rect = self.thumb.get_rect(centery=SCREEN_RECT.centery, left=(self.number - 1) * (THUMB_WIDTH + BORDER) + BORDER)
|
||||||
|
|
||||||
|
frame_chosen = self._resize2(event.frame_original, THUMB_WIDTH)
|
||||||
|
photo_chosen = shrink_inside(self.img, frame_chosen.width / event.frame_factor, frame_chosen.height / event.frame_factor)
|
||||||
|
self.chosen = Surface((frame_chosen.width, frame_chosen.height))
|
||||||
|
self.chosen.blit(pil_image_to_surface(photo_chosen), ((frame_chosen.width - photo_chosen.width) / 2, (frame_chosen.height - photo_chosen.height) / 2))
|
||||||
|
self.chosen.blit(pil_image_to_surface(frame_chosen), (0, 0))
|
||||||
|
self.chosen_rect = self.chosen.get_rect(right=WIDTH - 2 * BORDER, centery=SCREEN_RECT.centery)
|
||||||
|
|
||||||
|
def _resize(self, width: float):
|
||||||
|
height = width / self.img.width * self.img.height
|
||||||
|
return self.img.resize((int(width), int(height)), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
def _resize2(self, img: Image, width: float):
|
||||||
|
height = width / img.width * img.height
|
||||||
|
return img.resize((int(width), int(height)), Image.Resampling.LANCZOS)
|
||||||
136
config.py
Normal file
136
config.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import io
|
||||||
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
from pygame import Surface
|
||||||
|
from pygame.time import Clock
|
||||||
|
from qrcode.main import QRCode
|
||||||
|
|
||||||
|
DEBUG: bool = True
|
||||||
|
# DEBUG: bool = False
|
||||||
|
|
||||||
|
WIDTH: int = 800
|
||||||
|
HEIGHT: int = 480
|
||||||
|
BORDER: int = 10
|
||||||
|
|
||||||
|
SHOOTING_COUNT: int = 2
|
||||||
|
SHOOTING_INTERVAL: float = 0 if DEBUG else 1.75
|
||||||
|
COUNTDOWN_COUNT: int = 1 if DEBUG else 3
|
||||||
|
|
||||||
|
CHOICE_BORDER: int = 5
|
||||||
|
|
||||||
|
THUMB_WIDTH: int = (WIDTH - (SHOOTING_COUNT + 1) * BORDER) // SHOOTING_COUNT
|
||||||
|
THUMB_HEIGHT: int = int(THUMB_WIDTH / 1.6)
|
||||||
|
|
||||||
|
WHITE = (255, 255, 255)
|
||||||
|
BLACK = (0, 0, 0)
|
||||||
|
|
||||||
|
pygame.init()
|
||||||
|
|
||||||
|
screen: Surface = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||||
|
SCREEN_RECT = screen.get_rect()
|
||||||
|
|
||||||
|
clock: Clock = pygame.time.Clock()
|
||||||
|
|
||||||
|
|
||||||
|
def qr_create(content: str):
|
||||||
|
global FOTOBOX_QR_SURFACE
|
||||||
|
qr = QRCode(border=1)
|
||||||
|
qr.add_data(content)
|
||||||
|
qr.make(fit=True)
|
||||||
|
qr_img = qr.make_image()
|
||||||
|
qr_img_buffer = io.BytesIO()
|
||||||
|
qr_img.save(qr_img_buffer)
|
||||||
|
qr_img_buffer.seek(0)
|
||||||
|
return pygame.image.load(qr_img_buffer)
|
||||||
|
|
||||||
|
|
||||||
|
HTTPS = True
|
||||||
|
DOMAIN = "fotobox.online"
|
||||||
|
# HTTPS = False
|
||||||
|
# DOMAIN = "localhost:8080"
|
||||||
|
|
||||||
|
FONT_TITLE = pygame.font.SysFont(None, 70)
|
||||||
|
FOTOBOX_TITLE_SURFACE = FONT_TITLE.render("Berühren um Fotos zu schießen", True, (255, 255, 255))
|
||||||
|
FOTOBOX_TITLE_RECT = FOTOBOX_TITLE_SURFACE.get_rect(centerx=SCREEN_RECT.centerx, top=BORDER)
|
||||||
|
|
||||||
|
FONT_URL = pygame.font.SysFont(None, 60)
|
||||||
|
|
||||||
|
FONT_COUNTDOWN = pygame.font.SysFont(None, 500)
|
||||||
|
FOTOBOX_COUNTDOWN3_SURFACE = FONT_COUNTDOWN.render("3", True, (255, 255, 255))
|
||||||
|
FOTOBOX_COUNTDOWN3_RECT = FOTOBOX_COUNTDOWN3_SURFACE.get_rect(center=SCREEN_RECT.center)
|
||||||
|
FOTOBOX_COUNTDOWN2_SURFACE = FONT_COUNTDOWN.render("2", True, (255, 255, 255))
|
||||||
|
FOTOBOX_COUNTDOWN2_RECT = FOTOBOX_COUNTDOWN2_SURFACE.get_rect(center=SCREEN_RECT.center)
|
||||||
|
FOTOBOX_COUNTDOWN1_SURFACE = FONT_COUNTDOWN.render("1", True, (255, 255, 255))
|
||||||
|
FOTOBOX_COUNTDOWN1_RECT = FOTOBOX_COUNTDOWN1_SURFACE.get_rect(center=SCREEN_RECT.center)
|
||||||
|
|
||||||
|
FONT_LOADING = pygame.font.SysFont(None, 200)
|
||||||
|
LOADING_SURFACE = FONT_LOADING.render("Laden...", True, (255, 255, 255))
|
||||||
|
LOADING_RECT = LOADING_SURFACE.get_rect(center=SCREEN_RECT.center)
|
||||||
|
|
||||||
|
FONT_CHOOSE = pygame.font.SysFont(None, 100)
|
||||||
|
CHOOSE_SURFACE = FONT_CHOOSE.render("Bitte wählen", True, (255, 255, 255))
|
||||||
|
CHOOSE_RECT = CHOOSE_SURFACE.get_rect(centerx=SCREEN_RECT.centerx, top=BORDER)
|
||||||
|
|
||||||
|
FONT_CANCEL = pygame.font.SysFont(None, 60)
|
||||||
|
CANCEL_SURFACE = FONT_CANCEL.render("Abbruch", True, (255, 0, 0))
|
||||||
|
CANCEL_RECT = CANCEL_SURFACE.get_rect(left=BORDER, bottom=HEIGHT - BORDER)
|
||||||
|
|
||||||
|
FONT_SAVE = pygame.font.SysFont(None, 60)
|
||||||
|
SAVE_SURFACE = FONT_SAVE.render("Speichern", True, (0, 255, 0))
|
||||||
|
SAVE_RECT = SAVE_SURFACE.get_rect(right=WIDTH - BORDER, bottom=HEIGHT - BORDER)
|
||||||
|
|
||||||
|
FONT_PRINT = pygame.font.SysFont(None, 60)
|
||||||
|
PRINT_SURFACE = FONT_PRINT.render("Code drucken", True, (128, 128, 255))
|
||||||
|
PRINT_RECT = PRINT_SURFACE.get_rect(left=BORDER, bottom=HEIGHT - BORDER)
|
||||||
|
|
||||||
|
FONT_DONE = pygame.font.SysFont(None, 60)
|
||||||
|
DONE_SURFACE = FONT_DONE.render("Fertig", True, (0, 255, 0))
|
||||||
|
DONE_RECT = DONE_SURFACE.get_rect(right=WIDTH - BORDER, bottom=HEIGHT - BORDER)
|
||||||
|
|
||||||
|
|
||||||
|
def pil_image_to_surface(pil_image):
|
||||||
|
mode = pil_image.mode
|
||||||
|
size = pil_image.size
|
||||||
|
data = pil_image.tobytes()
|
||||||
|
|
||||||
|
return pygame.image.fromstring(data, size, mode).convert_alpha() if 'A' in mode else pygame.image.fromstring(data, size, mode).convert()
|
||||||
|
|
||||||
|
|
||||||
|
def read(path: str) -> str:
|
||||||
|
with open(path, "r") as file:
|
||||||
|
return file.read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def read_or_else(path: str, fallback: str) -> str:
|
||||||
|
try:
|
||||||
|
return read(path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
|
||||||
|
def write(path: str, value: str) -> None:
|
||||||
|
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(path, "w") as file:
|
||||||
|
file.write(value)
|
||||||
|
|
||||||
|
|
||||||
|
def file_increase(path: str) -> int:
|
||||||
|
last = int(read_or_else(path, "0"))
|
||||||
|
current = last + 1
|
||||||
|
write(path, str(current))
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
def iso(d: datetime):
|
||||||
|
return d.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||||
|
|
||||||
|
|
||||||
|
def now():
|
||||||
|
return datetime.now().astimezone()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_code(groups: int):
|
||||||
|
return '-'.join(''.join(random.choices('23456789abcdefghjkmnpqrstuvwxyz', k=4)) for _ in range(groups))
|
||||||
15
main.py
Normal file
15
main.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
import picamera
|
||||||
|
|
||||||
|
from Fotobox import Fotobox
|
||||||
|
|
||||||
|
with picamera.PiCamera() as camera:
|
||||||
|
fotobox = Fotobox(camera)
|
||||||
|
try:
|
||||||
|
fotobox.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
pygame.quit()
|
||||||
32
picamera.py
Normal file
32
picamera.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from config import DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyMethodMayBeStatic,PyShadowingBuiltins
|
||||||
|
class PiCamera:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_preview(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def capture(self, output, format):
|
||||||
|
with Image.open("./data/demo.jpg") as img:
|
||||||
|
img.save(output, "JPEG")
|
||||||
|
if not DEBUG:
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def stop_preview(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.close()
|
||||||
38
rsync.sh
Normal file
38
rsync.sh
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
WATCH_DIR="./data/photos/upload"
|
||||||
|
|
||||||
|
if [ ! -d "$WATCH_DIR" ]; then
|
||||||
|
echo "watch dir not found: $WATCH_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
UPLOAD_URL="https://fotobox.online/upload"
|
||||||
|
|
||||||
|
FOTOBOX_KEY="bla..." # TODO
|
||||||
|
|
||||||
|
SLEEP_INTERVAL=10
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
for filepath in "$WATCH_DIR"/*; do
|
||||||
|
[ -e "$filepath" ] || continue
|
||||||
|
|
||||||
|
filename=$(basename "$filepath")
|
||||||
|
filesize=$(stat -c %s "$filepath")
|
||||||
|
|
||||||
|
echo "uploading: $filepath"
|
||||||
|
response=$(curl -s -w "\n%{http_code}" -F "file=@${filepath}" -F "filename=${filename}" -F "fotoboxKey=${FOTOBOX_KEY}" "$UPLOAD_URL")
|
||||||
|
body=$(echo "$response" | head -n 1)
|
||||||
|
status=$(echo "$response" | tail -n 1)
|
||||||
|
|
||||||
|
expected="Fotobox2|||${filename}|||${filesize}"
|
||||||
|
|
||||||
|
if [ "$status" -eq 200 ] && [ "$body" = "$expected" ]; then
|
||||||
|
echo "SUCCESS"
|
||||||
|
rm -f "$filepath"
|
||||||
|
else
|
||||||
|
echo "FAILED: CODE=$status, Response=$body"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
sleep "$SLEEP_INTERVAL"
|
||||||
|
done
|
||||||
48
timer.py
Normal file
48
timer.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
|
class Timer:
|
||||||
|
|
||||||
|
def __init__(self, name: str, initial: float, seconds: float, count: int, callback: Callable[[], None] = None):
|
||||||
|
self._name = name
|
||||||
|
self._enabled = False
|
||||||
|
|
||||||
|
self._initial = initial
|
||||||
|
self._seconds = seconds
|
||||||
|
self._rest = 0
|
||||||
|
|
||||||
|
self._count = count
|
||||||
|
self._current = 0
|
||||||
|
|
||||||
|
self._callback = callback
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
self._enabled = True
|
||||||
|
self._current = 0
|
||||||
|
self._rest = self._initial
|
||||||
|
|
||||||
|
def tick(self, dt: float):
|
||||||
|
if not self._enabled or self._current >= self._count:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._rest -= dt
|
||||||
|
if self._rest <= 0:
|
||||||
|
self._current += 1
|
||||||
|
self._rest += self._seconds
|
||||||
|
if self._callback:
|
||||||
|
self._callback()
|
||||||
|
|
||||||
|
def current(self):
|
||||||
|
return self._current
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return self._count
|
||||||
|
|
||||||
|
def rest(self):
|
||||||
|
return self._count - self._current
|
||||||
|
|
||||||
|
def complete(self):
|
||||||
|
return self._current >= self._count
|
||||||
|
|
||||||
|
def running(self):
|
||||||
|
return self._enabled and not self.complete()
|
||||||
38
upload.sh
Normal file
38
upload.sh
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
WATCH_DIR="./data/photos/upload"
|
||||||
|
|
||||||
|
if [ ! -d "$WATCH_DIR" ]; then
|
||||||
|
echo "watch dir not found: $WATCH_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
UPLOAD_URL="https://fotobox.online/upload"
|
||||||
|
|
||||||
|
FOTOBOX_KEY="bla..." # TODO
|
||||||
|
|
||||||
|
SLEEP_INTERVAL=10
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
for filepath in "$WATCH_DIR"/*; do
|
||||||
|
[ -e "$filepath" ] || continue
|
||||||
|
|
||||||
|
filename=$(basename "$filepath")
|
||||||
|
filesize=$(stat -c %s "$filepath")
|
||||||
|
|
||||||
|
echo "uploading: $filepath"
|
||||||
|
response=$(curl -s -w "\n%{http_code}" -F "file=@${filepath}" -F "filename=${filename}" -F "fotoboxKey=${FOTOBOX_KEY}" "$UPLOAD_URL")
|
||||||
|
body=$(echo "$response" | head -n 1)
|
||||||
|
status=$(echo "$response" | tail -n 1)
|
||||||
|
|
||||||
|
expected="Fotobox2|||${filename}|||${filesize}"
|
||||||
|
|
||||||
|
if [ "$status" -eq 200 ] && [ "$body" = "$expected" ]; then
|
||||||
|
echo "SUCCESS"
|
||||||
|
rm -f "$filepath"
|
||||||
|
else
|
||||||
|
echo "FAILED: CODE=$status, Response=$body"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
sleep "$SLEEP_INTERVAL"
|
||||||
|
done
|
||||||
Loading…
Reference in New Issue
Block a user