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 ne 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 :
- Compresser le stock d'images contenues dans la base de données Joplin via un process fiable de remplacement
- Monter un système de compression automatique quotidienne des nouvelles images
Principes définis par Julien :
- 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
- 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.
| Container | Image | Rôle |
|---|---|---|
joplin |
joplin/server:latest |
Serveur de sync Joplin |
joplin-db |
postgres:15-alpine |
Base de données |
joplin-nginx |
nginx:alpine |
Reverse proxy interne |
joplin_to_obsidian |
image custom | Container migration (actif, sans impact) |
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 brutescontent_size(integer) — taille en octetscontent_storage_id= 1 → stockage de typeDatabase(tout est dans PostgreSQL, pas de fichiers externes)jop_type— type d'item Joplinupdated_time— timestamp de dernière modification (utilisé par les clients pour détecter les changements à sync)
Répartition par type :
| jop_type | Signification | Nombre | Poids total |
|---|---|---|---|
| 0 | Ressource (image/fichier joint) | 736 | 476 MB |
| 1 | Note | 699 | 8,7 MB |
| 4 | Tag | 733 | 4,5 MB |
| 13 | NoteTag (relation note↔tag) | 250 | 3,9 MB |
| 2 | Carnet (Folder) | 94 | 707 KB |
| 6 | Master Key | 87 | 19 KB |
| 5 | Setting | 39 | — |
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 imagespage_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 20 ressources les plus lourdes (état initial) :
| Rang | ID | Taille | Format |
|---|---|---|---|
| 1 | 0xh87pWrRt82z0DbO9n3yQ | 12,2 MB | ZIP (page_1.png 7MB + page_2.png 5MB) |
| 2 | M0C32hKC2hMqPAxpRGiVgp | 7,2 MB | PNG |
| 3 | UbAqQY3cEbH62vioIHU6Pj | 6,0 MB | PNG |
| 4 | ZPQJVSjptMSxE71pV7JbAt | 5,9 MB | PNG |
| 5 | h4Lhw1Trx9wgmD7doX9NyZ | 5,9 MB | PNG |
| 6 | sbktcNb4IUj0yIovgDt0fL | 5,3 MB | PNG |
| 7 | jtzuGpvrRTR12iLzwhcSNn | 5,2 MB | PNG |
| 8 | VasIoF2e9EGuNQt38aOFMx | 4,9 MB | JPEG |
| 9 | ZACDcQWzQzDLdnV7Qnf933 | 4,3 MB | PNG |
| 10 | hExoJEEWT2tHz1demE5Nhm | 4,3 MB | PNG |
| 11 | veu4HT3bStx07gRUlAEYvG | 4,2 MB | PNG |
| 12 | bz9Twmb2F5lj0mPIQi48IB | 4,2 MB | JPEG |
| 13 | TzWK21r4n0yvbEtJh02DGB | 4,2 MB | JPEG |
| 14 | eD165w1bdEHJg0tq3qWok5 | 4,2 MB | PNG |
| 15 | HD4SeqIx252nP9nh8HDVnH | 4,1 MB | PNG |
Architecture proposée
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 (credentials dans les variables d'environnement du container, mot de passe récupéré via docker inspect)
Traitement par ressource :
- Lire le blob
contentdepuisitems(jop_type=0) - Détecter le format (magic bytes)
- Ouvrir avec Pillow :
- PNG → re-save en PNG avec
optimize=True, compress_level=9(lossless) - JPEG → re-save en JPEG qualité 80,
optimize=True(légèrement lossy) - ZIP multi-pages → dézipper, compresser chaque image interne, reconstruire le ZIP
- PNG → re-save en PNG avec
- Si le gain est > 5% : mettre à jour
content,content_size,updated_timeen base - Logger le résultat (ID, taille avant, taille après, ratio, durée)
Tracking des items traités : fichier JSON local /home/debian/joplin_compress_log.json pour ne pas retraiter les ressources déjà compressées.
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 (le client compare l'updated_time serveur avec sa copie locale).
Phase 3 — Service systemd (cron quotidien)
Timer systemd joplin-compress.timer → joplin-compress.service :
- Déclenchement quotidien (ex. 3h du matin)
- Traite uniquement les nouvelles ressources (non présentes dans le log JSON)
- Rapport par page BookStack
Question ouverte
Choix de compression pour les PNG :
| Option | Compression | Pertes | Gain estimé |
|---|---|---|---|
| PNG optimisé (lossless) | compress_level=9 |
Aucune | 5–15% |
| PNG → JPEG 85% (lossy) | Pillow JPEG | Artefacts possibles sur le texte | 50–75% |
Pour des captures d'écran contenant du texte, la conversion PNG→JPEG peut introduire des artefacts visuels aux contours nets. Julien doit confirmer son choix avant que Claude code le script.