Search K
Arquitetura
O SnapOtter é um monorepo gerenciado com pnpm workspaces e Turborepo. Ele é implantado como uma pilha Docker Compose de 3 contêineres: a imagem do app SnapOtter, o PostgreSQL 17 e o Redis 8.
Estrutura do projeto
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 configPacotes
@snapotter/image-engine
A biblioteca principal de processamento de imagem construída sobre o Sharp. Ela lida com todas as operações que não são de IA: redimensionar, recortar, girar, espelhar, converter, comprimir, remover metadados e ajustes de cor (brilho, contraste, saturação, escala de cinza, sépia, inversão, canais de cor).
Este pacote não tem dependências de rede e roda inteiramente em processo.
@snapotter/ai
Uma camada de ponte que chama scripts Python para operações de ML. No primeiro uso, a ponte inicia um processo de dispatcher Python persistente que pré-importa bibliotecas pesadas (PIL, NumPy, MediaPipe, rembg) para que as chamadas de IA subsequentes dispensem o custo de importação. Se o dispatcher ainda não estiver pronto, a ponte recorre a gerar um novo subprocesso Python por requisição.
Os modelos não são pré-carregados. Cada script de ferramenta carrega os pesos do seu modelo do disco no momento da requisição e os descarta quando a requisição termina. Consulte Uso de recursos para o perfil de memória completo.
Operações suportadas: remoção de fundo (rembg/BiRefNet), upscaling (RealESRGAN), desfoque de rostos (MediaPipe), aprimoramento de rostos (GFPGAN/CodeFormer), apagamento de objetos (LaMa ONNX), OCR (PaddleOCR/Tesseract), colorização (DDColor), remoção de ruído, remoção de olhos vermelhos, restauração de fotos, geração de foto de passaporte, correção de transparência (HR-matting do BiRefNet) e redimensionamento com reconhecimento de conteúdo (binário caire em Go).
Os scripts Python ficam em packages/ai/python/. A imagem Docker pré-baixa todos os pesos de modelo durante o build para que o contêiner funcione totalmente offline.
@snapotter/shared
Tipos TypeScript compartilhados, constantes (como APP_VERSION e as definições de ferramentas) e strings de tradução i18n usadas tanto pelo frontend quanto pelo backend.
Aplicações
API (apps/api)
Um servidor Fastify v5 que expõe 241 rotas de ferramentas em cinco modalidades (image, video, audio, PDF, file) e lida com:
- Uploads de arquivos, gerenciamento de espaço de trabalho temporário e armazenamento persistente de arquivos
- Biblioteca de arquivos do usuário com cadeias de versão (tabela
user_files) - cada resultado processado remete de volta ao seu arquivo de origem e registra qual ferramenta foi aplicada, com miniaturas geradas automaticamente para a página Files - Execução de ferramentas (roteia cada requisição de ferramenta para o motor de imagem ou para a ponte de IA)
- Orquestração de pipelines (encadeamento de várias ferramentas em sequência)
- Processamento em lote com controle de concorrência via filas de jobs do BullMQ (pools: image, media, ai, docs, system)
- Autenticação de usuários, RBAC (funções admin/user com um conjunto completo de permissões), gerenciamento de chaves de API e limitação de taxa
- Gerenciamento de equipes - CRUD apenas para admin; os usuários são atribuídos a uma equipe por meio do campo
teamno seu perfil - Configurações de runtime - um armazenamento chave-valor na tabela
settingsque controladisabledTools,enableExperimentalTools,loginAttemptLimite outros parâmetros operacionais sem reimplantar - Marca personalizada e preferências de runtime por meio de configurações respaldadas pelo banco de dados
- Documentação Scalar/OpenAPI em
/api/docs - Serviço do frontend compilado como uma SPA em produção
Dependências principais: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod para validação.
O servidor lida com o encerramento gracioso em SIGTERM/SIGINT: ele drena as conexões HTTP, para os workers do BullMQ, desliga o dispatcher Python e fecha a conexão com o banco de dados.
Web (apps/web)
Um app de página única em React 19 construído com Vite. Usa Zustand para gerenciamento de estado, Tailwind CSS v4 para estilização e Lucide para ícones. Comunica-se com a API por REST e SSE (para acompanhamento de progresso).
As páginas incluem um espaço de trabalho de ferramentas, uma página Files para gerenciar uploads e resultados persistentes, um construtor de automação/pipelines e um painel de configurações de admin.
O frontend compilado é servido pelo backend Fastify em produção, portanto não há um servidor web separado no contêiner Docker.
Docs (apps/docs)
Este site VitePress. Implantado no Cloudflare Pages automaticamente a cada push para main.
Como uma requisição flui
- O usuário escolhe uma ferramenta na interface web e envia um arquivo.
- O frontend envia um POST multipart para
/api/v1/tools/:section/:toolIdcom o arquivo e as configurações. - A rota da API valida a entrada com o Zod e depois despacha o processamento.
- Para ferramentas padrão, o job é enfileirado no pool BullMQ apropriado (image, media ou docs, conforme a modalidade). O worker BullMQ em processo orienta automaticamente a imagem com base nos metadados EXIF, executa a função de processamento da ferramenta e retorna o resultado.
- Para ferramentas de IA, a ponte TypeScript envia uma requisição ao dispatcher Python persistente (ou gera um novo subprocesso como fallback), aguarda a conclusão e lê o arquivo de saída.
- O progresso do job é persistido na tabela
jobsno PostgreSQL, de modo que o estado sobrevive a reinícios do contêiner. As atualizações em tempo real são entregues via SSE em/api/v1/jobs/:jobId/progress. - A API retorna um
jobIde umdownloadUrl. O usuário baixa o arquivo processado de/api/v1/download/:jobId/:filename.
Para pipelines, a API alimenta a saída de cada etapa como entrada da próxima, executando-as em sequência.
Para o processamento em lote, a API usa flows do BullMQ com jobs filhos por etapa e retorna um arquivo ZIP com todos os arquivos processados.
Uso de recursos
O SnapOtter foi projetado para baixo uso de memória em repouso. Nada é pré-carregado ou mantido aquecido na inicialização.
Em repouso
O processo Node.js/Fastify, o PostgreSQL e o Redis estão em execução. A RAM típica em repouso é de cerca de 200 a 300 MB entre os três contêineres (processo Node.js, Postgres e Redis). Sem processo Python, sem pesos de modelo na memória.
O que inicia, e quando
| Componente | Inicia quando | Memória enquanto ativo |
|---|---|---|
| Servidor Fastify + Postgres + Redis | Início do contêiner | ~200-300 MB no total |
| Workers do BullMQ | Início do contêiner (em processo) | Um worker por pool (image, media, ai, docs, system) |
| Dispatcher Python | Primeira requisição de ferramenta de IA | Interpretador Python + bibliotecas pré-importadas (PIL, NumPy, MediaPipe, rembg) - sem pesos de modelo |
| Pesos de modelo de IA | Durante a requisição da ferramenta específica | Carregados do disco, liberados quando a requisição termina |
Carregamento de modelos
Todos os arquivos de pesos de modelo (somando vários GB) ficam no disco em /opt/models/ o tempo todo. Cada script de ferramenta de IA carrega na memória apenas o(s) seu(s) próprio(s) modelo(s) durante uma requisição e depois os libera. Alguns scripts chamam explicitamente del model e torch.cuda.empty_cache() após a inferência para garantir que a memória seja devolvida imediatamente.
Não há cache de modelo entre requisições. Executar a mesma ferramenta de IA em sequência recarrega o modelo a cada vez. Isso mantém a memória em repouso próxima de zero, ao custo de um atraso de carregamento do modelo em cada requisição de IA.
Cold start da primeira requisição de IA
O dispatcher Python não está em execução quando o contêiner inicia. A primeira requisição de IA dispara duas coisas em paralelo: o dispatcher começa a aquecer em segundo plano e a própria requisição recorre a gerar um subprocesso Python único e avulso. Assim que o dispatcher sinaliza que está pronto, todas as requisições de IA subsequentes o usam diretamente e evitam o custo de gerar subprocessos.
