This page was machine-translated. Spotted a mistake?Help improve it.
Skip to content

Arquitectura

SnapOtter es un monorepo gestionado con espacios de trabajo de pnpm y Turborepo. Se despliega como una pila de Docker Compose de 3 contenedores: la imagen de la app de SnapOtter, PostgreSQL 17 y Redis 8.

Estructura del proyecto

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

Paquetes

@snapotter/image-engine

La biblioteca principal de procesamiento de imágenes construida sobre Sharp. Gestiona todas las operaciones que no son de IA: redimensionar, recortar, rotar, voltear, convertir, comprimir, eliminar metadatos y ajustes de color (brillo, contraste, saturación, escala de grises, sepia, invertir, canales de color).

Este paquete no tiene dependencias de red y se ejecuta enteramente en el proceso.

@snapotter/ai

Una capa puente que llama a scripts de Python para operaciones de ML. En el primer uso, el puente inicia un proceso persistente de despachador de Python que preimporta bibliotecas pesadas (PIL, NumPy, MediaPipe, rembg) para que las llamadas de IA posteriores omitan la sobrecarga de importación. Si el despachador aún no está listo, el puente recurre a lanzar un subproceso de Python nuevo por cada solicitud.

Los modelos no se precargan. Cada script de herramienta carga sus pesos de modelo desde el disco en el momento de la solicitud y los descarta cuando la solicitud finaliza. Consulta Huella de recursos para ver el perfil de memoria completo.

Operaciones admitidas: eliminación de fondo (rembg/BiRefNet), escalado (RealESRGAN), difuminado de rostros (MediaPipe), mejora de rostros (GFPGAN/CodeFormer), borrado de objetos (LaMa ONNX), OCR (PaddleOCR/Tesseract), colorización (DDColor), eliminación de ruido, eliminación de ojos rojos, restauración de fotos, generación de foto de pasaporte, reparación de transparencia (matting de alta resolución de BiRefNet) y redimensionado con reconocimiento de contenido (binario caire de Go).

Los scripts de Python residen en packages/ai/python/. La imagen de Docker predescarga todos los pesos de los modelos durante la compilación para que el contenedor funcione completamente sin conexión.

@snapotter/shared

Tipos de TypeScript compartidos, constantes (como APP_VERSION y definiciones de herramientas) y cadenas de traducción i18n usadas tanto por el frontend como por el backend.

Aplicaciones

API (apps/api)

Un servidor Fastify v5 que expone 241 rutas de herramientas en cinco modalidades (imagen, vídeo, audio, PDF, archivo) y que gestiona:

  • Subidas de archivos, gestión del espacio de trabajo temporal y almacenamiento persistente de archivos
  • Biblioteca de archivos de usuario con cadenas de versiones (tabla user_files) - cada resultado procesado enlaza de vuelta a su archivo de origen y registra qué herramienta se aplicó, con miniaturas autogeneradas para la página de Archivos
  • Ejecución de herramientas (dirige cada solicitud de herramienta al motor de imágenes o al puente de IA)
  • Orquestación de canalizaciones (encadenado de varias herramientas de forma secuencial)
  • Procesamiento por lotes con control de concurrencia mediante colas de tareas de BullMQ (pools: image, media, ai, docs, system)
  • Autenticación de usuarios, RBAC (roles de administrador/usuario con un conjunto completo de permisos), gestión de claves de API y limitación de tasa
  • Gestión de equipos - CRUD solo para administradores; los usuarios se asignan a un equipo mediante el campo team en su perfil
  • Ajustes de tiempo de ejecución - un almacén de clave-valor en la tabla settings que controla disabledTools, enableExperimentalTools, loginAttemptLimit y otras palancas operativas sin necesidad de redesplegar
  • Personalización de marca y preferencias de tiempo de ejecución mediante ajustes respaldados por la base de datos
  • Documentación Scalar/OpenAPI en /api/docs
  • Servir el frontend compilado como una SPA en producción

Dependencias clave: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod para la validación.

El servidor gestiona un apagado ordenado ante SIGTERM/SIGINT: drena las conexiones HTTP, detiene los workers de BullMQ, apaga el despachador de Python y cierra la conexión a la base de datos.

Web (apps/web)

Una aplicación de página única de React 19 construida con Vite. Usa Zustand para la gestión de estado, Tailwind CSS v4 para el estilo y Lucide para los iconos. Se comunica con la API a través de REST y SSE (para el seguimiento del progreso).

Las páginas incluyen un espacio de trabajo de herramientas, una página de Archivos para gestionar subidas y resultados persistentes, un constructor de automatización/canalizaciones y un panel de ajustes de administrador.

El frontend compilado lo sirve el backend de Fastify en producción, por lo que no hay un servidor web separado en el contenedor de Docker.

Docs (apps/docs)

Este sitio de VitePress. Se despliega en Cloudflare Pages automáticamente al hacer push a main.

Cómo fluye una solicitud

  1. El usuario elige una herramienta en la interfaz web y sube un archivo.
  2. El frontend envía un POST multiparte a /api/v1/tools/:section/:toolId con el archivo y los ajustes.
  3. La ruta de la API valida la entrada con Zod y luego despacha el procesamiento.
  4. Para las herramientas estándar, la tarea se encola en el pool de BullMQ adecuado (image, media o docs según la modalidad). El worker de BullMQ en el proceso autoorienta la imagen según los metadatos EXIF, ejecuta la función de procesamiento de la herramienta y devuelve el resultado.
  5. Para las herramientas de IA, el puente de TypeScript envía una solicitud al despachador persistente de Python (o lanza un subproceso nuevo como alternativa), espera a que termine y lee el archivo de salida.
  6. El progreso de la tarea se persiste en la tabla jobs de PostgreSQL para que el estado sobreviva a los reinicios del contenedor. Las actualizaciones en tiempo real se entregan mediante SSE en /api/v1/jobs/:jobId/progress.
  7. La API devuelve un jobId y un downloadUrl. El usuario descarga el archivo procesado desde /api/v1/download/:jobId/:filename.

Para las canalizaciones, la API alimenta la salida de cada paso como entrada del siguiente, ejecutándolos de forma secuencial.

Para el procesamiento por lotes, la API usa flujos de BullMQ con tareas hijas por paso y devuelve un archivo ZIP con todos los archivos procesados.

Huella de recursos

SnapOtter está diseñado para un bajo uso de memoria en reposo. Nada se precarga ni se mantiene en caliente al arrancar.

En reposo

El proceso de Node.js/Fastify, PostgreSQL y Redis están en ejecución. La RAM típica en reposo es de ~200-300 MB entre los tres contenedores (proceso de Node.js, Postgres y Redis). Sin proceso de Python, sin pesos de modelo en memoria.

Qué se inicia, y cuándo

ComponenteSe inicia cuandoMemoria mientras está activo
Servidor Fastify + Postgres + RedisArranque del contenedor~200-300 MB en total
Workers de BullMQArranque del contenedor (en el proceso)Un worker por pool (image, media, ai, docs, system)
Despachador de PythonPrimera solicitud de herramienta de IAIntérprete de Python + bibliotecas preimportadas (PIL, NumPy, MediaPipe, rembg) - sin pesos de modelo
Pesos de modelos de IADurante la solicitud de la herramienta específicaCargados desde el disco, liberados cuando la solicitud finaliza

Carga de modelos

Todos los archivos de pesos de los modelos (que suman varios GB) residen en el disco en /opt/models/ en todo momento. Cada script de herramienta de IA carga en memoria solo su propio modelo (o modelos) durante la duración de una solicitud y luego los libera. Algunos scripts llaman explícitamente a del model y torch.cuda.empty_cache() tras la inferencia para asegurar que la memoria se devuelve de inmediato.

No hay caché de modelos entre solicitudes. Ejecutar la misma herramienta de IA de forma consecutiva recarga el modelo cada vez. Esto mantiene la memoria en reposo cercana a cero a costa de un retraso de carga de modelo en cada solicitud de IA.

Arranque en frío de la primera solicitud de IA

El despachador de Python no está en ejecución cuando el contenedor arranca. La primera solicitud de IA desencadena dos cosas en paralelo: el despachador empieza a calentarse en segundo plano y la propia solicitud recurre al lanzamiento puntual de un subproceso de Python. Una vez que el despachador señala que está listo, todas las solicitudes de IA posteriores lo usan directamente y omiten el coste de lanzar el subproceso.