Files
klarbild-oss/README.md
T
tillandClaude Opus 5 0798eb7438
CI / check (push) Has been cancelled
Astro 7, and stop a print-only instance calling a route it has switched off
The critical advisory in the dependency tree (GHSA-26w7-cxv4-gfx2, remote code
execution through AVIF image optimisation) has no fix inside Astro 5, so this
moves to Astro 7.3.2 with @astrojs/node 11 and current sharp. `npm audit` now
reports nothing. The only code the jump needed: `csp` graduated out of
`experimental`, and sharp's `Sharp` type is a named export now.

Also fixed, both found by testing with every module switched on rather than
only the print-only default:

- `src/pages/index.astro` passed `dict` to the studio island without ever
  defining it, so the studio page returned 500 on any instance with the AI
  module on. The print-only default redirects away from that page, which is
  why the earlier run never hit it.
- The print studio fetched `/api/delivery-targets` even with the delivery
  module off. The route answered 404 exactly as designed, but it left a red
  line in the console of every print-only instance, which reads like a fault.
  It now asks only when there is a module to ask, and hides the card otherwise.

Plus: the first-run box sizes itself to its content instead of miscounting
padding, and the README's counts are recounted rather than remembered.

Verified against a live instance on a fresh database, in both module sets:
sign-in, module gating, the German interface, an eight-up passport sheet and
its PDF. 124 tests, type check, build and secret scan all clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nh4A79bbqT9dcd2inzYUdg
2026-09-15 15:23:42 +00:00

166 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Klarbild
**Print pictures at the size you actually asked for.**
Your printer driver has a checkbox called "fit to page". Tick it and a 35 × 45 mm
passport photo comes out 34 × 44. Untick it and you are guessing. Photo shops have
software for this; the rest of us have a ruler and a bad afternoon.
Klarbild is that software, self-hosted. Give it an image and a physical size, and
it produces a PDF that measures exactly that on paper — with crop marks, with
bleed if you want it, with as many pictures on one sheet as fit, and with the
oversize cases handled rather than silently scaled.
It also does AI image generation, delivery to a server, share links, mail and a
Telegram bot. **All of that is optional.** The default install is the print studio,
a database, and nothing else — no API key, no model, no object store, no account
anywhere.
**[Lay out a sheet in your browser →](https://klarbild-demo.heidrich-digital.de)** — the demo
page runs this repository's own layout engine client-side and hands you the PDF. No account,
nothing uploaded.
```bash
git clone https://github.com/tillheidrich/klarbild.git && cd klarbild
cp .env.example .env # four values at the top; the file says which
docker compose up -d
docker compose logs app # your admin password is printed here, once
```
![Klarbild's print studio: eight passport photos laid out on one A4 sheet with corner marks](docs/screenshots/print-studio.png)
---
## What the print studio does
**Exact physical sizes.** 86 sizes in 10 groups — passport photo, small prints,
photo formats, US inches, instant-camera, poster and frame sizes, squares, DIN,
cards, plain aspect ratios — or type your own (`25x35`, `13x18`, `10x7,5`). The
output is a PDF laid out in millimetres, not a scaled bitmap, so 30 × 40 cm is
30 × 40 cm.
**Sheet layout.** Put several pictures on one sheet at 100 % with crop marks:
corner marks (0.25 pt, outside the trim) or continuous lines. Equal sizes snap to
an exact grid; mixed sizes go through a MaxRects packer. Bleed 0–10 mm. 19 paper
formats from A6 to A1 — including A3+ and photo paper 9 × 13 to 20 × 30, Letter
and Legal — or a custom sheet.
**Pictures bigger than the sheet, handled three ways.** A 20 × 30 cm print on A4
is not an error message. Choose **fit** (it tells you it will not fit), **overflow**
(centred across the whole sheet, and it quantifies the loss before you print —
"3 mm off each side"), or **tile** (poster print across several sheets, with a
dashed glue flap and a page number on each).
**Crop or letterbox, never distort.** When a picture does not match its target
ratio you decide per picture: crop it (with an interactive zoom-and-pan preview)
or leave a border in a colour you pick. Nothing is ever stretched.
**Captions, duplicates, presets.** A caption in one of six positions, automatic or
your own text — and it disappears by itself when there is no room at that edge.
The same picture can appear twice on one sheet at two different sizes. Presets for
the layouts you repeat; a history of the last 100 runs per account that stores the
*instructions*, never the PDF, so "print that again" costs 1–3 KB instead of 80 MB.
**The browser preview and the server PDF share one engine.** `printlayout.ts` is
pure arithmetic with its own tests. What you see is what gets rendered — the page
of the PDF above measures 210.00 × 297.00 mm, because that is what A4 is.
<p align="center">
<img src="docs/screenshots/print-de.png" width="49%" alt="The same screen in German" />
<img src="docs/screenshots/help-en.png" width="49%" alt="The built-in manual" />
</p>
## What else is in the box, if you switch it on
| Module | What it adds | What it needs |
|---|---|---|
| `print` | everything above | nothing — always on |
| `ai` | clean up screenshots, convert, combine, generate | an OpenRouter key (paid per image) |
| `delivery` | push finished files to an SFTP/FTPS server you already have | that server |
| `mirror` | a second copy of every result, sorted by month | a second target |
| `share` | public links: expiring, revocable, optional passphrase, ZIP download | a public URL |
| `mail` | password reset, job notices, sending share links | your own SMTP mailbox |
| `telegram` | the whole tool from a chat window | a bot token |
```bash
KLARBILD_MODULES=print # the default
KLARBILD_MODULES=print,ai,share # pick what you want
KLARBILD_MODULES=all # everything
```
A module that is off is off: no navigation entry, no settings card, and its API
routes answer 404 rather than 403 — a disabled instance does not advertise what it
could have done.
## Why self-hosted matters here
The pictures are family photos, passport photos, ID pictures and client work.
Klarbild keeps them on your machine. The print studio never makes an outbound
request at all — it is arithmetic, `sharp` and `pdf-lib`. Even with the AI module
on, only what you send to the model leaves the building, and the API key is yours.
Share links are built to be careful rather than convenient: they expire (30 days by
default), they can be revoked, they can carry a passphrase, downloads can be
switched off, and they are `noindex`. Access hangs on the link, not on the image, so
guessing an image id gets you a 404. The view counter records *when* and *with what*,
never *who*: the IP is hashed with a server secret **and** the link id, one row per
visitor per hour, deleted after 90 days.
## Stack and shape
Astro 5 (SSR, Node standalone) with React islands · PostgreSQL · `sharp` ·
`pdf-lib` · `pg-boss` · OKLCH design tokens, no Tailwind. Files go to a volume by
default; S3/MinIO is available but not required.
About 13,700 lines across 117 files, 124 tests, 16 forward-only idempotent
migrations that run themselves on boot. English and German interface — 1,067
entries, covering every translatable string in the source. The English sentence is
the translation key, so there is no key file to keep in sync and a missing entry
degrades to readable English rather than to `print.sheet.title`.
## Install
**Docker (recommended).** `docker-compose.yaml` is the print-only stack: the app
and Postgres, files on a volume. `docker-compose.full.yaml` adds MinIO and turns
every module on. Both read their secrets from `.env` and refuse to start rather than
boot with a default password.
**From source.**
```bash
npm install
cp .env.example .env
npm run dev
```
Migrations and first-run setup happen on the first request. There are no built-in
accounts: the first boot creates one administrator from `ADMIN_USERNAME` /
`ADMIN_PASSWORD`, or generates a password and prints it to the log once.
**Behind a reverse proxy**, set `PUBLIC_BASE_URL` to the address the browser sees.
Coolify users: both compose files carry a commented `SERVICE_FQDN_APP_4321` line.
## For agents and scripts
`GET /llms.txt` describes the whole API in a form a model can read. `/api/*` accepts
a `Bearer` token from the admin area. `mcp/` contains an MCP server, so an assistant
can lay out and fetch a print sheet directly — `exact_size` and `print_sheet` are
the two tools worth knowing.
## Contributing
[klarbild-demo.heidrich-digital.de](https://klarbild-demo.heidrich-digital.de) is the product
page.
Issues and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for
how the code is laid out and what the house style is. Security reports go to the
address in [SECURITY.md](SECURITY.md), not to the issue tracker.
## Licence
[GNU AGPL-3.0](LICENSE). Use it, change it, run it for yourself or for others — if
you run a modified version as a service, the people using it get your changes too.
Klarbild grew out of a private tool for getting family photos onto paper at the
right size. It is maintained by [Till Heidrich](https://github.com/tillheidrich).