Files
SnapOtter/apps/docs/fr/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

10 KiB
Raw Blame History

description, i18n_output_hash, i18n_source_hash, i18n_provenance
description i18n_output_hash i18n_source_hash i18n_provenance
Structure du monorepo, architecture des applications et des packages, cycle de vie des requêtes et empreinte de ressources de SnapOtter. 31c734145cf5 733cb3c10884 human

Architecture

SnapOtter est un monorepo géré avec les espaces de travail pnpm et Turborepo. Il se déploie sous forme de pile Docker Compose à 3 conteneurs : l'image de l'application SnapOtter, PostgreSQL 17 et Redis 8.

Structure du projet

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

Packages

@snapotter/image-engine

La bibliothèque principale de traitement d'images construite sur Sharp. Elle gère toutes les opérations sans IA : redimensionnement, rognage, rotation, retournement, conversion, compression, suppression des métadonnées et ajustements de couleur (luminosité, contraste, saturation, niveaux de gris, sépia, inversion, canaux de couleur).

Ce package n'a aucune dépendance réseau et s'exécute entièrement en cours de processus.

@snapotter/ai

Une couche de pont qui appelle les environnements d'exécution natifs et Python ML. La plupart des outils Python utilisent un dispatcher persistant qui pré-importe des bibliothèques lourdes (PIL, NumPy, MediaPipe, rembg), de sorte que les appels ultérieurs ignorent la surcharge d'importation. OCR est isolé de cet environnement partagé mutable : fast invoque Tesseract natif, tandis que balanced et best utilisent un JSONL persistant dédié dispatcher épinglé à la génération RapidOCR/ONNX active et immuable. Chaque requête contient un generation lease. L'activation exécute d'abord un smoke test sur un candidat, puis passe atomiquement à son dispatcher. Le dispatcher précédent est drainé avant que sa génération ne soit récupérée.

Les modèles ne sont pas préchargés. Chaque script d'outil charge les poids de son modèle depuis le disque au moment de la requête et les libère une fois la requête terminée. Consultez Empreinte de ressources pour le profil mémoire complet.

Opérations prises en charge : suppression de l'arrière-plan (rembg/BiRefNet), mise à l'échelle (RealESRGAN), flou du visage (MediaPipe), amélioration du visage (GFPGAN/CodeFormer), effacement d'objets (LaMa ONNX), OCR (Tesseract et RapidOCR avec les modèles PP-OCR ONNX), colorisation (DDColor), suppression du bruit, suppression des yeux rouges, restauration de photos, génération de photos d'identité, correction de la transparence. (BiRefNet HR-matting) et redimensionnement sensible au contenu (binaire Go caire).

Les scripts Python résident dans packages/ai/python/. De grands packs de modèles facultatifs sont installés à la demande dans le volume persistant /data/ai. Accurate OCR utilise des artefacts signés et spécifiques à la plate-forme ; le niveau Tesseract intégré ne nécessite aucun téléchargement de pack de modèles.

@snapotter/shared

Types TypeScript partagés, constantes (comme APP_VERSION et les définitions d'outils) et chaînes de traduction i18n utilisées à la fois par le frontend et le backend.

Applications

API (apps/api)

Un serveur Fastify v5 exposant 241 routes d'outils réparties sur cinq modalités (image, vidéo, audio, PDF, fichier) qui gère :

  • Les téléversements de fichiers, la gestion de l'espace de travail temporaire et le stockage persistant des fichiers
  • La bibliothèque de fichiers utilisateur avec chaînes de versions (table user_files) - chaque résultat traité renvoie à son fichier source et enregistre l'outil appliqué, avec des vignettes auto-générées pour la page Fichiers
  • L'exécution des outils (achemine chaque requête d'outil vers le moteur d'images ou le pont d'IA)
  • L'orchestration de pipelines (enchaînement séquentiel de plusieurs outils)
  • Le traitement par lots avec contrôle de la concurrence via les files d'attente de tâches BullMQ (pools : image, media, ai, docs, system)
  • L'authentification des utilisateurs, le RBAC (rôles admin/user avec un ensemble complet de permissions), la gestion des clés d'API et la limitation de débit
  • La gestion des équipes - CRUD réservé aux admins ; les utilisateurs sont affectés à une équipe via le champ team de leur profil
  • Les paramètres d'exécution - un magasin clé-valeur dans la table settings qui contrôle disabledTools, enableExperimentalTools, loginAttemptLimit et d'autres réglages opérationnels sans redéploiement
  • L'image de marque personnalisée et les préférences d'exécution via des paramètres stockés en base de données
  • La documentation Scalar/OpenAPI à /api/docs
  • La distribution du frontend compilé sous forme de SPA en production

Dépendances clés : Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod pour la validation.

Le serveur gère l'arrêt gracieux sur SIGTERM/SIGINT : il draine les connexions HTTP, arrête les workers BullMQ, arrête le dispatcher Python et ferme la connexion à la base de données.

Web (apps/web)

Une application monopage React 19 construite avec Vite. Utilise Zustand pour la gestion de l'état, Tailwind CSS v4 pour le style et Lucide pour les icônes. Communique avec l'API via REST et SSE (pour le suivi de la progression).

Les pages comprennent un espace de travail d'outils, une page Fichiers pour gérer les téléversements et résultats persistants, un constructeur d'automatisation/pipeline, et un panneau de paramètres d'administration.

Le frontend compilé est distribué par le backend Fastify en production, il n'y a donc pas de serveur web distinct dans le conteneur Docker.

Docs (apps/docs)

Ce site VitePress. Déployé automatiquement sur Cloudflare Pages à chaque push sur main.

Cheminement d'une requête

  1. L'utilisateur choisit un outil dans l'interface web et téléverse un fichier.
  2. Le frontend envoie une requête POST multipart à /api/v1/tools/:section/:toolId avec le fichier et les paramètres.
  3. La route de l'API valide l'entrée avec Zod, puis lance le traitement.
  4. Pour les outils standards, la tâche est mise en file d'attente dans le pool BullMQ approprié (image, media ou docs selon la modalité). Le worker BullMQ en cours de processus oriente automatiquement l'image d'après les métadonnées EXIF, exécute la fonction de traitement de l'outil et renvoie le résultat.
  5. Pour la plupart des outils d'IA, le pont TypeScript envoie une requête au Python dispatcher persistant. OCR rapide appelle à la place Tesseract, et OCR précis démarre l'exécutable épinglé à partir de la génération OCR immuable active. Le niveau OCR demandé est fixé lors de lentrée et nest jamais modifié silencieusement pendant lexécution.
  6. La progression de la tâche est persistée dans la table jobs de PostgreSQL afin que l'état survive aux redémarrages du conteneur. Les mises à jour en temps réel sont livrées via SSE à /api/v1/jobs/:jobId/progress.
  7. L'API renvoie un jobId et une downloadUrl. L'utilisateur télécharge le fichier traité depuis /api/v1/download/:jobId/:filename.

Pour les pipelines, l'API alimente l'étape suivante avec la sortie de chaque étape, en les exécutant séquentiellement.

Pour le traitement par lots, l'API utilise des flux BullMQ avec des tâches enfants par étape et renvoie un fichier ZIP contenant tous les fichiers traités.

Empreinte de ressources

SnapOtter est conçu pour une faible utilisation de mémoire au repos. Rien n'est préchargé ni maintenu chaud au démarrage.

Au repos

Le processus Node.js/Fastify, PostgreSQL et Redis sont en cours d'exécution. La RAM typique au repos est de ~200 à 300 Mo répartie sur les trois conteneurs (processus Node.js, Postgres et Redis). Aucun processus Python, aucun poids de modèle en mémoire.

Ce qui démarre, et quand

Composant Démarre quand Mémoire pendant l'activité
Serveur Fastify + Postgres + Redis Démarrage du conteneur ~200 à 300 Mo au total
Workers BullMQ Démarrage du conteneur (en cours de processus) Un worker par pool (image, media, ai, docs, system)
Dispatcher Python Première requête d'outil d'IA Interpréteur Python + bibliothèques pré-importées (PIL, NumPy, MediaPipe, rembg) - aucun poids de modèle
Poids des modèles d'IA Pendant la requête de l'outil concerné Chargés depuis le disque, libérés à la fin de la requête

Chargement des modèles

Tous les fichiers de poids des modèles (totalisant plusieurs Go) résident en permanence sur le disque dans /opt/models/. Chaque script d'outil d'IA charge en mémoire uniquement son ou ses modèles pour la durée d'une requête, puis les libère. Certains scripts appellent explicitement del model et torch.cuda.empty_cache() après l'inférence pour garantir la restitution immédiate de la mémoire.

Il n'y a pas de cache de modèles entre les requêtes. Exécuter le même outil d'IA de manière consécutive recharge le modèle à chaque fois. Cela maintient la mémoire au repos proche de zéro au prix d'un délai de chargement du modèle à chaque requête d'IA.

Démarrage à froid de la première requête d'IA

Le dispatcher Python n'est pas en cours d'exécution au démarrage du conteneur. La première requête d'IA déclenche deux choses en parallèle : le dispatcher commence à se préchauffer en arrière-plan, et la requête elle-même se rabat sur le lancement ponctuel d'un sous-processus Python. Une fois que le dispatcher signale qu'il est prêt, toutes les requêtes d'IA suivantes l'utilisent directement et évitent le coût du lancement d'un sous-processus.