diff --git a/.gitignore b/.gitignore index 82622f8..a4f6e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,11 @@ notebooks/out/ .server.pid *.pid ditherbooth_config.json +server_config.json + +# Environment / secrets +.env + +# Printer command files +*.epl +*.zpl diff --git a/README.md b/README.md index d5f9a46..3fa865e 100644 --- a/README.md +++ b/README.md @@ -7,67 +7,75 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![FastAPI](https://img.shields.io/badge/FastAPI-009688?logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com/) -Turn photos into crisp 1‑bit dithered prints on a Zebra label printer. +A free, browser-based photo booth and label designer for any EPL/ZPL thermal printer.

- Original example - Processed 1-bit + Original photo + Dithered 1-bit output

-Original (left) and final 1‑bit dithered image (right). See [Image processing script](#image-processing-script) to reproduce the example locally. +## What is this? -Ditherbooth is a small FastAPI service and single-page app that turns uploaded photos into 1‑bit dithered bitmaps and prints them on a Zebra LP2844 label printer. +Ditherbooth turns any phone or computer into a photo booth for thermal label printers. Upload or snap a photo, and it gets converted to a crisp 1-bit dithered bitmap and printed instantly. It also includes a full label designer for creating custom labels with text, images, and shapes. Works with any EPL2 or ZPL compatible printer — Zebra LP2844, GK420d, ZD220, and many others. -## Table of Contents +## Screenshots -- [Features](#features) -- [Quick Start](#quick-start) -- [Troubleshooting](#troubleshooting) -- [Developer Settings and Test Mode](#developer-settings-and-test-mode) -- [API Test with cURL](#api-test-with-curl) -- [Media Presets](#media-presets) -- [Image Processing Script](#image-processing-script) -- [Formatting and Linting](#formatting-and-linting) -- [Testing](#testing) -- [Compiling](#compiling) -- [Notes](#notes) -- [Code Coverage](#code-coverage) -- [Contributing](#contributing) -- [License](#license) +

+ Photo booth flow +

+

Photo flow — upload or capture, preview the dithered output, print

+ +

+ Label designer +

+

Label designer — text, images, shapes, templates, and multi-label queue

+ +

+ Mobile view +

+

Works on any device — open it on your phone over the local network

## Features -* Upload or capture images via the web UI -* Convert photos to 1‑bit dithered bitmaps using Pillow -* Encode to EPL2 or ZPL and send raw jobs to CUPS -* Configure media widths and printer language -* Optional test mode to skip actual printing +**Photo booth** +- Upload or capture photos from any device +- Floyd-Steinberg dithering to 1-bit black & white +- Live preview before printing +- Multiple media sizes (continuous rolls and fixed labels) + +**Label designer** +- Fabric.js canvas editor with text, images, and shapes +- Font picker, fill toggle, object layering +- Smart snapping guides (PowerPoint-style) +- Save and load templates +- Multi-label print queue with drag-to-reorder + +**Printer support** +- EPL2 and ZPL encoding +- Prints via CUPS — works on macOS and Linux +- Compatible with any EPL/ZPL thermal printer +- Test mode for development without a printer +- Kiosk mode to lock down the UI ## Quick Start ### Prerequisites -* Python 3.9+ -* [CUPS](https://www.cups.org/) with a printer queue named `Zebra_LP2844` -* Zebra LP2844 or LP2844‑Z printer connected via USB -* `DITHERBOOTH_PRINTER` environment variable (optional) to override the CUPS queue name; defaults to `Zebra_LP2844` - -#### Create a printer queue +- Python 3.9+ +- [CUPS](https://www.cups.org/) with a configured printer queue +- Any EPL2/ZPL thermal printer connected via USB -**macOS:** +### Set up your printer -macOS no longer supports raw CUPS queues. Use the EPL2 driver instead: +**macOS** (no raw queue support — use the EPL2 driver): ```bash -# Find your printer's USB device (note the serial number) -lpinfo -v | grep -i zebra +lpinfo -v | grep -i zebra # Find your printer's USB device -# Create printer with EPL2 driver (update serial number to match yours) sudo lpadmin -p Zebra_LP2844 -E \ -v "usb://Zebra/LP2844?serial=YOUR_SERIAL_NUMBER" \ -m drv:///sample.drv/zebraep2.ppd -# Enable and accept jobs cupsenable Zebra_LP2844 cupsaccept Zebra_LP2844 ``` @@ -78,232 +86,99 @@ cupsaccept Zebra_LP2844 sudo lpadmin -p Zebra_LP2844 -E -v usb://Zebra/LP2844 -m raw ``` -Verify the queue: - -```bash -lpstat -p Zebra_LP2844 -``` - -#### Smoke-test the printer - -Print a simple label to confirm the queue works: - -```bash -echo -e "N\nq400\nQ200,24\nA50,50,0,3,1,1,N,\"Hello\"\nP1" | lpr -P Zebra_LP2844 -``` - -If successful, the printer should print a small label with "Hello" text. - -### Installation +### Install and run ```bash python3 -m venv venv source venv/bin/activate pip install -r requirements.txt -# For development and testing -pip install -r requirements-dev.txt -``` - -### Run the development server - -```bash make dev ``` -Visit [http://localhost:8000](http://localhost:8000) and upload or capture a photo. Choose media width and printer language and press **Print** to send the job to the printer. +Open [http://localhost:8000](http://localhost:8000) and print something. -#### Access on your network - -Run the dev server bound to your LAN IP: +### Access from your phone ```bash -make dev HOST=0.0.0.0 PORT=8000 +make dev HOST=0.0.0.0 ``` -Find your IP: - -* macOS: `ipconfig getifaddr en0` -* Linux: `hostname -I` - -Open `http://:8000` on your phone. Use the gear icon (Dev Settings) to enable Test Mode so no printer is required. - -## Troubleshooting - -### Printer shows as paused or disabled - -If the printer appears paused in System Settings or `lpstat` shows it as disabled: - -```bash -cupsenable Zebra_LP2844 # Enable the printer -cupsaccept Zebra_LP2844 # Accept jobs -lpstat -p Zebra_LP2844 # Verify status -``` - -### Print jobs are stuck in queue - -```bash -cancel -a Zebra_LP2844 # Cancel all pending jobs -lpstat -o # Check queue is empty -``` - -### "Unable to send data to printer" error - -This usually means the printer lost communication. Try: - -1. Check USB cable is connected -2. Verify printer is powered on and shows a green ready light -3. Check the printer appears in USB devices: - ```bash - lpinfo -v | grep -i zebra - ``` -4. If the device URI is wrong or shows `/dev/null`, reconfigure: - ```bash - # Find the correct USB device - lpinfo -v | grep -i zebra - - # Update the printer (use your serial number) - sudo lpadmin -p Zebra_LP2844 -v "usb://Zebra/LP2844?serial=YOUR_SERIAL" +Then open `http://:8000` on your phone (`ipconfig getifaddr en0` on macOS, `hostname -I` on Linux). - # Re-enable - cupsenable Zebra_LP2844 - cupsaccept Zebra_LP2844 - ``` +### No printer? No problem -### Web interface returns "Printer error" (502) - -The app returns HTTP 502 when CUPS cannot communicate with the printer. Check: - -1. Printer status: `lpstat -p Zebra_LP2844` -2. Recent CUPS errors: `tail -20 /var/log/cups/error_log` -3. Follow steps above to enable printer and clear stuck jobs - -### Development without a printer - -Use test mode to develop without a physical printer: +Enable test mode via the gear icon in the UI (password: `dev`), or: ```bash curl -X PUT -H "X-Dev-Password: dev" -H "Content-Type: application/json" \ -d '{"test_mode": true}' http://localhost:8000/api/dev/settings ``` -Or enable it through the web UI using the gear icon (Dev Settings) with password `dev`. - -## Developer settings and test mode - -The app exposes a small password-protected settings API to make the frontend configurable without exposing options to end users. These settings are persisted to a JSON file (`ditherbooth_config.json` by default) and are used both by the UI and the print endpoint. +## Configuration -* `DITHERBOOTH_DEV_PASSWORD`: Password required for the settings API (default: `dev` for local/testing). Set this in production. -* `DITHERBOOTH_CONFIG_PATH`: Optional path to the settings JSON file. Useful in tests or deployments. +Settings are persisted to a JSON config file and accessible via a password-protected API. -Endpoints: +| Setting | Type | Description | +|---------|------|-------------| +| `test_mode` | bool | Process images without printing | +| `default_media` | string | Default media preset | +| `default_lang` | string | `EPL` or `ZPL` | +| `lock_controls` | bool | Hide selectors for kiosk mode | +| `printer_name` | string | Override CUPS queue name | +| `epl_darkness` | int (0-15) | EPL darkness setting | +| `epl_speed` | int (1-6) | EPL speed setting | -* `GET /api/public-config`: Public, returns defaults and whether to lock controls in the UI. -* `GET /api/dev/settings`: Requires header `X-Dev-Password`. Returns full config. -* `PUT /api/dev/settings`: Requires header `X-Dev-Password`. Accepts JSON fields: - * `test_mode` (bool) — if true, the `/print` endpoint will process the image but skip spooling to the printer and return `{status:"ok", mode:"test"}`. - * `default_media` (string: one of `continuous58`, `continuous80`, `label100x150`, `label55x30`) - * `default_lang` (string: `EPL` or `ZPL`) - * `lock_controls` (bool) — hides media/language selectors in the UI for kiosk usage. - * `printer_name` (string, optional) — override the printer queue name used by the backend (otherwise falls back to `DITHERBOOTH_PRINTER` env, then `Zebra_LP2844`). - -## API test with cURL - -Send an image directly to the `/print` endpoint without the UI: - -```bash -curl -F "file=@path/to/image.jpg" \ - -F media=continuous58 \ - -F lang=EPL \ - http://localhost:8000/print -``` +**Environment variables:** -A JSON response of `{ "status": "ok" }` indicates the job was submitted. +| Variable | Default | Description | +|----------|---------|-------------| +| `DITHERBOOTH_PRINTER` | `Zebra_LP2844` | CUPS queue name | +| `DITHERBOOTH_DEV_PASSWORD` | `dev` | Password for settings API | +| `DITHERBOOTH_CONFIG_PATH` | `~/.config/ditherbooth/config.json` | Config file location | -If test mode is enabled via settings, the response will include `{ "status": "ok", "mode": "test", ... }` and no job is sent to the printer. +**API endpoints:** -## Media presets - -Available media widths (dots): - -* `continuous58` → 463 -* `continuous80` → 640 (default) -* `label100x150` → 800 -* `label55x30` → 440 - -You can change the default in the Dev Settings modal, or via the API. - -## Image processing script - -* `notebooks/image_processing.py` (uses `# %%` cells) - -Run end-to-end (auto-picks an image in `notebooks/` if available): - -```bash -python notebooks/image_processing.py -``` +- `GET /api/public-config` — public config (media dimensions, defaults) +- `GET /api/dev/settings` — full config (requires `X-Dev-Password` header) +- `PUT /api/dev/settings` — update config (requires `X-Dev-Password` header) -Examples: +## API ```bash -# Use a specific image in notebooks/ -python notebooks/image_processing.py -i notebooks/your_image.png +# Print an image +curl -F "file=@photo.jpg" -F media=continuous58 -F lang=EPL http://localhost:8000/print -# Change printer width (dots) -python notebooks/image_processing.py -i notebooks/your_image.png -w 463 - -# Choose a custom output directory -python notebooks/image_processing.py -i notebooks/your_image.png --out notebooks/out_custom +# Preview (returns dithered PNG) +curl -F "file=@photo.jpg" -F media=continuous58 http://localhost:8000/preview -o preview.png ``` -You can also open the script in VS Code/Jupyter and run the `# %%` cells interactively. - -Optional intermediate views used for illustration (not sent to the printer): - -

- Grayscale - Resized to printer width -

- -## Formatting and linting - -Format the code with: - -```bash -pip install -r requirements-dev.txt -make format -``` +## Media presets -## Testing +| Preset | Width (dots) | Height | Type | +|--------|-------------|--------|------| +| `continuous58` | 463 | auto | Continuous roll | +| `continuous80` | 640 | auto | Continuous roll (default) | +| `label100x150` | 800 | 1200 | Fixed label | +| `label55x30` | 440 | 240 | Fixed label | +| `label50x30` | 400 | 240 | Fixed label | -Run the test suite: +## Development ```bash pip install -r requirements-dev.txt -pytest -``` - -## Compiling - -Check that the Python sources are syntactically valid: - -```bash -python -m py_compile ditherbooth/app.py ditherbooth/imaging/process.py ditherbooth/printer/cups.py ditherbooth/printer/epl.py ditherbooth/printer/zpl.py +make format # Black formatter +pytest # Run tests ``` -## Notes - -Camera capture in the browser requires HTTPS on non‑localhost hosts. When deploying on a LAN, run the service behind a self‑signed certificate or a local CA such as `mkcert`. +Camera capture requires HTTPS on non-localhost hosts. For LAN use, run behind a self-signed cert or [mkcert](https://github.com/FiloSottile/mkcert). -## Code coverage - -This repository publishes test coverage to Codecov via GitHub Actions. The workflow runs `pytest --cov=. --cov-report=xml` and uploads `coverage.xml`. +## Troubleshooting -* Public repo: no token required. -* Private repo: set `CODECOV_TOKEN` in repository secrets if needed. +See [docs/troubleshooting.md](docs/troubleshooting.md) for printer setup issues, stuck jobs, and common errors. ## Contributing -Pull requests are welcome! Please open an issue for major changes and ensure tests pass before submitting. +Pull requests welcome. Please open an issue for major changes and ensure tests pass before submitting. ```bash make format @@ -312,4 +187,4 @@ pytest ## License -This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details. +MIT — see [LICENSE](LICENSE). diff --git a/docs/screenshots/designer-tab.png b/docs/screenshots/designer-tab.png new file mode 100644 index 0000000..fcfacbe Binary files /dev/null and b/docs/screenshots/designer-tab.png differ diff --git a/docs/screenshots/mobile-photo.png b/docs/screenshots/mobile-photo.png new file mode 100644 index 0000000..38f28a8 Binary files /dev/null and b/docs/screenshots/mobile-photo.png differ diff --git a/docs/screenshots/photo-tab.png b/docs/screenshots/photo-tab.png new file mode 100644 index 0000000..470b3c1 Binary files /dev/null and b/docs/screenshots/photo-tab.png differ diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..b1ff263 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,60 @@ +# Troubleshooting + +## Printer shows as paused or disabled + +If the printer appears paused in System Settings or `lpstat` shows it as disabled: + +```bash +cupsenable Zebra_LP2844 # Enable the printer +cupsaccept Zebra_LP2844 # Accept jobs +lpstat -p Zebra_LP2844 # Verify status +``` + +## Print jobs are stuck in queue + +```bash +cancel -a Zebra_LP2844 # Cancel all pending jobs +lpstat -o # Check queue is empty +``` + +## "Unable to send data to printer" error + +This usually means the printer lost communication. Try: + +1. Check USB cable is connected +2. Verify printer is powered on and shows a green ready light +3. Check the printer appears in USB devices: + ```bash + lpinfo -v | grep -i zebra + ``` +4. If the device URI is wrong or shows `/dev/null`, reconfigure: + ```bash + # Find the correct USB device + lpinfo -v | grep -i zebra + + # Update the printer (use your serial number) + sudo lpadmin -p Zebra_LP2844 -v "usb://Zebra/LP2844?serial=YOUR_SERIAL" + + # Re-enable + cupsenable Zebra_LP2844 + cupsaccept Zebra_LP2844 + ``` + +## Web interface returns "Printer error" (502) + +The app returns HTTP 502 when CUPS cannot communicate with the printer. Check: + +1. Printer status: `lpstat -p Zebra_LP2844` +2. Recent CUPS errors: `tail -20 /var/log/cups/error_log` +3. Follow steps above to enable printer and clear stuck jobs + +## Development without a printer + +Use test mode to develop without a physical printer: + +```bash +curl -X PUT -H "X-Dev-Password: dev" -H "Content-Type: application/json" \ + -d '{"test_mode": true}' http://localhost:8000/api/dev/settings +``` + +Or enable it through the web UI using the gear icon (Dev Settings) with password `dev`. diff --git a/init.md b/init.md deleted file mode 100644 index 303ff98..0000000 --- a/init.md +++ /dev/null @@ -1 +0,0 @@ -first file diff --git a/server_config.json b/server_config.json deleted file mode 100644 index b99122e..0000000 --- a/server_config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "test_mode": false, - "default_media": "continuous58", - "default_lang": "EPL", - "lock_controls": false, - "printer_name": "Zebra_LP2844", - "test_mode_delay_ms": 0 -}