Skip to main content

260828-Tofs à compresse sur SASNEXTE

Compression des photos du NAS SASNEXTE — étude de faisabilité

2026-08-28. Suite de la procédure Photos-Caesium (page 284), appliquée cette fois au stock photo du NAS. Copie de référence : Jux-scripts/Photos-Caesium/doc_bookstack_285.md.

En bref

C'est faisable, et le NAS est le bon endroit pour le faire — pas le PC à travers le VPN.

Débit mesuré sur le NAS (Celeron J4025, 2 cœurs) 1,55 Mo/s
Gain mesuré sur 300 photos réelles -59,5 %
Stock JPEG concerné 16 422 fichiers, 37,59 Go
Durée estimée du rattrapage complet ~6,7 heures
Espace récupérable ~22,4 Go
EXIF, GPS, dates, dimensions préservés, vérifié sur les fichiers du NAS

Une réserve bloquante avant tout déploiement : la majorité de ces photos n'a pas de copie hors du NAS. Voir « Points de vigilance ».


1. Rétablissement de l'accès SSH

Le NAS était joignable en SMB par le tunnel WireGuard (page 245) mais pas en SSH.

Cause réelle : le service SSH était désactivé et le port 22 fermé par les règles de sécurité DSM posées par Julien. Leur neutralisation a tout débloqué immédiatement.

Ce qui m'a égaré, et qu'il faut noter pour ne pas s'y reprendre : le port 22 répondait par une bannière SSH-2.0-OpenSSH_8.2, annonçait publickey,password et évaluait les identifiants avant de les rejeter par « Permission denied ». J'en ai conclu à tort que le service tournait et que le problème venait de l'autorisation — j'ai successivement soupçonné le groupe administrators, puis la vérification en deux étapes. Les deux hypothèses étaient fausses. Une bannière SSH qui répond ne prouve pas que l'accès soit ouvert sur ce NAS.

Diagnostics utiles conservés au passage :

Test Résultat Enseignement
smbclient -L //10.0.0.2 -N NT_STATUS_LOGON_FAILURE pas d'accès anonyme, le NAS est correctement fermé
smbclient //10.0.0.2/NEXTE -U sas_nexte listing obtenu le mot de passe documenté est valide
ssh -o PreferredAuthentications=none publickey,password méthodes proposées, sans risquer d'échec comptabilisé

Encodage du mot de passe : le § final ne survit pas au passage par Git Bash vers plink. Le contourner en construisant le mot de passe côté cible en octets explicites :

printf 'xAPIJU5108\xc2\xa7' > /tmp/pw
sshpass -f /tmp/pw ssh -o NumberOfPasswordPrompts=1 sas_nexte@10.0.0.2

-o NumberOfPasswordPrompts=1 est important : sans lui, un échec compte pour trois tentatives auprès du blocage automatique de DSM.

Le compte

uid=1026(SAS_NEXTE) gid=100(users) groups=100(users),101(administrators),1023(http)

Le compte Unix s'appelle SAS_NEXTE en majuscules, alors qu'on s'y connecte en sas_nexte. DSM est insensible à la casse à l'ouverture de session, mais le dossier personnel et le propriétaire des fichiers portent la forme majuscule. C'est à surveiller dans tout script.

Authentification par clé — non aboutie

Une paire ed25519 a été générée sur le PC (D:\SSH\nas_sasnexte, hors de l'arbre Syncthing, empreinte SHA256:4yK8Mxz5e9jmxSdIh6PT/Murip0pIpoFlA0MzDanB98) et la clé publique déposée par SMB dans \\10.0.0.2\home\.ssh\authorized_keys. Le fichier est bien arrivé, intact, 105 octets — mais l'authentification reste refusée.

Cause probable : le StrictModes d'OpenSSH, qui rejette un ~/.ssh ou un dossier personnel accessibles en écriture au groupe ou à tous, ce que Synology crée par défaut. Non résolu à ce jour ; on travaille au mot de passe en attendant. Correctif à tenter, en root :

H=$(getent passwd sas_nexte | cut -d: -f6)
chown -R SAS_NEXTE "$H/.ssh"; chmod 700 "$H/.ssh"; chmod 600 "$H/.ssh/authorized_keys"

Deux pièges DSM à connaître

  1. scp échoue — dest open: No such file or directory. DSM n'expose pas de sous-système SFTP, dont dépend scp moderne. Transférer par un tube SSH :
    ssh nas 'cat > /chemin/cible' < fichier_local
    
  2. /tmp est monté noexec (tmpfs … rw,nosuid,nodev,noexec). Un binaire copié là est bien marqué exécutable mais refuse de démarrer avec « Permission denied ». Les exécutables doivent vivre sur /volume1, qui est en btrfs sans noexec.

2. La machine

Modèle Synology DS220+
Processeur Intel Celeron J4025 @ 2,00 GHz — 2 cœurs, pas d'HyperThreading
Mémoire 5,8 Go (2 Go d'origine + extension)
Volume btrfs, 5,3 To, 1,4 To libres (75 % occupé)
Binaire déposé /volume1/homes/SAS_NEXTE/bin/caesiumclt — v1.4.0, build x86_64-unknown-linux-musl

Le binaire musl est statique et sans aucune dépendance : il s'exécute nativement sur DSM sans rien installer, sans Docker, sans Entware.


3. Le stock réel de /volume1/photo

Mesuré en excluant les dossiers techniques. Total du dossier : 79 Go (du), dont 78,8 Go retrouvés fichier par fichier — les deux mesures concordent.

Contenu Fichiers Poids Traité par Caesium ?
JPEG 16 422 37,59 Go oui — la cible
MP4 + MOV 413 21,11 Go non
PNG 624 2,95 Go oui, en sans-perte
HEIC 691 0,79 Go non pris en charge
TIFF, PSD, divers ~157 0,76 Go TIFF oui, PSD non
Contenu réel 18 307 63,20 Go
@eaDir — vignettes Synology 94 791 15,36 Go à ne jamais toucher

Attention au recensement naïf : un find … -iname '*.jpg' sans exclure @eaDir compte 71 802 fichiers au lieu de 16 422, parce qu'il ramasse les vignettes générées par Synology. Un échantillon constitué ainsi fausse gravement la taille moyenne — c'est l'erreur commise en première approche ici.

La corbeille s'est vidée pendant l'étude, à 07:19 : /volume1/photo est passé de 91 Go à 79 Go. Cela explique deux écarts de mesure qui semblaient d'abord contradictoires.


4. Banc d'essai

Méthode : 300 JPEG prélevés dans tout l'arbre (un fichier sur 200, pour ne pas biaiser vers un dossier ou une époque), copiés dans un dossier de travail, compressés vers un autre. Aucun original n'a été modifié. Dossier de test supprimé après mesure.

caesiumclt -q 80 -e --keep-dates --min-savings 1% --threads 0 --json -o SORTIE ENTREE
Échantillon 299 fichiers exploitables, 201,4 Mo
Résultat 81,6 Mo
Gain -59,5 % (13 fichiers sans gain suffisant, originaux conservés)
Durée 130 s sur 2 threads
Débit 1,55 Mo/s

À titre de comparaison, le même profil q80 sur le Ryzen 5 2600X du PC (12 threads) donne 11,4 Mo/s — soit un facteur 7,4, cohérent avec l'écart de cœurs et d'IPC.


5. Vérification des métadonnées

C'était la question décisive : compresser 16 000 photos de famille ne doit pas coûter une date ni une position.

EXIF, dates, dimensions — 6 photos tirées du stock

avant après gain bloc EXIF DateTimeOriginal dimensions
p1 305 586 o 184 102 o -40 % identique 2013:11:24 12:02:25 1606×1886
p2 — — — gain insuffisant, original conservé
p3 1 129 457 o 417 739 o -64 % identique 2006:05:23 11:31:57 1944×2592
p4 394 396 o 347 306 o -12 % identique 2008:01:01 00:02:15 1600×1200
p5 1 953 775 o 391 469 o -80 % identique 2012:01:01 16:05:02 3264×2448
p6 3 559 252 o 1 283 096 o -64 % identique 2012:06:17 14:43:37 3264×2448

Bloc EXIF identique à l'octet près, date de prise de vue inchangée, dimensions inchangées, date du fichier préservée par --keep-dates.

GPS — sur des photos qui en portent réellement

Les six photos ci-dessus, prises entre 2006 et 2013 par des appareils photo, ne contenaient aucune coordonnée. Un second test a donc cherché des fichiers réellement géolocalisés :

Fichier GPS avant GPS après
PhotoLab445379.jpg 43.161944, 5.916944 43.161944, 5.916944
2013 Marseille/Gaetan.jpg 43.3025, 5.364722 43.3025, 5.364722
2013 Marseille/20130915_122108_Tunnel du Vieux-Port.jpg 43.294444, 5.365556 43.294444, 5.365556
2013 Marseille/20130915_114356_Tunnel du Vieux-Port.jpg 43.294444, 5.365556 43.294444, 5.365556

Coordonnées préservées au chiffre près. Les positions correspondent bien au Vieux-Port de Marseille et à la région de La Ciotat.

⚠ Tout cela dépend de l'option -e, qui n'est PAS le comportement par défaut de Caesium. Sans elle, l'EXIF entier est supprimé — dates et GPS avec. Ne jamais la retirer des options communes. Ne pas utiliser --strip-icc non plus, qui l'annule pour les profils colorimétriques.


6. NAS ou PC à travers le VPN ?

Le tunnel WireGuard est en étoile : chaque octet entre le PC et le NAS fait PC → VPS OVH → NAS, deux sauts WAN, sans liaison directe. Débit mesuré en lecture : 4,01 Mo/s (32 Mbit/s).

Sur le NAS Depuis le PC via le tunnel
Facteur limitant CPU (1,55 Mo/s) réseau (4 Mo/s, lecture et réécriture)
Durée pour 37,6 Go ~6,7 h ~3,7 h
Bande passante consommée aucune 53 Go à travers le VPS
PC allumé requis non oui, en continu
Lien de Toulon intact saturé pendant toute la durée

Le PC est plus rapide en temps de mur, mais il monopolise le lien du site de Toulon pendant quatre heures et impose de laisser la machine allumée. Le NAS travaille la nuit, ne coûte rien et personne ne l'attend. C'est le bon choix pour le rattrapage du stock ; le PC resterait pertinent pour un lot ponctuel de quelques gigaoctets.


7. Points de vigilance

1. La sauvegarde — point bloquant. Le script sync_kdrive_complete.sh du NAS ne synchronise que /volume1/photo/2026 vers kDrive, et en rclone sync --delete-during, c'est-à-dire un miroir. Les 37,6 Go de JPEG couvrant 1967 à 2025 n'ont donc aucune copie hors du NAS par cette voie, et une compression sur place propagerait les versions compressées en écrasant le miroir pour 2026. Vérifier ce qui existe par ailleurs (Hyper Backup, ActiveBackupforBusiness) avant toute réécriture de masse.

2. La compression sur place est irréversible. Le profil q80 est un ré-encodage : on ne revient pas en arrière. Deux parades, à combiner : écrire d'abord dans une arborescence parallèle et ne basculer qu'après contrôle (37,6 Go de plus sur 1,4 To libres, c'est indolore), et commencer par un seul dossier pour juger la qualité à l'œil.

3. Ne jamais toucher aux @eaDir. 15,36 Go de vignettes et d'index Synology. Les compresser casserait Synology Photos, qui les régénérerait aussitôt.

4. --keep-dates protège l'indexation. En préservant la date de modification, on évite de déclencher une réindexation complète de Synology Photos sur 16 000 fichiers — ce qui occuperait le NAS bien plus longtemps que la compression elle-même.

5. HEIC et vidéos restent hors de portée. 691 HEIC et 21 Go de vidéos ne sont pas concernés. Pour les vidéos, le levier serait un ré-encodage H.265, un tout autre chantier, très au-delà des capacités d'un J4025.


8. Reste à faire

  • Établir l'état réel des sauvegardes de /volume1/photo — préalable à tout déploiement
  • Faire aboutir l'authentification par clé (chmod sur ~/.ssh en root) pour cesser de passer le mot de passe
  • Adapter compress_photos.py (page 284) au NAS : chemins, écriture en arborescence parallèle, exclusion de @eaDir et #recycle, journal dans /volume1/homes/SAS_NEXTE/logs/
  • Le déclencher par le planificateur de tâches de DSM, la nuit, avec une limite de durée
  • Décider du sort des 624 PNG (2,95 Go) : le sans-perte y rend environ -28 % sans aucune altération

9. Immich et la chronologie — la question decisive (2026-08-28)

Crainte soulevee par Julien : « Immich ne lit que la date de modification du fichier pour composer l'historique. Avec cette compression, tout mon historique photo va devenir illisible. »

Elle est fondee pour une partie du stock. Verifie sur la bibliotheque reelle (18 156 elements, Immich v2.7.5, /home/debian/photo monte en lecture seule sur /mnt/media/photo — bibliotheque externe) :

Cas observe fileModifiedAt Chronologie (localDateTime) Ce qui fait foi
IMG_20260718_181515.jpg — avec DateTimeOriginal EXIF 2026-07-21 22:01 2026-07-18 18:15 l'EXIF, le mtime est ignore
IMG_20260705.jpg, images WhatsApp — sans date EXIF 2026-07-21 22:00 2026-07-21 22:00 le mtime du fichier

Autrement dit : quand la photo porte une vraie date de prise de vue, Immich s'en sert et se moque du fichier. Quand elle n'en a pas — images WhatsApp, captures d'ecran, scans — Immich se rabat sur la date de modification du fichier. Ces photos-la sont exactement celles que la crainte visait.

Ce qui a ete change en consequence

--keep-dates de Caesium preserve bien le mtime (verifie). Mais faire reposer l'historique photo d'une famille sur une option d'un outil tiers etait trop fragile. Le script ne dependant plus de cette option :

  • il releve st_mtime, st_atime, st_uid, st_gid et les permissions de l'original avant traitement ;
  • il les reimpose lui-meme sur le fichier produit (os.utime, os.chown, os.chmod) ;
  • il relit ensuite le fichier et leve une exception si le mtime a bouge, ne serait-ce que d'une seconde.

Ce n'est plus une promesse mais une assertion verifiee fichier par fichier, aussi bien en remplacement sur place qu'en arborescence parallele.

Preuve

Dossier temoin /volume1/photo/1968-1994 - photos famille dont Noumea/1990, 26 photos, mode parallele :

26 fichiers compares, 0 ecart(s)
originaux inchanges (taille et date) : oui
VERDICT : aucune date ni aucun proprietaire alteres

⚠ La tache DSM doit imperativement tourner en ROOT

Le controle ci-dessus a immediatement servi. Lance en tant que SAS_NEXTE, le meme test signale 26 ecarts de proprietaire : les photos appartiennent a admin:users, l'appel os.chown echoue faute de privileges, et les fichiers produits basculent sur SAS_NEXTE. Sur 16 422 fichiers, cela casserait l'acces SMB et Synology Photos.

Relance en root (sudo), la meme verification donne 0 ecart.

A retenir : dans le planificateur de taches de DSM, choisir l'utilisateur root, jamais SAS_NEXTE.

Etat de la chronologie avant intervention

Sur 18 156 elements indexes, 2 seulement sont dates de l'an 4501 (4501-01-01) — des scans totalement depourvus de metadonnees, pour lesquels Immich n'a trouve aucune date exploitable. C'est preexistant, marginal, et sans rapport avec la compression.

Acces Immich depuis Claude

Il n'y a pas de MCP Immich dans cette session : mcp_immich.py fait partie des serveurs perdus avec le profil Windows eliob et jamais reecrits (voir la liste « reste a ecrire » dans CLAUDE.md). L'API REST suffit :

d = json.dumps({'email': 'bertrand.dadone@gmail.com', 'password': '...'}).encode()
r = urllib.request.Request('https://immich.juxjux.ovh/api/auth/login', data=d,
                           headers={'Content-Type': 'application/json'})
jeton = json.loads(urllib.request.urlopen(r).read())['accessToken']

Endpoints utiles : POST /api/search/metadata (avec withExif: true pour obtenir dateTimeOriginal), GET /api/timeline/buckets?size=MONTH (comptage par mois — le seul moyen fiable d'obtenir des totaux, total dans la recherche ne renvoyant que le compte de la page), GET /api/assets/statistics.

Les deux photos datees de l'an 4501 — corrigees le 2026-08-28

Symptome decrit par Julien : « j'ai beau reformater, cette date ridicule revient ».

Cause : les deux fichiers proviennent d'un scanner de film Nikon LS-5000 (SF-Nikon / LS-5000), dont le logiciel a inscrit 4501:01:01 comme date de prise de vue dans le fichier lui-meme. La bibliotheque etant externe, Immich relit le fichier a chaque analyse et ecrase toute correction faite dans l'interface. Corriger dans Immich ne pouvait pas tenir — il fallait traiter la source.

Le PNG cachait la date a deux endroits, ce qui explique qu'une correction partielle aurait echoue :

Fichier Emplacement de la date Forme
BOITE09_0061.jpg EXIF DateTimeOriginal (0x9003), position absolue 282 4501:01:01 00:00:00 en clair
BOITE09_0068.png chunk zTXt « Raw profile type exif » EXIF compresse en zlib, hexadecimal — invisible a une recherche de chaine
BOITE09_0068.png chunk iTXt XMP exif:DateTimeOriginal="4501-01-01T00:00:00+01:00" en clair

Methode — script Jux-scripts/Photos-Caesium/corriger_date_exif.py, stdlib seule :

  • JPEG : les chaines de date EXIF sont de longueur fixe (20 octets), donc 4501 -> 1979 est un remplacement de 4 octets sur place, sans rien restructurer.
  • PNG : le fichier est reconstruit chunk par chunk. Le zTXt est decompresse, l'hexadecimal de la date remplace, puis recompresse — la longueur du chunk change, donc le CRC32 de chaque chunk est recalcule. Le iTXt XMP est corrige en clair, longueur inchangee.
  • Sauvegarde de chaque original avant ecriture, ecriture via un fichier temporaire, puis os.replace.
  • Date, proprietaire et permissions du fichier preserves et verifies apres coup.

Corrige aux deux emplacements : sur le montage kDrive du VPS (/home/debian/photo, ce que lit Immich) et sur le NAS pour le JPEG (/volume1/photo/...), le PNG n'existant que cote kDrive. Sauvegardes dans /home/debian/backup_4501/ et /volume1/homes/SAS_NEXTE/backup_4501/.

Relecture par Immich : le mtime ayant ete volontairement preserve, l'analyse de bibliotheque ne detecte aucun changement. Il faut donc forcer la relecture :

POST /api/assets/jobs   {"assetIds": [...], "name": "refresh-metadata"}

Resultat : les deux photos sont datees du 1er janvier 1979, et plus aucun element de la bibliotheque n'est date apres 2100.

10. Le script de production

Jux-scripts/Photos-Caesium/compress_photos_nas.py — stdlib seule, deploye sur le NAS dans /volume1/homes/SAS_NEXTE/scripts/.

python3 compress_photos_nas.py [--source CHEMIN] [--parallele CHEMIN]
                               [--profil q80|q90|sans-perte] [--minutes N]
                               [--simulation] [--reprendre]

Choix de conception :

  1. Appel de caesiumclt sans -R, dossier par dossier. Verifie sur le NAS : sans -R, il ne descend pas dans les sous-dossiers. C'est ce qui garantit qu'aucun @eaDir n'est jamais touche, sans avoir besoin d'une option d'exclusion que Caesium ne propose pas.
  2. Liste de fichiers explicite, limitee aux .jpg/.jpeg. Passer un dossier ferait traiter aussi les PNG et TIFF avec un profil lossy, ce qui les degraderait.
  3. Ecriture en dossier temporaire, puis validation, puis remplacement. Un fichier produit n'ecrase l'original que s'il existe, commence bien par FF D8, et est plus petit. Jamais d'original ecrase par un fichier partiel.
  4. Metadonnees reimposees et verifiees (section 9).
  5. Reprise : les dossiers traites sont memorises dans ~/.photos_caesium_etat.json, --reprendre saute ceux qui sont faits.
  6. Budget de temps (--minutes, 360 par defaut) : arret propre en fin de nuit, reprise le lendemain.
  7. Journal dans /volume1/homes/SAS_NEXTE/logs/photos_caesium_AAAAMMJJ.log.

Mesure sur les vieux scans familiaux (466 fichiers, 22 dossiers) : -28,6 % seulement, a 0,82 Mo/s. Bien loin des -59,5 % du tirage aleatoire — ces scans anciens sont de petits fichiers deja compresses, et le surcout par fichier domine. Le gain reel sera donc tres inegal selon les albums : fort sur les photos recentes de telephone, faible sur les numerisations anciennes.


PROCÉDURE RETENUE — reprise du stock photo en quatre étapes

Définie par Julien le 2026-08-28. Elle remplace la recommandation directe des sections 6 à 8 : on n'attaque plus le stock au fil de l'eau, on inventorie, on statue, on valide, puis on écrase.

Le principe

On ne compresse pas une photo qui n'en a pas besoin.

Règle opérationnelle : tout fichier de moins de 2 Mo est écarté. Aucun bénéfice à recompresser une numérisation ancienne de 700 Ko pour lui gagner 80 Ko, au prix de la réécriture d'un fichier irremplaçable.

Justification chiffrée du seuil

Seuil Fichiers à traiter Poids couvert Gain estimé Durée
1 Mo 12 253 (74,6 %) 95,6 % 21,6 Go 6,6 h
2 Mo 9 077 (55,3 %) 82,4 % 18,6 Go 5,7 h
3 Mo 4 839 (29,5 %) 55,4 % 12,5 Go 3,8 h

À 2 Mo on écarte 45 % des fichiers pour seulement 18 % du poids. Descendre à 1 Mo ajouterait 3 Go de gain contre 3 176 fichiers supplémentaires ; monter à 3 Mo sacrifierait 6 Go pour n'économiser que deux heures. Le seuil de 2 Mo est le point d'équilibre.

Effet secondaire heureux, vérifié à l'inventaire : les albums de numérisations anciennes sortent d'eux-mêmes du périmètre. 1967 à 2000 - photos famille Bertrand (536 images, 2,62 Go) et 2005 - naissance Elio (869 images) ont zéro fichier à traiter. La règle protège le patrimoine le plus fragile sans qu'on ait eu à le désigner.


Étape 1 — Inventaire (FAIT le 2026-08-28)

Script : Jux-scripts/Photos-Caesium/inventaire_photos_nas.py, stdlib seule, ne modifie aucun fichier.

python3 /volume1/homes/SAS_NEXTE/scripts/inventaire_photos_nas.py [--seuil-mo 2]

Durée réelle : 6 minutes pour 17 835 images. Sorties dans /volume1/homes/SAS_NEXTE/inventaire/, accessibles en SMB sous \\10.0.0.2\home\inventaire\ :

Fichier Usage
photos.csv tableur, ou import Metabase
photos.sqlite interrogation SQL
videos.csv les 417 vidéos, mises de côté
resume.txt synthèse chiffrée

Colonnes de la table photos

album, chemin, nom, format, octets, mo, date_prise_vue, origine_date, date_modif, largeur, hauteur, appareil, gps_lat, gps_lon, a_traiter, motif_ecart, empreinte, doublon_de, quasi_doublon.

origine_date est la colonne ajoutée à la suite de l'analyse Immich (section 9) : elle vaut exif quand la photo porte un DateTimeOriginal, et mtime quand Immich doit se rabattre sur la date du fichier. Ces dernières sont les seules dont la place dans la chronologie dépend de la préservation du mtime. 2 123 photos, soit 11,9 % du stock, sont dans ce cas.

Détection des doublons

Deux mécanismes, tous deux en stdlib :

  • Doublons exacts : SHA-256 intégral, mais calculé uniquement entre fichiers de taille identique. Sur 17 835 images, seuls 2 360 candidats ont eu besoin d'être lus — ce qui évite de hacher 41 Go.
  • Quasi-doublons : même DateTimeOriginal et mêmes dimensions. Attrape la même prise de vue enregistrée deux fois sous des noms différents.

Résultat de l'inventaire

Nombre Poids
Images recensées 17 835 41,67 Go
À retraiter (≥ 2 Mo) 9 077 30,98 Go
Écartées 8 758 10,69 Go
Vidéos (chantier ultérieur) 417 21,22 Go
Doublons exacts 1 334 1,93 Go récupérables sans rien compresser
Quasi-doublons 1 278 —
Photos géolocalisées 7 129 (40,0 %) —
Date issue du mtime 2 123 (11,9 %) —

Les 1 334 doublons exacts sont un gain gratuit : 1,93 Go libérables par simple suppression, sans toucher à un seul pixel. À traiter en premier, c'est sans risque et instantané.


Étape 2 — Statuer sur la base

Julien et Cécile examinent l'inventaire et arrêtent la liste définitive. La colonne a_traiter porte la proposition automatique issue du seuil ; elle se corrige à la main.

Requêtes utiles sur photos.sqlite :

-- ce qui sera traité, par album
SELECT album, count(*) AS n, round(sum(mo)/1024, 2) AS go
FROM photos WHERE a_traiter = 1 GROUP BY album ORDER BY go DESC;

-- les doublons exacts, à supprimer avant toute compression
SELECT chemin, doublon_de, mo FROM photos WHERE doublon_de <> '' ORDER BY mo DESC;

-- les photos dont la chronologie Immich dépend du mtime
SELECT album, count(*) FROM photos WHERE origine_date = 'mtime' GROUP BY album;

-- exclure un album entier du traitement
UPDATE photos SET a_traiter = 0, motif_ecart = 'exclu manuellement' WHERE album = '...';

Étape 3 — Traitement dans un dossier de validation

Destination retenue : /volume1/homes/SAS_NEXTE/Photos/photos_reprise.

C'est l'espace photo personnel de SAS_NEXTE, indexé nativement par Synology Photos : la validation se fait donc dans l'application, en plein écran, et non en parcourant un partage SMB. C'est ce qui a fait écarter le dossier /volume1/julien16021972 envisagé d'abord — lequel n'existait pas et aurait demandé la création d'un dossier partagé.

Les originaux ne sont pas touchés à cette étape. Le traitement s'appuie sur compress_photos_nas.py en mode --parallele, qui reproduit l'arborescence des albums et préserve date, propriétaire et permissions (vérifié, section 9).

Points de vigilance :

  • Synology Photos va indexer les 9 077 fichiers déposés et générer leurs vignettes : comptez du temps machine et quelques gigaoctets de @eaDir supplémentaires, restitués dès que le dossier de validation sera vidé.
  • Hyper Backup sauvegardera aussi ce dossier la nuit suivante. Gonflement temporaire de l'espace kDrive, résorbé après l'étape 4.
  • Aucune interférence avec Immich, qui lit /home/debian/photo sur le VPS (le miroir kDrive de /volume1/photo) et ignore les dossiers personnels.

Étape 4 — Écrasement et mise à jour de la base

Julien et Cécile parcourent photos_reprise dans Synology Photos et suppriment les photos dont la qualité ne convient pas. Ce qui reste vaut validation.

Le refus est donc une suppression, et il ne demande aucune autre action. C'est ce qui rend la procédure sûre : une photo absente du dossier de validation au moment du rebasculement voit simplement son original conservé tel quel. L'inaction est le comportement par défaut, et c'est toujours le comportement sûr.

  1. Chaque fichier subsistant dans photos_reprise écrase son original dans /volume1/photo, métadonnées préservées.
  2. Les photos supprimées du dossier de validation laissent leur original intact.
  3. La base est mise à jour : statut, nouvelle taille, date de traitement, gain obtenu.
  4. Le lot traité est retiré du dossier de validation.

⚠ Toute l'étape 4 doit tourner en ROOT. Les photos appartiennent à admin:users ; exécutée sous SAS_NEXTE, l'opération les réattribuerait toutes et casserait l'accès SMB et Synology Photos. Vérifié en conditions réelles (section 9).


Travail par lots

Les 9 077 photos ne sont pas traitées d'un bloc. On avance par lots — un album ou une année à la fois (2006, 2007, 2012 - Eté…) — pour trois raisons :

  • une bascule de 31 Go d'un coup serait lourde à valider et lourde à sauvegarder ;
  • Synology Photos n'a à indexer que quelques centaines de fichiers à la fois ;
  • une erreur de jugement sur un lot ne coûte qu'un lot.

Cadence naturelle : déposer un lot, le laisser à la validation, basculer, passer au suivant. Rien n'oblige à enchaîner — la procédure est faite pour s'interrompre.

Ce que le suivi par lots impose à la base

Travailler progressivement veut dire que la base doit savoir où en est chaque photo, sinon on perd le fil entre deux sessions. Colonnes de suivi à tenir dans photos.sqlite :

Colonne Valeurs Rôle
statut a_traiter, en_validation, valide, refuse, bascule, ecarte où en est la photo
lot nom du lot (2012 - Eté…) regroupement de travail
octets_apres entier taille obtenue après compression
date_traitement horodatage quand la bascule a eu lieu

Le passage en_validation → valide ou refuse se déduit automatiquement : est validée toute photo encore présente dans photos_reprise au moment de la bascule, est refusée toute photo absente. Aucune saisie manuelle n'est nécessaire — le tri se fait dans Synology Photos, la base le constate.

C'est aussi ce qui rend l'ensemble reprenable : on peut relire à tout moment quels lots sont basculés, lesquels attendent, et quel gain cumulé a été obtenu.


Ce que la procédure apporte, par rapport à une compression au fil de l'eau

  • Auditable : chaque décision est tracée dans une base interrogeable, pas dispersée dans un journal.
  • Discutable à deux : Cécile valide sur les photos elles-mêmes, pas sur une promesse de taux de compression.
  • Réversible sans recourir aux sauvegardes : tant que l'étape 4 n'a pas tourné, aucun original n'a bougé.
  • Elle a déjà rapporté un gain imprévu : 1 334 doublons exacts, 1,93 Go, que personne ne cherchait.

Nettoyage des doublons — fait le 2026-08-28

Script : Jux-scripts/Photos-Caesium/nettoyer_doublons_nas.py. Premier gain du chantier, obtenu sans compresser un seul pixel.

Ce que l'inventaire avait trouvé

945 groupes de fichiers identiques à l'octet près, soit 1 334 fichiers en trop, 1,93 Go. Il aurait été tentant de tout supprimer. C'eût été une faute, et l'analyse préalable l'a montré.

Nature du doublon Fichiers Poids Décision
Même dossier, relation de préfixe évidente 51 0,16 Go supprimés
Même dossier, photos renommées à la main 264 0,60 Go laissés intacts
Sous-dossiers différents d'un même album 1 — laissé intact
Albums différents 1 018 1,17 Go laissés intacts

Pourquoi les doublons inter-albums ne se suppriment pas

La combinaison la plus lourde était 1930-2016 - Photos BERTRAND MER + 2016-best photos pour 70 ans — 516 fichiers. Ce n'est pas un accident : c'est une sélection faite pour les 70 ans, qui puise dans l'archive familiale. Supprimer les doublons aurait vidé l'album de son contenu. Même schéma pour 2016 - 70 ans Maman + 2016- photos Maman Papa (48 fichiers) et plusieurs autres lignes touchant « best photos ».

Un doublon réparti entre deux albums n'est pas forcément une erreur — c'est souvent une intention. Ne jamais dédoublonner sur le seul critère de l'empreinte.

Le critère de conservation : la relation de préfixe

Un suffixe de copie est toujours ajouté au nom d'origine. Donc : si l'un des noms d'un groupe est un préfixe strict de tous les autres, c'est l'original. Ce critère est structurel, sans heuristique, et couvre tous les cas rencontrés :

GARDE   IMG_0952.JPG
ecarte  IMG_0952_NASMAISON_Oct-01-180016-2024_conflict_current.JPG
ecarte  IMG_0952_NASMAISON_Oct-01-180254-2024_conflict_current.JPG
ecarte  IMG_0952_..._conflict_current_..._conflict_current.JPG          (x7 au total)

Les conflits Syncthing sont le principal pourvoyeur, avec des cascades — chaque résolution ratée ajoutant son suffixe au précédent. Un fichier existait en sept exemplaires.

Deux pièges rencontrés, à ne pas refaire

1. _\d+$ est un mauvais marqueur de copie. Cette règle, qui paraît évidente, prend les numéros d'appareil photo pour des suffixes : IMG_0322, 20190830_221854, Cess_IMG_20240506_120512 sont tous signalés à tort. Elle ne donnait le bon résultat que par accident, via le départage sur la longueur du nom. Supprimée au profit du critère de préfixe. Le garde-fou intégré au script (« fichiers conservés portant encore un marqueur de copie ») est ce qui l'a révélée — un contrôle qui accuse son propre auteur mérite d'être écrit.

2. Le suffixe Windows français est « - Copie », avec des espaces autour du tiret. Une expression du type [-_](copie|copy)$ ne l'attrape pas. Motif correct : \s*[-_]\s*(copie|copy)(\s*\(?\d+\)?)?\s*$.

Le mode prudent

Restaient 264 groupes où le même cliché existe sous deux noms sans lien :

20150808_185538.jpg   ←→   Croatie_août_2015_142.jpg

Le contenu est identique ; choisir le nom qui survit est une décision humaine, pas technique — quelqu'un a pris la peine de renommer ces photos. Décision de Julien : « la règle la plus prudente, tant pis s'il reste des doublons. » Le script s'abstient donc par défaut sur tout groupe sans relation de préfixe ; --inclure-ambigus force le contraire, et est déconseillé.

Résultat

  • 51 fichiers déplacés, 165 Mo, zéro erreur.
  • Quarantaine, pas suppression : /volume1/homes/SAS_NEXTE/doublons_ecartes, arborescence préservée. L'espace n'est libéré qu'après rm -rf de ce dossier — la suppression définitive reste une décision humaine.
  • Base mise à jour : motif_ecart porte le chemin du fichier conservé, a_traiter passe à 0.
  • Contrôle après action : 51 originaux toujours en place, 51 copies en quarantaine, 0 manquant. Aucune photo perdue.

Seconde passe : les fichiers de conflit Google Drive (2026-08-28)

Parmi les doublons laissés en attente, Julien a tranché un cas : « on peut traiter comme doublon tous ceux qui portent la mention (conflit) ». C'est fondé — un fichier nommé Copie de P1060905 (conflit du 19-03-2018 à 00h18).jpg est un artefact logiciel, jamais un fichier déposé volontairement. Il ne peut donc pas relever d'une sélection, contrairement aux doublons inter-albums.

Ces 24 fichiers avaient échappé aux deux passes précédentes pour une raison structurelle : le nom encadre l'original au lieu de le suffixer — Copie de devant, (conflit du …) derrière. Le critère de préfixe, qui suppose un suffixe ajouté, ne pouvait pas les voir. D'où une passe dédiée, --conflits, indépendante des règles d'album.

Garde-fou indispensable, et il a servi : un fichier de conflit n'est écarté que s'il subsiste dans son groupe au moins un exemplaire qui n'en est pas un. Six fichiers se trouvaient dans des groupes où tous les exemplaires étaient des copies de conflit :

Copie de 20140424_114612 (conflit du 19-03-2018 à 00h12).jpg     (deux fois, dossiers différents)

Les supprimer aurait fait disparaître la photo. Ils sont restés en place.

Résultat de la passe : 24 fichiers écartés, 6 laissés intacts, zéro erreur.

Bilan cumulé du nettoyage

Passe Fichiers Poids
1 — relation de préfixe (_1, - Copie, cascades Syncthing) 51 165 Mo
2 — fichiers de conflit Google Drive (--conflits) 24 25 Mo
Total en quarantaine 75 190 Mo

Contrôle après chaque passe : 75 originaux toujours en place, 75 copies en quarantaine, 0 manquant. Aucune photo perdue.

Ce qui reste en attente d'un arbitrage

Fichiers Poids
Photos renommées (même dossier, noms sans lien) 252 0,58 Go
Doublons inter-albums, dont sélections volontaires 1 018 1,17 Go
Fichiers de conflit sans exemplaire propre 6 —

Ces 1,75 Go ne se récupèrent qu'en tranchant album par album, à la main. La commande de départ :

SELECT chemin, doublon_de, mo FROM photos WHERE doublon_de <> '' ORDER BY mo DESC;

Les fichiers « (conflit) » sont les ORIGINAUX — découverte du 2026-08-28

À retenir avant toute suppression de fichiers de conflit. Sur ce stock, un fichier nommé Copie de X (conflit du 19-03-2018 à …).jpg est la meilleure version de la photo, et le fichier au nom propre qui porte le même nom de base est une recompression dégradée. C'est l'inverse de ce que le nom suggère. Vérifié sur 12 paires : dans 10 cas sur 12, la version « conflit » l'emporte nettement.

La mesure

Comparaison objective par la table de quantification JPEG (segment DQT) : plus la somme de ses valeurs est basse, moins l'image a été dégradée à l'encodage. À dimensions rigoureusement identiques :

Photo Version « conflit » Version « propre »
2014-mur effondré/20141129_110119 3,80 Mo — DQT 292 1,81 Mo — DQT 1002
2014-mur effondré/20141129_101232 3,22 Mo — DQT 369 1,75 Mo — DQT 962
2016-Crete/12134705image0027 2,47 Mo — DQT 292 1,07 Mo — DQT 913
2016-Crete/16133120image0045 2,67 Mo — DQT 292 1,20 Mo — DQT 949
2016-Parc Vert Coteau/20160130_231252 2,47 Mo — DQT 292 0,96 Mo — DQT 696

Trois indices concordants : la table de quantification (292 contre 700–1000), la taille (deux à trois fois plus lourd) et le bloc EXIF (14 à 20 ko contre 5 à 9 ko). Un événement de mars 2018 a réécrit ces photos en qualité réduite et relégué les originaux sous un nom de conflit.

Exceptions : 2016-Crete/14132257image0026, où les deux versions se valent (DQT 711 contre 736), et 2024/2024-Via Reggio/IMG20240503174633…_conflict_current.jpg, qui n'a pas de jumeau — simple renommage.

Le garde-fou qui a évité le pire

La consigne initiale était : « on peut traiter comme doublon tous ceux qui portent la mention (conflit) ». Appliquée à la lettre, elle aurait détruit la meilleure version de dix photos. Ce qui les a sauvées est le garde-fou « n'écarter un conflit que s'il subsiste un exemplaire propre dans le groupe » — et il les a écartées du traitement pour une raison qui n'était même pas la bonne : ces paires n'étaient pas des doublons exacts, leurs octets diffèrent, donc elles n'ont jamais été appariées.

Leçon générale : un garde-fou conservateur protège aussi contre les erreurs qu'on n'avait pas prévues.

Décision en attente

Pour les dix paires : supprimer la version dégradée et rendre son nom d'origine à la version de conflit. Gain double — la qualité et un nom propre. C'est l'inverse de la consigne du matin, donc rien n'est fait sans arbitrage. Reporté par Julien.

Outils prêts, aucun n'a été exécuté en écriture :

  • renommer_conflits.py — retire l'encadrement Copie de …(conflit du …) ; ne renomme que si le nom cible est libre (12 des 13 sont bloqués, précisément parce que la version dégradée occupe la place)
  • comparer_versions.py — la comparaison DQT ci-dessus

Reconstruction de l'inventaire après tri manuel

Julien a trié à la main, surtout sur 2014. Base reconstruite le 2026-08-28 à 09:30, en 5 min 21 s.

Avant Après Écart
Images 17 835 17 675 -160
Poids 41,67 Go 41,32 Go -0,35 Go
À retraiter (≥ 2 Mo) 9 077 8 991 -86
Doublons exacts 1 334 1 180 -154
Vidéos 417 417 inchangé

Sur les 160 images en moins, 75 sont les mises en quarantaine automatiques — 85 relèvent du tri manuel, dont 79 doublons.

Restructuration notable : l'album 2014 (256 images, 0,50 Go) a été dissous, son contenu réparti dans les sous-albums de l'année (2014-Hiver Printemps 46 → 149, 2014- été 119 → 166, 2014 - Automne Noel 22 → 53). Et le doublon d'arborescence 2014/Avril 2014/2014-Barcelone a été vidé au profit de 2014-Barcelone — ce qui a résolu du même coup les trois photos de conflit orphelines signalées plus haut.

L'inventaire précédent est conservé dans /volume1/homes/SAS_NEXTE/inventaire/ sous photos_precedent.sqlite et resume_precedent.txt. Toujours en faire une copie avant de relancer inventaire_photos_nas.py : c'est le seul moyen de mesurer l'effet d'un tri manuel.


La chaîne d'outils, terminée le 2026-08-28

Cinq scripts dans Jux-scripts/Photos-Caesium/, stdlib seule, déployés sur le NAS dans /volume1/homes/SAS_NEXTE/scripts/. Ils correspondent un pour un aux étapes de la procédure.

Script Étape Rôle
inventaire_photos_nas.py 1 recense tout, détecte les doublons, propose la sélection
nettoyer_doublons_nas.py 1 bis écarte les doublons certains, en quarantaine
traiter_lot_nas.py 3 compresse un lot vers le dossier de validation
basculer_lot_nas.py 4 écrase les originaux validés, constate les refus
comparer_versions.py — compare la qualité de deux versions d'une même photo (DQT)

Le cycle d'un lot

# voir les lots disponibles et leur poids
python3 traiter_lot_nas.py --lot X --lister

# etape 3 : compresser vers le dossier de validation (originaux non touches)
python3 traiter_lot_nas.py --lot 2022 --minutes 300

#   -> Julien et Cecile ouvrent photos_reprise dans Synology Photos
#   -> ils SUPPRIMENT les photos qui ne conviennent pas

# etape 4 : simulation, puis ecrasement
python3 basculer_lot_nas.py --lot 2022
sudo python3 basculer_lot_nas.py --lot 2022 --go

--lot est un préfixe d'album : 2022 prend 2022 - été Flandres NL Paris, 2022-hiver à Paques, 2022-automne a Noel et 2022-Noel. Un nom d'album complet fonctionne aussi bien, pour un lot plus fin.

Les statuts

Statut Signification
a_traiter retenue par le seuil, pas encore compressée
ecarte sous le seuil, ou format non pris en charge
en_validation compressée, en attente du jugement dans Synology Photos
bascule validée et écrasée — octets porte la nouvelle taille
refuse supprimée du dossier de validation : original conservé
sans_gain la compression n'apportait rien, original conservé

Le refus ne demande aucune action. Une photo absente du dossier de validation au moment de la bascule voit son original conservé, sans qu'on ait rien à cocher ni à saisir. L'inaction est le comportement sûr — un lot abandonné en route, une session interrompue, un oubli : rien ne se perd par négligence, seulement par décision explicite.

Garde-fous intégrés

  • traiter_lot_nas.py appelle caesiumclt sans -R, dossier par dossier : aucun @eaDir ne peut être touché. Liste de fichiers explicite limitée aux JPEG — passer un dossier ferait traiter les PNG et TIFF en lossy. Sortie validée (FF D8, taille inférieure) avant d'être déposée. Budget --minutes, reprise en relançant la même commande.
  • basculer_lot_nas.py refuse de s'exécuter sans root (geteuid), avec un message explicite : les photos appartiennent à admin:users et un fichier laissé à SAS_NEXTE casserait SMB et Synology Photos.
  • Les deux réimposent puis vérifient la date de modification, et lèvent une exception au moindre écart — c'est elle qui porte la chronologie Immich des 12 % de photos sans DateTimeOriginal.

L'inventaire conserve le suivi des lots lors d'une reconstruction. Les statuts, lots, tailles obtenues et dates de traitement sont reportés depuis la base précédente pour toute photo dont le chemin n'a pas changé. Sans cela, un simple tri manuel suivi d'un inventaire_photos_nas.py effacerait l'avancement de tous les lots déjà traités.

Premier lot lancé : 2022

Album Photos à traiter Poids
2022 - été Flandres NL Paris 210 0,88 Go
2022-hiver à Paques 94 0,43 Go
2022-automne a Noel 43 0,13 Go
2022-Noel 12 0,03 Go
Total 359 1,47 Go

Choisi comme galop d'essai pour sa taille : 359 photos restent confortables à parcourir à deux. Le lot 2023, avec ses 891 photos, aurait été trop lourd pour un premier jugement.

Voir aussi

  • Page 284 — la procédure Photos-Caesium sur le PC, dont celle-ci reprend le moteur et les réglages
  • Page 245 — le tunnel WireGuard qui donne accès au NAS
  • Page 238 — le NAS SASNEXTE et sa synchronisation vers kDrive