Años de móviles, tarjetas SD y copias de seguridad de WhatsApp acaban en lo mismo: decenas de miles de fotos donde las de verdad (la familia) están enterradas bajo memes, capturas de pantalla y tarjetas de buenos días. Me monté Immich en casa para sacar las fotos de Google, y luego usé IA local para separar el grano de la paja: 74.000 fotos revisadas por máquina, y la máquina no borró ni una. Así lo hice.
Ingenieria
Soft2Help (Internal)
Immich, Python, CLIP (open_clip), PyTorch, Docker, CUDA / GPU propia, TrueNAS / ZFS, NFS
Como casi todo el mundo, mi fototeca familiar era una suma de restos: el carrete de varios móviles, tarjetas de cámaras viejas, carpetas sueltas en discos duros y, lo peor de todo, los backups de WhatsApp de años. Quien haya abierto alguna vez la carpeta de imágenes de WhatsApp sabe lo que hay dentro: por cada foto real de tus hijos hay diez memes, tarjetas de «buenos días familia», capturas de conversaciones, recibos fotografiados y cadenas reenviadas mil veces. Todo eso, mezclado, sumaba más de 74.000 imágenes.
Y encima estaba donde no quería que estuviera: en Google. Las fotos de mi familia son probablemente lo más privado que tengo en digital, y las tenía en el mismo sitio donde ya no me caben sin pagar y donde las condiciones las pone otro. Ese argumento lo cuento más en serio en cómo usar ChatGPT en la empresa sin regalar tus datos; con las fotos de los niños aplica el doble.
Elegí Immich tras comparar con PhotoPrism, LibrePhotos y Nextcloud Photos. Gana en lo que importa para sustituir de verdad a Google Fotos: app móvil con subida automática, reconocimiento de caras que funciona, búsqueda en lenguaje natural («tarta de cumpleaños», «playa al atardecer») y mapa por GPS. Es, con diferencia, el proyecto más vivo de los cuatro.
Todo esto corre en un único servidor de segunda mano: un Dell PowerEdge R720XD con Proxmox, la caja clásica de homelab (dos procesadores, mucha RAM DDR3 barata y doce bahías de disco). Sobre él, tres máquinas virtuales con papeles muy distintos:
Móviles de la familia (app Immich, subida automática)
│
▼
┌────────────────────────────────────────────────────────────┐
│ Dell R720XD · Proxmox │
│ │
│ ┌───────────────────────────┐ │
│ │ VM de servicios (GPU) │ ◄── 2× Tesla P4 │
│ │ Immich + Postgres │ │
│ │ caras · CLIP · vídeo │ │
│ └──────────┬────────────────┘ │
│ │ NFS (solo el dataset de fotos) │
│ ▼ │
│ ┌───────────────────────────┐ ┌────────────────────────┐ │
│ │ VM TrueNAS PERSONAL │ │ VM TrueNAS TRABAJO │ │
│ │ pool ZFS propio (3 HDD) │ │ pool ZFS propio │ │
│ │ dataset photos │ │ (raidz1, 4 HDD) │ │
│ │ snapshots 30d + 12sem │ │ backups + assets │ │
│ └───────────────────────────┘ └────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
La decisión que más me ha compensado es la separación personal/trabajo a nivel de NAS: no son dos carpetas, son dos TrueNAS completas, cada una con su pool ZFS sobre sus propios discos físicos. La controladora del servidor pasa los discos a cada VM y cada TrueNAS gestiona los suyos: tres discos para el pool personal, cuatro en raidz1 para el de trabajo. Nada se comparte: ni usuarios, ni permisos, ni snapshots, ni radio de explosión. Un error gordo administrando el NAS de trabajo (que los hay) no puede tocar ni un byte de las fotos de la familia, y al revés tampoco. Y el día que una de las dos necesite crecer o mudarse de servidor, se lleva sus discos y listo.
Sobre esa base, el reparto de papeles:
photos) con snapshots automáticos: diarios que guardo 30 y semanales que guardo 12. Si mañana Immich se rompe, me equivoco borrando o un ransomware toca lo montado, las fotos están a un snapshot de solo lectura de distancia.Cuando lo volqué todo, la línea de tiempo era inservible: entre las fotos del bautizo aparecían capturas de un instalador de Windows, el meme del lunes y el cartel reenviado de la verbena de 2019. Revisar 74.000 imágenes a mano, a un segundo por foto, son más de veinte horas sin parpadear. No iba a pasar.
La solución fue un script de menos de 200 líneas con CLIP en zero-shot: sin entrenar nada, sin etiquetar nada. CLIP puntúa cuánto se parece una imagen a una frase, así que escribí dos listas de frases: lo que es basura y lo que hay que conservar.
JUNK = [
"a screenshot of a smartphone user interface",
"a screenshot of a chat conversation",
"a scanned document or printed page of text",
"a photo of a receipt or ticket",
"an internet meme with caption text",
"a greeting card image with large text like good morning",
"a screenshot of computer software or an application window",
"a screenshot of a website in a web browser",
]
KEEP = [
"a candid photo of people", "a family photograph",
"a selfie of a person", "a photo of a child",
"people at a party or celebration",
"a landscape or nature photograph",
"a photo of food", "a photo of a pet or animal",
]
Cada foto pasa por el modelo, se calcula la probabilidad de que sea basura frente a foto de verdad, y por encima de un umbral se marca. En la Tesla P4 va a unas 44 imágenes por segundo: la librería entera, unos 30 minutos.
Aquí está la decisión de diseño más importante, y es la misma que aplico en los proyectos de OCR para clientes: la máquina propone y la persona dispone. El script no borra ni una foto. Lo único que hace es meter las sospechosas en un álbum de Immich llamado «WhatsApp basura (revisar)». Luego yo abro ese álbum con un café, repaso en vista de miniaturas (ahí sí, a golpe de vista van rapidísimo, porque el álbum ya es 95% basura de verdad) y borro yo.
Y como red de seguridad extra, otro script recorre el álbum de revisión y rescata cualquier foto donde Immich haya reconocido una cara con nombre: si sale alguien de la familia etiquetado, esa foto sale del álbum de basura automáticamente. Prefiero un meme superviviente que una foto del abuelo borrada.
Dos limpiezas más, cada una con su herramienta:
Primera pasada real, sobre 60.900 imágenes:
Detalles que agradeces cuando lo relanzas:
--dry-run --limit 500 para probar prompts y umbrales sin tocar ningún álbum.Hoy la línea de tiempo de Immich son fotos de familia, de la primera a la última. Buscas «nieve» y sale el día de la nevada, no una captura de la app del tiempo. Las caras están agrupadas y con nombre, los originales duermen en ZFS con snapshots, los móviles suben solos y Google no tiene nada.
Y la lección transferible, que es la misma de mi banco de pruebas de OCR: la IA local, bien acotada, convierte tareas imposibles por volumen en tareas de una tarde. Sin subir un solo dato a nadie, con hardware que cuesta lo que una cena, y con la última palabra siempre en manos de una persona. Eso mismo, aplicado a los documentos de una empresa en vez de a las fotos de una familia, es a lo que me dedico.
La versión de esta historia sin jerga técnica (por qué saqué las fotos de Google y qué gana una familia con esto) la cuento en el blog: Tus fotos fuera de Google: mi experiencia con Immich.
Con un modelo de IA local (CLIP) al que le das dos listas de descripciones: qué aspecto tiene el ruido (memes, capturas de pantalla, tarjetas de buenos días, documentos fotografiados) y qué aspecto tiene una foto de verdad (gente, celebraciones, paisajes). Con eso la máquina revisó 74.000 imágenes en menos de una hora, sin enviar ni una foto fuera de casa.
Un servidor de segunda mano da de sobra: en este caso un Dell R720XD con Proxmox, almacenamiento ZFS sobre TrueNAS para los originales y una GPU modesta para la parte de IA. La app del móvil sube las fotos sola, como con Google Fotos, y el reconocimiento de caras y la búsqueda por texto corren en local.
No: la máquina solo clasifica y separa para revisar, y de 74.000 fotos no borró ni una. La decisión de borrar sigue siendo humana; la IA se limita a convertir veinte horas de criba manual en una lista ya ordenada que se repasa en un rato.