Files
SnapOtter/apps/docs/de/guide/architecture.md
T
SnapOtterandGitHub 991c981529 fix: make OCR portable and reliable across AMD64 and ARM64 (#519)
* fix: make OCR portable and reliable

* fix: harden OCR installation portability

* fix: pin OCR partials across downloads

* fix: make OCR execution reliably asynchronous

* fix: harden OCR portability and docs routes

* fix: preserve decoder and docs safeguards
2026-07-15 03:34:24 +08:00

9.9 KiB

description, i18n_output_hash, i18n_source_hash, i18n_provenance
description i18n_output_hash i18n_source_hash i18n_provenance
Monorepo-Struktur, App- und Paketarchitektur, Request-Lebenszyklus und Ressourcen-Footprint von SnapOtter. af8ecb20c86e 733cb3c10884 human

Architektur

SnapOtter ist ein Monorepo, das mit pnpm-Workspaces und Turborepo verwaltet wird. Es wird als 3-Container-Docker-Compose-Stack ausgeliefert: das SnapOtter-App-Image, PostgreSQL 17 und Redis 8.

Projektstruktur

snapotter/
├── apps/
│   ├── api/          # Fastify backend
│   ├── web/          # React + Vite frontend
│   └── docs/         # This VitePress site
├── packages/
│   ├── image-engine/ # Sharp-based image operations
│   ├── media-engine/ # FFmpeg spawn + progress parsing
│   ├── doc-engine/   # qpdf, LibreOffice, ghostscript wrappers
│   ├── ai/           # Python AI model bridge
│   └── shared/       # Types, constants, i18n
└── docker/           # Dockerfile and Compose config

Pakete

@snapotter/image-engine

Die zentrale Bildverarbeitungsbibliothek, aufgebaut auf Sharp. Sie übernimmt alle Nicht-KI-Operationen: Skalieren, Zuschneiden, Drehen, Spiegeln, Konvertieren, Komprimieren, Metadaten entfernen und Farbanpassungen (Helligkeit, Kontrast, Sättigung, Graustufen, Sepia, Invertieren, Farbkanäle).

Dieses Paket hat keine Netzwerkabhängigkeiten und läuft vollständig im Prozess.

@snapotter/ai

Eine Brückenschicht, die native und Python ML-Laufzeiten aufruft. Die meisten Python-Tools verwenden ein persistentes dispatcher, das umfangreiche Bibliotheken (PIL, NumPy, MediaPipe, rembg) vorimportiert, sodass nachfolgende Aufrufe den Importaufwand überspringen. OCR ist von dieser veränderlichen gemeinsamen Umgebung isoliert: fast ruft natives Tesseract auf, während balanced und best ein dediziertes persistentes JSONL dispatcher verwenden, das an die aktive unveränderliche RapidOCR/ONNX-Generation angeheftet ist. Jede Anfrage enthält einen generation lease. Bei der Aktivierung wird zunächst ein smoke test für einen Kandidaten ausgeführt und dann atomar zu seinem dispatcher gewechselt. Der vorherige dispatcher wird entleert, bevor seine Generierung in die Speicherbereinigung aufgenommen wird.

Modelle werden nicht vorgeladen. Jedes Werkzeug-Skript lädt seine Modellgewichte zur Anfragezeit von der Festplatte und verwirft sie, sobald die Anfrage abgeschlossen ist. Siehe Ressourcen-Footprint für das vollständige Speicherprofil.

Unterstützte Vorgänge: Hintergrundentfernung (rembg/BiRefNet), Hochskalierung (RealESRGAN), Gesichtsunschärfe (MediaPipe), Gesichtsverbesserung (GFPGAN/CodeFormer), Objektlöschung (LaMa ONNX), OCR (Tesseract und RapidOCR mit PP-OCR ONNX-Modellen), Kolorierung (DDColor), Rauschentfernung, Rote-Augen-Entfernung, Fotowiederherstellung, Passfoto Generierung, Transparenzkorrektur (BiRefNet HR-Matting) und inhaltsbezogene Größenänderung (Go Caire Binary).

Python-Skripte leben in packages/ai/python/. Große optionale Modellpakete werden bei Bedarf im persistenten /data/ai-Volume installiert. Accurate OCR verwendet signierte, plattformspezifische Artefakte; Für die integrierte Tesseract-Stufe ist kein Download des Modellpakets erforderlich.

@snapotter/shared

Gemeinsam genutzte TypeScript-Typen, Konstanten (wie APP_VERSION und Werkzeugdefinitionen) und i18n-Übersetzungsstrings, die sowohl vom Frontend als auch vom Backend verwendet werden.

Anwendungen

API (apps/api)

Ein Fastify-v5-Server, der 241 Werkzeug-Routen über fünf Modalitäten (image, video, audio, PDF, file) bereitstellt und Folgendes übernimmt:

  • Datei-Uploads, Verwaltung des temporären Arbeitsbereichs und persistenter Dateispeicher
  • Benutzer-Dateibibliothek mit Versionsketten (user_files-Tabelle) - jedes verarbeitete Ergebnis verweist zurück auf seine Quelldatei und erfasst, welches Werkzeug angewendet wurde, mit automatisch generierten Thumbnails für die Files-Seite
  • Werkzeugausführung (leitet jede Werkzeuganfrage an die Image-Engine oder die KI-Brücke weiter)
  • Pipeline-Orchestrierung (das sequenzielle Verketten mehrerer Werkzeuge)
  • Stapelverarbeitung mit Nebenläufigkeitssteuerung über BullMQ-Job-Warteschlangen (Pools: image, media, ai, docs, system)
  • Benutzerauthentifizierung, RBAC (admin-/user-Rollen mit einem vollständigen Berechtigungssatz), API-Schlüsselverwaltung und Ratenbegrenzung
  • Teamverwaltung - Admin-only-CRUD; Benutzer werden über das Feld team in ihrem Profil einem Team zugewiesen
  • Laufzeiteinstellungen - ein Schlüssel-Wert-Speicher in der settings-Tabelle, der disabledTools, enableExperimentalTools, loginAttemptLimit und andere betriebliche Stellschrauben ohne erneutes Deployment steuert
  • Benutzerdefiniertes Branding und Laufzeiteinstellungen über datenbankgestützte Settings
  • Scalar-/OpenAPI-Dokumentation unter /api/docs
  • Auslieferung des gebauten Frontends als SPA in der Produktion

Wichtige Abhängigkeiten: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod für die Validierung.

Der Server behandelt das kontrollierte Herunterfahren bei SIGTERM/SIGINT: Er lässt HTTP-Verbindungen auslaufen, stoppt die BullMQ-Worker, fährt den Python-Dispatcher herunter und schließt die Datenbankverbindung.

Web (apps/web)

Eine React-19-Single-Page-App, gebaut mit Vite. Nutzt Zustand für die Zustandsverwaltung, Tailwind CSS v4 für das Styling und Lucide für Icons. Kommuniziert mit der API über REST und SSE (für die Fortschrittsverfolgung).

Zu den Seiten gehören ein Werkzeug-Arbeitsbereich, eine Files-Seite zur Verwaltung persistenter Uploads und Ergebnisse, ein Automatisierungs-/Pipeline-Builder und ein Admin-Einstellungspanel.

Das gebaute Frontend wird in der Produktion vom Fastify-Backend ausgeliefert, sodass es im Docker-Container keinen separaten Webserver gibt.

Docs (apps/docs)

Diese VitePress-Site. Wird bei jedem Push auf main automatisch auf Cloudflare Pages bereitgestellt.

Wie eine Anfrage abläuft

  1. Der Benutzer wählt in der Web-UI ein Werkzeug aus und lädt eine Datei hoch.
  2. Das Frontend sendet einen Multipart-POST an /api/v1/tools/:section/:toolId mit der Datei und den Einstellungen.
  3. Die API-Route validiert die Eingabe mit Zod und stellt dann die Verarbeitung zu.
  4. Bei Standardwerkzeugen wird der Job in den passenden BullMQ-Pool eingereiht (image, media oder docs je nach Modalität). Der In-Prozess-BullMQ-Worker richtet das Bild anhand der EXIF-Metadaten automatisch aus, führt die Prozessfunktion des Werkzeugs aus und gibt das Ergebnis zurück.
  5. Bei den meisten KI-Tools sendet die TypeScript-Brücke eine Anfrage an den persistenten Python dispatcher. Schnelles OCR ruft stattdessen Tesseract auf, und genaues OCR startet die angeheftete ausführbare Datei aus der aktiven unveränderlichen OCR-Generation. Die angeforderte OCR-Stufe ist beim Eingang festgelegt und wird während der Ausführung nie stillschweigend geändert.
  6. Der Job-Fortschritt wird in der jobs-Tabelle in PostgreSQL persistiert, sodass der Zustand Container-Neustarts überdauert. Echtzeit-Updates werden über SSE unter /api/v1/jobs/:jobId/progress geliefert.
  7. Die API gibt ein jobId und ein downloadUrl zurück. Der Benutzer lädt die verarbeitete Datei von /api/v1/download/:jobId/:filename herunter.

Bei Pipelines führt die API die Ausgabe jedes Schritts als Eingabe an den nächsten weiter und führt sie sequenziell aus.

Bei der Stapelverarbeitung nutzt die API BullMQ-Flows mit Kind-Jobs pro Schritt und gibt eine ZIP-Datei mit allen verarbeiteten Dateien zurück.

Ressourcen-Footprint

SnapOtter ist auf geringen Speicherverbrauch im Leerlauf ausgelegt. Beim Start wird nichts vorgeladen oder warmgehalten.

Im Leerlauf

Der Node.js-/Fastify-Prozess, PostgreSQL und Redis laufen. Der typische Leerlauf-RAM beträgt ~200-300 MB über alle drei Container hinweg (Node.js-Prozess, Postgres und Redis). Kein Python-Prozess, keine Modellgewichte im Speicher.

Was startet, und wann

Komponente Startet bei Speicher während aktiv
Fastify-Server + Postgres + Redis Containerstart ~200-300 MB gesamt
BullMQ-Worker Containerstart (im Prozess) Ein Worker pro Pool (image, media, ai, docs, system)
Python-Dispatcher Erste KI-Werkzeuganfrage Python-Interpreter + vorab importierte Bibliotheken (PIL, NumPy, MediaPipe, rembg) - keine Modellgewichte
KI-Modellgewichte Während der Anfrage des jeweiligen Werkzeugs Von der Festplatte geladen, nach Abschluss der Anfrage freigegeben

Modellladen

Alle Modellgewichtsdateien (insgesamt mehrere GB) liegen jederzeit auf der Festplatte in /opt/models/. Jedes KI-Werkzeug-Skript lädt nur seine eigenen Modelle für die Dauer einer Anfrage in den Speicher und gibt sie dann frei. Einige Skripte rufen nach der Inferenz explizit del model und torch.cuda.empty_cache() auf, um sicherzustellen, dass der Speicher sofort zurückgegeben wird.

Es gibt keinen Modell-Cache zwischen Anfragen. Führt man dasselbe KI-Werkzeug direkt hintereinander aus, wird das Modell jedes Mal neu geladen. Das hält den Leerlaufspeicher nahe null, auf Kosten einer Modellladeverzögerung bei jeder KI-Anfrage.

Kaltstart bei der ersten KI-Anfrage

Der Python-Dispatcher läuft nicht, wenn der Container startet. Die erste KI-Anfrage löst zwei Dinge parallel aus: Der Dispatcher beginnt im Hintergrund aufzuwärmen, und die Anfrage selbst weicht auf einen einmaligen Python-Subprozess-Start aus. Sobald der Dispatcher bereit signalisiert, nutzen alle nachfolgenden KI-Anfragen ihn direkt und sparen sich die Kosten des Subprozess-Starts.