Arquitetura
Três contêineres principais, mais um serviço mDNS, sem intermediário de mensagens separado nem serviços em nuvem obrigatórios. As fotos são enviadas ou importadas de pastas do NAS e depois processadas para extrair metadados, miniaturas, vetores semânticos, rostos e texto. O resultado é uma biblioteca de fotos pesquisável por linguagem natural, pessoa e lugar.
Backend em Go, serviço de IA e PostgreSQL, mais um serviço mDNS que anuncia o domínio local
metadata, thumbnail, clip, face, ocr
Vetores imagem-texto do Chinese-CLIP (512, 768 ou 1024, conforme o nível de modelos); as características faciais do ArcFace têm 512 dimensões
Índice de distância cosseno do pgvector; busca e comparação de rostos acontecem no banco de dados
Arquitetura do sistema
O navegador só conversa com o backend em Go, que cuida dos uploads, do agendamento de tarefas, das miniaturas e das permissões, envia as prévias ao serviço de IA para inferência e grava todos os resultados no PostgreSQL. O serviço de IA não guarda estado: nunca acessa o banco de dados nem armazena arquivos, então pode rodar numa máquina mais potente.
O app web vai embutido no binário, então a instalação não precisa de Node. As migrações do banco rodam sozinhas na inicialização, e atualizar é só trocar a imagem.
O serviço de IA embutido roda na rede interna do Docker e processa na CPU. Informe um endereço remoto nas configurações web para passar a usar um PC com placa de vídeo ou um Mac de casa.
Informações das fotos, pessoas, configurações, fila de tarefas e vetores ficam todos no PostgreSQL, então os dados do app e o estado das tarefas podem ir juntos para o backup. Os arquivos de foto precisam de um backup separado.
Pipeline de processamento de fotos
Depois da importação, cada foto primeiro tem os metadados extraídos e as miniaturas geradas; em seguida, vetores semânticos, rostos e texto são calculados em paralelo. Cada etapa entra na fila separadamente, então reiniciar ou perder o serviço de IA não perde trabalho: o processamento continua quando tudo volta.
ingest- Uploads web via tus, retomam após quedas
- Pastas /photos varridas a cada 15 minutos
- Soma SHA-1 calculada e gravada em assets
metadata- ExifTool lê dimensões e data da foto
- GPS, câmera, lente, abertura e obturador
- Horários sem fuso usam o fuso configurado
thumbnail- libvips gera as miniaturas
- E uma prévia de 1440 px
- FFmpeg extrai quadros e converte vídeos
clip- Chinese-CLIP calcula vetores de 1024-d
- Busca em linguagem natural, categorias
- Nota estética, fotos parecidas
face- SCRFD detecta e alinha por 5 pontos
- ArcFace calcula características de 512-d
- Agrupados em pessoas aos poucos
ocr- PP-OCR lê o texto das fotos
- Buscar fotos pelo texto
- Identifica documentos e capturas
Agendamento de tarefas em segundo plano
Não há intermediário de mensagens separado: a fila de tarefas é uma tabela jobs no PostgreSQL, gravada na mesma transação que os dados da foto, então uma foto salva sempre tem suas tarefas na fila. Os workers pegam tarefas com SKIP LOCKED, arrendamentos evitam travamentos e as falhas são repetidas com espera quadrática.
-- Pegar uma tarefa (server/internal/jobs/worker.go)
UPDATE jobs SET status = 'running', attempts = attempts + 1,
locked_by = $1, locked_until = now() + $2::interval
WHERE id = (
SELECT id FROM jobs
WHERE status = 'pending' AND run_after <= now() AND type = ANY($3)
ORDER BY priority DESC, run_after
FOR UPDATE SKIP LOCKED -- workers simultâneos nunca pegam a mesma
LIMIT 1
)
RETURNING id, type, asset_id, payload, attempts, max_attempts;- Uma tarefa do mesmo tipo para a mesma foto nunca entra duas vezes na fila enquanto outra espera ou roda.
- Com a fila vazia, a consulta é feita a cada segundo; as tarefas são pegas por prioridade (maior primeiro) e depois por prazo (mais cedo primeiro).
- O arrendamento de cada tarefa (que também é o seu tempo-limite) é de 10 minutos; ao reiniciar, um processo devolve à fila as próprias tarefas inacabadas.
- Enquanto alguém envia fotos, nenhuma tarefa de IA é pega, para que a banda e a CPU fiquem primeiro com o upload.
- Se o serviço de IA não responde, todo o worker de IA para e espera, em vez de pegar tarefas uma a uma só para falharem e gastarem repetições.
- O nível "Automático" detecta primeiro o hardware do servidor de IA e escolhe modelos adequados antes de pegar tarefas, o que ajuda a evitar carregar num Raspberry Pi ou parecido modelos que excedam a memória disponível.
Agendamento da IA
O serviço de IA só recebe prévias e devolve rostos, vetores e texto. O backend decide o endereço do serviço, os modelos e o paralelismo; o serviço de IA escolhe CUDA, CoreML / MPS, OpenVINO ou CPU conforme o hardware.
No "Automático", o nível segue o hardware do servidor de IA: Alta precisão com placa de vídeo utilizável; só com CPU, Leve com menos de 6 GB de memória e Equilibrado com 6 GB ou mais.
| Nível | Hardware indicado | Rostos | Busca semântica |
|---|---|---|---|
| quality · Alta precisão | Mac com Apple Silicon, GPU NVIDIA | SCRFD-10G + ArcFace R100 (antelopev2) | Chinese-CLIP ViT-H/14 · 1024-d |
| balanced · Equilibrado | NAS classe Intel i5 | SCRFD-10G + ArcFace R100 (antelopev2) | Chinese-CLIP ViT-L/14 · 768-d |
| lite · Leve | N100, Raspberry Pi 5 | SCRFD + ArcFace R50 (buffalo_l) | Chinese-CLIP ViT-B/16 · 512-d |
POST /predict (multipart: image + tasks)
Authorization: Bearer <token>
tasks = {
"face": {"model": "antelopev2", "min_score": 0.5},
"clip": {"model": "chinese-clip-vit-h-14"},
"ocr": {}
}
→ {
"face": [{"box": [x, y, w, h], "score": 0.93,
"embedding": [512 números], "yaw": 0.12, "blur": 340}],
"clip": {"embedding": [1024 números]},
"ocr": {"text": "...", "lines": [...]},
"model_ids": {"face": "antelopev2", "clip": "chinese-clip-vit-h-14"}
}Algoritmos principais
Os modelos, algoritmos e limiares por trás da busca, das pessoas, das sequências e das escolhas da IA. Alguns dependem de modelos de IA; outros, de cálculo vetorial e regras.
O Chinese-CLIP coloca fotos e texto no mesmo espaço vetorial. Os vetores de imagem são calculados na importação; na busca, uma frase como "terceiro aniversário do bebê" vira um vetor de texto, e as fotos mais próximas são encontradas por distância cosseno no índice HNSW do pgvector.
- Três fontes de resultados são combinadas: primeiro as fotos em que as palavras buscadas foram reconhecidas, depois as tiradas num lugar com esse nome e, por fim, as mais próximas em significado.
- Só voltam correspondências claras: um resultado precisa estar quase tão perto quanto o melhor e claramente mais perto que uma foto qualquer. Isso ajuda a filtrar correspondências fracas em vez de devolver fotos pouco relacionadas.
- Se a busca for exatamente o nome de uma categoria (paisagens, comida, capturas de tela…), essa categoria é devolvida diretamente.
O SCRFD encontra cada rosto e seus 5 pontos de referência, alinha e recorta em 112×112, e o ArcFace calcula um vetor de 512 dimensões (após a normalização, o produto escalar é a similaridade cosseno). O agrupamento acontece aos poucos, a cada foto concluída, sem esperar a biblioteca inteira.
- Os "rostos bons", nítidos, de frente e grandes o bastante, procuram os mais parecidos: se alguns já pertencem a uma pessoa, entram nela por maioria; se nenhum está atribuído, mas pelo menos 3 se parecem, uma nova pessoa é criada.
- Rostos borrados, de perfil ou pequenos demais nunca criam uma pessoa; só se juntam a alguém com quem se parecem muito. Quem aparece por acaso uma vez não é cadastrado.
- Agrupamentos simultâneos usam o mesmo bloqueio consultivo do banco, então ninguém é criado em dobro. As capas preferem rostos únicos nítidos (variância do laplaciano), de frente (yaw) e grandes.
- Fotos seguidas da mesma cena costumam ter similaridade CLIP de 0,94 a 0,98, enquanto uma troca de cena ou de pose costuma ficar entre 0,7 e 0,89; um limiar de similaridade de 0,93 ajuda a identificar prováveis sequências.
- A foto recomendada: com rostos, aquela em que eles estão nítidos, de olhos abertos e olhando para a câmera; sem rostos, o arquivo maior (com a mesma câmera e configuração, fotos tremidas têm menos detalhe e ficam menores após a compressão).
- Várias cópias do mesmo arquivo vão para "Duplicadas", sem precisar escolher. "Manter" move o resto para "Apagadas recentemente", de onde pode ser restaurado até ser apagado definitivamente.
Nenhum modelo extra é executado; os vetores CLIP são reaproveitados. A média dos vetores de um conjunto de descrições de "obras fotográficas" menos a de um conjunto de "fotos do dia a dia" dá uma direção de "fica bonito". A projeção de uma foto nessa direção é a sua nota.
- Serve só para ordenar, nunca como nota absoluta; o carrossel de destaques da tela inicial escolhe as fotos por ela.
- Outro conjunto de descrições identifica as fotos que deram errado (tremidas, estouradas etc.), avaliadas à parte de quão bonita a foto é.
Tecnologias usadas
Apenas componentes de código aberto maduros, sem serviços proprietários; cada peça tem alternativa.
- GoAPI, fila de tarefas, agendamento de miniaturas
- libvipsMiniaturas e prévias
- FFmpegQuadros de vídeo, conversão
- ExifToolLeitura e gravação de metadados
- tusUploads retomáveis
- Python + FastAPIServiço HTTP sem estado
- ONNX RuntimeInferência de rostos e texto
- PyTorchChinese-CLIP
- InsightFaceSCRFD + ArcFace
- RapidOCRReconhecimento de texto PP-OCR
- PostgreSQL 17Dados do app e fila de tarefas
- pgvectorArmazenamento de vetores
- HNSWÍndice de distância cosseno
- ip2regionLocal por IP no histórico de acessos (offline)
- NominatimGeocodificação reversa (pode ser desligada)
- React + TypeScriptApp web
- Vite + Tailwind CSSBuild e estilos
- MapLibre GLGlobo por satélite
- Docker ComposeImagens amd64 / arm64
- mDNSDomínio local wanmory.local
Privacidade e segurança
Por padrão, as fotos e os resultados da IA ficam dentro da sua rede privada. Se você configurar um serviço de IA remoto, as prévias são enviadas a esse servidor para processamento.
O serviço de IA só recebe prévias de 1440 pixels, não guarda nenhum arquivo e nunca toca no banco de dados, nos nomes ou nos originais.
O serviço de IA embutido só existe na rede interna do Docker e apenas o backend chega até ele; quando há um token definido para um serviço de IA remoto, toda requisição precisa levar um token Bearer.
Os membros só podem apagar as fotos que enviaram, e os administradores cuidam de tudo; fotos ocultas só aparecem para quem as ocultou; o histórico de acessos mostra uma localização aproximada pelo IP.
As pastas do NAS podem ser montadas só para leitura com :ro, assim o app não consegue alterar nem apagar os arquivos delas.
Depois de baixar os modelos, o reconhecimento e a busca acontecem localmente; recursos que precisam de internet, como a geocodificação reversa e as imagens de satélite, podem ser desligados.
Os modelos faciais do InsightFace são só para uso não comercial; os modelos não vêm na imagem, e as configurações mostram a licença de cada um antes do download.