Search K
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 configPaquetes
@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
teamen su perfil - Ajustes de tiempo de ejecución - un almacén de clave-valor en la tabla
settingsque controladisabledTools,enableExperimentalTools,loginAttemptLimity 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
- El usuario elige una herramienta en la interfaz web y sube un archivo.
- El frontend envía un POST multiparte a
/api/v1/tools/:section/:toolIdcon el archivo y los ajustes. - La ruta de la API valida la entrada con Zod y luego despacha el procesamiento.
- 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.
- 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.
- El progreso de la tarea se persiste en la tabla
jobsde 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. - La API devuelve un
jobIdy undownloadUrl. 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
| Componente | Se inicia cuando | Memoria mientras está activo |
|---|---|---|
| Servidor Fastify + Postgres + Redis | Arranque del contenedor | ~200-300 MB en total |
| Workers de BullMQ | Arranque del contenedor (en el proceso) | Un worker por pool (image, media, ai, docs, system) |
| Despachador de Python | Primera solicitud de herramienta de IA | Intérprete de Python + bibliotecas preimportadas (PIL, NumPy, MediaPipe, rembg) - sin pesos de modelo |
| Pesos de modelos de IA | Durante la solicitud de la herramienta específica | Cargados 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.
