feat: custom formats (WxH/stickerN), JPG output option, failed-job status

- format.ts parses free formats from the key: sticker<N> -> N×N cm,
  <W>x<H> -> W×H cm. Fixes 'Unbekanntes Format: sticker5' (the seeded
  'Sticker 5 cm' recipe was unusable) and lets admins define ANY format
  without a migration. Guarded to <=300 cm.
- Output file format PNG/JPG: global default (settings.output_ext) plus
  per-recipe/per-conversion override. JPG only for non-alpha; cutouts
  stay PNG. file.ts sniffs content-type from magic bytes.
- Jobs can now be 'failed' (all items failed) -> red in the queue; add
  finished time to queue list + detail. migration 012.
- Studio: custom-measure input + Dateiformat selector; Admin: global
  Standard-Dateiformat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
tillandClaude Opus 4.8 committed 2026-07-24 08:32:03 +00:00
1 parent efcb889c96
commit 3a061aa612
12 files changed
+142 -21

No files matched your search

+6 -1
View File
@@ -31,7 +31,12 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
try {
let buf = await getObject(path);
const download = q.get('download') === '1';
let contentType = path.endsWith('.webp') ? 'image/webp' : 'image/png';
// Inhaltstyp aus den Magic Bytes ableiten — Ergebnisse können PNG oder JPG sein.
const sniff = (b: Buffer): string =>
b.length > 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff ? 'image/jpeg'
: b.length > 12 && b.toString('ascii', 8, 12) === 'WEBP' ? 'image/webp'
: 'image/png';
let contentType = path.endsWith('.webp') ? 'image/webp' : sniff(buf);
// Schnelle Vollbild-Vorschau: nicht-transparente Ergebnisse als JPEG (deutlich kleiner,
// schneller auf Mobil) — reicht fürs „In Fotos sichern". Der PNG-Download bleibt unverändert.
if (q.get('preview') === '1' && !download && !item?.has_alpha) {