commit 9ba3e58f3365599614f786bb78cbd9f95801c45b Author: Patrick Haßel Date: Fri Jun 27 14:45:36 2025 +0200 working on dev (not raspberry yet) diff --git a/ConsolePrinter.py b/ConsolePrinter.py new file mode 100644 index 0000000..187e6b1 --- /dev/null +++ b/ConsolePrinter.py @@ -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 diff --git a/Event.py b/Event.py new file mode 100644 index 0000000..6986b61 --- /dev/null +++ b/Event.py @@ -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) diff --git a/Fotobox.py b/Fotobox.py new file mode 100644 index 0000000..e25cd44 --- /dev/null +++ b/Fotobox.py @@ -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()) diff --git a/Photo.py b/Photo.py new file mode 100644 index 0000000..25e7857 --- /dev/null +++ b/Photo.py @@ -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) diff --git a/config.py b/config.py new file mode 100644 index 0000000..89154fd --- /dev/null +++ b/config.py @@ -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)) diff --git a/main.py b/main.py new file mode 100644 index 0000000..edb87b6 --- /dev/null +++ b/main.py @@ -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() diff --git a/picamera.py b/picamera.py new file mode 100644 index 0000000..d8b3099 --- /dev/null +++ b/picamera.py @@ -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() diff --git a/rsync.sh b/rsync.sh new file mode 100644 index 0000000..ba8a57d --- /dev/null +++ b/rsync.sh @@ -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 diff --git a/timer.py b/timer.py new file mode 100644 index 0000000..7e65812 --- /dev/null +++ b/timer.py @@ -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() diff --git a/upload.sh b/upload.sh new file mode 100644 index 0000000..ba8a57d --- /dev/null +++ b/upload.sh @@ -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