# 260614 - procedure Joplin-compression

## Contexte — instructions de Julien (260614)

J'utilise l'application Joplin sur tous mes appareils. C'est ma mémoire de tout (travail, hobbies, maison...) et donc à force, la base de données se remplit. Elle se compose de notes à l'intérieur desquelles on trouve des images. Même en n'important que des captures d'écran, le poids de ces images est devenu important — de l'ordre de 300 Mo initialement estimé (476 Mo constatés en réalité, voir ci-dessous).

**Appareils synchronisés sous Joplin :**

- VPS Juxjux.OVH (serveur de sync)
- PC Nexte (PC du travail de Julien)
- PC Elio+Jux (PC maison de Julien)
- Mobile Xiaomi T15pro de Julien
- Tablette Samsung S5 de Julien
- Session Ubuntu sur clé SSD de Julien

**Objectif de la procédure :**

1. Compresser le stock d'images contenues dans la base de données Joplin via un process fiable de remplacement
2. Monter un système de compression automatique quotidienne des nouvelles images

**Principes définis par Julien :**

1. Tenir un carnet de bord (observatoire) du poids digital de la BDD Joplin dans la page BookStack Joplin, avec les 15 pièces jointes les plus lourdes
2. Mettre en place sur le VPS une procédure duplication → compression → remplacement d'images, qui se propage via la sync Joplin sur tous les appareils

---

## Exploration technique — Claude, 14/06/2026

### Infrastructure Joplin sur le VPS

Contrairement à ce qu'on pourrait attendre, Joplin Server **n'utilise pas SQLite** mais **PostgreSQL**.

<table id="bkmrk-container-image-r%C3%B4le"><thead><tr><th>Container</th><th>Image</th><th>Rôle</th></tr></thead><tbody><tr><td>`joplin`</td><td>`joplin/server:latest`</td><td>Serveur de sync Joplin</td></tr><tr><td>`joplin-db`</td><td>`postgres:15-alpine`</td><td>Base de données</td></tr><tr><td>`joplin-nginx`</td><td>`nginx:alpine`</td><td>Reverse proxy interne</td></tr><tr><td>`joplin_to_obsidian`</td><td>image custom</td><td>Container migration (actif, sans impact)</td></tr></tbody></table>

**Volume de données :** `/home/debian/joplin-data` → `/home/joplin/.config/joplin` (bind mount)

**Connexion DB :** `POSTGRES_HOST=joplin-db`, `POSTGRES_DATABASE=joplin`, `POSTGRES_USER=joplin`

**Important :** le port 5432 de joplin-db n'est **pas exposé** à l'extérieur du réseau Docker. Tout script de manipulation doit tourner sur le VPS et se connecter via l'IP interne Docker.

### Structure de la base de données

La table centrale est `items` (23 tables au total). Chaque note, ressource et paramètre Joplin est une ligne dans cette table.

Colonnes clés :

- `content` (bytea) — données binaires brutes
- `content_size` (integer) — taille en octets
- `content_storage_id` = 1 → stockage de type `Database` (tout est dans PostgreSQL, pas de fichiers externes)
- `jop_type` — type d'item Joplin
- `updated_time` — timestamp de dernière modification (utilisé par les clients pour détecter les changements à sync)

**Répartition par type :**

<table id="bkmrk-jop_type-significati"><thead><tr><th>jop\_type</th><th>Signification</th><th>Nombre</th><th>Poids total</th></tr></thead><tbody><tr><td>0</td><td>Ressource (image/fichier joint)</td><td>736</td><td>**476 MB**</td></tr><tr><td>1</td><td>Note</td><td>699</td><td>8,7 MB</td></tr><tr><td>4</td><td>Tag</td><td>733</td><td>4,5 MB</td></tr><tr><td>13</td><td>NoteTag (relation note↔tag)</td><td>250</td><td>3,9 MB</td></tr><tr><td>2</td><td>Carnet (Folder)</td><td>94</td><td>707 KB</td></tr><tr><td>6</td><td>Master Key</td><td>87</td><td>19 KB</td></tr><tr><td>5</td><td>Setting</td><td>39</td><td>—</td></tr></tbody></table>

### Analyse des ressources (jop\_type = 0)

Les ressources sont stockées comme **bytes bruts** dans la colonne `content`. Le format est détectable via les magic bytes :

- **PNG** (`\x89PNG`) — majorité des ressources
- **JPEG** (`\xff\xd8`) — portion significative
- **ZIP** (`PK\x03\x04`) — cas particulier : ZIP contenant plusieurs images `page_1.png`, `page_2.png`... (documents multi-pages)

La colonne `mime_type` est vide pour toutes les ressources — le type est implicite dans les bytes du contenu.

**Top 15 ressources les plus lourdes (état initial) :**

<table id="bkmrk-rang-id-taille-forma"><thead><tr><th>Rang</th><th>ID</th><th>Taille</th><th>Format</th></tr></thead><tbody><tr><td>1</td><td>0xh87pWrRt82z0DbO9n3yQ</td><td>12,2 MB</td><td>ZIP (page\_1.png 7MB + page\_2.png 5MB)</td></tr><tr><td>2</td><td>M0C32hKC2hMqPAxpRGiVgp</td><td>7,2 MB</td><td>PNG</td></tr><tr><td>3</td><td>UbAqQY3cEbH62vioIHU6Pj</td><td>6,0 MB</td><td>PNG</td></tr><tr><td>4</td><td>ZPQJVSjptMSxE71pV7JbAt</td><td>5,9 MB</td><td>PNG</td></tr><tr><td>5</td><td>h4Lhw1Trx9wgmD7doX9NyZ</td><td>5,9 MB</td><td>PNG</td></tr><tr><td>6</td><td>sbktcNb4IUj0yIovgDt0fL</td><td>5,3 MB</td><td>PNG</td></tr><tr><td>7</td><td>jtzuGpvrRTR12iLzwhcSNn</td><td>5,2 MB</td><td>PNG</td></tr><tr><td>8</td><td>VasIoF2e9EGuNQt38aOFMx</td><td>4,9 MB</td><td>JPEG</td></tr><tr><td>9</td><td>ZACDcQWzQzDLdnV7Qnf933</td><td>4,3 MB</td><td>PNG</td></tr><tr><td>10</td><td>hExoJEEWT2tHz1demE5Nhm</td><td>4,3 MB</td><td>PNG</td></tr><tr><td>11</td><td>veu4HT3bStx07gRUlAEYvG</td><td>4,2 MB</td><td>PNG</td></tr><tr><td>12</td><td>bz9Twmb2F5lj0mPIQi48IB</td><td>4,2 MB</td><td>JPEG</td></tr><tr><td>13</td><td>TzWK21r4n0yvbEtJh02DGB</td><td>4,2 MB</td><td>JPEG</td></tr><tr><td>14</td><td>eD165w1bdEHJg0tq3qWok5</td><td>4,2 MB</td><td>PNG</td></tr><tr><td>15</td><td>HD4SeqIx252nP9nh8HDVnH</td><td>4,1 MB</td><td>PNG</td></tr></tbody></table>

---

## Architecture retenue

### Choix de compression — décision Julien, 14/06/2026

**PNG → JPEG 85%** (lossy). Toutes les images, qu'elles soient PNG ou JPEG à l'origine, sont converties/re-sauvegardées en JPEG qualité 85. Gain estimé : 50–75% par image. Acceptable pour des captures d'écran.

Pour les ZIP multi-pages : chaque image interne est convertie en JPEG 85%, le ZIP est reconstruit.

### Phase 1 — Observatoire

Script `joplin_observatoire.py` sur le VPS :

- Connexion psycopg2 à joplin-db via IP réseau Docker interne
- Calcul des stats globales (taille totale, nombre d'items par format)
- Liste des 15 ressources les plus lourdes avec format et taille
- Mise à jour de la page BookStack Joplin (page 166) avec ces informations

### Phase 2 — Compression (script principal)

Script `joplin_compress.py` sur le VPS :

**Connexion :** psycopg2 → IP Docker interne de `joplin-db` : 5432

**Traitement par ressource :**

1. Lire le blob `content` depuis `items` (jop\_type=0)
2. Détecter le format (magic bytes)
3. Ouvrir avec Pillow, convertir en JPEG 85 (`quality=85, optimize=True`)
4. Pour les ZIP multi-pages : dézipper → compresser chaque image → reconstruire le ZIP
5. Si le gain est &gt; 5% : mettre à jour `content`, `content_size`, `updated_time` en base
6. Logger le résultat (ID, taille avant, taille après, ratio)

**Tracking des items traités :** fichier JSON local `/home/debian/joplin_compress_log.json` — évite de retraiter les ressources déjà compressées lors des passages quotidiens.

**Propagation sync :** Joplin détecte les changements via `updated_time`. Lors de la prochaine synchronisation de chaque client, les ressources compressées sont re-téléchargées automatiquement.

### Phase 3 — Service systemd (cron quotidien)

Timer systemd `joplin-compress.timer` → `joplin-compress.service` :

- Déclenchement quotidien (3h du matin)
- Traite uniquement les nouvelles ressources (non présentes dans le log JSON)
- Met à jour l'observatoire BookStack après chaque passage

---

## Mise en production — 14/06/2026

### Résultat du premier run (stock complet)

<table id="bkmrk-ressources-trait%C3%A9es7"><tbody><tr><th>Ressources traitées</th><td>736 au total</td></tr><tr><th>Compressées</th><td>424</td></tr><tr><th>Ignorées (gain &lt; 5%)</th><td>312</td></tr><tr><th>Erreurs</th><td>0</td></tr><tr><th>Poids avant</th><td>476 Mo</td></tr><tr><th>Poids après</th><td>119,7 Mo</td></tr><tr><th>Économie</th><td>**<span style="color:#008000;">356 Mo (-78,9%)</span>**</td></tr></tbody></table>

### Scripts déployés sur le VPS

<table id="bkmrk-scriptr%C3%B4leoptions-%2Fh"><thead><tr><th>Script</th><th>Rôle</th><th>Options</th></tr></thead><tbody><tr><td>`/home/debian/joplin_compress.py`</td><td>Compression Pillow PNG/JPEG → JPEG 85%, ZIP multi-pages, mise à jour PostgreSQL</td><td>`--dry-run` (simulation) / `--limit N` (N ressources max)</td></tr><tr><td>`/home/debian/joplin_observatoire.py`</td><td>Stats DB + top 15 → mise à jour page BookStack Joplin (ID 166)</td><td>—</td></tr></tbody></table>

**Log tracking :** `/home/debian/joplin_compress_log.json` — liste des ressources déjà traitées, évite les doublons aux runs suivants.

### Timer systemd

<table id="bkmrk-servicejoplin-compre"><tbody><tr><th>Service</th><td>`joplin-compress.service`</td></tr><tr><th>Timer</th><td>`joplin-compress.timer`</td></tr><tr><th>Déclenchement</th><td>Chaque nuit à 3h UTC (`OnCalendar=*-*-* 03:00:00`)</td></tr><tr><th>Logs</th><td>`/var/log/joplin-compress.log`</td></tr><tr><th>Statut</th><td><span style="color:#008000;">active (waiting)</span> — prochain run : 15/06/2026 03:00 UTC</td></tr></tbody></table>

### Notes techniques

- joplin-db IP Docker interne : `172.27.0.4` (peut changer si le container est recréé — vérifier avec `docker inspect joplin-db -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'`)
- La compression modifie `content`, `content_size` et `updated_time` dans la table `items`
- Les clients Joplin re-téléchargent les ressources modifiées lors de la prochaine synchronisation (détection via `updated_time`)
- Les images à fond transparent (RGBA/LA/P) sont aplaties sur fond blanc avant conversion JPEG